View Javadoc

1   /*
2    * Copyright (c) 2001 - 2005 ivata limited.
3    * All rights reserved.
4    * -----------------------------------------------------------------------------
5    * ivata masks may be redistributed under the GNU General Public
6    * License as published by the Free Software Foundation;
7    * version 2 of the License.
8    *
9    * These programs are free software; you can redistribute them and/or
10   * modify them under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; version 2 of the License.
12   *
13   * These programs are distributed in the hope that they will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16   *
17   * See the GNU General Public License in the file LICENSE.txt for more
18   * details.
19   *
20   * If you would like a copy of the GNU General Public License write to
21   *
22   * Free Software Foundation, Inc.
23   * 59 Temple Place - Suite 330
24   * Boston, MA 02111-1307, USA.
25   *
26   *
27   * To arrange commercial support and licensing, contact ivata at
28   *                  http://www.ivata.com/contact.jsp
29   * -----------------------------------------------------------------------------
30   * $Log: MaskRequestProcessorImplementation.java,v $
31   * Revision 1.3  2005/04/29 16:33:17  colinmacleod
32   * Fixes for non-string types on dyna forms.
33   *
34   * Revision 1.2  2005/04/27 17:23:28  colinmacleod
35   * Fixed bugs resulting from ivata masks changes
36   * for ivata groupware v0.11.
37   *
38   * Revision 1.1  2005/04/11 12:21:15  colinmacleod
39   * Changed MaskRequestProcessorImpl to
40   * MaskRequestProcessorImplementation.
41   * Improved tiles support.
42   *
43   * Revision 1.5  2005/03/10 10:44:35  colinmacleod
44   * Fixed validation conversion of Struts classes to
45   * ivata masks validation errors.
46   *
47   * Revision 1.4  2005/01/19 13:14:02  colinmacleod
48   * Renamed CausedByException to SystemException.
49   *
50   * Revision 1.3  2005/01/07 08:08:24  colinmacleod
51   * Moved up a version number.
52   * Changed copyright notices to 2005.
53   * Updated the documentation:
54   *   - started working on multiproject:site docu.
55   *   - changed the logo.
56   * Added checkstyle and fixed LOADS of style issues.
57   * Added separate thirdparty subproject.
58   * Added struts (in web), util and webgui (in webtheme) from ivata op.
59   *
60   * Revision 1.2  2004/12/29 15:34:07  colinmacleod
61   * Improved checking for non-ivata masks forms.
62   *
63   * Revision 1.1  2004/12/23 21:28:32  colinmacleod
64   * Modifications to add ivata op compatibility.
65   * -----------------------------------------------------------------------------
66   */
67  package com.ivata.mask.web.struts;
68  import java.beans.PropertyDescriptor;
69  import java.io.IOException;
70  import java.io.StringReader;
71  import java.lang.reflect.Constructor;
72  import java.lang.reflect.InvocationTargetException;
73  import java.util.Arrays;
74  import java.util.Collection;
75  import java.util.Enumeration;
76  import java.util.HashMap;
77  import java.util.List;
78  import java.util.Map;
79  
80  import javax.servlet.ServletException;
81  import javax.servlet.http.HttpServletRequest;
82  import javax.servlet.http.HttpServletResponse;
83  
84  import org.apache.commons.beanutils.DynaProperty;
85  import org.apache.commons.beanutils.PropertyUtils;
86  import org.apache.log4j.Logger;
87  import org.apache.struts.Globals;
88  import org.apache.struts.action.Action;
89  import org.apache.struts.action.ActionForm;
90  import org.apache.struts.action.ActionMapping;
91  import org.apache.struts.action.ActionServlet;
92  import org.apache.struts.action.DynaActionForm;
93  import org.apache.struts.config.FormBeanConfig;
94  import org.apache.struts.config.ModuleConfig;
95  import org.apache.struts.taglib.html.Constants;
96  import org.dom4j.Document;
97  import org.dom4j.DocumentException;
98  import org.dom4j.Element;
99  import org.dom4j.Node;
100 import org.dom4j.io.SAXReader;
101 
102 import com.ivata.mask.Mask;
103 import com.ivata.mask.MaskFactory;
104 import com.ivata.mask.field.DefaultFieldValueConvertorFactory;
105 import com.ivata.mask.field.Field;
106 import com.ivata.mask.field.FieldValueConvertor;
107 import com.ivata.mask.field.FieldValueConvertorConstants;
108 import com.ivata.mask.field.FieldValueConvertorFactory;
109 import com.ivata.mask.persistence.PersistenceException;
110 import com.ivata.mask.persistence.PersistenceManager;
111 import com.ivata.mask.persistence.PersistenceSession;
112 import com.ivata.mask.util.StringHandling;
113 import com.ivata.mask.util.SystemException;
114 import com.ivata.mask.validation.ValidationErrors;
115 import com.ivata.mask.valueobject.ValueObject;
116 /***
117  * <p>
118  * Mask request processor implementation - kept separate so we can share
119  * funcitonality between the regular ({@link MaskRequestProcessor}) and tiles (
120  * {@link MaskTilesRequestProcessor}) versions.
121  * </p>
122  *
123  * @since ivata masks 0.4 (2004-12-23)
124  * @author Colin MacLeod
125  * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
126  * @version $Revision: 1.3 $
127  */
128 public class MaskRequestProcessorImplementation {
129     /***
130      * <p>
131      * Used to generate convertors to convert property types from strings.
132      * </p>
133      */
134     private FieldValueConvertorFactory fieldValueConvertorFactory;
135     /***
136      * <p>
137      * This log provides tracing and debugging information.
138      * </p>
139      */
140     private Logger log = Logger.getLogger(MaskRequestProcessorImplementation.class);
141     /***
142      * <p>
143      * This factory is needed to access the masks and groups of masks.
144      * </p>
145      */
146     private MaskFactory maskFactory;
147     /***
148      * <p>
149      * Used to locate the value objects by their shared base class.
150      * </p>
151      */
152     private PersistenceManager persistenceManager;
153     /***
154      * <p>
155      * Initializes the mask factory, the value object locator and the the
156      * standard field value convertors.
157      * </p>
158      *
159      * @param maskFactoryParam
160      *            needed to access the masks and groups of masks.
161      * @param persistenceManagerParam
162      *            used to locate the value objects by their shared base class.
163      */
164     public MaskRequestProcessorImplementation(final MaskFactory maskFactoryParam,
165             final PersistenceManager persistenceManagerParam,
166             final FieldValueConvertorFactory fieldValueConvertorFactoryParam) {
167         this.maskFactory = maskFactoryParam;
168         this.persistenceManager = persistenceManagerParam;
169         this.fieldValueConvertorFactory = fieldValueConvertorFactoryParam;
170     }
171     /***
172      * <p>
173      * Override this method to define how to create actions in your environment.
174      * This lets you use ivata masks with a standard framework.
175      * </p>
176      *
177      * <p>
178      * The default implementation looks for the action classes it knows about
179      * and initializes them directly. This is a simple starting point; in real
180      * life it is better to use a standard framework such as <a
181      * href="http://picocontainer.org">picocontainer </a>.
182      * </p>
183      *
184      *
185      * @param className
186      *            full path and name of the action class to be created.
187      * @param request
188      *            request for which we are creating an action.
189      * @param response
190      *            response we are sending.
191      * @param mapping
192      *            <strong>Struts </strong> mapping.
193      * @return valid action for the path, or <code>null</code> if the action
194      *         can and should be created using the default <strong>Struts
195      *         </strong> framework.
196      * @throws IOException
197      *             if the action cannot be created.
198      */
199     protected Action createAction(final String className,
200             final HttpServletRequest request,
201             final HttpServletResponse response,
202             final ActionMapping mapping,
203             final Map actions,
204             final ActionServlet servlet)
205             throws IOException, SystemException {
206         // if the class is not one of our ivata masks actions, get out...
207         if (!className.startsWith("com.ivata.mask.web.struts")) {
208             return null;
209         }
210         // if the class is one of our types, instantiate it accordingly
211         Action action = null;
212         MaskAuthenticator maskAuthenticator = new DefaultMaskAuthenticator();
213         if (className.endsWith("FindAction")) {
214             action = new FindAction(persistenceManager, maskFactory,
215                     maskAuthenticator);
216         } else if (className.endsWith("InputMaskAction")) {
217             action = new InputMaskAction(maskFactory, persistenceManager,
218                     maskAuthenticator);
219         } else if (className.endsWith("ListAction")) {
220             action = new ListAction(persistenceManager, maskFactory,
221                     maskAuthenticator);
222         } else if (className.endsWith("NewAction")) {
223             action = new NewAction(maskFactory, maskAuthenticator);
224         } else {
225             throw new IOException("Unknow action '" + className + "'");
226         }
227         return action;
228     }
229     /***
230      * <p>
231      * Override this method to define how to create action forms in your
232      * environment. This lets you use ivata masks with a standard framework.
233      * </p>
234      *
235      * <p>
236      * The default implementation looks for the form classes it knows about and
237      * initializes them directly. This is a simple starting point; in real life
238      * it is better to use a standard framework such as <a
239      * href="http://picocontainer.org">picocontainer </a>.
240      * </p>
241      *
242      * @param formBeanConfig
243      *            instance of <code>FormBeanConfig</code> defining the form to
244      *            be created.
245      * @param request
246      *            request for which we are creating an action.
247      * @param response
248      *            response we are sending.
249      * @param mapping
250      *            <strong>Struts </strong> mapping.
251      * @return valid action for the path, or <code>null</code> if the form can
252      *         and should be created using the default <strong>Struts </strong>
253      *         framework.
254      * @throws SystemException
255      *             if the form cannot be created for any reason.
256      */
257     protected ActionForm createActionForm(final FormBeanConfig formBeanConfig,
258             final HttpServletRequest request,
259             final HttpServletResponse response,
260             final ActionMapping mapping) throws SystemException {
261         String className = formBeanConfig.getType();
262         // if the class is not one of our ivata masks actions, get out...
263         if (!className.startsWith("com.ivata.mask.web.struts")) {
264             return null;
265         }
266         ActionForm form = null;
267         if (className.endsWith("InputMaskForm")) {
268             String baseClassName = request.getParameter("baseClass");
269             if (baseClassName == null) {
270                 throw new NullPointerException(
271                         "ERROR in MaskRequestProcessor: "
272                                 + "you must specify a request parameter called "
273                                 + "'baseClass'.");
274             }
275             Class baseClass;
276             try {
277                 baseClass = Class.forName(baseClassName);
278             } catch (ClassNotFoundException e) {
279                 throw new RuntimeException("ERROR in MaskRequestProcessor: no "
280                         + "class called '" + baseClassName + "'");
281             }
282             // if we were given a value object class to create specifically,
283             // use that
284             String valueObjectClassName = request
285                     .getParameter("valueObjectClass");
286             ValueObject valueObject;
287             if (valueObjectClassName == null) {
288                 String idString = request.getParameter("idString");
289                 if (idString == null) {
290                     throw new NullPointerException(
291                         "ERROR in MaskRequestProcessor: "
292                         + "you must specify either a request parameter called "
293                         + "'valueObjectClass', or one called 'idString'.");
294                 }
295                 PersistenceSession persistenceSession = persistenceManager
296                         .openSession();
297                 try {
298                     valueObject = persistenceManager.findByPrimaryKey(
299                             persistenceSession, baseClass, idString);
300                 } finally {
301                     persistenceSession.close();
302                 }
303                 if (valueObject == null) {
304                     throw new NullPointerException(
305                             "ERROR in MaskRequestProcessor: "
306                                     + "no value object with id '" + idString
307                                     + "' in class '" + baseClass + "'");
308                 }
309             } else {
310                 Class valueObjectClass;
311                 try {
312                     valueObjectClass = Class.forName(valueObjectClassName);
313                 } catch (ClassNotFoundException e) {
314                     throw new RuntimeException(
315                             "ERROR in MaskRequestProcessor: no "
316                                     + "class called '" + valueObjectClassName
317                                     + "'");
318                 }
319                 try {
320                     valueObject = (ValueObject) valueObjectClass.newInstance();
321                 } catch (InstantiationException e) {
322                     throw new RuntimeException("ERROR ("
323                             + e.getClass().getName() + "): " + e.getMessage());
324                 } catch (IllegalAccessException e) {
325                     throw new RuntimeException("ERROR ("
326                             + e.getClass().getName() + "): " + e.getMessage());
327                 }
328             }
329             String inputMask = request.getParameter("inputMask");
330             if (inputMask == null) {
331                 inputMask = maskFactory.getDefaultInputMask();
332             }
333             Mask mask = maskFactory.getMask(valueObject.getClass(), inputMask);
334             form = new InputMaskForm(valueObject, mask, baseClass);
335         }
336         return form;
337     }
338     /***
339      * Used to generate convertors to convert property types from strings.
340      * @return Returns the FieldValueConvertor factory.
341      */
342     public FieldValueConvertorFactory getFieldValueConvertorFactory() {
343         return fieldValueConvertorFactory;
344     }
345     /***
346      * Refer to {@link }.
347      *
348      * @param servletParam
349      * @param moduleConfigParam
350      * @throws javax.servlet.ServletException
351      * @see org.apache.struts.action.RequestProcessor#init(org.apache.struts.action.ActionServlet, org.apache.struts.config.ModuleConfig)
352      */
353     public void init(ActionServlet servletParam, ModuleConfig moduleConfigParam)
354             throws ServletException {
355     }
356     /***
357      * <p>
358      * Overridden to populate forms, and convert the string values of value
359      * object properties into the correct form for their class.
360      * </p>
361      *
362      * @param request
363      *            servlet request we are processing.
364      * @param response
365      *            servlet response we are creating.
366      * @param form
367      *            form instance we are populating.
368      * @param mapping
369      *            <strong>Struts </strong> mapping we are using.
370      *
371      * @exception ServletException
372      *                if an exception is thrown while setting property values
373      * @see org.apache.struts.action.RequestProcessor#processPopulate
374      */
375     protected void processPopulate(final HttpServletRequest request,
376             final HttpServletResponse response,
377             final ActionForm form, final ActionMapping mapping)
378             throws ServletException {
379         // if cancel was pressed, don't submit the form
380         if ((request.getParameter(Constants.CANCEL_PROPERTY) != null)
381                 || (request.getParameter(Constants.CANCEL_PROPERTY_X) != null)) {
382             request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
383             return;
384         }
385         form.reset(mapping, request);
386         Enumeration parameterNames = request.getParameterNames();
387         Map properties = new HashMap();
388         ValidationErrors allErrors = new ValidationErrors();
389         while (parameterNames.hasMoreElements()) {
390             String parameterName = (String) parameterNames.nextElement();
391             // we're only interested in value object fields
392             if (!parameterName
393                     .startsWith(FieldValueConvertorConstants
394                             .PARAMETER_NAME_PREFIX)) {
395                 setNonFieldProperty(form, parameterName, request);
396                 continue;
397             }
398             setFieldProperty(form, parameterName, request, allErrors);
399         }
400         if (!allErrors.isEmpty()) {
401             request.setAttribute(
402                     FieldValueConvertorConstants.ERROR_REQUEST_ATTRIBUTE,
403                     allErrors);
404         }
405     }
406     /***
407      * <p>
408      * Set a target type of <code>Collection.</code>
409      * </p>
410      *
411      * @param request Refer to {@link #processPopulate}.
412      * @param parameterName name of the request parameter which evaluates to
413      * a collection.
414      * @param propertyType name of the concrete target class, a subclass of
415      * <code>Collection</code>.
416      * @param field field definition for the target.
417      * @param valueObject value object which contains the collection as a
418      * property.
419      * @param allErrors used to store all errors encountered.
420      * @throws ServletException if the collection cannot be set for any reason.
421      */
422     private void setCollection(final HttpServletRequest request,
423             final String parameterName,
424             final Class propertyType,
425             final Field field,
426             final ValueObject valueObject,
427             final ValidationErrors allErrors)
428             throws ServletException {
429         Collection collection;
430         try {
431             collection = (Collection) PropertyUtils.getProperty(valueObject,
432                     field.getName());
433         } catch (IllegalAccessException e) {
434             throw new ServletException(e);
435         } catch (InvocationTargetException e) {
436             throw new ServletException(e);
437         } catch (NoSuchMethodException e) {
438             throw new ServletException(e);
439         }
440         Class dOClass = field.getDOClass();
441         if (dOClass == null) {
442             throw new NullPointerException(
443                 "ERROR: for collection '"
444                 + field.getName()
445                 + "', you must specify a data object class (attribute 'class' "
446                 + "in the ivata masks config).");
447         }
448         collection.clear();
449         // sublists are handled separately - they store values as XML
450         if ("sublist".equals(field.getType())) {
451             setSubList(request, parameterName, propertyType, dOClass, field,
452                     allErrors, collection);
453         } else {
454             // if it is not a sublist, it is a standard array of ids
455             setValueObjectList(request, parameterName, dOClass, collection);
456         }
457     }
458     /***
459      * <p>
460      * Use a value convertor to convert and set a single field value.
461      * </p>
462      *
463      * @param value string equivalent of the field's value.
464      * @param field field defition of the field to be set.
465      * @param descriptor property descriptor for this field in the value object.
466      * @param valueObject value object which contains the field as a
467      * property.
468      * @param allErrors stores all errors encountered processing the form.
469      * @throws ServletException Not thrown by this class. Use in subclasses, if
470      * you cannot set the field value for any reason.
471      */
472     private void setField(final String value,
473             final Field field,
474             final PropertyDescriptor descriptor,
475             final ValueObject valueObject,
476             final ValidationErrors allErrors) throws ServletException {
477         // convert from primitive types to their wrappers
478         Class type = descriptor.getPropertyType();
479         if (type.isPrimitive()) {
480             try {
481                 type = DefaultFieldValueConvertorFactory
482                     .convertPrimitiveType(type);
483             } catch (SystemException e) {
484                 throw new ServletException(e);
485             }
486         }
487         FieldValueConvertor convertor;
488         try {
489             convertor = fieldValueConvertorFactory.getFieldValueConvertorForClass(type);
490         } catch (SystemException e) {
491             throw new ServletException(e);
492         }
493         ValidationErrors theseErrors = convertor.setStringValue(valueObject,
494                 field, value);
495         if (!theseErrors.isEmpty()) {
496             allErrors.addAll(theseErrors);
497         }
498     }
499     /***
500      * Set a property on a form which does represents an <strong>ivata
501      * masks</strong> field.
502      *
503      * @param form form instance we are populating.
504      * @param parameterName name of the parameter containing the field value.
505      * @param request servlet request we are processing.
506      * @param allErrors Any validation errors will be appended to this
507      * collection.
508      * @throws ServletException if the field value cannot be set.
509      */
510     private void setFieldProperty(ActionForm form, String parameterName,
511             HttpServletRequest request, ValidationErrors allErrors)
512             throws ServletException {
513         String fieldId = parameterName
514             .substring(FieldValueConvertorConstants.PARAMETER_NAME_PREFIX
515                     .length());
516         if (fieldId.length() == 0) {
517             if (log.isDebugEnabled()) {
518                 log.debug("Empty field found for '"
519                         + parameterName
520                         + "' on form '"
521                         + form + "'");
522             }
523             return;
524         }
525         PropertyDescriptor descriptor;
526         try {
527             descriptor = PropertyUtils.getPropertyDescriptor(form,
528                     parameterName);
529         } catch (IllegalAccessException e) {
530             throw new ServletException(e);
531         } catch (InvocationTargetException e) {
532             throw new ServletException(e);
533         } catch (IllegalArgumentException e) {
534             descriptor = null;
535         } catch (NoSuchMethodException e) {
536             descriptor = null;
537         }
538         if (descriptor == null) {
539             if (log.isDebugEnabled()) {
540                 log.debug("Property descriptor is null for '"
541                         + parameterName
542                         + "' on form '"
543                         + form + "'");
544             }
545             return;
546         }
547         InputMaskForm inputMaskForm;
548         Mask mask;
549         ValueObject valueObject;
550         if (form instanceof InputMaskForm) {
551             inputMaskForm = (InputMaskForm) form;
552             mask = inputMaskForm.getMask();
553             valueObject = inputMaskForm.getValueObject();
554         } else {
555             return;
556         }
557         Class propertyType = descriptor.getPropertyType();
558 
559         Field field = mask.getField(fieldId);
560         if (field == null) {
561             if (log.isDebugEnabled()) {
562                 log.debug("(Normally OK): no field found for '"
563                         + parameterName
564                         + "' on form '"
565                         + form + "'");
566             }
567             return;
568         }
569         // how we set the field value depends on the type of the feild
570         if (Collection.class.isAssignableFrom(propertyType)) {
571             setCollection(request, parameterName, propertyType, field,
572                     valueObject, allErrors);
573             // if it is a value object, assign it by id
574         } else if (ValueObject.class.isAssignableFrom(propertyType)) {
575             setValueObject(valueObject, field, propertyType, request
576                     .getParameter(parameterName));
577             // otherwise use a specific convertor
578         } else {
579             setField(request.getParameter(parameterName), field,
580                     descriptor, valueObject, allErrors);
581         }
582     }
583     /***
584      * Set a property on a form which does not represent an <strong>ivata
585      * masks</strong> field. This method uses {@link FieldValueConvertor}
586      * instances to convert to and from strings.
587      *
588      * @param form form instance we are populating.
589      * @param parameterName name of the parameter containing the field value.
590      * @param request servlet request we are processing.
591      * @throws ServletException if the field value cannot be set.
592      */
593     private void setNonFieldProperty(ActionForm form, String parameterName,
594             HttpServletRequest request) throws ServletException {
595         PropertyDescriptor descriptor;
596         try {
597             descriptor = PropertyUtils.getPropertyDescriptor(form,
598                     parameterName);
599         } catch (IllegalAccessException e) {
600             throw new ServletException(e);
601         } catch (InvocationTargetException e) {
602             throw new ServletException(e);
603         } catch (NoSuchMethodException e) {
604             // if there is no such method, we can ignore it
605             descriptor = null;
606         }
607         Class type = null;
608 
609         if (descriptor != null) {
610             type = descriptor.getPropertyType();
611             
612         // if this is a dyna form, it is still possible to get the type
613         } else if (form instanceof DynaActionForm) {
614             DynaActionForm dynaForm = (DynaActionForm) form;
615             DynaProperty dynaProperty = dynaForm.getDynaClass()
616                 .getDynaProperty(parameterName);
617             if (dynaProperty != null) {
618                 type = dynaProperty.getType();
619             }
620         }
621 
622         // no descriptor? this could be some other kind of dyna form, in which
623         // case, just try setting the string
624         if (type == null) {
625             if (log.isDebugEnabled()) {
626                 log.debug("(Normally OK): no field found for '"
627                         + parameterName
628                         + "' on form '"
629                         + form
630                         + "'");
631             }
632             try {
633                 PropertyUtils.setProperty(form, parameterName,
634                         request.getParameter(parameterName));
635             } catch (IllegalAccessException e) {
636                 if (log.isDebugEnabled()) {
637                     log.debug("(Normally OK): "
638                             + e.getClass().getName()
639                             + " thrown setting property '"
640                             + parameterName
641                             + "' on form '"
642                             + form
643                             + "'", e);
644                 }
645             } catch (InvocationTargetException e) {
646                 if (log.isDebugEnabled()) {
647                     log.debug("(Normally OK): "
648                             + e.getClass().getName()
649                             + " thrown setting property '"
650                             + parameterName
651                             + "' on form '"
652                             + form
653                             + "'", e);
654                 }
655             } catch (NoSuchMethodException e) {
656                 if (log.isDebugEnabled()) {
657                     log.debug("(Normally OK): "
658                             + e.getClass().getName()
659                             + " thrown setting property '"
660                             + parameterName
661                             + "' on form '"
662                             + form
663                             + "'", e);
664                 }
665             }
666             return;
667         }
668         Object value;
669         // if the descriptor is a string, then set it directly,
670         // otherwise assume the type has a constructor with a string
671         // as an argument - this works for things like Boolean, Integer
672         if (type.isAssignableFrom(String.class)) {
673             value = request.getParameter(parameterName);
674         } else if (Object[].class.isAssignableFrom(type)) {
675             value = request.getParameterValues(parameterName);
676         } else if (type.isAssignableFrom(List.class)) {
677             value = Arrays.asList(request.getParameterValues(parameterName));
678         } else {
679             // get a convertor for this type
680             FieldValueConvertor convertor;
681             try {
682                 convertor = fieldValueConvertorFactory
683                     .getFieldValueConvertorForClass(type);
684             } catch (SystemException e2) {
685                 throw new ServletException(e2);
686             }
687             if (convertor == null) {
688                 Constructor constructor;
689                 try {
690                     constructor = type.getConstructor(new Class[] {
691                                 String.class
692                             });
693                     value = constructor.newInstance(new Object[] {
694                             request.getParameter(parameterName)
695                     });
696                 } catch (SecurityException e) {
697                     throw new ServletException(e);
698                 } catch (NoSuchMethodException e) {
699                     if (log.isDebugEnabled()) {
700                         log.debug("No constructor found of type '"
701                                 + type.getName()
702                                 + "' with a string constructor on form '"
703                                 + form
704                                 + "'");
705                     }
706                     return;
707                 } catch (IllegalArgumentException e) {
708                     throw new ServletException(e);
709                 } catch (InstantiationException e) {
710                     throw new ServletException(e);
711                 } catch (IllegalAccessException e) {
712                     throw new ServletException(e);
713                 } catch (InvocationTargetException e) {
714                     throw new ServletException(e);
715                 }
716             } else {
717                 value = convertor.convertFromString(type,
718                         request.getParameter(parameterName));
719             }
720         }
721         try {
722             PropertyUtils.setProperty(form, parameterName,
723                     value);
724         } catch (IllegalAccessException e1) {
725             throw new ServletException(e1);
726         } catch (InvocationTargetException e1) {
727             throw new ServletException(e1);
728         } catch (NoSuchMethodException e1) {
729             // this is ok - it means there is no setter
730             if (log.isDebugEnabled()) {
731                 log.debug("(Normally OK): no setter for property' "
732                         + parameterName
733                         + "'", e1);
734             }
735         }
736     }
737     /***
738      * <p>
739      * Set all the elements of a value object sublist.
740      * </p>
741      *
742      * @param requestParam Refer to {@link #setCollection}.
743      * @param parameterNameParam Refer to {@link #setCollection}.
744      * @param propertyTypeParam Refer to {@link #setCollection}.
745      * @param dOClassParam Refer to {@link #setCollection}.
746      * @param fieldParam Refer to {@link #setCollection}.
747      * @param allErrorsParam Refer to {@link #setCollection}.
748      * @param collectionParam Refer to {@link #setCollection}.
749      * @throws ServletException Refer to {@link #setCollection}.
750      */
751     private void setSubList(final HttpServletRequest requestParam,
752             final String parameterNameParam,
753             final Class propertyTypeParam,
754             final Class dOClassParam,
755             final Field fieldParam,
756             final ValidationErrors allErrorsParam,
757             final Collection collectionParam)
758             throws ServletException {
759         SAXReader reader = new SAXReader();
760         Document document;
761         try {
762             document = reader.read(new StringReader(requestParam
763                     .getParameter(parameterNameParam)));
764         } catch (DocumentException e1) {
765             throw new ServletException(e1);
766         }
767         Element rootElement = document.getRootElement();
768         // the root element should be 'sublist'
769         if (!"sublist".equals(rootElement.getName())) {
770             throw new ServletException(
771                     "Unexpected root element in sublist. Found '"
772                             + rootElement.getName()
773                             + "' , expected 'sublist'.");
774         }
775         // go thro' the value objects in the root
776         for (int i = 0, rootSize = rootElement.nodeCount(); i < rootSize; ++i) {
777             Node node = rootElement.node(i);
778             // the root element should only contain value
779             // objects...
780             if (!((node instanceof Element) && "valueObject"
781                     .equals(((Element) node).getName()))) {
782                 throw new ServletException(
783                         "Unexpected value object element in sublist. Found '"
784                                 + node.asXML()
785                                 + "' , expected element called 'valueObject'.");
786             }
787             Element dOElement = (Element) node;
788             String id = dOElement.attributeValue("id");
789             String schema = dOElement.attributeValue("schema");
790             // ignore extra value objects just there to give us the schema
791             if ("true".equals(schema)) {
792                 continue;
793             }
794             // if the id is specified, get the value object
795             // we're changing
796             ValueObject subObject;
797             if (!StringHandling.isNullOrEmpty(id)) {
798                 try {
799                     PersistenceSession persistenceSession = persistenceManager
800                             .openSession();
801                     try {
802                         subObject = persistenceManager.findByPrimaryKey(
803                                 persistenceSession, dOClassParam, id);
804                     } finally {
805                         persistenceSession.close();
806                     }
807                 } catch (PersistenceException e) {
808                     throw new ServletException(e);
809                 }
810             } else {
811                 // ...otherwise, we're creating a new value object
812                 // TODO: assuming a default constructor exists
813                 // for the time being - might want to create
814                 // an overridable method here...
815                 try {
816                     subObject = (ValueObject) dOClassParam.getConstructor(null)
817                             .newInstance(null);
818                 } catch (IllegalArgumentException e) {
819                     throw new ServletException(e);
820                 } catch (SecurityException e) {
821                     throw new ServletException(e);
822                 } catch (InstantiationException e) {
823                     throw new ServletException(e);
824                 } catch (IllegalAccessException e) {
825                     throw new ServletException(e);
826                 } catch (InvocationTargetException e) {
827                     throw new ServletException(e);
828                 } catch (NoSuchMethodException e) {
829                     throw new ServletException(e);
830                 }
831             }
832             Mask subMask = fieldParam.getValueObjectMask();
833             // now go thro' all the fields in the value object
834             for (int j = 0, dOSize = dOElement.nodeCount(); j < dOSize; ++j) {
835                 node = dOElement.node(j);
836                 // the value object element should only contain
837                 // fields
838                 if (!((node instanceof Element) && "field"
839                         .equals(((Element) node).getName()))) {
840                     String xML = null;
841                     if (node != null) {
842                         xML = node.asXML();
843                     }
844                     throw new ServletException(
845                             "Unexpected field element in sublist. Found '"
846                                     + xML
847                                     + "' , expected element called 'field'.");
848                 }
849                 Element fieldElement = (Element) node;
850                 String subFieldId = fieldElement.attributeValue("id");
851                 Field subField = subMask.getField(subFieldId);
852                 PropertyDescriptor subDescriptor;
853                 try {
854                     subDescriptor = PropertyUtils.getPropertyDescriptor(
855                             subObject, subFieldId);
856                 } catch (IllegalAccessException e) {
857                     throw new ServletException(e);
858                 } catch (InvocationTargetException e) {
859                     throw new ServletException(e);
860                 } catch (NoSuchMethodException e) {
861                     throw new ServletException(e);
862                 }
863                 Class subPropertyType = subDescriptor.getPropertyType();
864                 try {
865                     FieldValueConvertor subConvertor =
866                         fieldValueConvertorFactory
867                             .getFieldValueConvertorForClass(subPropertyType);
868                 } catch (SystemException e2) {
869                     throw new ServletException(e2);
870                 }
871                 // if it is a value object, assign it by id
872                 if (ValueObject.class.isAssignableFrom(subPropertyType)) {
873                     setValueObject(subObject, subField, subPropertyType,
874                             fieldElement.getText());
875                     // otherwise use a specific convertor
876                 } else {
877                     setField(fieldElement.getText(), subField, subDescriptor,
878                             subObject, allErrorsParam);
879                 }
880             }
881             // if the sub object has an id, then update it, otherwise add it
882             try {
883                 PersistenceSession persistenceSession = persistenceManager
884                         .openSession();
885                 try {
886                     if (StringHandling.isNullOrEmpty(id)) {
887                         subObject = persistenceManager.add(persistenceSession,
888                                 subObject);
889                     } else {
890                         persistenceManager.amend(persistenceSession, subObject);
891                     }
892                 } finally {
893                     persistenceSession.close();
894                 }
895             } catch (PersistenceException e) {
896                 throw new ServletException(e);
897             }
898             collectionParam.add(subObject);
899         }
900     }
901     /***
902      * <p>
903      * Set an individual value object.
904      * </p>
905      *
906      * @param valueObject value object containing the value object to be set.
907      * <b>Note</b> this is the container, not the 'property' value object.
908      * @param field field definition of this value object.
909      * @param propertyType class of value object.
910      * @param id value object unique identifier.
911      * @throws ServletException if the value object cannot be set for any
912      * reason.
913      */
914     private void setValueObject(final ValueObject valueObject,
915             final Field field, final Class propertyType,
916             final String id)
917             throws ServletException {
918         ValueObject subObject;
919         if (StringHandling.isNullOrEmpty(id)) {
920             subObject = null;
921         } else {
922             try {
923                 PersistenceSession persistenceSession = persistenceManager
924                         .openSession();
925                 try {
926                     subObject = persistenceManager.findByPrimaryKey(
927                             persistenceSession, propertyType, id);
928                 } finally {
929                     persistenceSession.close();
930                 }
931                 if (subObject == null) {
932                     throw new NullPointerException(
933                         "ERROR: unable to locate sub-value object of class '"
934                         + propertyType + "', with id '" + id + "'");
935                 }
936             } catch (PersistenceException e) {
937                 throw new ServletException(e);
938             }
939         }
940         try {
941             PropertyUtils.setProperty(valueObject, field.getName(), subObject);
942         } catch (IllegalAccessException e) {
943             throw new ServletException(e);
944         } catch (InvocationTargetException e) {
945             throw new ServletException(e);
946         } catch (NoSuchMethodException e) {
947             throw new ServletException(e);
948         }
949     }
950     /***
951      * <p>
952      * Set a list of value objects, identified by their id values.
953      * </p>
954      *
955      * @param request current servlet request we are processing.
956      * @param parameterName name of the request parameter which contains the
957      * value object list details.
958      * @param dOClass data object class of the value objects in the list.
959      * @param collection collection to add the value objects to.
960      * @throws ServletException if the value objects cannot be set for any
961      * reason.
962      */
963     private void setValueObjectList(final HttpServletRequest request,
964             final String parameterName,
965             final Class dOClass,
966             final Collection collection)
967             throws ServletException {
968         String[] ids = request.getParameterValues(parameterName);
969         for (int i = 0; i < ids.length; i++) {
970             ValueObject subObject;
971             try {
972                 PersistenceSession persistenceSession = persistenceManager
973                         .openSession();
974                 try {
975                     subObject = persistenceManager.findByPrimaryKey(
976                             persistenceSession, dOClass, ids[i]);
977                 } finally {
978                     persistenceSession.close();
979                 }
980             } catch (PersistenceException e) {
981                 throw new ServletException(e);
982             }
983             if (subObject == null) {
984                 throw new NullPointerException(
985                         "ERROR: unable to locate sub value object of class '"
986                                 + dOClass + "', with id '" + ids[i] + "'");
987             }
988             collection.add(subObject);
989         }
990     }
991 }
992