2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Copyright 2004-2006 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.shale.validator;
18  
19  import java.io.IOException;
20  import java.io.Serializable;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.net.URL;
25  import java.text.MessageFormat;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.MissingResourceException;
32  import java.util.ResourceBundle;
33  import java.util.StringTokenizer;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  
37  import javax.faces.application.Application;
38  import javax.faces.application.FacesMessage;
39  import javax.faces.component.UIComponent;
40  import javax.faces.context.ExternalContext;
41  import javax.faces.context.FacesContext;
42  import javax.faces.validator.Validator;
43  import javax.faces.validator.ValidatorException;
44  
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.apache.commons.validator.Arg;
48  import org.apache.commons.validator.Field;
49  import org.apache.commons.validator.Form;
50  import org.apache.commons.validator.GenericValidator;
51  import org.apache.commons.validator.ValidatorAction;
52  import org.apache.commons.validator.ValidatorResources;
53  import org.apache.commons.validator.Var;
54  import org.apache.shale.util.ConverterHelper;
55  import org.apache.shale.util.Messages;
56  import org.apache.shale.util.Tags;
57  import org.xml.sax.SAXException;
58  
59  /***
60   * <p>This is a JSF validator that uses
61   *    a Commons Validator to perform validation, either
62   *    client-side or server-side.
63   * </p>
64   *
65   *  <p>The current implementation is dependent on version 1.3 of commons validator.
66   * Some new conventions have been adopted for registering a validation rule in
67   * the validator's XML configuration file.  In the action framework, validation
68   * was suited for declaring rules associated with a form.  This worked well since
69   * generally a single action had the responsibility of handling all the posted
70   * form data at once.</p>
71   *
72   * <p>However, JSF takes a component based approach.  Each component has the
73   * responsibility of managing its posted data and rendering its state.   In the
74   * component based world, it is easier to associated configuration values at
75   * that level versus what works best for struts action.</p>
76   *
77   * <p>In an effort to reuse as much of commons validator and provide a method of
78   * registering new rules, a new convention was adopted for declaring validation
79   * rules.
80   * </p>
81   *
82   * <pre>
83   *    &lt;global&gt;
84   *      &lt;validator name="mask"
85   *                classname="org.apache.commons.validator.GenericValidator"
86   *                method="matchRegexp"
87   *                methodParams="java.lang.String,java.lang.String"
88   *                msg="errors.invalid"
89   *                jsFunctionName="validateMask"
90   *                jsFunction="org.apache.commons.validator.javascript.validateMask"
91   *                depends=""/&gt;
92   *    &lt;/global&gt;
93   * </pre>
94   *
95   * <p>The rules declaration is the same but an added form is required to capture extra
96   * configuration information.  The form is associated with the validation rule using a
97   * naming convention.  The prefix of the form name is "org.apache.shale.validator.XXXX"
98   * where "XXXX" is the validation rule name.</p>
99   *
100  * <pre>
101  *   &lt;formset&gt;
102  *     &lt;form name="org.apache.shale.validator.mask"&gt;
103  * </pre>
104  *
105  *
106  * <p>The form is followed by a field and the property attribute of the form has the
107  * same value as the rule name.</p>
108  *
109  * <pre>
110  *           &lt;field property="mask"&gt;
111  * </pre>
112  *
113  * <p>Within the field definition, arg's are used to define the parameters in order
114  * for message substitution and method argument value resolution.  There are two reserved
115  * name values for the arg node used to define messages and parameters.</p>
116  *
117  * <pre>
118  *               &lt;arg position="0" name="message" key="arg" resource="false"/&gt;
119  *               &lt;arg position="1" name="message" key="mask" resource="false"/&gt;
120  *               &lt;arg position="2" name="message" key="submittedValue" resource="false"/&gt;
121  *
122  *               &lt;arg position="0" name="parameter" key="submittedValue" resource="false"/&gt;
123  *               &lt;arg position="1" name="parameter" key="mask" resource="false"/&gt;
124  * </pre>
125  *
126  * <p>The "message" name arguments defines the possible <code>MessageFormat</code> parameter substitution
127  * where the "position" corresponds to the substitution parameter.</p>
128  *
129  * <pre>
130  *    errors.invalid={0} is invalid.
131  * </pre>
132  *
133  * <p>The "parameter" arguments define the variable names that hold values for the target validatior method
134  * identified by the validator rule name.  The comma delimited class types in the "methodParms" value list
135  * correspond to the parameters by position.</p>
136  *
137  * <pre>
138  *   methodParams="java.lang.String,java.lang.String"
139  * </pre>
140  *
141  * <p>The var node is also used to explicitly define a JavaScript variable type.  If not
142  * defined, the default is "string".  The var-value is ignored because its captured by
143  * the shale commons validator instance.</p>
144  *
145  * <pre>
146  *               &lt;var&gt;
147  *                  &lt;var-name&gt;mask&lt;/var-name&gt;
148  *                  &lt;var-value&gt;&lt;/var-value&gt;
149  *                  &lt;var-jstype&gt;regexp&lt;/var-jstype&gt;
150  *               &lt;/var&gt;
151  * </pre>
152  *
153  * $Id: CommonsValidator.java 422939 2006-07-18 02:01:48Z gvanmatre $
154  */
155 
156 public class CommonsValidator implements Validator, Serializable {
157 
158     // -------------------------------------------------------- Static Variables
159 
160 
161     /***
162      * <p>Log instance for this class.</p>
163      */
164     private static Logger logger = Logger.getLogger("org.apache.shale.validator");
165 
166     /***
167      * <p>Map of standard types: boolean, byte, char, etc.
168      *    and their corresponding classes.</p>
169      */
170     private static Map standardTypes;
171 
172 
173     /***
174      * <p>Localized messages for this class.</p>
175      */
176     private static final Messages messages =
177         new Messages("org.apache.shale.resources.Bundle");
178 
179 
180     /***
181      * <p>Log instance for this class.</p>
182      */
183     private static final Log log = LogFactory.getLog(CommonsValidator.class);
184 
185 
186     /***
187      * <p>The name of the parent form used for javascript.</p>
188      */
189     private String formName = null;
190 
191     /***
192      * <p>Returns the parent's form name.</p>
193      *
194      * @return form name the validator is contained in
195      */
196     public String getFormName() {
197        return formName;
198     }
199 
200     /***
201      * <p>Sets the validator's owning form name.</p>
202      *
203      * @param formName The new form name
204      */
205     public void setFormName(String formName) {
206        this.formName = formName;
207     }
208 
209 
210     // -------------------------------------------------------- Instance Variables
211 
212     /***
213      * <p>Validator type.</p>
214      */
215     private String type;
216 
217     /***
218      * <p>Enable client-side validation?</p>
219      */
220     private Boolean client;
221 
222 
223     /***
224      * <p>Enable server-side validation?</p>
225      */
226     private Boolean server;
227 
228 
229     /***
230      * <p>The <code>validate</code> method uses this
231      *    as the text of an error message it stores on the
232      *    FacesContext when validation fails.</p>
233      */
234     private String message;
235 
236 
237     /***
238      * <p>Parameters for the specific Commons Validator to be used.</p>
239      */
240     private Map vars = new HashMap();
241 
242     /***
243      * <p>Returns a <code>Map</code> of variables that can be passed to a
244      * commons validator method or used to create a parameterized error
245      * message.  Several of the public properties are contained within
246      * the <code>vars</code> collection.  These include: arg, min, max,
247      * minlength, maxlength, mask, datePatternStrict.</p>
248      *
249      * @return A value paired collection of variables used to invoke a
250      * validator method.
251      */
252     public Map getVars() {
253        return vars;
254     }
255 
256 
257     // -------------------------------------------------------- Transient Variables
258 
259 
260 
261     // ----------------------------------------------------- Property Accessors
262 
263 
264     /***
265      * <p>The setter method for the <code>type</code> property. This property is
266      *    passed through to the Commons Validator.</p>
267      *
268      * @param newValue The new value for the <code>type</code> property.
269      */
270    public void setType(String newValue) {
271        type = newValue;
272    }
273 
274 
275     /***
276      * <p>The getter method for the <code>type</code> property. This property is
277      *    passed through to the Commons Validator.</p>
278      *
279      *  @return validation rule to apply
280      */
281    public String getType() {
282        return type;
283    }
284 
285 
286     /***
287      * <p>The setter method for the <code>client</code> property. This property is
288      *    passed through to the Commons Validator.</p>
289      *
290      * @param newValue The new value for the <code>client</code> property.
291      */
292    public void setClient(Boolean newValue) {
293        client = newValue;
294    }
295 
296 
297     /***
298      * <p>The getter method for the <code>client</code> property. This property is
299      *    passed through to the Commons Validator.</p>
300      *
301      * @return <code>true</code> if using JavaScript validation
302      */
303    public Boolean getClient() {
304        return client;
305    }
306 
307 
308     /***
309      * <p>The setter method for the <code>server</code> property. This property is
310      *    passed through to the Commons Validator.</p>
311      *
312      * @param newValue The new value for the <code>server</code> property.
313      */
314    public void setServer(Boolean newValue) {
315        server = newValue;
316    }
317 
318 
319     /***
320      * <p>The getter method for the <code>server</code> property. This property is
321      *    passed through to the Commons Validator.</p>
322      *
323      * @return <code>true</code> if using server side validation
324      */
325    public Boolean getServer() {
326        return server;
327    }
328 
329 
330     /***
331      * <p>The setter method for the <code>message</code> property. This property is
332      *    passed through to the Commons Validator.</p>
333      *
334      * @param newValue The new value for the <code>message</code> property.
335      */
336    public void setMessage(String newValue) {
337        message = newValue;
338    }
339 
340 
341     /***
342      * <p>The getter method for the <code>message</code> property. This property is
343      *    passed through to the Commons Validator.</p>
344      *
345      * @return validation message override
346      */
347    public String getMessage() {
348        return message;
349    }
350 
351    /***
352     * <p>Name of the <code>arg</code> property in the <code>vars</code> Map.</p>
353     */
354    private static final String ARG_VARNAME = "arg";
355 
356     /***
357      * <p>The setter method for the <code>arg</code> property. This property is
358      *    passed through to the Commons Validator.</p>
359      *
360      * @param newValue The new value for the <code>arg</code> property.
361      */
362    public void setArg(String newValue) {
363        vars.put(ARG_VARNAME, newValue);
364    }
365 
366 
367    /***
368     * <p>Name of the <code>min</code> property in the <code>vars</code> Map.</p>
369     */
370    public static final String MIN_VARNAME = "min";
371 
372     /***
373      * <p>The setter method for the <code>min</code> property. This property is
374      *    passed through to the Commons Validator.</p>
375      *
376      * @param newValue The new value for the <code>min</code> property.
377      */
378    public void setMin(String newValue) {
379        vars.put(MIN_VARNAME, newValue);
380    }
381 
382    /***
383     * <p>Name of the <code>max</code> property in the <code>vars</code> Map.</p>
384     */
385    public static final String MAX_VARNAME = "max";
386 
387     /***
388      * <p>The setter method for the <code>max</code> property. This property is
389      *    passed through to the Commons Validator.</p>
390      *
391      * @param newValue The new value for the <code>max</code> property.
392      */
393    public void setMax(String newValue) {
394        vars.put(MAX_VARNAME, newValue);
395    }
396 
397 
398    /***
399     * <p>Name of the <code>minLength</code> property in the <code>vars</code> Map.</p>
400     */
401    public static final String MIN_LENGTH_VARNAME = "minlength";
402 
403    /***
404      * <p>The setter method for the <code>minlength</code> property. This property is
405      *    passed through to the Commons Validator.</p>
406      *
407      * @param newValue The new value for the <code>minlength</code> property.
408      */
409    public void setMinLength(String newValue) { vars.put(MIN_LENGTH_VARNAME,  newValue); }
410 
411 
412    /***
413     * <p>Name of the <code>maxLength</code> property in the <code>vars</code> Map.</p>
414     */
415    public static final String MAX_LENGTH_VARNAME = "maxlength";
416 
417     /***
418      * <p>The setter method for the <code>maxlength</code> property. This property is
419      *    passed through to the Commons Validator.</p>
420      *
421      * @param newValue The new value for the <code>maxlength</code> property.
422      */
423    public void setMaxLength(String newValue) {
424        vars.put(MAX_LENGTH_VARNAME, newValue);
425    }
426 
427    /***
428     * <p>Name of the <code>mask</code> property in the <code>vars</code> Map.</p>
429     */
430    public static final String MASK_VARNAME = "mask";
431 
432     /***
433      * <p>The setter method for the <code>mask</code> property. This property is
434      *    passed through to the Commons Validator.</p>
435      *
436      * @param newValue The new value for the <code>mask</code> property.
437      */
438    public void setMask(String newValue) {
439        vars.put(MASK_VARNAME, newValue);
440    }
441 
442 
443    /***
444     * <p>Name of the <code>datePatternStrict</code> in the <code>vars</code> Map.</p>
445     */
446    public static final String DATE_PATTERN_STRICT_VARNAME = "datePatternStrict";
447 
448 
449     /***
450      * <p>The setter method for the <code>datePatternStrict</code> property. This property is
451      *    passed through to the Commons Validator.</p>
452      *
453      * @param newValue The new value for the <code>datePatternStrict</code> property.
454      */
455    public void setDatePatternStrict(String newValue) {
456       vars.put(DATE_PATTERN_STRICT_VARNAME, newValue);
457    }
458 
459 
460    /***
461     * This method lazily configures validator resources by reading either
462     * the default <code>validalidator-rules.xml</code> file in
463     * shale-core.jar or the list of resources configured using the init
464     * param <code>org.apache.shale.validator.VALIDATOR_RULES</code>.
465     *
466     * @return validator resources loaded from the configuration file.
467     */
468     private static ValidatorResources getValidatorResources() {
469         final String VALIDATOR_RESOURCES_KEY =
470             "org.apache.shale.validator.resources";
471        FacesContext context = FacesContext.getCurrentInstance();
472        ExternalContext external = context.getExternalContext();
473        Map applicationMap = external.getApplicationMap();
474        ValidatorResources validatorResources
475           = (ValidatorResources) applicationMap.get(VALIDATOR_RESOURCES_KEY);
476        if (validatorResources == null) {
477           try {
478              String pathnames = external.getInitParameter(Globals.VALIDATOR_RULES);
479              if (pathnames == null || pathnames.length() <= 0) {
480                 pathnames = Globals.DEFAULT_VALIDATOR_RULES;
481              }
482              StringTokenizer st = new StringTokenizer(pathnames, ",");
483              List urlList = new ArrayList();
484              while (st.hasMoreTokens()) {
485                 String validatorRules = st.nextToken().trim();
486                 logger.log(Level.INFO,
487                   messages.getMessage("commonsValidator.loadresource",
488                                       new Object[] {validatorRules}));
489                 URL input = external.getResource(validatorRules);
490                 if (input == null) {
491                    input = CommonsValidator.class.getResource(validatorRules);
492                 }
493                 if (input != null) {
494                    urlList.add(input);
495                 } else {
496                    logger.log(Level.WARNING,
497                      messages.getMessage("commonsValidator.skipresource",
498                                           new Object[] {validatorRules}));
499                 }
500              }
501              int urlSize = urlList.size();
502              String[] urlArray = new String[urlSize];
503              for (int urlIndex = 0; urlIndex < urlSize; urlIndex++) {
504                 URL url = (URL) urlList.get(urlIndex);
505                 urlArray[urlIndex] = url.toExternalForm();
506              }
507              validatorResources = new ValidatorResources(urlArray);
508              applicationMap.put(VALIDATOR_RESOURCES_KEY, validatorResources);
509           } catch (IOException ex) {
510              logger.log(Level.SEVERE, messages.getMessage("commonsValidator.loaderror"), ex);
511              return null;
512           } catch (SAXException ex) {
513              logger.log(Level.SEVERE, messages.getMessage("commonsValidator.loaderror"), ex);
514              return null;
515           }
516        }
517 
518        return validatorResources;
519     }
520 
521 
522     /***
523      * <p>Name of the <code>message</code> property in the <code>vars</code> Map.</p>
524      */
525     private static final String MESSAGE_ARG_NAME = "message";
526 
527     /***
528      * <p>Returns an array of values for message parameter replacement
529      * arguments.  The list and ordering is determined by a form
530      * registered in the common validators XML.  The form name
531      * and the fields property is tied to the validation rule name
532      * by convention.  The the <code>arg</code> name attribute is
533      * assumed to be "message" for message argument grouping.</p>
534      *
535      * @param ruleName name of the validation rule
536      * @param localVars snapshot of EL vars captured at renderering time
537      *        and used by the script collector
538      * @return array of objects used to fill the message
539      */
540     protected Object[] getMessageArgs(String ruleName, Map localVars) {
541 
542        Tags tagUtils = new Tags();
543        Arg[] templateArgs = getArgs(MESSAGE_ARG_NAME, ruleName);
544        assert templateArgs != null;
545 
546        Object[] target = new Object[templateArgs.length];
547 
548        for (int i = 0; i < templateArgs.length; i++) {
549           Object value = vars.get(templateArgs[i].getKey());
550 
551           // look for a local var override
552           if (localVars != null && localVars.containsKey(templateArgs[i].getKey())) {
553              value = localVars.get(templateArgs[i].getKey());
554           } else if (value != null && value instanceof String) {
555               // if a String, check for a value binding expression
556              value = tagUtils.eval((String) value);
557           }
558           target[i] = value;
559        }
560 
561        return target;
562     }
563 
564     /***
565      * <p>Returns an array of class types corresponding to the the
566      * target validation rules method signature.  The params are
567      * configured by the <code>validator</code>'s <code>methodParams</code>
568      * attribute.</p>
569      *
570      * @param validationAction the validators configuration bean populated from the XML file.
571      * @return an array of class types for the formal parameter list.
572      * @throws ClassNotFoundException validation rule class not found
573      */
574     protected Class[] loadMethodParamClasses(ValidatorAction validationAction) throws ClassNotFoundException {
575 
576         List tmp = new ArrayList();
577         StringTokenizer tokenizer = new StringTokenizer(validationAction.getMethodParams(), ",");
578         while (tokenizer.hasMoreTokens()) {
579             String token = tokenizer.nextToken().trim();
580             if (token.length() > 0) {
581                 tmp.add(token);
582             }
583         }
584 
585         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
586         if (classLoader == null) {
587             classLoader = this.getClass().getClassLoader();
588         }
589         assert classLoader != null;
590 
591 
592         Class[] parameterClasses = new Class[tmp.size()];
593         for (int i = 0; i < tmp.size(); i++) {
594             String className = (String) tmp.get(i);
595             if (standardTypes.containsKey(className)) {
596                 parameterClasses[i] = (Class) standardTypes.get(className);
597             } else {
598                 parameterClasses[i] = classLoader.loadClass(className);
599             }
600         }
601 
602         return parameterClasses;
603     }
604 
605 
606     /***
607      * <p>The args name used to define the parameter values for
608      * a validation rule.</p>
609      */
610     private static final String PARAMETER_ARG_NAME = "parameter";
611 
612     /***
613      * <p>Returns an array of parameter names in the target validator's
614      * method.  The parameter names are defined in the validators configuration
615      * file under a form and field that correspond to the target rule.
616      * The name attribute of the nested <code>arg</code> is assumed to be
617      * "parameter".
618      *
619      * @param name the name of the target validation rule
620      * @return array of formal parameter names
621      */
622     public String[] getMethodParamNames(String name) {
623         Arg[] templateArgs = getArgs(PARAMETER_ARG_NAME, name);
624         assert templateArgs != null;
625 
626         String[] target = new String[templateArgs.length];
627 
628         for (int i = 0; i < templateArgs.length; i++) {
629            target[i] = templateArgs[i].getKey();
630         }
631 
632         return target;
633     }
634 
635     private static final String FORM_NAME_PREFIX = "org.apache.shale.validator.";
636 
637     /***
638      * <p>Returns validator <code>Arg</code> beans from the configuration file.
639      * The <code>form</code> and <code>field</code> nodes that contain the target
640      * arguments have a naming convention to a validation rule.  This convention
641      * was adopted for JSF validators because the <code>var</code>'s of the defining
642      * validator is defined by the JSF validator object and not in the config file.
643      * The JSF implementation doesn't have a concept of defining global form field
644      * characteristics outside of the associated JSF component.  But, we needed
645      * a place to explicitly declare parameter names and message arguments.<p>
646      *
647      * @param name the name of the <code>arg</code> name attribute.
648      * @param ruleName the name of the validator rule
649      * @return an array of validator <code>Arg</code> beans.
650      */
651     protected static Arg[] getArgs(String name, String ruleName) {
652 
653         StringBuffer formName = new StringBuffer(FORM_NAME_PREFIX);
654         formName.append(ruleName);
655 
656         Form formDef = getValidatorResources().getForm(Locale.getDefault(), formName.toString());
657         assert formDef != null;
658 
659         Field field = formDef.getField(ruleName);
660         assert field != null;
661 
662         Arg[] templateArgs = field.getArgs(name);
663 
664         int max = -1;
665         for (int i = templateArgs.length - 1; i > -1; i--) {
666            if (templateArgs[i] != null) {
667               max = i;
668               break;
669            }
670         }
671         if (max == -1) {
672            return new Arg[0];
673         } else if (max < templateArgs.length - 1) {
674            Arg[] tmp = new Arg[max + 1];
675            System.arraycopy(templateArgs, 0, tmp, 0, max + 1);
676            templateArgs = tmp;
677         }
678         return templateArgs;
679     }
680 
681 
682     /***
683      * <p>Returns the JavaScript type for a <code>var</code> collection
684      * item.  The default is <code>Var.JSTYPE_STRING</code>.  The type
685      * can be overridden by adding a <code>var</code> node to the
686      * <code>field</code> node in the validator configuration file.
687      * The <code>var-value</code> is ignored but a value is required
688      * for well-formness.  The <code>var-jstype</code>
689      * and <code>var-name</code> are the only values used.
690      * The <code>form</code> and the <code>field</code> the <code>var</code>
691      * is nested under has an association with the <code>type</code>.
692      * </p>
693      * @param varName The name of the target variable
694      * @return The JavaScript variable type ("string", "int", "regexp")
695      */
696     public String getVarType(String varName) {
697         StringBuffer formName = new StringBuffer(FORM_NAME_PREFIX);
698         formName.append(getType());
699 
700         Form formDef = getValidatorResources().getForm(Locale.getDefault(), formName.toString());
701         assert formDef != null;
702 
703         Field field = formDef.getField(getType());
704         assert field != null;
705 
706         String jsType = Var.JSTYPE_STRING; // default type
707         Var var = field.getVar(varName);
708 
709         if (var != null && var.getJsType() != null) {
710            jsType = var.getJsType();
711         }
712 
713         return jsType;
714     }
715 
716 
717     /***
718      * <p>Assumed argument name that captures the javascript
719      * validation rule callback.</p>
720      */
721     private static final String JSCALLBACK_ARG_NAME = "jscallback";
722 
723 
724     /***
725      * <p>Returns a mnemonic used to build the commons validator
726      * javascript call back.  This method is invoked from the
727      * {@link org.apache.shale.component.ValidatorScript} component.
728      * The routine looks for an <code>arg</code> with a name of
729      * <code>jscallback</code> under a form name and field
730      * property corresponding to the rule.  If there is not
731      * a matching arg found, the <code>ruleName</code> is
732      * returned as the default.
733      * </p>
734      * @param ruleName name of the target rule to invoke
735      * @return code used to create the javacript call back function
736      */
737     public static String getJsCallbackMnemonic(String ruleName) {
738         Arg[] args = getArgs(JSCALLBACK_ARG_NAME, ruleName);
739         if (args == null || args.length == 0) {
740            return ruleName;
741         } else {
742            return args[0].getKey();
743         }
744     }
745 
746     /***
747      * <p>Loads an array of method parameter values corresponding to the
748      * formal parameter of the target validator's method.<p>
749      * @param context faces context
750      * @param validatorAction <code>ValidatorAction</code> configuration bean.
751      * @param methodParamClasses <code>Class[]</code> of the parameters of the target method.
752      * @return An array of object valuse for each method parameter.
753      */
754     protected Object[] loadMethodParamValues(FacesContext context, ValidatorAction validatorAction, Class[] methodParamClasses) {
755         Tags tagUtils = new Tags();
756 
757         String[] paramNames = getMethodParamNames(validatorAction.getName());
758         assert paramNames != null;
759 
760         Object[] target = new Object[paramNames.length];
761         assert paramNames.length == methodParamClasses.length;
762 
763         for (int i = 0; i < paramNames.length; i++) {
764            Object obj = vars.get(paramNames[i]);
765            if (obj != null && obj instanceof String) {
766               obj = tagUtils.eval((String) obj);
767            }
768            target[i] = convert(context, obj, methodParamClasses[i]);
769         }
770 
771         return target;
772      }
773 
774 
775     /***
776      * <p>Returns the Commons validator action that's appropriate
777      *    for the validator with the given <code>name</code>.</p>
778      *
779      * @param name The name of the validator
780      * @return Validator rules config bean
781      */
782    public static ValidatorAction getValidatorAction(String name) {
783       return getValidatorResources().getValidatorAction(name);
784    }
785    /***
786     * <p>Returns the commons validator action associated with
787     * the <code>type</code> attribute.</p>
788     *
789     * @return Validator rules config bean
790     */
791    public ValidatorAction getValidatorAction() {
792        return getValidatorResources().getValidatorAction(getType());
793    }
794 
795    /***
796     * <p>For a given commons validator rule, returns an array of
797     * rule names that are dependent of the <code>name</code>.
798     * Rule dependencies will be first in the returned array.
799     * </p>
800     * @param name target validator rule
801     * @return array of all dependent rules for the target name
802     */
803    protected String[] getDependencies(String name) {
804 
805        ValidatorAction action = getValidatorAction(name);
806        assert action != null;
807 
808        List dependencies = action.getDependencyList();
809        String[] types = new String[dependencies.size() + 1];
810 
811        for (int i = 0; i < dependencies.size(); i++) {
812            types[i] = (String) dependencies.get(i);
813        }
814        types[types.length - 1] = name;
815 
816        return types;
817    }
818 
819    /***
820     * <p>Name used to store the component's submitted value in the
821     * <code>vars</code> Map.</p>
822     */
823    private static final String SUBMITTED_VALUE_VARNAME = "submittedValue";
824 
825     /***
826      * <p>This <code>validate</code> method is called by JSF to verify
827      *    the component to which the validator is attached.</p>
828      *
829      * @param context The faces context
830      * @param component The component to validate
831      * @param value the component's submitted value after the converter applied.
832      */
833    public void validate(FacesContext context, UIComponent component, Object value) {
834 
835        if (Boolean.FALSE.equals(getServer())) {
836            return;
837        }
838        String[] types = getDependencies(getType());
839        for (int j = 0; j < types.length; j++) {
840            ValidatorAction validatorAction = this.getValidatorAction(types[j]);
841 
842            try {
843                vars.put(SUBMITTED_VALUE_VARNAME, value);
844                Class validatorClass = loadValidatorClass(validatorAction);
845                Class[] paramClasses = this.loadMethodParamClasses(validatorAction);
846                Object[] paramValues = this.loadMethodParamValues(context, validatorAction, paramClasses);
847                Method validatorMethod = this.loadValidatorMethod(validatorAction, validatorClass,  paramClasses);
848                Object validator = null;
849                if (!Modifier.isStatic(validatorMethod.getModifiers())) {
850                    validator = validatorClass.newInstance();
851                }
852                Boolean r = (Boolean) validatorMethod.invoke(validatorClass, paramValues);
853                if (r.equals(Boolean.FALSE)) {
854                    throw new ValidatorException(new FacesMessage(
855                            FacesMessage.SEVERITY_ERROR,
856                            getErrorMessage(context, validatorAction, null), null));
857                }
858            } catch (IllegalArgumentException e) {
859                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
860                        new Object[] {getType(), component.getId()}), e);
861            } catch (ClassNotFoundException e) {
862                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
863                        new Object[] {getType(), component.getId()}), e);
864            } catch (InstantiationException e) {
865                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
866                        new Object[] {getType(), component.getId()}), e);
867            } catch (IllegalAccessException e) {
868                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
869                        new Object[] {getType(), component.getId()}), e);
870            } catch (NoSuchMethodException e) {
871                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
872                        new Object[] {getType(), component.getId()}), e);
873            } catch (InvocationTargetException e) {
874                throw new RuntimeException(messages.getMessage("commonsValidator.intException",
875                        new Object[] {getType(), component.getId()}), e);
876            } finally {
877                vars.remove(SUBMITTED_VALUE_VARNAME);
878            }
879 
880        }
881    }
882 
883 
884    /***
885     * <p>Loads the commons validator class containing the target rule.</p>
886     * @param validatorAction the validator rules config bean
887     * @return the class having the target validation method
888     *
889     * @throws ClassNotFoundException if the specified class cannot be found
890     * @throws InstantiationException if a new instance cannot be instantiated
891     * @throws IllegalAccessException if there is no public constructor
892     */
893    protected Class loadValidatorClass(ValidatorAction validatorAction)
894      throws ClassNotFoundException, InstantiationException, IllegalAccessException {
895 
896        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
897        if (classLoader == null) {
898            classLoader = this.getClass().getClassLoader();
899        }
900        assert classLoader != null;
901 
902        //find the target class having the validator rule
903        Class validatorClass = classLoader.loadClass(validatorAction.getClassname());
904 
905        return validatorClass;
906    }
907 
908    /***
909     * <p>Loads the <code>Method</code> of the <code>validatorClass</code> having
910     * using definitions from the <code>validatorAction</code> bean.
911     * </p>
912     * @param validatorAction The config info bean of the target rule.
913     * @param validatorClass The class having the validation method.
914     * @param methodParamClasses The method formal parameter class signature.
915     * @return target commons validator method to invoke.
916     * @throws NoSuchMethodException if the specified method cannot be found
917     */
918    protected Method loadValidatorMethod(ValidatorAction validatorAction,
919            Class validatorClass,
920            Class[] methodParamClasses) throws NoSuchMethodException {
921        //find the method on the target validator rule class
922        Method validatorMethod = validatorClass.getMethod(validatorAction.getMethod(), methodParamClasses);
923        return validatorMethod;
924    }
925 
926 
927     /***
928      * <p>Retrieves an error message, using the validator's message combined
929      *    with the errant value.</p>
930      * @param context the all knowing faces conext object.
931      * @param validatorAction config bean defining the commons validator rule.
932      * @param localVars snapshot of EL variables at renderering time; used by client side
933      * @return the message after parameter substitution.
934      */
935    public String getErrorMessage(FacesContext context, ValidatorAction validatorAction, Map localVars) {
936       final String DEFAULT_BUNDLE_NAME = "org.apache.shale.validator.messages";
937 
938       Locale locale = context.getViewRoot().getLocale();
939       String msg = getMessage();
940       if (msg == null) {
941          String msgkey = validatorAction.getMsg();
942          ClassLoader loader = Thread.currentThread().getContextClassLoader();
943          if (loader == null) { loader = getClass().getClassLoader(); }
944          Application app = context.getApplication();
945          String appBundleName = app.getMessageBundle();
946          if (appBundleName != null) {
947             ResourceBundle bundle
948                = ResourceBundle.getBundle(appBundleName, locale, loader);
949             if (bundle != null) {
950                try {
951                   msg = bundle.getString(msgkey);
952                } catch (MissingResourceException ex) {
953                    ; // Ignore this
954                }
955             }
956          }
957          if (msg == null) {
958             ResourceBundle bundle
959                = ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale, loader);
960             if (bundle != null) {
961                try {
962                   msg = bundle.getString(msgkey);
963                } catch (MissingResourceException ex) {
964                    ; // Ignore this
965                }
966             }
967          }
968       }
969       Object[] params = getMessageArgs(validatorAction.getName(), localVars);
970       msg = new MessageFormat(msg, locale).format(params);
971       return msg;
972    }
973 
974 
975     // ----------------------------------------------------- Static Initialization
976 
977     /***
978      * <p>A utility method that converts an object to an instance
979      *    of a given class, such as converting <code>"true"</code>
980      *    for example, into <code>Boolean.TRUE</code>.</p>
981      * <p>If the component passed to this method is an instance of
982      *    <code>EditableValueHolder</code> and the object's class is
983      *    <code>String</code>, this method returns the component's
984      *     submitted value, without converting it to a string. The
985      *     <code>component</code> parameter can be <code>null</code>.
986      *  </p>
987      *
988      * @param context faces context
989      * @param obj The object to convert
990      * @param cl The type of object to convert to
991      * @return target object type
992      */
993    private static Object convert(FacesContext context, Object obj, Class cl) {
994 
995       ConverterHelper converterHelper = new ConverterHelper();
996       // correct target type
997       if (cl.isInstance(obj)) {
998           return obj;
999       }
1000       // target type is String
1001       if (cl == String.class) {
1002           if (obj instanceof String) {
1003              return obj;
1004           } else {
1005              return converterHelper.asString(context, obj.getClass(), obj);
1006           }
1007       }
1008       
1009       if (obj instanceof String) {   
1010           // String to object
1011           return converterHelper.asObject(context, cl, (String) obj);  
1012       } else {
1013           //Object to String
1014           String source = converterHelper.asString(context, obj.getClass(), obj);
1015           // String to Object
1016           return converterHelper.asObject(context, cl, source);
1017       }
1018 
1019    }
1020 
1021 
1022     /***
1023      * <p>A utility method that returns <code>true</code> if
1024      *    the supplied string has a length greater than zero.
1025      * </p>
1026      *
1027      * @param str The string
1028      * @return <code>true</code> if not an empty String
1029      */
1030    // these two methods are referenced in validator-utils.xml
1031    public static boolean isSupplied(String str) {
1032       return str.trim().length() > 0;
1033    }
1034 
1035 
1036     /***
1037      * <p>A utility method that returns <code>true</code> if
1038      *    the supplied string represents a date.</p>
1039      *
1040      * @param d The string representation of the date.
1041      * @param datePatternStrict Commons validator property
1042      * @return <code>true</code> if <code>d</code> is a date
1043      */
1044    public static boolean isDate(String d, String datePatternStrict) {
1045       return GenericValidator.isDate(d, datePatternStrict, true);
1046    }
1047 
1048 
1049     // ----------------------------------------------------- Static Initialization
1050 
1051 
1052     /***
1053      * <p>Standard types for conversions</p>
1054      */
1055    static {
1056       standardTypes = new HashMap();
1057       standardTypes.put("boolean", boolean.class);
1058       standardTypes.put("byte", byte.class);
1059       standardTypes.put("char", char.class);
1060       standardTypes.put("double", double.class);
1061       standardTypes.put("float", float.class);
1062       standardTypes.put("int", int.class);
1063       standardTypes.put("long", long.class);
1064       standardTypes.put("short", short.class);
1065       standardTypes.put("java.lang.String", String.class);
1066    }
1067 
1068 }