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