View Javadoc

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