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