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