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