View Javadoc

1   package org.apache.turbine.services.intake.model;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;
29  
30  import org.apache.turbine.om.Retrievable;
31  import org.apache.turbine.services.intake.IntakeException;
32  import org.apache.turbine.services.intake.TurbineIntake;
33  import org.apache.turbine.services.intake.xmlmodel.AppData;
34  import org.apache.turbine.services.intake.xmlmodel.XmlField;
35  import org.apache.turbine.services.intake.xmlmodel.XmlGroup;
36  import org.apache.turbine.util.TurbineException;
37  import org.apache.turbine.util.parser.ValueParser;
38  
39  /***
40   * Holds a group of Fields
41   *
42   * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
43   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
44   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
45   * @version $Id: Group.java 264148 2005-08-29 14:21:04Z henning $
46   */
47  public class Group
48  {
49      public static final String EMPTY = "";
50  
51      /*
52       * An id representing a new object.
53       */
54      public static final String NEW = "_0";
55  
56      private static final Log log;
57      private static final boolean isDebugEnabled;
58  
59      static
60      {
61          log = LogFactory.getLog(Group.class);
62          isDebugEnabled = log.isDebugEnabled();
63      }
64  
65      /***
66       * The key used to represent this group in a parameter.
67       * This key is usually a prefix as part of a field key.
68       */
69      protected final String gid;
70  
71      /***
72       * The name used in templates and java code to refer to this group.
73       */
74      protected final String name;
75  
76      /***
77       * The number of Groups with the same name that will be pooled.
78       */
79      private final int poolCapacity;
80  
81      /***
82       * A map of the fields in this group mapped by field name.
83       */
84      protected Map fields;
85  
86      /***
87       * Map of the fields by mapToObject
88       */
89      protected Map mapToObjectFields;
90  
91      /***
92       * An array of fields in this group.
93       */
94      protected Field[] fieldsArray;
95  
96      /***
97       * The object id used to associate this group to a bean
98       * for one request cycle
99       */
100     protected String oid;
101 
102     /***
103      * The object containing the request data
104      */
105     protected ValueParser pp;
106 
107     /***
108      * A flag to help prevent duplicate hidden fields declaring this group.
109      */
110     protected boolean isDeclared;
111 
112     /***
113      * Constructs a new Group based on the xml specification.  Groups are
114      * instantiated and pooled by the IntakeService and should not
115      * be instantiated otherwise.
116      *
117      * @param group a <code>XmlGroup</code> value
118      * @exception IntakeException if an error occurs in other classes
119      */
120     public Group(XmlGroup group) throws IntakeException
121     {
122         gid = group.getKey();
123         name = group.getName();
124         poolCapacity = Integer.parseInt(group.getPoolCapacity());
125 
126         List inputFields = group.getFields();
127         int size = inputFields.size();
128         fields = new HashMap((int) (1.25 * size + 1));
129         mapToObjectFields = new HashMap((int) (1.25 * size + 1));
130         fieldsArray = new Field[size];
131         for (int i = size - 1; i >= 0; i--)
132         {
133             XmlField f = (XmlField) inputFields.get(i);
134             Field field = FieldFactory.getInstance(f, this);
135             fieldsArray[i] = field;
136             fields.put(f.getName(), field);
137 
138             // map fields by their mapToObject
139             List tmpFields = (List) mapToObjectFields.get(f.getMapToObject());
140             if (tmpFields == null)
141             {
142                 tmpFields = new ArrayList(size);
143                 mapToObjectFields.put(f.getMapToObject(), tmpFields);
144             }
145             tmpFields.add(field);
146         }
147 
148         // Change the mapToObjectFields values to Field[]
149         for (Iterator keys = mapToObjectFields.keySet().iterator(); keys.hasNext();)
150         {
151             Object key = keys.next();
152             List tmpFields = (List) mapToObjectFields.get(key);
153             mapToObjectFields.put(key,
154                     tmpFields.toArray(new Field[tmpFields.size()]));
155         }
156     }
157 
158     /***
159      * Initializes the default Group using parameters.
160      *
161      * @param pp a <code>ValueParser</code> value
162      * @return this Group
163      */
164     public Group init(ValueParser pp) throws TurbineException
165     {
166         return init(NEW, pp);
167     }
168 
169     /***
170      * Initializes the Group with parameters from RunData
171      * corresponding to key.
172      *
173      * @param pp a <code>ValueParser</code> value
174      * @return this Group
175      */
176     public Group init(String key, ValueParser pp) throws IntakeException
177     {
178         this.oid = key;
179         this.pp = pp;
180         for (int i = fieldsArray.length - 1; i >= 0; i--)
181         {
182             fieldsArray[i].init(pp);
183         }
184         return this;
185     }
186 
187     /***
188      * Initializes the group with properties from an object.
189      *
190      * @param obj a <code>Persistent</code> value
191      * @return a <code>Group</code> value
192      */
193     public Group init(Retrievable obj)
194     {
195         this.oid = obj.getQueryKey();
196 
197         Class cls = obj.getClass();
198         while (cls != null)
199         {
200             Field[] flds = (Field[]) mapToObjectFields.get(cls.getName());
201             if (flds != null)
202             {
203                 for (int i = flds.length - 1; i >= 0; i--)
204                 {
205                     flds[i].init(obj);
206                 }
207             }
208 
209             cls = cls.getSuperclass();
210         }
211 
212         return this;
213     }
214 
215     /***
216      * Gets a list of the names of the fields stored in this object.
217      *
218      * @return A String array containing the list of names.
219      */
220     public String[] getFieldNames()
221     {
222         String nameList[] = new String[fieldsArray.length];
223         for (int i = 0; i < nameList.length; i++)
224         {
225             nameList[i] = fieldsArray[i].name;
226         }
227         return nameList;
228     }
229 
230     /***
231      * Return the name given to this group.  The long name is to
232      * avoid conflicts with the get(String key) method.
233      *
234      * @return a <code>String</code> value
235      */
236     public String getIntakeGroupName()
237     {
238         return name;
239     }
240 
241     /***
242      * Get the number of Group objects that will be pooled.
243      *
244      * @return an <code>int</code> value
245      */
246     public int getPoolCapacity()
247     {
248         return poolCapacity;
249     }
250 
251     /***
252      * Get the part of the key used to specify the group.
253      * This is specified in the key attribute in the xml file.
254      *
255      * @return a <code>String</code> value
256      */
257     public String getGID()
258     {
259         return gid;
260     }
261 
262     /***
263      * Get the part of the key that distinguishes a group
264      * from others of the same name.
265      *
266      * @return a <code>String</code> value
267      */
268     public String getOID()
269     {
270         return oid;
271     }
272 
273     /***
274      * Concatenation of gid and oid.
275      *
276      * @return a <code>String</code> value
277      */
278     public String getObjectKey()
279     {
280         return gid + oid;
281     }
282 
283     /***
284      * Describe <code>getObjects</code> method here.
285      *
286      * @param pp a <code>ValueParser</code> value
287      * @return an <code>ArrayList</code> value
288      * @exception IntakeException if an error occurs
289      */
290     public ArrayList getObjects(ValueParser pp) throws IntakeException
291     {
292         ArrayList objs = null;
293         String[] oids = pp.getStrings(gid);
294         if (oids != null)
295         {
296             objs = new ArrayList(oids.length);
297             for (int i = oids.length - 1; i >= 0; i--)
298             {
299                 objs.add(TurbineIntake.getGroup(name).init(oids[i], pp));
300             }
301         }
302         return objs;
303     }
304 
305     /***
306      * Get the Field .
307      * @return Field.
308      * @throws IntakeException indicates the field could not be found.
309      */
310     public Field get(String fieldName)
311             throws IntakeException
312     {
313         if (fields.containsKey(fieldName))
314         {
315             return (Field) fields.get(fieldName);
316         }
317         else
318         {
319             throw new IntakeException("Intake Field name: " + fieldName +
320                     " not found!");
321         }
322     }
323 
324     /***
325      * Performs an AND between all the fields in this group.
326      *
327      * @return a <code>boolean</code> value
328      */
329     public boolean isAllValid()
330     {
331         boolean valid = true;
332         for (int i = fieldsArray.length - 1; i >= 0; i--)
333         {
334             valid &= fieldsArray[i].isValid();
335             if (isDebugEnabled && !fieldsArray[i].isValid())
336             {
337                 log.debug("Group(" + oid + "): " + name + "; Field: "
338                         + fieldsArray[i].name + "; value=" +
339                         fieldsArray[i].getValue() + " is invalid!");
340             }
341         }
342         return valid;
343     }
344 
345     /***
346      * Calls a setter methods on obj, for fields which have been set.
347      *
348      * @param obj Object to be set with the values from the group.
349      * @throws IntakeException indicates that a failure occurred while
350      * executing the setter methods of the mapped object.
351      */
352     public void setProperties(Object obj) throws IntakeException
353     {
354         Class cls = obj.getClass();
355 
356         while (cls != null)
357         {
358             if (isDebugEnabled)
359             {
360                 log.debug("setProperties(" + cls.getName() + ")");
361             }
362 
363             Field[] flds = (Field[]) mapToObjectFields.get(cls.getName());
364             if (flds != null)
365             {
366                 for (int i = flds.length - 1; i >= 0; i--)
367                 {
368                     flds[i].setProperty(obj);
369                 }
370             }
371 
372             cls = cls.getSuperclass();
373         }
374         log.debug("setProperties() finished");
375     }
376 
377     /***
378      * Calls a setter methods on obj, for fields which pass validity tests.
379      * In most cases one should call Intake.isAllValid() and then if that
380      * test passes call setProperties.  Use this method when some data is
381      * known to be invalid, but you still want to set the object properties
382      * that are valid.
383      */
384     public void setValidProperties(Object obj)
385     {
386         Class cls = obj.getClass();
387         while (cls != null)
388         {
389             Field[] flds = (Field[]) mapToObjectFields.get(cls.getName());
390             if (flds != null)
391             {
392                 for (int i = flds.length - 1; i >= 0; i--)
393                 {
394                     try
395                     {
396                         flds[i].setProperty(obj);
397                     }
398                     catch (Exception e)
399                     {
400                         // just move on to next field
401                     }
402                 }
403             }
404 
405             cls = cls.getSuperclass();
406         }
407     }
408 
409     /***
410      * Calls getter methods on objects that are known to Intake
411      * so that field values in forms can be initialized from
412      * the values contained in the intake tool.
413      *
414      * @param obj Object that will be used to as a source of data for
415      * setting the values of the fields within the group.
416      * @throws IntakeException indicates that a failure occurred while
417      * executing the setter methods of the mapped object.
418      */
419     public void getProperties(Object obj) throws IntakeException
420     {
421         Class cls = obj.getClass();
422         while (cls != null)
423         {
424             Field[] flds = (Field[]) mapToObjectFields.get(cls.getName());
425             if (flds != null)
426             {
427                 for (int i = flds.length - 1; i >= 0; i--)
428                 {
429                     flds[i].getProperty(obj);
430                 }
431             }
432 
433             cls = cls.getSuperclass();
434         }
435     }
436 
437     /***
438      * Removes references to this group and its fields from the
439      * query parameters
440      */
441     public void removeFromRequest()
442     {
443         if (pp != null)
444         {
445             String[] groups = pp.getStrings(gid);
446             if (groups != null)
447             {
448                 pp.remove(gid);
449                 for (int i = 0; i < groups.length; i++)
450                 {
451                     if (groups[i] != null && !groups[i].equals(oid))
452                     {
453                         pp.add(gid, groups[i]);
454                     }
455                 }
456                 for (int i = fieldsArray.length - 1; i >= 0; i--)
457                 {
458                     fieldsArray[i].removeFromRequest();
459                 }
460             }
461         }
462     }
463 
464     /***
465      * To be used in the event this group is used within multiple
466      * forms within the same template.
467      */
468     public void resetDeclared()
469     {
470         isDeclared = false;
471     }
472 
473     /***
474      * A xhtml valid hidden input field that notifies intake of the
475      * group's presence.
476      *
477      * @return a <code>String</code> value
478      */
479     public String getHtmlFormInput()
480     {
481         StringBuffer sb = new StringBuffer(64);
482         appendHtmlFormInput(sb);
483         return sb.toString();
484     }
485 
486     /***
487      * A xhtml valid hidden input field that notifies intake of the
488      * group's presence.
489      */
490     public void appendHtmlFormInput(StringBuffer sb)
491     {
492         if (!isDeclared)
493         {
494             isDeclared = true;
495             sb.append("<input type=\"hidden\" name=\"")
496                     .append(gid)
497                     .append("\" value=\"")
498                     .append(oid)
499                     .append("\"/>\n");
500         }
501     }
502 
503     // ********** PoolableObjectFactory implementation ******************
504 
505     public static class GroupFactory
506             extends BaseKeyedPoolableObjectFactory
507     {
508         private AppData appData;
509 
510         public GroupFactory(AppData appData)
511         {
512             this.appData = appData;
513         }
514 
515         /***
516          * Creates an instance that can be returned by the pool.
517          * @return an instance that can be returned by the pool.
518          * @throws IntakeException indicates that the group could not be retreived
519          */
520         public Object makeObject(Object key) throws IntakeException
521         {
522             return new Group(appData.getGroup((String) key));
523         }
524 
525         /***
526          * Uninitialize an instance to be returned to the pool.
527          * @param obj the instance to be passivated
528          */
529         public void passivateObject(Object key, Object obj)
530         {
531             Group group = (Group) obj;
532             group.oid = null;
533             group.pp = null;
534             for (int i = group.fieldsArray.length - 1; i >= 0; i--)
535             {
536                 group.fieldsArray[i].dispose();
537             }
538             group.isDeclared = false;
539         }
540     }
541 }
542 
543