001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator; 018 019import java.io.Serializable; 020import java.lang.reflect.InvocationTargetException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.StringTokenizer; 029 030import org.apache.commons.beanutils.PropertyUtils; 031import org.apache.commons.collections.FastHashMap; // DEPRECATED 032import org.apache.commons.validator.util.ValidatorUtils; 033 034/** 035 * This contains the list of pluggable validators to run on a field and any 036 * message information and variables to perform the validations and generate 037 * error messages. Instances of this class are configured with a 038 * <field> xml element. 039 * <p> 040 * The use of FastHashMap is deprecated and will be replaced in a future 041 * release. 042 * </p> 043 * 044 * @see org.apache.commons.validator.Form 045 */ 046// TODO mutable non-private fields 047public class Field implements Cloneable, Serializable { 048 049 private static final long serialVersionUID = -8502647722530192185L; 050 051 /** 052 * This is the value that will be used as a key if the <code>Arg</code> 053 * name field has no value. 054 */ 055 private static final String DEFAULT_ARG = 056 "org.apache.commons.validator.Field.DEFAULT"; 057 058 /** 059 * This indicates an indexed property is being referenced. 060 */ 061 public static final String TOKEN_INDEXED = "[]"; 062 063 /** 064 * The start of a token. 065 */ 066 protected static final String TOKEN_START = "${"; 067 068 /** 069 * The end of a token. 070 */ 071 protected static final String TOKEN_END = "}"; 072 073 /** 074 * A Vriable token. 075 */ 076 protected static final String TOKEN_VAR = "var:"; 077 078 /** 079 * The Field's property name. 080 */ 081 protected String property; 082 083 /** 084 * The Field's indexed property name. 085 */ 086 protected String indexedProperty; 087 088 /** 089 * The Field's indexed list property name. 090 */ 091 protected String indexedListProperty; 092 093 /** 094 * The Field's unique key. 095 */ 096 protected String key; 097 098 /** 099 * A comma separated list of validator's this field depends on. 100 */ 101 protected String depends; 102 103 /** 104 * The Page Number 105 */ 106 protected int page; 107 108 /** 109 * The flag that indicates whether scripting should be generated 110 * by the client for client-side validation. 111 * @since 1.4 112 */ 113 protected boolean clientValidation = true; 114 115 /** 116 * The order of the Field in the Form. 117 */ 118 protected int fieldOrder; 119 120 /** 121 * Internal representation of this.depends String as a List. This List 122 * gets updated whenever setDepends() gets called. This List is 123 * synchronized so a call to setDepends() (which clears the List) won't 124 * interfere with a call to isDependency(). 125 */ 126 private final List<String> dependencyList = Collections.synchronizedList(new ArrayList<>()); 127 128 /** 129 * @deprecated Subclasses should use getVarMap() instead. 130 */ 131 @Deprecated 132 protected FastHashMap hVars = new FastHashMap(); // <String, Var> 133 134 /** 135 * @deprecated Subclasses should use getMsgMap() instead. 136 */ 137 @Deprecated 138 protected FastHashMap hMsgs = new FastHashMap(); // <String, Msg> 139 140 /** 141 * Holds Maps of arguments. args[0] returns the Map for the first 142 * replacement argument. Start with a 0 length array so that it will 143 * only grow to the size of the highest argument position. 144 * @since 1.1 145 */ 146 @SuppressWarnings("unchecked") // cannot instantiate generic array, so have to assume this is OK 147 protected Map<String, Arg>[] args = new Map[0]; 148 149 /** 150 * Add an <code>Arg</code> to the replacement argument list. 151 * @since 1.1 152 * @param arg Validation message's argument. 153 */ 154 public void addArg(final Arg arg) { 155 // TODO this first if check can go away after arg0, etc. are removed from dtd 156 if (arg == null || arg.getKey() == null || arg.getKey().isEmpty()) { 157 return; 158 } 159 160 determineArgPosition(arg); 161 ensureArgsCapacity(arg); 162 163 Map<String, Arg> argMap = this.args[arg.getPosition()]; 164 if (argMap == null) { 165 argMap = new HashMap<>(); 166 this.args[arg.getPosition()] = argMap; 167 } 168 169 if (arg.getName() == null) { 170 argMap.put(DEFAULT_ARG, arg); 171 } else { 172 argMap.put(arg.getName(), arg); 173 } 174 175 } 176 177 /** 178 * Add a <code>Msg</code> to the <code>Field</code>. 179 * @param msg A validation message. 180 */ 181 public void addMsg(final Msg msg) { 182 getMsgMap().put(msg.getName(), msg); 183 } 184 185 /** 186 * Add a <code>Var</code>, based on the values passed in, to the 187 * <code>Field</code>. 188 * @param name Name of the validation. 189 * @param value The Argument's value. 190 * @param jsType The JavaScript type. 191 */ 192 public void addVar(final String name, final String value, final String jsType) { 193 this.addVar(new Var(name, value, jsType)); 194 } 195 196 /** 197 * Add a <code>Var</code> to the <code>Field</code>. 198 * @param v The Validator Argument. 199 */ 200 public void addVar(final Var v) { 201 this.getVarMap().put(v.getName(), v); 202 } 203 204 /** 205 * Creates and returns a copy of this object. 206 * @return A copy of the Field. 207 */ 208 @Override 209 public Object clone() { 210 Field field = null; 211 try { 212 field = (Field) super.clone(); 213 } catch (final CloneNotSupportedException e) { 214 throw new UnsupportedOperationException(e.toString(), e); 215 } 216 217 @SuppressWarnings("unchecked") // empty array always OK; cannot check this at compile time 218 final Map<String, Arg>[] tempMap = new Map[this.args.length]; 219 field.args = tempMap; 220 for (int i = 0; i < this.args.length; i++) { 221 if (this.args[i] == null) { 222 continue; 223 } 224 225 final Map<String, Arg> argMap = new HashMap<>(this.args[i]); 226 argMap.forEach((validatorName, arg) -> argMap.put(validatorName, (Arg) arg.clone())); 227 field.args[i] = argMap; 228 } 229 230 field.hVars = ValidatorUtils.copyFastHashMap(hVars); 231 field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs); 232 233 return field; 234 } 235 236 /** 237 * Calculate the position of the Arg 238 */ 239 private void determineArgPosition(final Arg arg) { 240 241 final int position = arg.getPosition(); 242 243 // position has been explicity set 244 if (position >= 0) { 245 return; 246 } 247 248 // first arg to be added 249 if (args == null || args.length == 0) { 250 arg.setPosition(0); 251 return; 252 } 253 254 // determine the position of the last argument with 255 // the same name or the last default argument 256 final String keyName = arg.getName() == null ? DEFAULT_ARG : arg.getName(); 257 int lastPosition = -1; 258 int lastDefault = -1; 259 for (int i = 0; i < args.length; i++) { 260 if (args[i] != null && args[i].containsKey(keyName)) { 261 lastPosition = i; 262 } 263 if (args[i] != null && args[i].containsKey(DEFAULT_ARG)) { 264 lastDefault = i; 265 } 266 } 267 268 if (lastPosition < 0) { 269 lastPosition = lastDefault; 270 } 271 272 // allocate the next position 273 arg.setPosition(++lastPosition); 274 275 } 276 277 /** 278 * Ensures that the args array can hold the given arg. Resizes the array as 279 * necessary. 280 * @param arg Determine if the args array is long enough to store this arg's 281 * position. 282 */ 283 private void ensureArgsCapacity(final Arg arg) { 284 if (arg.getPosition() >= this.args.length) { 285 @SuppressWarnings("unchecked") // cannot check this at compile time, but it is OK 286 final 287 Map<String, Arg>[] newArgs = new Map[arg.getPosition() + 1]; 288 System.arraycopy(this.args, 0, newArgs, 0, this.args.length); 289 this.args = newArgs; 290 } 291 } 292 293 /** 294 * Generate correct <code>key</code> value. 295 */ 296 public void generateKey() { 297 if (this.isIndexed()) { 298 this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property; 299 } else { 300 this.key = this.property; 301 } 302 } 303 304 /** 305 * Gets the default <code>Arg</code> object at the given position. 306 * @param position Validation message argument's position. 307 * @return The default Arg or null if not found. 308 * @since 1.1 309 */ 310 public Arg getArg(final int position) { 311 return this.getArg(DEFAULT_ARG, position); 312 } 313 314 /** 315 * Gets the <code>Arg</code> object at the given position. If the key 316 * finds a {@code null} value then the default value will be 317 * retrieved. 318 * @param key The name the Arg is stored under. If not found, the default 319 * Arg for the given position (if any) will be retrieved. 320 * @param position The Arg number to find. 321 * @return The Arg with the given name and position or null if not found. 322 * @since 1.1 323 */ 324 public Arg getArg(final String key, final int position) { 325 if (position >= this.args.length || this.args[position] == null) { 326 return null; 327 } 328 329 final Arg arg = args[position].get(key); 330 331 // Didn't find default arg so exit, otherwise we would get into 332 // infinite recursion 333 if (arg == null && key.equals(DEFAULT_ARG)) { 334 return null; 335 } 336 337 return arg == null ? this.getArg(position) : arg; 338 } 339 340 /** 341 * Retrieves the Args for the given validator name. 342 * @param key The validator's args to retrieve. 343 * @return An Arg[] sorted by the Args' positions (i.e. the Arg at index 0 344 * has a position of 0). 345 * @since 1.1.1 346 */ 347 public Arg[] getArgs(final String key) { 348 final Arg[] argList = new Arg[this.args.length]; 349 350 for (int i = 0; i < this.args.length; i++) { 351 argList[i] = this.getArg(key, i); 352 } 353 354 return argList; 355 } 356 357 /** 358 * Gets an unmodifiable <code>List</code> of the dependencies in the same 359 * order they were defined in parameter passed to the setDepends() method. 360 * @return A list of the Field's dependancies. 361 */ 362 public List<String> getDependencyList() { 363 return Collections.unmodifiableList(this.dependencyList); 364 } 365 366 /** 367 * Gets the validation rules for this field as a comma separated list. 368 * @return A comma separated list of validator names. 369 */ 370 public String getDepends() { 371 return this.depends; 372 } 373 374 /** 375 * Gets the position of the <code>Field</code> in the validation list. 376 * @return The field position. 377 */ 378 public int getFieldOrder() { 379 return this.fieldOrder; 380 } 381 382 /** 383 * Gets the indexed property name of the field. This 384 * is the method name that will return an array or a 385 * <code>Collection</code> used to retrieve the 386 * list and then loop through the list performing the specified 387 * validations. 388 * @return The field's indexed List property name. 389 */ 390 public String getIndexedListProperty() { 391 return this.indexedListProperty; 392 } 393 394 /** 395 * Gets the indexed property name of the field. This 396 * is the method name that can take an <code>int</code> as 397 * a parameter for indexed property value retrieval. 398 * @return The field's indexed property name. 399 */ 400 public String getIndexedProperty() { 401 return this.indexedProperty; 402 } 403 404 /** 405 * Returns an indexed property from the object we're validating. 406 * 407 * @param bean The bean to extract the indexed values from. 408 * @throws ValidatorException If there's an error looking up the property 409 * or, the property found is not indexed. 410 */ 411 Object[] getIndexedProperty(final Object bean) throws ValidatorException { 412 Object indexProp = null; 413 414 try { 415 indexProp = PropertyUtils.getProperty(bean, this.getIndexedListProperty()); 416 417 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 418 throw new ValidatorException(e.getMessage()); 419 } 420 421 if (indexProp instanceof Collection) { 422 return ((Collection<?>) indexProp).toArray(); 423 424 } 425 if (indexProp.getClass().isArray()) { 426 return (Object[]) indexProp; 427 428 } 429 throw new ValidatorException(this.getKey() + " is not indexed"); 430 431 } 432 433 /** 434 * Returns the size of an indexed property from the object we're validating. 435 * 436 * @param bean The bean to extract the indexed values from. 437 * @throws ValidatorException If there's an error looking up the property 438 * or, the property found is not indexed. 439 */ 440 private int getIndexedPropertySize(final Object bean) throws ValidatorException { 441 Object indexProp = null; 442 443 try { 444 indexProp = PropertyUtils.getProperty(bean, this.getIndexedListProperty()); 445 446 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 447 throw new ValidatorException(e.getMessage()); 448 } 449 450 if (indexProp == null) { 451 return 0; 452 } 453 if (indexProp instanceof Collection) { 454 return ((Collection<?>) indexProp).size(); 455 } 456 if (indexProp.getClass().isArray()) { 457 return ((Object[]) indexProp).length; 458 } 459 throw new ValidatorException(this.getKey() + " is not indexed"); 460 461 } 462 463 /** 464 * Gets a unique key based on the property and indexedProperty fields. 465 * @return a unique key for the field. 466 */ 467 public String getKey() { 468 if (this.key == null) { 469 this.generateKey(); 470 } 471 472 return this.key; 473 } 474 475 /** 476 * Retrieve a message object. 477 * @since 1.1.4 478 * @param key Validation key. 479 * @return A validation message for a specified validator. 480 */ 481 public Msg getMessage(final String key) { 482 return getMsgMap().get(key); 483 } 484 485 /** 486 * The <code>Field</code>'s messages are returned as an 487 * unmodifiable <code>Map</code>. 488 * @since 1.1.4 489 * @return Map of validation messages for the field. 490 */ 491 public Map<String, Msg> getMessages() { 492 return Collections.unmodifiableMap(getMsgMap()); 493 } 494 495 /** 496 * Retrieve a message value. 497 * @param key Validation key. 498 * @return A validation message for a specified validator. 499 */ 500 public String getMsg(final String key) { 501 final Msg msg = getMessage(key); 502 return msg == null ? null : msg.getKey(); 503 } 504 505 /** 506 * Returns a Map of String Msg names to Msg objects. 507 * @since 1.2.0 508 * @return A Map of the Field's messages. 509 */ 510 @SuppressWarnings("unchecked") // FastHashMap does not support generics 511 protected Map<String, Msg> getMsgMap() { 512 return hMsgs; 513 } 514 515 /** 516 * Gets the page value that the Field is associated with for 517 * validation. 518 * @return The page number. 519 */ 520 public int getPage() { 521 return this.page; 522 } 523 524 /** 525 * Gets the property name of the field. 526 * @return The field's property name. 527 */ 528 public String getProperty() { 529 return this.property; 530 } 531 532 /** 533 * Retrieve a variable. 534 * @param mainKey The Variable's key 535 * @return the Variable 536 */ 537 public Var getVar(final String mainKey) { 538 return getVarMap().get(mainKey); 539 } 540 541 /** 542 * Returns a Map of String Var names to Var objects. 543 * @since 1.2.0 544 * @return A Map of the Field's variables. 545 */ 546 @SuppressWarnings("unchecked") // FastHashMap does not support generics 547 protected Map<String, Var> getVarMap() { 548 return hVars; 549 } 550 551 /** 552 * The <code>Field</code>'s variables are returned as an 553 * unmodifiable <code>Map</code>. 554 * @return the Map of Variable's for a Field. 555 */ 556 public Map<String, Var> getVars() { 557 return Collections.unmodifiableMap(getVarMap()); 558 } 559 560 /** 561 * Retrieve a variable's value. 562 * @param mainKey The Variable's key 563 * @return the Variable's value 564 */ 565 public String getVarValue(final String mainKey) { 566 String value = null; 567 568 final Var v = getVarMap().get(mainKey); 569 if (v != null) { 570 value = v.getValue(); 571 } 572 573 return value; 574 } 575 576 /** 577 * Called when a validator name is used in a depends clause but there is 578 * no know ValidatorAction configured for that name. 579 * @param name The name of the validator in the depends list. 580 * @throws ValidatorException 581 */ 582 private void handleMissingAction(final String name) throws ValidatorException { 583 throw new ValidatorException("No ValidatorAction named " + name 584 + " found for field " + this.getProperty()); 585 } 586 587 /** 588 * Determines whether client-side scripting should be generated 589 * for this field. The default is {@code true} 590 * @return {@code true} for scripting; otherwise false 591 * @see #setClientValidation(boolean) 592 * @since 1.4 593 */ 594 public boolean isClientValidation() { 595 return this.clientValidation; 596 } 597 598 /** 599 * Checks if the validator is listed as a dependency. 600 * @param validatorName Name of the validator to check. 601 * @return Whether the field is dependant on a validator. 602 */ 603 public boolean isDependency(final String validatorName) { 604 return this.dependencyList.contains(validatorName); 605 } 606 607 /** 608 * If there is a value specified for the indexedProperty field then 609 * {@code true} will be returned. Otherwise it will be 610 * {@code false}. 611 * @return Whether the Field is indexed. 612 */ 613 public boolean isIndexed() { 614 return indexedListProperty != null && !indexedListProperty.isEmpty(); 615 } 616 617 /** 618 * Replace constants with values in fields and process the depends field 619 * to create the dependency <code>Map</code>. 620 */ 621 void process(final Map<String, String> globalConstants, final Map<String, String> constants) { 622 this.hMsgs.setFast(false); 623 this.hVars.setFast(true); 624 625 this.generateKey(); 626 627 // Process FormSet Constants 628 for (final Entry<String, String> entry : constants.entrySet()) { 629 final String key1 = entry.getKey(); 630 final String key2 = TOKEN_START + key1 + TOKEN_END; 631 final String replaceValue = entry.getValue(); 632 633 property = ValidatorUtils.replace(property, key2, replaceValue); 634 635 processVars(key2, replaceValue); 636 637 this.processMessageComponents(key2, replaceValue); 638 } 639 640 // Process Global Constants 641 for (final Entry<String, String> entry : globalConstants.entrySet()) { 642 final String key1 = entry.getKey(); 643 final String key2 = TOKEN_START + key1 + TOKEN_END; 644 final String replaceValue = entry.getValue(); 645 646 property = ValidatorUtils.replace(property, key2, replaceValue); 647 648 processVars(key2, replaceValue); 649 650 this.processMessageComponents(key2, replaceValue); 651 } 652 653 // Process Var Constant Replacement 654 for (final String key1 : getVarMap().keySet()) { 655 final String key2 = TOKEN_START + TOKEN_VAR + key1 + TOKEN_END; 656 final Var var = this.getVar(key1); 657 final String replaceValue = var.getValue(); 658 659 this.processMessageComponents(key2, replaceValue); 660 } 661 662 hMsgs.setFast(true); 663 } 664 665 /** 666 * Replace the arg <code>Collection</code> key value with the key/value 667 * pairs passed in. 668 */ 669 private void processArg(final String key, final String replaceValue) { 670 for (final Map<String, Arg> argMap : this.args) { 671 if (argMap == null) { 672 continue; 673 } 674 for (final Arg arg : argMap.values()) { 675 if (arg != null) { 676 arg.setKey(ValidatorUtils.replace(arg.getKey(), key, replaceValue)); 677 } 678 } 679 } 680 } 681 682 /** 683 * Replace the args key value with the key/value pairs passed in. 684 */ 685 private void processMessageComponents(final String key, final String replaceValue) { 686 final String varKey = TOKEN_START + TOKEN_VAR; 687 // Process Messages 688 if (key != null && !key.startsWith(varKey)) { 689 for (final Msg msg : getMsgMap().values()) { 690 msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue)); 691 } 692 } 693 694 this.processArg(key, replaceValue); 695 } 696 697 /** 698 * Replace the vars value with the key/value pairs passed in. 699 */ 700 private void processVars(final String key, final String replaceValue) { 701 for (final String varKey : getVarMap().keySet()) { 702 final Var var = this.getVar(varKey); 703 var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue)); 704 } 705 706 } 707 708 /** 709 * Calls all of the validators that this validator depends on. 710 * TODO ValidatorAction should know how to run its own dependencies. 711 * @param va Run dependent validators for this action. 712 * @param results 713 * @param actions 714 * @param pos 715 * @return true if all of the dependent validations passed. 716 * @throws ValidatorException If there's an error running a validator 717 */ 718 private boolean runDependentValidators( 719 final ValidatorAction va, 720 final ValidatorResults results, 721 final Map<String, ValidatorAction> actions, 722 final Map<String, Object> params, 723 final int pos) 724 throws ValidatorException { 725 726 final List<String> dependentValidators = va.getDependencyList(); 727 728 if (dependentValidators.isEmpty()) { 729 return true; 730 } 731 732 for (final String depend : dependentValidators) { 733 final ValidatorAction action = actions.get(depend); 734 if (action == null) { 735 this.handleMissingAction(depend); 736 } 737 738 if (!this.validateForRule(action, results, actions, params, pos)) { 739 return false; 740 } 741 } 742 743 return true; 744 } 745 746 /** 747 * Sets the flag that determines whether client-side scripting should 748 * be generated for this field. 749 * @param clientValidation the scripting flag 750 * @see #isClientValidation() 751 * @since 1.4 752 */ 753 public void setClientValidation(final boolean clientValidation) { 754 this.clientValidation = clientValidation; 755 } 756 757 /** 758 * Sets the validation rules for this field as a comma separated list. 759 * @param depends A comma separated list of validator names. 760 */ 761 public void setDepends(final String depends) { 762 this.depends = depends; 763 764 this.dependencyList.clear(); 765 766 final StringTokenizer st = new StringTokenizer(depends, ","); 767 while (st.hasMoreTokens()) { 768 final String depend = st.nextToken().trim(); 769 770 if (depend != null && !depend.isEmpty()) { 771 this.dependencyList.add(depend); 772 } 773 } 774 } 775 776 /** 777 * Sets the position of the <code>Field</code> in the validation list. 778 * @param fieldOrder The field position. 779 */ 780 public void setFieldOrder(final int fieldOrder) { 781 this.fieldOrder = fieldOrder; 782 } 783 784 /** 785 * Sets the indexed property name of the field. 786 * @param indexedListProperty The field's indexed List property name. 787 */ 788 public void setIndexedListProperty(final String indexedListProperty) { 789 this.indexedListProperty = indexedListProperty; 790 } 791 /** 792 * Sets the indexed property name of the field. 793 * @param indexedProperty The field's indexed property name. 794 */ 795 public void setIndexedProperty(final String indexedProperty) { 796 this.indexedProperty = indexedProperty; 797 } 798 799 /** 800 * Sets a unique key for the field. This can be used to change 801 * the key temporarily to have a unique key for an indexed field. 802 * @param key a unique key for the field 803 */ 804 public void setKey(final String key) { 805 this.key = key; 806 } 807 808 /** 809 * Sets the page value that the Field is associated with for 810 * validation. 811 * @param page The page number. 812 */ 813 public void setPage(final int page) { 814 this.page = page; 815 } 816 817 /** 818 * Sets the property name of the field. 819 * @param property The field's property name. 820 */ 821 public void setProperty(final String property) { 822 this.property = property; 823 } 824 825 /** 826 * Returns a string representation of the object. 827 * @return A string representation of the object. 828 */ 829 @Override 830 public String toString() { 831 final StringBuilder results = new StringBuilder(); 832 833 results.append("\t\tkey = " + key + "\n"); 834 results.append("\t\tproperty = " + property + "\n"); 835 results.append("\t\tindexedProperty = " + indexedProperty + "\n"); 836 results.append("\t\tindexedListProperty = " + indexedListProperty + "\n"); 837 results.append("\t\tdepends = " + depends + "\n"); 838 results.append("\t\tpage = " + page + "\n"); 839 results.append("\t\tfieldOrder = " + fieldOrder + "\n"); 840 841 if (hVars != null) { 842 results.append("\t\tVars:\n"); 843 for (final Object key1 : getVarMap().keySet()) { 844 results.append("\t\t\t"); 845 results.append(key1); 846 results.append("="); 847 results.append(getVarMap().get(key1)); 848 results.append("\n"); 849 } 850 } 851 852 return results.toString(); 853 } 854 855 /** 856 * Run the configured validations on this field. Run all validations 857 * in the depends clause over each item in turn, returning when the first 858 * one fails. 859 * @param params A Map of parameter class names to parameter values to pass 860 * into validation methods. 861 * @param actions A Map of validator names to ValidatorAction objects. 862 * @return A ValidatorResults object containing validation messages for 863 * this field. 864 * @throws ValidatorException If an error occurs during validation. 865 */ 866 public ValidatorResults validate(final Map<String, Object> params, final Map<String, ValidatorAction> actions) 867 throws ValidatorException { 868 869 if (this.getDepends() == null) { 870 return new ValidatorResults(); 871 } 872 873 final ValidatorResults allResults = new ValidatorResults(); 874 875 final Object bean = params.get(Validator.BEAN_PARAM); 876 final int numberOfFieldsToValidate = this.isIndexed() ? this.getIndexedPropertySize(bean) : 1; 877 878 for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) { 879 880 final ValidatorResults results = new ValidatorResults(); 881 synchronized (dependencyList) { 882 for (final String depend : this.dependencyList) { 883 884 final ValidatorAction action = actions.get(depend); 885 if (action == null) { 886 this.handleMissingAction(depend); 887 } 888 889 final boolean good = validateForRule(action, results, actions, params, fieldNumber); 890 891 if (!good) { 892 allResults.merge(results); 893 return allResults; 894 } 895 } 896 } 897 allResults.merge(results); 898 } 899 900 return allResults; 901 } 902 903 /** 904 * Executes the given ValidatorAction and all ValidatorActions that it 905 * depends on. 906 * @return true if the validation succeeded. 907 */ 908 private boolean validateForRule( 909 final ValidatorAction va, 910 final ValidatorResults results, 911 final Map<String, ValidatorAction> actions, 912 final Map<String, Object> params, 913 final int pos) 914 throws ValidatorException { 915 916 final ValidatorResult result = results.getValidatorResult(this.getKey()); 917 if (result != null && result.containsAction(va.getName())) { 918 return result.isValid(va.getName()); 919 } 920 921 if (!this.runDependentValidators(va, results, actions, params, pos)) { 922 return false; 923 } 924 925 return va.executeValidationMethod(this, params, results, pos); 926 } 927} 928