Home » commons-validator-1.3.1-src » org.apache.commons » validator » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.commons.validator;
   18   
   19   import java.io.BufferedReader;
   20   import java.io.IOException;
   21   import java.io.InputStream;
   22   import java.io.InputStreamReader;
   23   import java.io.Serializable;
   24   import java.lang.reflect.InvocationTargetException;
   25   import java.lang.reflect.Method;
   26   import java.lang.reflect.Modifier;
   27   import java.util.ArrayList;
   28   import java.util.Collections;
   29   import java.util.List;
   30   import java.util.Map;
   31   import java.util.StringTokenizer;
   32   
   33   import org.apache.commons.logging.Log;
   34   import org.apache.commons.logging.LogFactory;
   35   import org.apache.commons.validator.util.ValidatorUtils;
   36   
   37   /**
   38    * Contains the information to dynamically create and run a validation
   39    * method.  This is the class representation of a pluggable validator that can 
   40    * be defined in an xml file with the <validator> element.
   41    *
   42    * <strong>Note</strong>: The validation method is assumed to be thread safe.
   43    *
   44    * @version $Revision: 478334 $ $Date: 2006-11-22 21:31:54 +0000 (Wed, 22 Nov 2006) $
   45    */
   46   public class ValidatorAction implements Serializable {
   47       
   48       /**
   49        * Logger.
   50        */
   51       private transient Log log = LogFactory.getLog(ValidatorAction.class);
   52   
   53       /**
   54        * The name of the validation.
   55        */
   56       private String name = null;
   57   
   58       /**
   59        * The full class name of the class containing
   60        * the validation method associated with this action.
   61        */
   62       private String classname = null;
   63       
   64       /**
   65        * The Class object loaded from the classname.
   66        */
   67       private Class validationClass = null;
   68   
   69       /**
   70        * The full method name of the validation to be performed.  The method
   71        * must be thread safe.
   72        */
   73       private String method = null;
   74       
   75       /**
   76        * The Method object loaded from the method name.
   77        */
   78       private Method validationMethod = null;
   79   
   80       /**
   81        * <p>
   82        * The method signature of the validation method.  This should be a comma
   83        * delimited list of the full class names of each parameter in the correct 
   84        * order that the method takes.
   85        * </p>
   86        * <p>
   87        * Note: <code>java.lang.Object</code> is reserved for the
   88        * JavaBean that is being validated.  The <code>ValidatorAction</code>
   89        * and <code>Field</code> that are associated with a field's
   90        * validation will automatically be populated if they are
   91        * specified in the method signature.
   92        * </p>
   93        */
   94       private String methodParams =
   95               Validator.BEAN_PARAM
   96               + ","
   97               + Validator.VALIDATOR_ACTION_PARAM
   98               + ","
   99               + Validator.FIELD_PARAM;
  100               
  101       /**
  102        * The Class objects for each entry in methodParameterList.
  103        */        
  104       private Class[] parameterClasses = null;
  105   
  106       /**
  107        * The other <code>ValidatorAction</code>s that this one depends on.  If 
  108        * any errors occur in an action that this one depends on, this action will 
  109        * not be processsed.
  110        */
  111       private String depends = null;
  112   
  113       /**
  114        * The default error message associated with this action.
  115        */
  116       private String msg = null;
  117   
  118       /**
  119        * An optional field to contain the name to be used if JavaScript is 
  120        * generated.
  121        */
  122       private String jsFunctionName = null;
  123   
  124       /**
  125        * An optional field to contain the class path to be used to retrieve the
  126        * JavaScript function.
  127        */
  128       private String jsFunction = null;
  129   
  130       /**
  131        * An optional field to containing a JavaScript representation of the
  132        * java method assocated with this action.
  133        */
  134       private String javascript = null;
  135   
  136       /**
  137        * If the java method matching the correct signature isn't static, the 
  138        * instance is stored in the action.  This assumes the method is thread 
  139        * safe.
  140        */
  141       private Object instance = null;
  142   
  143       /**
  144        * An internal List representation of the other <code>ValidatorAction</code>s
  145        * this one depends on (if any).  This List gets updated
  146        * whenever setDepends() gets called.  This is synchronized so a call to
  147        * setDepends() (which clears the List) won't interfere with a call to
  148        * isDependency().
  149        */
  150       private List dependencyList = Collections.synchronizedList(new ArrayList());
  151   
  152       /**
  153        * An internal List representation of all the validation method's 
  154        * parameters defined in the methodParams String.
  155        */
  156       private List methodParameterList = new ArrayList();
  157   
  158       /**
  159        * Gets the name of the validator action.
  160        * @return Validator Action name.
  161        */
  162       public String getName() {
  163           return name;
  164       }
  165   
  166       /**
  167        * Sets the name of the validator action.
  168        * @param name Validator Action name.
  169        */
  170       public void setName(String name) {
  171           this.name = name;
  172       }
  173   
  174       /**
  175        * Gets the class of the validator action.
  176        * @return Class name of the validator Action.
  177        */
  178       public String getClassname() {
  179           return classname;
  180       }
  181   
  182       /**
  183        * Sets the class of the validator action.
  184        * @param classname Class name of the validator Action.
  185        */
  186       public void setClassname(String classname) {
  187           this.classname = classname;
  188       }
  189   
  190       /**
  191        * Gets the name of method being called for the validator action.
  192        * @return The method name.
  193        */
  194       public String getMethod() {
  195           return method;
  196       }
  197   
  198       /**
  199        * Sets the name of method being called for the validator action.
  200        * @param method The method name.
  201        */
  202       public void setMethod(String method) {
  203           this.method = method;
  204       }
  205   
  206       /**
  207        * Gets the method parameters for the method.
  208        * @return Method's parameters.
  209        */
  210       public String getMethodParams() {
  211           return methodParams;
  212       }
  213   
  214       /**
  215        * Sets the method parameters for the method.
  216        * @param methodParams A comma separated list of parameters.
  217        */
  218       public void setMethodParams(String methodParams) {
  219           this.methodParams = methodParams;
  220   
  221           this.methodParameterList.clear();
  222   
  223           StringTokenizer st = new StringTokenizer(methodParams, ",");
  224           while (st.hasMoreTokens()) {
  225               String value = st.nextToken().trim();
  226   
  227               if (value != null && value.length() > 0) {
  228                   this.methodParameterList.add(value);
  229               }
  230           }
  231       }
  232   
  233       /**
  234        * Gets the dependencies of the validator action as a comma separated list 
  235        * of validator names.
  236        * @return The validator action's dependencies.
  237        */
  238       public String getDepends() {
  239           return this.depends;
  240       }
  241   
  242       /**
  243        * Sets the dependencies of the validator action.
  244        * @param depends A comma separated list of validator names.
  245        */
  246       public void setDepends(String depends) {
  247           this.depends = depends;
  248   
  249           this.dependencyList.clear();
  250   
  251           StringTokenizer st = new StringTokenizer(depends, ",");
  252           while (st.hasMoreTokens()) {
  253               String depend = st.nextToken().trim();
  254   
  255               if (depend != null && depend.length() > 0) {
  256                   this.dependencyList.add(depend);
  257               }
  258           }
  259       }
  260   
  261       /**
  262        * Gets the message associated with the validator action.
  263        * @return The message for the validator action.
  264        */
  265       public String getMsg() {
  266           return msg;
  267       }
  268   
  269       /**
  270        * Sets the message associated with the validator action.
  271        * @param msg The message for the validator action.
  272        */
  273       public void setMsg(String msg) {
  274           this.msg = msg;
  275       }
  276   
  277       /**
  278        * Gets the Javascript function name.  This is optional and can
  279        * be used instead of validator action name for the name of the
  280        * Javascript function/object.
  281        * @return The Javascript function name.
  282        */
  283       public String getJsFunctionName() {
  284           return jsFunctionName;
  285       }
  286   
  287       /**
  288        * Sets the Javascript function name.  This is optional and can
  289        * be used instead of validator action name for the name of the
  290        * Javascript function/object.
  291        * @param jsFunctionName The Javascript function name.
  292        */
  293       public void setJsFunctionName(String jsFunctionName) {
  294           this.jsFunctionName = jsFunctionName;
  295       }
  296   
  297       /**
  298        * Sets the fully qualified class path of the Javascript function.
  299        * <p>
  300        * This is optional and can be used <strong>instead</strong> of the setJavascript().
  301        * Attempting to call both <code>setJsFunction</code> and <code>setJavascript</code>
  302        * will result in an <code>IllegalStateException</code> being thrown. </p>
  303        * <p>
  304        * If <strong>neither</strong> setJsFunction or setJavascript is set then 
  305        * validator will attempt to load the default javascript definition.
  306        * </p>
  307        * <pre>
  308        * <b>Examples</b>
  309        *   If in the validator.xml :
  310        * #1:
  311        *      &lt;validator name="tire"
  312        *            jsFunction="com.yourcompany.project.tireFuncion"&gt;
  313        *     Validator will attempt to load com.yourcompany.project.validateTireFunction.js from
  314        *     its class path.
  315        * #2:
  316        *    &lt;validator name="tire"&gt;
  317        *      Validator will use the name attribute to try and load
  318        *         org.apache.commons.validator.javascript.validateTire.js
  319        *      which is the default javascript definition.
  320        * </pre>
  321        * @param jsFunction The Javascript function's fully qualified class path.
  322        */
  323       public void setJsFunction(String jsFunction) {
  324           if (javascript != null) {
  325               throw new IllegalStateException("Cannot call setJsFunction() after calling setJavascript()");
  326           }
  327   
  328           this.jsFunction = jsFunction;
  329       }
  330   
  331       /**
  332        * Gets the Javascript equivalent of the java class and method
  333        * associated with this action.
  334        * @return The Javascript validation.
  335        */
  336       public String getJavascript() {
  337           return javascript;
  338       }
  339   
  340       /**
  341        * Sets the Javascript equivalent of the java class and method
  342        * associated with this action.
  343        * @param javascript The Javascript validation.
  344        */
  345       public void setJavascript(String javascript) {
  346           if (jsFunction != null) {
  347               throw new IllegalStateException("Cannot call setJavascript() after calling setJsFunction()");
  348           }
  349   
  350           this.javascript = javascript;
  351       }
  352   
  353       /**
  354        * Initialize based on set.
  355        */
  356       protected void init() {
  357           this.loadJavascriptFunction();
  358       }
  359   
  360       /**
  361        * Load the javascript function specified by the given path.  For this
  362        * implementation, the <code>jsFunction</code> property should contain a 
  363        * fully qualified package and script name, separated by periods, to be 
  364        * loaded from the class loader that created this instance.
  365        *
  366        * TODO if the path begins with a '/' the path will be intepreted as 
  367        * absolute, and remain unchanged.  If this fails then it will attempt to 
  368        * treat the path as a file path.  It is assumed the script ends with a 
  369        * '.js'.
  370        */
  371       protected synchronized void loadJavascriptFunction() {
  372   
  373           if (this.javascriptAlreadyLoaded()) {
  374               return;
  375           }
  376   
  377           if (getLog().isTraceEnabled()) {
  378               getLog().trace("  Loading function begun");
  379           }
  380   
  381           if (this.jsFunction == null) {
  382               this.jsFunction = this.generateJsFunction();
  383           }
  384   
  385           String javascriptFileName = this.formatJavascriptFileName();
  386   
  387           if (getLog().isTraceEnabled()) {
  388               getLog().trace("  Loading js function '" + javascriptFileName + "'");
  389           }
  390   
  391           this.javascript = this.readJavascriptFile(javascriptFileName);
  392   
  393           if (getLog().isTraceEnabled()) {
  394               getLog().trace("  Loading javascript function completed");
  395           }
  396   
  397       }
  398   
  399       /**
  400        * Read a javascript function from a file.
  401        * @param javascriptFileName The file containing the javascript.
  402        * @return The javascript function or null if it could not be loaded.
  403        */
  404       private String readJavascriptFile(String javascriptFileName) {
  405           ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  406           if (classLoader == null) {
  407               classLoader = this.getClass().getClassLoader();
  408           }
  409   
  410           InputStream is = classLoader.getResourceAsStream(javascriptFileName);
  411           if (is == null) {
  412               is = this.getClass().getResourceAsStream(javascriptFileName);
  413           }
  414   
  415           if (is == null) {
  416               getLog().debug("  Unable to read javascript name "+javascriptFileName);
  417               return null;
  418           }
  419   
  420           StringBuffer buffer = new StringBuffer();
  421           BufferedReader reader = new BufferedReader(new InputStreamReader(is));
  422           try {
  423               String line = null;
  424               while ((line = reader.readLine()) != null) {
  425                   buffer.append(line + "\n");
  426               }
  427   
  428           } catch(IOException e) {
  429               getLog().error("Error reading javascript file.", e);
  430   
  431           } finally {
  432               try {
  433                   reader.close();
  434               } catch(IOException e) {
  435                   getLog().error("Error closing stream to javascript file.", e);
  436               }
  437           }
  438           
  439           String function = buffer.toString();
  440           return function.equals("") ? null : function;
  441       }
  442   
  443       /**
  444        * @return A filename suitable for passing to a 
  445        * ClassLoader.getResourceAsStream() method.
  446        */
  447       private String formatJavascriptFileName() {
  448           String name = this.jsFunction.substring(1);
  449   
  450           if (!this.jsFunction.startsWith("/")) {
  451               name = jsFunction.replace('.', '/') + ".js";
  452           }
  453   
  454           return name;
  455       }
  456   
  457       /**
  458        * @return true if the javascript for this action has already been loaded.
  459        */
  460       private boolean javascriptAlreadyLoaded() {
  461           return (this.javascript != null);
  462       }
  463   
  464       /**
  465        * Used to generate the javascript name when it is not specified.
  466        */
  467       private String generateJsFunction() {
  468           StringBuffer jsName =
  469                   new StringBuffer("org.apache.commons.validator.javascript");
  470   
  471           jsName.append(".validate");
  472           jsName.append(name.substring(0, 1).toUpperCase());
  473           jsName.append(name.substring(1, name.length()));
  474   
  475           return jsName.toString();
  476       }
  477   
  478       /**
  479        * Checks whether or not the value passed in is in the depends field.
  480        * @param validatorName Name of the dependency to check.
  481        * @return Whether the named validator is a dependant.
  482        */
  483       public boolean isDependency(String validatorName) {
  484           return this.dependencyList.contains(validatorName);
  485       }
  486   
  487       /**
  488        * Returns the dependent validator names as an unmodifiable
  489        * <code>List</code>.
  490        * @return List of the validator action's depedents.
  491        */
  492       public List getDependencyList() {
  493           return Collections.unmodifiableList(this.dependencyList);
  494       }
  495   
  496       /**
  497        * Returns a string representation of the object.
  498        * @return a string representation.
  499        */
  500       public String toString() {
  501           StringBuffer results = new StringBuffer("ValidatorAction: ");
  502           results.append(name);
  503           results.append("\n");
  504   
  505           return results.toString();
  506       }
  507       
  508       /**
  509        * Dynamically runs the validation method for this validator and returns 
  510        * true if the data is valid.
  511        * @param field
  512        * @param params A Map of class names to parameter values.
  513        * @param results
  514        * @param pos The index of the list property to validate if it's indexed.
  515        * @throws ValidatorException
  516        */
  517       boolean executeValidationMethod(
  518           Field field,
  519           Map params,
  520           ValidatorResults results,
  521           int pos)
  522           throws ValidatorException {
  523   
  524           params.put(Validator.VALIDATOR_ACTION_PARAM, this);
  525   
  526           try {
  527               if (this.validationMethod == null) {
  528                   synchronized(this) {
  529                       ClassLoader loader = this.getClassLoader(params);
  530                       this.loadValidationClass(loader);
  531                       this.loadParameterClasses(loader);
  532                       this.loadValidationMethod();
  533                   }
  534               }
  535   
  536               Object[] paramValues = this.getParameterValues(params);
  537               
  538               if (field.isIndexed()) {
  539                   this.handleIndexedField(field, pos, paramValues);
  540               }
  541   
  542               Object result = null;
  543               try {
  544                   result =
  545                       validationMethod.invoke(
  546                           getValidationClassInstance(),
  547                           paramValues);
  548   
  549               } catch (IllegalArgumentException e) {
  550                   throw new ValidatorException(e.getMessage());
  551               } catch (IllegalAccessException e) {
  552                   throw new ValidatorException(e.getMessage());
  553               } catch (InvocationTargetException e) {
  554   
  555                   if (e.getTargetException() instanceof Exception) {
  556                       throw (Exception) e.getTargetException();
  557   
  558                   } else if (e.getTargetException() instanceof Error) {
  559                       throw (Error) e.getTargetException();
  560                   }
  561               }
  562   
  563               boolean valid = this.isValid(result);
  564               if (!valid || (valid && !onlyReturnErrors(params))) {
  565                   results.add(field, this.name, valid, result);
  566               }
  567   
  568               if (!valid) {
  569                   return false;
  570               }
  571   
  572               // TODO This catch block remains for backward compatibility.  Remove
  573               // this for Validator 2.0 when exception scheme changes.
  574           } catch (Exception e) {
  575               if (e instanceof ValidatorException) {
  576                   throw (ValidatorException) e;
  577               }
  578   
  579               getLog().error(
  580                   "Unhandled exception thrown during validation: " + e.getMessage(),
  581                   e);
  582   
  583               results.add(field, this.name, false);
  584               return false;
  585           }
  586   
  587           return true;
  588       }
  589       
  590       /**
  591        * Load the Method object for the configured validation method name.
  592        * @throws ValidatorException
  593        */
  594       private void loadValidationMethod() throws ValidatorException {
  595           if (this.validationMethod != null) {
  596               return;
  597           }
  598        
  599           try {
  600               this.validationMethod =
  601                   this.validationClass.getMethod(this.method, this.parameterClasses);
  602        
  603           } catch (NoSuchMethodException e) {
  604               throw new ValidatorException("No such validation method: " + 
  605                   e.getMessage());
  606           }
  607       }
  608       
  609       /**
  610        * Load the Class object for the configured validation class name.
  611        * @param loader The ClassLoader used to load the Class object.
  612        * @throws ValidatorException
  613        */
  614       private void loadValidationClass(ClassLoader loader) 
  615           throws ValidatorException {
  616           
  617           if (this.validationClass != null) {
  618               return;
  619           }
  620           
  621           try {
  622               this.validationClass = loader.loadClass(this.classname);
  623           } catch (ClassNotFoundException e) {
  624               throw new ValidatorException(e.toString());
  625           }
  626       }
  627       
  628       /**
  629        * Converts a List of parameter class names into their Class objects.
  630        * @return An array containing the Class object for each parameter.  This 
  631        * array is in the same order as the given List and is suitable for passing 
  632        * to the validation method.
  633        * @throws ValidatorException if a class cannot be loaded.
  634        */
  635       private void loadParameterClasses(ClassLoader loader)
  636           throws ValidatorException {
  637   
  638           if (this.parameterClasses != null) {
  639               return;
  640           }
  641           
  642           Class[] parameterClasses = new Class[this.methodParameterList.size()];
  643   
  644           for (int i = 0; i < this.methodParameterList.size(); i++) {
  645               String paramClassName = (String) this.methodParameterList.get(i);
  646   
  647               try {
  648                   parameterClasses[i] = loader.loadClass(paramClassName);
  649                       
  650               } catch (ClassNotFoundException e) {
  651                   throw new ValidatorException(e.getMessage());
  652               }
  653           }
  654   
  655           this.parameterClasses = parameterClasses;
  656       }
  657       
  658       /**
  659        * Converts a List of parameter class names into their values contained in 
  660        * the parameters Map.
  661        * @param params A Map of class names to parameter values.
  662        * @return An array containing the value object for each parameter.  This 
  663        * array is in the same order as the given List and is suitable for passing 
  664        * to the validation method.
  665        */
  666       private Object[] getParameterValues(Map params) {
  667   
  668           Object[] paramValue = new Object[this.methodParameterList.size()];
  669   
  670           for (int i = 0; i < this.methodParameterList.size(); i++) {
  671               String paramClassName = (String) this.methodParameterList.get(i);
  672               paramValue[i] = params.get(paramClassName);
  673           }
  674   
  675           return paramValue;
  676       }
  677       
  678       /**
  679        * Return an instance of the validation class or null if the validation 
  680        * method is static so does not require an instance to be executed.
  681        */
  682       private Object getValidationClassInstance() throws ValidatorException {
  683           if (Modifier.isStatic(this.validationMethod.getModifiers())) {
  684               this.instance = null;
  685   
  686           } else {
  687               if (this.instance == null) {
  688                   try {
  689                       this.instance = this.validationClass.newInstance();
  690                   } catch (InstantiationException e) {
  691                       String msg =
  692                           "Couldn't create instance of "
  693                               + this.classname
  694                               + ".  "
  695                               + e.getMessage();
  696   
  697                       throw new ValidatorException(msg);
  698   
  699                   } catch (IllegalAccessException e) {
  700                       String msg =
  701                           "Couldn't create instance of "
  702                               + this.classname
  703                               + ".  "
  704                               + e.getMessage();
  705   
  706                       throw new ValidatorException(msg);
  707                   }
  708               }
  709           }
  710   
  711           return this.instance;
  712       }
  713       
  714       /**
  715        * Modifies the paramValue array with indexed fields.
  716        *
  717        * @param field
  718        * @param pos
  719        * @param paramValues
  720        */
  721       private void handleIndexedField(Field field, int pos, Object[] paramValues)
  722           throws ValidatorException {
  723   
  724           int beanIndex = this.methodParameterList.indexOf(Validator.BEAN_PARAM);
  725           int fieldIndex = this.methodParameterList.indexOf(Validator.FIELD_PARAM);
  726   
  727           Object indexedList[] = field.getIndexedProperty(paramValues[beanIndex]);
  728   
  729           // Set current iteration object to the parameter array
  730           paramValues[beanIndex] = indexedList[pos];
  731   
  732           // Set field clone with the key modified to represent
  733           // the current field
  734           Field indexedField = (Field) field.clone();
  735           indexedField.setKey(
  736               ValidatorUtils.replace(
  737                   indexedField.getKey(),
  738                   Field.TOKEN_INDEXED,
  739                   "[" + pos + "]"));
  740   
  741           paramValues[fieldIndex] = indexedField;
  742       }
  743       
  744       /**
  745        * If the result object is a <code>Boolean</code>, it will return its 
  746        * value.  If not it will return <code>false</code> if the object is 
  747        * <code>null</code> and <code>true</code> if it isn't.
  748        */
  749       private boolean isValid(Object result) {
  750           if (result instanceof Boolean) {
  751               Boolean valid = (Boolean) result;
  752               return valid.booleanValue();
  753           } else {
  754               return (result != null);
  755           }
  756       }
  757   
  758       /**
  759        * Returns the ClassLoader set in the Validator contained in the parameter
  760        * Map.
  761        */
  762       private ClassLoader getClassLoader(Map params) {
  763           Validator v = (Validator) params.get(Validator.VALIDATOR_PARAM);
  764           return v.getClassLoader();
  765       }
  766       
  767       /**
  768        * Returns the onlyReturnErrors setting in the Validator contained in the 
  769        * parameter Map.
  770        */
  771       private boolean onlyReturnErrors(Map params) {
  772           Validator v = (Validator) params.get(Validator.VALIDATOR_PARAM);
  773           return v.getOnlyReturnErrors();
  774       }
  775   
  776       /**
  777        * Accessor method for Log instance.
  778        *
  779        * The Log instance variable is transient and
  780        * accessing it through this method ensures it
  781        * is re-initialized when this instance is
  782        * de-serialized.
  783        *
  784        * @return The Log instance.
  785        */
  786       private Log getLog() {
  787           if (log == null) {
  788               log =  LogFactory.getLog(ValidatorAction.class);
  789           }
  790           return log;
  791       }
  792   }

Save This Page
Home » commons-validator-1.3.1-src » org.apache.commons » validator » [javadoc | source]