Coverage details for org.chwf.servlet.engine.ArgumentMapper

LineHitsSource
1 /*
2 Chrysalis Web Framework [http://chrysalis.sourceforge.net]
3 Copyright (c) 2002, 2003, 2004, Paul Strack
4  
5 All rights reserved.
6  
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9  
10 1. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12  
13 2. Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16  
17 3. Neither the name of the copyright holder nor the names of its contributors
18 may be used to endorse or promote products derived from this software without
19 specific prior written permission.
20  
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32  
33 package org.chwf.servlet.engine;
34  
35 import java.lang.reflect.Array;
36 import java.util.HashMap;
37 import java.util.LinkedList;
38 import java.util.List;
39 import java.util.Map;
40  
41 import javax.servlet.http.HttpServletRequest;
42  
43 import org.chwf.converter.ConversionException;
44 import org.chwf.converter.Converter;
45 import org.chwf.filter.BeanFilter;
46 import org.chwf.filter.InitializationException;
47 import org.chwf.filter.OperationException;
48 import org.chwf.filter.PropertyFilter;
49 import org.chwf.servlet.Controller;
50 import org.chwf.servlet.ControllerException;
51 import org.chwf.servlet.MissingParameterException;
52 import org.chwf.servlet.MultiMissingParameterException;
53 import org.chwf.servlet.MultiParameterException;
54 import org.chwf.servlet.ParameterException;
55 import org.chwf.util.MiscUtils;
56  
57 /**
58  * Super class of argument mappers.
59  *
60  * @author <a href="mailto:pfstrack@users.sourceforge.net">Paul Strack</a>
61  */
62 abstract class ArgumentMapper {
63  
64   /** The parameter name. */
65   private final String parameter;
66  
67   /** The parameter type. */
68   private final Class type;
69  
70   /** The parameter default value. */
71   private final Object defaultValue;
72  
73   /** The method mapper. */
74   private final MethodMapper methodMapper;
75  
76   /**
77    * Factory method for ArgumentMapper. This method determines and returns
78    * the correct type of argument mapper needed for a given method argument.
79    *
80    * @param parameter The request parameter name. This does not need
81    * to be the same as the method argument name, but the sake of
82    * clarity, it should be.
83    * @param type The argument type.
84    * @param defalt The argument's default value, if it is missing from
85    * the request. This value must match the argument type, or equal
86    * the constants <code>Controller.DEFAULT_NONE</code> or
87    * <code>Controller.DEFAULT_NEW_OBJECT</code>.
88    * @param methodMapper The method mapper for this argument mapper.
89    * @return The argument mapper.
90    * @throws ControllerException If the mapper cannot be initialized.
91    */
92   static ArgumentMapper newInstance(
93     String parameter,
94     Class type,
95     Object defalt,
96     MethodMapper methodMapper)
97     throws ControllerException {
98  
9932    if (defalt == Controller.DEFAULT_NEW_OBJECT) {
1004      return new NewBeanMapper(parameter, type, defalt, methodMapper);
101     }
102  
10328    if (!methodMapper.isInitializer()) {
10423      MethodMapper init = methodMapper.getInitMethod(parameter);
10523      if (init != null) {
1068        return new BeanMapper(parameter, type, defalt, methodMapper, init);
107       }
108     }
109  
11020    if (type.isArray()) {
1111      return new MultiValueMapper(parameter, type, defalt, methodMapper);
112     }
113  
11419    return new SingleValueMapper(parameter, type, defalt, methodMapper);
115   }
116  
117   /**
118    * Constructor for ArgumentMapper.
119    *
120    * @param parameter The request parameter name. This does not need
121    * to be the same as the method argument name, but the sake of
122    * clarity, it should be.
123    * @param type The argument type.
124    * @param defaultValue The argument's default value, if it is missing from
125    * the request. This value must match the argument type, or equal
126    * the constants <code>Controller.DEFAULT_NONE</code> or
127    * <code>Controller.DEFAULT_NEW_OBJECT</code>.
128    * @param methodMapper The method mapper for this argument mapper.
129    * @throws ControllerException If the mapper cannot be initialized.
130    */
131   ArgumentMapper(
132     String parameter,
133     Class type,
134     Object defaultValue,
135     MethodMapper methodMapper)
13632    throws ControllerException {
137  
13832    this.parameter = parameter;
13932    this.type = type;
14032    this.defaultValue = defaultValue;
14132    this.methodMapper = methodMapper;
142  
14332    checkValidityOfDefault();
14431  }
145  
146   /**
147    * Format the data from the request as an appropriate object, ready to
148    * be passed as a method argument.
149    *
150    * @param request The request.
151    * @return The argument value as an object.
152    * @throws ParameterException With accumulated errors.
153    * @throws MissingParameterException For missing parameter.
154    */
155   abstract Object format(HttpServletRequest request)
156     throws ParameterException, MissingParameterException;
157  
158   /**
159    * Returns the mapped request parameter name.
160    *
161    * @return The mapped request parameter name.
162    */
163   String getParameter() {
16422    return parameter;
165   }
166  
167   /**
168    * Returns expected method argument type.
169    *
170    * @return Expected method argument type.
171    */
172   Class getType() {
17313    return type;
174   }
175  
176   /**
177    * Returns the default value for missing request parameters.
178    *
179    * @return The default value for missing request parameters.
180    */
181   Object getDefaultValue() {
18250    return defaultValue;
183   }
184  
185   /**
186    * Returns the method mapper for the method being mapped.
187    *
188    * @return The method mapper for the method being mapped.
189    */
190   MethodMapper getMethodMapper() {
1914    return methodMapper;
192   }
193  
194   /**
195    * Retrieve a converter for the given type.
196    *
197    * @param type The class being converted.
198    * @return The converter.
199    * @throws ControllerException If the converter cannot be initialized.
200    */
201   Converter getConverter(Class type) throws ControllerException {
20219    Converter converter = Converter.getConverter(type);
20319    if (converter.isSimpleType()) {
20418      return converter;
205     } else {
2061      Object[] arguments = { // Message arguments:
207         getParameter(),
208           getType().getName(),
209           getMethodMapper().getMethodName(),
210           getMethodMapper().getControllerClass().getName()};
2111      throw new ControllerException(
212         ControllerMessages.MESSAGE_BAD_PARAMETER_CONVERTER,
213         arguments);
214     }
215   }
216  
217   /**
218    * Retrieve filter for the given class.
219    *
220    * @param cls The class being converted.
221    * @return The filter.
222    * @throws ControllerException If the converter cannot be initialized.
223    */
224   BeanFilter findFilter(Class cls) throws ControllerException {
225     try {
22611      return BeanFilter.findFilter(cls);
227     } catch (InitializationException ex) {
2280      Object[] arguments = { // Message arguments:
229         getParameter(),
230           getType().getName(),
231           getMethodMapper().getMethodName(),
232           getMethodMapper().getControllerClass().getName()};
2330      throw new ControllerException(
234         ControllerMessages.MESSAGE_BAD_BEAN_FILTER,
235         arguments);
236     }
237   }
238  
239   /**
240    * Update a bean using request data.
241    *
242    * @param request The request.
243    * @param bean The bean.
244    * @param filter The filter.
245    * @throws ParameterException For parameter errors.
246    * @throws MissingParameterException For missing parameters.
247    */
248   void updateBean(HttpServletRequest request, Object bean, BeanFilter filter)
249     throws ParameterException, MissingParameterException {
25010    List missingParams = null;
25110    Map invalidParams = null;
252  
25310    PropertyFilter[] properties = filter.getPropertyFilters();
25443    for (int i = 0; i < properties.length; i++) {
25533      PropertyFilter property = properties[i];
25633      if (property.isWriteable()) {
257         try {
25817          String value = request.getParameter(property.getName());
25917          if (value == null) {
2604            if (missingParams == null) {
2613              missingParams = new LinkedList();
262             }
2634            missingParams.add(property.getName());
264           } else {
26513            property.set(bean, value);
266           }
26714        } catch (OperationException ex) {
2682          invalidParams =
269             initInvalidParams(invalidParams, property, ex.getRootCause());
2702        } catch (ConversionException ex) {
2711          invalidParams = initInvalidParams(invalidParams, property, ex);
272         }
273       }
274     }
275  
27610    if (missingParams != null) {
2773      throw new MultiMissingParameterException(missingParams);
278     }
2797    if (invalidParams != null) {
2802      throw new MultiParameterException(invalidParams);
281     }
2825  }
283  
284   /**
285    * Accumulate invalid parameters.
286    *
287    * @param invalidParameters Map of invalid parameters.
288    * @param property The property filter.
289    * @param ex The exception.
290    * @return The updated map.
291    */
292   private static Map initInvalidParams(
293     Map invalidParameters,
294     PropertyFilter property,
295     Throwable ex) {
296  
2973    if (invalidParameters == null) {
2982      invalidParameters = new HashMap();
299     }
3003    invalidParameters.put(property.getName(), ex.getMessage());
3013    return invalidParameters;
302   }
303  
304   /**
305    * Throw a ControllerException if the default is not valid.
306    *
307    * @throws ControllerException If the default is not valid.
308    */
309   private void checkValidityOfDefault() throws ControllerException {
31032    if (!isSpecialDefault(getDefaultValue())
311       && !isInstance(getType(), getDefaultValue())) {
3121      Object[] arguments = { // Message arguments:
313         getDefaultValue(),
314           getParameter(),
315           getType().getName(),
316           getMethodMapper().getMethodName(),
317           getMethodMapper().getControllerClass().getName()};
3181      throw new ControllerException(
319         ControllerMessages.MESSAGE_BAD_PARAMETER_DEFAULT,
320         arguments);
321     }
32231  }
323  
324   /**
325    * True if the value is an instance of the given class. For primitive types,
326    * true if the value is an instance of its wrapper class.
327    *
328    * @param type The expected type.
329    * @param value The object value.
330    * @return true if object is an instance of the class.
331    */
332   private static boolean isInstance(Class type, Object value) {
3336    return MiscUtils.getWrapper(type).isInstance(value);
334   }
335  
336   /**
337    * True if the default is one of the special default values defined in
338    * {@link Controller}.
339    *
340    * @param defaultValue The default value.
341    * @return true if the default is special.
342    */
343   private static boolean isSpecialDefault(Object defaultValue) {
34432    return (
345       (defaultValue == Controller.DEFAULT_NONE)
346         || (defaultValue == Controller.DEFAULT_NEW_OBJECT));
347   }
348  
349   /**
350    * Mapper for single-valued arguments.
351    *
352    * @author Paul Strack
353    */
354   static class SingleValueMapper extends ArgumentMapper {
355  
356     /** The converter for the argument type. */
357     private final Converter converter;
358  
359     /**
360      * Constructor.
361      *
362      * @param parameter The request parameter name. This does not need
363      * to be the same as the method argument name, but the sake of
364      * clarity, it should be.
365      * @param type The argument type.
366      * @param defaultValue The argument's default value, if it is missing from
367      * the request. This value must match the argument type, or equal
368      * the constants <code>Controller.DEFAULT_NONE</code> or
369      * <code>Controller.DEFAULT_NEW_OBJECT</code>.
370      * @param methodMapper The method mapper for this argument mapper.
371      * @throws ControllerException If the mapper cannot be initialized.
372      */
373     SingleValueMapper(
374       String parameter,
375       Class type,
376       Object defaultValue,
377       MethodMapper methodMapper)
378       throws ControllerException {
379  
380       super(parameter, type, defaultValue, methodMapper);
381  
382       this.converter = getConverter(type);
383     }
384  
385     /**
386      * Returns the request parameter value as an object of the argument type.
387      *
388      * @param request The request.
389      * @return The argument value as an object.
390      * @throws ParameterException With accumulated errors.
391      * @throws MissingParameterException For missing parameters.
392      */
393     Object format(HttpServletRequest request)
394       throws ParameterException, MissingParameterException {
395       String value = request.getParameter(getParameter());
396       if (value == null) {
397         if (getDefaultValue() == Controller.DEFAULT_NONE) {
398           throw new MissingParameterException(getParameter());
399         } else {
400           return getDefaultValue();
401         }
402       } else {
403         try {
404           return converter.parse(value);
405         } catch (ConversionException ex) {
406           throw new ParameterException(ex, getParameter());
407         }
408       }
409     }
410   }
411  
412   /**
413    * Mapper for multi-valued arguments.
414    *
415    * @author Paul Strack
416    */
417   static class MultiValueMapper extends ArgumentMapper {
418  
419     /** The converter for the argument type. */
420     private final Converter converter;
421  
422     /**
423      * Constructor.
424      *
425      * @param parameter The request parameter name. This does not need
426      * to be the same as the method argument name, but the sake of
427      * clarity, it should be.
428      * @param type The argument type.
429      * @param defaultValue The argument's default value, if it is missing from
430      * the request. This value must match the argument type, or equal
431      * the constants <code>Controller.DEFAULT_NONE</code> or
432      * <code>Controller.DEFAULT_NEW_OBJECT</code>.
433      * @param methodMapper The method mapper for this argument mapper.
434      * @throws ControllerException If the mapper cannot be initialized.
435      */
436     MultiValueMapper(
437       String parameter,
438       Class type,
439       Object defaultValue,
440       MethodMapper methodMapper)
441       throws ControllerException {
442  
443       super(parameter, type, initDefaultType(type, defaultValue), methodMapper);
444       this.converter = getConverter(type.getComponentType());
445     }
446  
447     /**
448      * Returns the request parameter value(s) as an array of the argument type.
449      *
450      * @param request The request.
451      * @return The argument value as an object.
452      * @throws ParameterException With accumulated errors.
453      */
454     Object format(HttpServletRequest request) throws ParameterException {
455       String[] values = request.getParameterValues(getParameter());
456       if (values == null) {
457         return getDefaultValue();
458       } else {
459         Object results =
460           Array.newInstance(getType().getComponentType(), values.length);
461         for (int i = 0; i < values.length; i++) {
462           try {
463             Array.set(results, i, converter.parse(values[i]));
464           } catch (ConversionException ex) {
465             throw new ParameterException(ex, getParameter());
466           }
467         }
468         return results;
469       }
470     }
471  
472     /**
473      * Creates an empty array for DEFAULT_NONE.
474      *
475      * @param type The base type.
476      * @param defaultValue The default value.
477      * @return The initialized array.
478      */
479     private static Object initDefaultType(Class type, Object defaultValue) {
480       if (Controller.DEFAULT_NONE.equals(defaultValue)) {
481         defaultValue = Array.newInstance(type.getComponentType(), 0);
482       }
483       return defaultValue;
484     }
485   }
486  
487   /**
488    * Mapper for beans initialized with an init method.
489    *
490    * @author Paul Strack
491    */
492   static class BeanMapper extends ArgumentMapper {
493  
494     /** The bean initializer. */
495     private final MethodMapper initMethodMapper;
496  
497     /** The filter for the argument type. */
498     private final BeanFilter filter;
499  
500     /**
501      * Constructor.
502      *
503      * @param parameter The request parameter name. This does not need
504      * to be the same as the method argument name, but the sake of
505      * clarity, it should be.
506      * @param type The argument type.
507      * @param defaultValue The argument's default value, if it is missing from
508      * the request. This value must match the argument type, or equal
509      * the constants <code>Controller.DEFAULT_NONE</code> or
510      * <code>Controller.DEFAULT_NEW_OBJECT</code>.
511      * @param methodMapper The method mapper for this argument mapper.
512      * @param initMethodMapper The init method mapper.
513      * @throws ControllerException If the mapper cannot be initialized.
514      */
515     BeanMapper(
516       String parameter,
517       Class type,
518       Object defaultValue,
519       MethodMapper methodMapper,
520       MethodMapper initMethodMapper)
521       throws ControllerException {
522  
523       super(parameter, type, defaultValue, methodMapper);
524       if (initMethodMapper.getMethod().getReturnType().equals(type)) {
525         this.initMethodMapper = initMethodMapper;
526         this.filter = findFilter(type);
527       } else {
528         Object[] args =
529           {
530             initMethodMapper.getMethodName(),
531             parameter,
532             methodMapper.getMethodName(),
533             methodMapper.getControllerClass().getName()};
534         throw new ControllerException(
535           ControllerMessages.MESSAGE_BEAN_PARAMETER_TYPE_MISMATCH,
536           args);
537       }
538     }
539  
540     /**
541      * Initializes, updates and returns a bean argument. In particular, the bean
542      * is initialized using its init method, all bean properties are updated
543      * using data in the request, and the updated bean is returned, ready to use
544      * as a method argument.
545      *
546      * @param request The request.
547      * @return The argument value as an object.
548      * @throws ParameterException With accumulated errors.
549      * @throws MissingParameterException For missing parameters.
550      */
551     Object format(HttpServletRequest request)
552       throws ParameterException, MissingParameterException {
553       try {
554         Object bean = initMethodMapper.invoke(request);
555         updateBean(request, bean, filter);
556         return bean;
557       } catch (MissingParameterException ex) {
558         throw ex;
559       } catch (ParameterException ex) {
560         throw ex;
561       } catch (Exception ex) {
562         throw new ParameterException(ex, getParameter());
563       }
564     }
565  
566   }
567  
568   /**
569    * Mapper for new beans.
570    *
571    * @author Paul Strack
572    */
573   static class NewBeanMapper extends ArgumentMapper {
574  
575     /** The filter for the argument type. */
576     private final BeanFilter filter;
577  
578     /**
579      * Constructor.
580      *
581      * @param parameter The request parameter name. This does not need
582      * to be the same as the method argument name, but the sake of
583      * clarity, it should be.
584      * @param type The argument type.
585      * @param defaultValue The argument's default value, if it is missing from
586      * the request. This value must match the argument type, or equal
587      * the constants <code>Controller.DEFAULT_NONE</code> or
588      * <code>Controller.DEFAULT_NEW_OBJECT</code>.
589      * @param methodMapper The method mapper for this argument mapper.
590      * @throws ControllerException If the mapper cannot be initialized.
591      */
592     NewBeanMapper(
593       String parameter,
594       Class type,
595       Object defaultValue,
596       MethodMapper methodMapper)
597       throws ControllerException {
598  
599       super(parameter, type, defaultValue, methodMapper);
600  
601       this.filter = findFilter(type);
602     }
603  
604     /**
605      * Initializes, updates and returns a bean argument. In particular, the bean
606      * is initialized using to a new bean, all bean properties are updated using
607      * data in the request, and the updated bean is returned, ready to use as a
608      * method argument.
609      *
610      * @param request The request.
611      * @return The argument value as an object.
612      * @throws ParameterException With accumulated errors.
613      * @throws MissingParameterException For missing parameters.
614      */
615     Object format(HttpServletRequest request)
616       throws ParameterException, MissingParameterException {
617       try {
618         Object bean = getType().newInstance();
619         updateBean(request, bean, filter);
620         return bean;
621       } catch (MissingParameterException ex) {
622         throw ex;
623       } catch (ParameterException ex) {
624         throw ex;
625       } catch (Exception ex) {
626         throw new ParameterException(ex, getParameter());
627       }
628     }
629   }
630 }

this report was generated by version 1.0.5 of jcoverage.
visit www.jcoverage.com for updates.

copyright © 2003, jcoverage ltd. all rights reserved.
Java is a trademark of Sun Microsystems, Inc. in the United States and other countries.