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

This page was automatically generated by Maven