1 package org.apache.turbine.services.intake;
2
3
4 /*
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 */
22
23
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.fulcrum.intake.IntakeException;
31 import org.apache.fulcrum.intake.IntakeService;
32 import org.apache.fulcrum.intake.Retrievable;
33 import org.apache.fulcrum.intake.model.Group;
34 import org.apache.fulcrum.parser.ValueParser;
35 import org.apache.fulcrum.pool.Recyclable;
36 import org.apache.turbine.annotation.TurbineService;
37 import org.apache.turbine.services.pull.ApplicationTool;
38 import org.apache.turbine.util.RunData;
39
40
41 /**
42 * The main class through which Intake is accessed. Provides easy access
43 * to the Fulcrum Intake component.
44 *
45 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
46 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
47 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
48 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
49 * @version $Id: IntakeTool.java 1773378 2016-12-09 13:19:59Z tv $
50 */
51 public class IntakeTool
52 implements ApplicationTool, Recyclable
53 {
54 /** Used for logging */
55 protected static final Log log = LogFactory.getLog(IntakeTool.class);
56
57 /** Constant for default key */
58 public static final String DEFAULT_KEY = "_0";
59
60 /** Constant for the hidden fieldname */
61 public static final String INTAKE_GRP = "intake-grp";
62
63 /** Groups from intake.xml */
64 protected HashMap<String, Group> groups = null;
65
66 /** ValueParser instance */
67 protected ValueParser pp;
68
69 private final HashMap<String, Group> declaredGroups = new HashMap<String, Group>();
70 private final StringBuilder allGroupsSB = new StringBuilder(256);
71 private final StringBuilder groupSB = new StringBuilder(128);
72
73 /** The cache of PullHelpers. **/
74 private Map<String, IntakeTool.PullHelper> pullMap = null;
75
76 /**
77 * The Intake service.
78 */
79 @TurbineService
80 protected IntakeService intakeService;
81
82 /**
83 * Constructor
84 */
85 public IntakeTool()
86 {
87 }
88
89 /**
90 * Prepares intake for a single request
91 */
92 @Override
93 public void init(Object runData)
94 {
95 if (groups == null) // Initialize only once
96 {
97 String[] groupNames = intakeService.getGroupNames();
98 int groupCount = 0;
99 if (groupNames != null)
100 {
101 groupCount = groupNames.length;
102 }
103 groups = new HashMap<String, Group>((int) (1.25 * groupCount + 1));
104 pullMap = new HashMap<String, IntakeTool.PullHelper>((int) (1.25 * groupCount + 1));
105
106 for (int i = groupCount - 1; i >= 0; i--)
107 {
108 pullMap.put(groupNames[i], new PullHelper(groupNames[i]));
109 }
110 }
111
112 this.pp = ((RunData) runData).getParameters();
113
114 String[] groupKeys = pp.getStrings(INTAKE_GRP);
115 String[] groupNames = null;
116 if (groupKeys == null || groupKeys.length == 0)
117 {
118 groupNames = intakeService.getGroupNames();
119 }
120 else
121 {
122 groupNames = new String[groupKeys.length];
123 for (int i = groupKeys.length - 1; i >= 0; i--)
124 {
125 groupNames[i] = intakeService.getGroupName(groupKeys[i]);
126 }
127
128 }
129
130 for (int i = groupNames.length - 1; i >= 0; i--)
131 {
132 try
133 {
134 List<Group> foundGroups = intakeService.getGroup(groupNames[i])
135 .getObjects(pp);
136
137 if (foundGroups != null)
138 {
139 for (Group group : foundGroups)
140 {
141 groups.put(group.getObjectKey(), group);
142 }
143 }
144 }
145 catch (IntakeException e)
146 {
147 log.error(e);
148 }
149 }
150 }
151
152 /**
153 * Add all registered group ids to the value parser
154 *
155 * @param vp the value parser
156 */
157 public void addGroupsToParameters(ValueParser vp)
158 {
159 for (Group group : groups.values())
160 {
161 if (!declaredGroups.containsKey(group.getIntakeGroupName()))
162 {
163 declaredGroups.put(group.getIntakeGroupName(), null);
164 vp.add("intake-grp", group.getGID());
165 }
166 vp.add(group.getGID(), group.getOID());
167 }
168 declaredGroups.clear();
169 }
170
171 /**
172 * A convenience method to write out the hidden form fields
173 * that notify intake of the relevant groups. It should be used
174 * only in templates with 1 form. In multiform templates, the groups
175 * that are relevant for each form need to be declared using
176 * $intake.newForm() and $intake.declareGroup($group) for the relevant
177 * groups in the form.
178 *
179 * @return the HTML that declares all groups to Intake in hidden input fields
180 *
181 */
182 public String declareGroups()
183 {
184 allGroupsSB.setLength(0);
185 for (Group group : groups.values())
186 {
187 declareGroup(group, allGroupsSB);
188 }
189 return allGroupsSB.toString();
190 }
191
192 /**
193 * A convenience method to write out the hidden form fields
194 * that notify intake of the group.
195 *
196 * @param group the group to declare
197 * @return the HTML that declares the group to Intake in a hidden input field
198 */
199 public String declareGroup(Group group)
200 {
201 groupSB.setLength(0);
202 declareGroup(group, groupSB);
203 return groupSB.toString();
204 }
205
206 /**
207 * xhtml valid hidden input field(s) that notifies intake of the
208 * group's presence.
209 * @param group the group to declare
210 * @param sb a String Builder where the hidden field HTML will be appended
211 */
212 public void declareGroup(Group group, StringBuilder sb)
213 {
214 if (!declaredGroups.containsKey(group.getIntakeGroupName()))
215 {
216 declaredGroups.put(group.getIntakeGroupName(), null);
217 sb.append("<input type=\"hidden\" name=\"")
218 .append(INTAKE_GRP)
219 .append("\" value=\"")
220 .append(group.getGID())
221 .append("\"/>\n");
222 }
223 group.appendHtmlFormInput(sb);
224 }
225
226 /**
227 * Declare that a new form starts
228 */
229 public void newForm()
230 {
231 declaredGroups.clear();
232 for (Group group : groups.values())
233 {
234 group.resetDeclared();
235 }
236 }
237
238 /**
239 * Implementation of ApplicationTool interface is not needed for this
240 * tool as it is request scoped
241 */
242 @Override
243 public void refresh()
244 {
245 // empty
246 }
247
248 /**
249 * Inner class to present a nice interface to the template designer
250 */
251 public class PullHelper
252 {
253 /** Name of the group used by the pull helper */
254 String groupName;
255
256 /**
257 * Protected constructor to force use of factory method.
258 *
259 * @param groupName the group name
260 */
261 protected PullHelper(String groupName)
262 {
263 this.groupName = groupName;
264 }
265
266 /**
267 * Populates the object with the default values from the XML File
268 *
269 * @return a Group object with the default values
270 * @throws IntakeException if getting the group fails
271 */
272 public Group getDefault()
273 throws IntakeException
274 {
275 return setKey(DEFAULT_KEY);
276 }
277
278 /**
279 * Calls setKey(key,true)
280 *
281 * @param key the group key
282 * @return an Intake Group
283 * @throws IntakeException if getting the group fails
284 */
285 public Group setKey(String key)
286 throws IntakeException
287 {
288 return setKey(key, true);
289 }
290
291 /**
292 * Return the group identified by its key
293 *
294 * @param key the group key
295 * @param create true if a non-existing group should be created
296 * @return an Intake Group
297 * @throws IntakeException if getting the group fails
298 */
299 public Group setKey(String key, boolean create)
300 throws IntakeException
301 {
302 Group g = null;
303
304 String inputKey = intakeService.getGroupKey(groupName) + key;
305 if (groups.containsKey(inputKey))
306 {
307 g = groups.get(inputKey);
308 }
309 else if (create)
310 {
311 g = intakeService.getGroup(groupName);
312 groups.put(inputKey, g);
313 g.init(key, pp);
314 }
315
316 return g;
317 }
318
319 /**
320 * maps an Intake Group to the values from a Retrievable object.
321 *
322 * @param obj A retrievable object
323 * @return an Intake Group
324 */
325 public Group mapTo(Retrievable obj)
326 {
327 Group g = null;
328
329 try
330 {
331 String inputKey = intakeService.getGroupKey(groupName)
332 + obj.getQueryKey();
333 if (groups.containsKey(inputKey))
334 {
335 g = groups.get(inputKey);
336 }
337 else
338 {
339 g = intakeService.getGroup(groupName);
340 groups.put(inputKey, g);
341 }
342
343 return g.init(obj);
344 }
345 catch (IntakeException e)
346 {
347 log.error(e);
348 }
349
350 return null;
351 }
352 }
353
354 /**
355 * get a specific group
356 * @param groupName the name of the group
357 * @return a {@link PullHelper} wrapper around the group
358 */
359 public PullHelper get(String groupName)
360 {
361 return pullMap.get(groupName);
362 }
363
364 /**
365 * Get a specific group
366 *
367 * @param groupName the name of the group
368 * @param throwExceptions if false, exceptions will be suppressed.
369 * @return a {@link PullHelper} wrapper around the group
370 * @throws IntakeException could not retrieve group
371 */
372 public PullHelper get(String groupName, boolean throwExceptions)
373 throws IntakeException
374 {
375 return pullMap.get(groupName);
376 }
377
378 /**
379 * Loops through all of the Groups and checks to see if
380 * the data within the Group is valid.
381 * @return true if all groups are valid
382 */
383 public boolean isAllValid()
384 {
385 boolean allValid = true;
386 for (Group group : groups.values())
387 {
388 allValid &= group.isAllValid();
389 }
390 return allValid;
391 }
392
393 /**
394 * Get a specific group by name and key.
395 * @param groupName the name of the group
396 * @param key the key for the group
397 * @return the {@link Group}
398 * @throws IntakeException if the group could not be retrieved
399 */
400 public Group get(String groupName, String key)
401 throws IntakeException
402 {
403 return get(groupName, key, true);
404 }
405
406 /**
407 * Get a specific group by name and key. Also specify
408 * whether or not you want to create a new group.
409 * @param groupName the name of the group
410 * @param key the key for the group
411 * @param create true if a new group should be created
412 * @return the {@link Group}
413 * @throws IntakeException if the group could not be retrieved
414 */
415 public Group get(String groupName, String key, boolean create)
416 throws IntakeException
417 {
418 if (groupName == null)
419 {
420 throw new IntakeException("intakeService.get: groupName == null");
421 }
422 if (key == null)
423 {
424 throw new IntakeException("intakeService.get: key == null");
425 }
426
427 PullHelper ph = get(groupName);
428 return (ph == null) ? null : ph.setKey(key, create);
429 }
430
431 /**
432 * Removes group. Primary use is to remove a group that has
433 * been processed by an action and is no longer appropriate
434 * in the view (screen).
435 * @param group the group instance to remove
436 */
437 public void remove(Group group)
438 {
439 if (group != null)
440 {
441 groups.remove(group.getObjectKey());
442 group.removeFromRequest();
443
444 String[] groupKeys = pp.getStrings(INTAKE_GRP);
445
446 pp.remove(INTAKE_GRP);
447
448 if (groupKeys != null)
449 {
450 for (int i = 0; i < groupKeys.length; i++)
451 {
452 if (!groupKeys[i].equals(group.getGID()))
453 {
454 pp.add(INTAKE_GRP, groupKeys[i]);
455 }
456 }
457 }
458
459 try
460 {
461 intakeService.releaseGroup(group);
462 }
463 catch (IntakeException ie)
464 {
465 log.error("Tried to release unknown group "
466 + group.getIntakeGroupName());
467 }
468 }
469 }
470
471 /**
472 * Removes all groups. Primary use is to remove groups that have
473 * been processed by an action and are no longer appropriate
474 * in the view (screen).
475 */
476 public void removeAll()
477 {
478 Object[] allGroups = groups.values().toArray();
479 for (int i = allGroups.length - 1; i >= 0; i--)
480 {
481 Group group = (Group) allGroups[i];
482 remove(group);
483 }
484 }
485
486 /**
487 * Get a Map containing all the groups.
488 *
489 * @return the Group Map
490 */
491 public Map<String, Group> getGroups()
492 {
493 return groups;
494 }
495
496 // ****************** Recyclable implementation ************************
497
498 private boolean disposed;
499
500 /**
501 * Recycles the object for a new client. Recycle methods with
502 * parameters must be added to implementing object and they will be
503 * automatically called by pool implementations when the object is
504 * taken from the pool for a new client. The parameters must
505 * correspond to the parameters of the constructors of the object.
506 * For new objects, constructors can call their corresponding recycle
507 * methods whenever applicable.
508 * The recycle methods must call their super.
509 */
510 @Override
511 public void recycle()
512 {
513 disposed = false;
514 }
515
516 /**
517 * Disposes the object after use. The method is called
518 * when the object is returned to its pool.
519 * The dispose method must call its super.
520 */
521 @Override
522 public void dispose()
523 {
524 for (Group group : groups.values())
525 {
526 try
527 {
528 intakeService.releaseGroup(group);
529 }
530 catch (IntakeException ie)
531 {
532 log.error("Tried to release unknown group "
533 + group.getIntakeGroupName());
534 }
535 }
536
537 groups.clear();
538 declaredGroups.clear();
539 pp = null;
540
541 disposed = true;
542 }
543
544 /**
545 * Checks whether the recyclable has been disposed.
546 *
547 * @return true, if the recyclable is disposed.
548 */
549 @Override
550 public boolean isDisposed()
551 {
552 return disposed;
553 }
554 }