1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.commons.validator;
23
24 import java.io.Serializable;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.StringTokenizer;
34
35 import org.apache.commons.beanutils.PropertyUtils;
36 import org.apache.commons.collections.FastHashMap;
37 import org.apache.commons.validator.util.ValidatorUtils;
38
39 /***
40 * This contains the list of pluggable validators to run on a field and any
41 * message information and variables to perform the validations and generate
42 * error messages. Instances of this class are configured with a
43 * <field> xml element.
44 * <p>
45 * The use of FastHashMap is deprecated and will be replaced in a future
46 * release.
47 * </p>
48 * @see org.apache.commons.validator.Form
49 */
50 public class Field implements Cloneable, Serializable {
51
52 /***
53 * This is the value that will be used as a key if the <code>Arg</code>
54 * name field has no value.
55 */
56 private static final String DEFAULT_ARG =
57 "org.apache.commons.validator.Field.DEFAULT";
58
59 /***
60 * This indicates an indexed property is being referenced.
61 */
62 public static final String TOKEN_INDEXED = "[]";
63
64 /***
65 * The start of a token.
66 */
67 protected static final String TOKEN_START = "${";
68
69 /***
70 * The end of a token.
71 */
72 protected static final String TOKEN_END = "}";
73
74 /***
75 * A Vriable token.
76 */
77 protected static final String TOKEN_VAR = "var:";
78
79 /***
80 * The Field's property name.
81 */
82 protected String property = null;
83
84 /***
85 * The Field's indexed property name.
86 */
87 protected String indexedProperty = null;
88
89 /***
90 * The Field's indexed list property name.
91 */
92 protected String indexedListProperty = null;
93
94 /***
95 * The Field's unique key.
96 */
97 protected String key = null;
98
99 /***
100 * A comma separated list of validator's this field depends on.
101 */
102 protected String depends = null;
103
104 /***
105 * The Page Number
106 */
107 protected int page = 0;
108
109 /***
110 * The order of the Field in the Form.
111 */
112 protected int fieldOrder = 0;
113
114 /***
115 * Internal representation of this.depends String as a List. This List
116 * gets updated whenever setDepends() gets called. This List is
117 * synchronized so a call to setDepends() (which clears the List) won't
118 * interfere with a call to isDependency().
119 */
120 private List dependencyList = Collections.synchronizedList(new ArrayList());
121
122 /***
123 * @deprecated Subclasses should use getVarMap() instead.
124 */
125 protected FastHashMap hVars = new FastHashMap();
126
127 /***
128 * @deprecated Subclasses should use getMsgMap() instead.
129 */
130 protected FastHashMap hMsgs = new FastHashMap();
131
132 /***
133 * Holds Maps of arguments. args[0] returns the Map for the first
134 * replacement argument. Start with a 0 length array so that it will
135 * only grow to the size of the highest argument position.
136 * @since Validator 1.1
137 */
138 protected Map[] args = new Map[0];
139
140 /***
141 * Gets the page value that the Field is associated with for
142 * validation.
143 * @return The page number.
144 */
145 public int getPage() {
146 return this.page;
147 }
148
149 /***
150 * Sets the page value that the Field is associated with for
151 * validation.
152 * @param page The page number.
153 */
154 public void setPage(int page) {
155 this.page = page;
156 }
157
158 /***
159 * Gets the position of the <code>Field</code> in the validation list.
160 * @return The field position.
161 */
162 public int getFieldOrder() {
163 return this.fieldOrder;
164 }
165
166 /***
167 * Sets the position of the <code>Field</code> in the validation list.
168 * @param fieldOrder The field position.
169 */
170 public void setFieldOrder(int fieldOrder) {
171 this.fieldOrder = fieldOrder;
172 }
173
174 /***
175 * Gets the property name of the field.
176 * @return The field's property name.
177 */
178 public String getProperty() {
179 return this.property;
180 }
181
182 /***
183 * Sets the property name of the field.
184 * @param property The field's property name.
185 */
186 public void setProperty(String property) {
187 this.property = property;
188 }
189
190 /***
191 * Gets the indexed property name of the field. This
192 * is the method name that can take an <code>int</code> as
193 * a parameter for indexed property value retrieval.
194 * @return The field's indexed property name.
195 */
196 public String getIndexedProperty() {
197 return this.indexedProperty;
198 }
199
200 /***
201 * Sets the indexed property name of the field.
202 * @param indexedProperty The field's indexed property name.
203 */
204 public void setIndexedProperty(String indexedProperty) {
205 this.indexedProperty = indexedProperty;
206 }
207
208 /***
209 * Gets the indexed property name of the field. This
210 * is the method name that will return an array or a
211 * <code>Collection</code> used to retrieve the
212 * list and then loop through the list performing the specified
213 * validations.
214 * @return The field's indexed List property name.
215 */
216 public String getIndexedListProperty() {
217 return this.indexedListProperty;
218 }
219
220 /***
221 * Sets the indexed property name of the field.
222 * @param indexedListProperty The field's indexed List property name.
223 */
224 public void setIndexedListProperty(String indexedListProperty) {
225 this.indexedListProperty = indexedListProperty;
226 }
227
228 /***
229 * Gets the validation rules for this field as a comma separated list.
230 * @return A comma separated list of validator names.
231 */
232 public String getDepends() {
233 return this.depends;
234 }
235
236 /***
237 * Sets the validation rules for this field as a comma separated list.
238 * @param depends A comma separated list of validator names.
239 */
240 public void setDepends(String depends) {
241 this.depends = depends;
242
243 this.dependencyList.clear();
244
245 StringTokenizer st = new StringTokenizer(depends, ",");
246 while (st.hasMoreTokens()) {
247 String depend = st.nextToken().trim();
248
249 if (depend != null && depend.length() > 0) {
250 this.dependencyList.add(depend);
251 }
252 }
253 }
254
255 /***
256 * Add a <code>Msg</code> to the <code>Field</code>.
257 * @param msg A validation message.
258 */
259 public void addMsg(Msg msg) {
260 hMsgs.put(msg.getName(), msg);
261 }
262
263 /***
264 * Retrieve a message value.
265 * @param key Validation key.
266 * @return A validation message for a specified validator.
267 */
268 public String getMsg(String key) {
269 Msg msg = getMessage(key);
270 return (msg == null) ? null : msg.getKey();
271 }
272
273 /***
274 * Retrieve a message object.
275 * @since Validator 1.1.4
276 * @param key Validation key.
277 * @return A validation message for a specified validator.
278 */
279 public Msg getMessage(String key) {
280 return (Msg) hMsgs.get(key);
281 }
282
283 /***
284 * The <code>Field</code>'s messages are returned as an
285 * unmodifiable <code>Map</code>.
286 * @since Validator 1.1.4
287 * @return Map of validation messages for the field.
288 */
289 public Map getMessages() {
290 return Collections.unmodifiableMap(hMsgs);
291 }
292
293 /***
294 * Add an <code>Arg</code> to the replacement argument list.
295 * @since Validator 1.1
296 * @param arg Validation message's argument.
297 */
298 public void addArg(Arg arg) {
299
300 if (arg == null || arg.getKey() == null || arg.getKey().length() == 0) {
301 return;
302 }
303
304 determineArgPosition(arg);
305 ensureArgsCapacity(arg);
306
307 Map argMap = this.args[arg.getPosition()];
308 if (argMap == null) {
309 argMap = new HashMap();
310 this.args[arg.getPosition()] = argMap;
311 }
312
313 if (arg.getName() == null) {
314 argMap.put(DEFAULT_ARG, arg);
315 } else {
316 argMap.put(arg.getName(), arg);
317 }
318
319 }
320
321 /***
322 * Calculate the position of the Arg
323 */
324 private void determineArgPosition(Arg arg) {
325
326 int position = arg.getPosition();
327
328
329 if (position >= 0) {
330 return;
331 }
332
333
334 if (args == null || args.length == 0) {
335 arg.setPosition(0);
336 return;
337 }
338
339
340
341 String key = arg.getName() == null ? DEFAULT_ARG : arg.getName();
342 int lastPosition = -1;
343 int lastDefault = -1;
344 for (int i = 0; i < args.length; i++) {
345 if (args[i] != null && args[i].containsKey(key)) {
346 lastPosition = i;
347 }
348 if (args[i] != null && args[i].containsKey(DEFAULT_ARG)) {
349 lastDefault = i;
350 }
351 }
352
353 if (lastPosition < 0) {
354 lastPosition = lastDefault;
355 }
356
357
358 arg.setPosition(++lastPosition);
359
360 }
361
362 /***
363 * Ensures that the args array can hold the given arg. Resizes the array as
364 * necessary.
365 * @param arg Determine if the args array is long enough to store this arg's
366 * position.
367 */
368 private void ensureArgsCapacity(Arg arg) {
369 if (arg.getPosition() >= this.args.length) {
370 Map[] newArgs = new Map[arg.getPosition() + 1];
371 System.arraycopy(this.args, 0, newArgs, 0, this.args.length);
372 this.args = newArgs;
373 }
374 }
375
376 /***
377 * Gets the default <code>Arg</code> object at the given position.
378 * @param position Validation message argument's position.
379 * @return The default Arg or null if not found.
380 * @since Validator 1.1
381 */
382 public Arg getArg(int position) {
383 return this.getArg(DEFAULT_ARG, position);
384 }
385
386 /***
387 * Gets the <code>Arg</code> object at the given position. If the key
388 * finds a <code>null</code> value then the default value will be
389 * retrieved.
390 * @param key The name the Arg is stored under. If not found, the default
391 * Arg for the given position (if any) will be retrieved.
392 * @param position The Arg number to find.
393 * @return The Arg with the given name and position or null if not found.
394 * @since Validator 1.1
395 */
396 public Arg getArg(String key, int position) {
397 if ((position >= this.args.length) || (this.args[position] == null)) {
398 return null;
399 }
400
401 Arg arg = (Arg) args[position].get(key);
402
403
404
405 if ((arg == null) && key.equals(DEFAULT_ARG)) {
406 return null;
407 }
408
409 return (arg == null) ? this.getArg(position) : arg;
410 }
411
412 /***
413 * Retrieves the Args for the given validator name.
414 * @param key The validator's args to retrieve.
415 * @return An Arg[] sorted by the Args' positions (i.e. the Arg at index 0
416 * has a position of 0).
417 * @since Validator 1.1.1
418 */
419 public Arg[] getArgs(String key){
420 Arg[] args = new Arg[this.args.length];
421
422 for (int i = 0; i < this.args.length; i++) {
423 args[i] = this.getArg(key, i);
424 }
425
426 return args;
427 }
428
429 /***
430 * Add a <code>Var</code> to the <code>Field</code>.
431 * @param v The Validator Argument.
432 */
433 public void addVar(Var v) {
434 this.hVars.put(v.getName(), v);
435 }
436
437 /***
438 * Add a <code>Var</code>, based on the values passed in, to the
439 * <code>Field</code>.
440 * @param name Name of the validation.
441 * @param value The Argument's value.
442 * @param jsType The Javascript type.
443 */
444 public void addVar(String name, String value, String jsType) {
445 this.addVar(new Var(name, value, jsType));
446 }
447
448 /***
449 * Retrieve a variable.
450 * @param mainKey The Variable's key
451 * @return the Variable
452 */
453 public Var getVar(String mainKey) {
454 return (Var) hVars.get(mainKey);
455 }
456
457 /***
458 * Retrieve a variable's value.
459 * @param mainKey The Variable's key
460 * @return the Variable's value
461 */
462 public String getVarValue(String mainKey) {
463 String value = null;
464
465 Object o = hVars.get(mainKey);
466 if (o != null && o instanceof Var) {
467 Var v = (Var) o;
468 value = v.getValue();
469 }
470
471 return value;
472 }
473
474 /***
475 * The <code>Field</code>'s variables are returned as an
476 * unmodifiable <code>Map</code>.
477 * @return the Map of Variable's for a Field.
478 */
479 public Map getVars() {
480 return Collections.unmodifiableMap(hVars);
481 }
482
483 /***
484 * Gets a unique key based on the property and indexedProperty fields.
485 * @return a unique key for the field.
486 */
487 public String getKey() {
488 if (this.key == null) {
489 this.generateKey();
490 }
491
492 return this.key;
493 }
494
495 /***
496 * Sets a unique key for the field. This can be used to change
497 * the key temporarily to have a unique key for an indexed field.
498 * @param key a unique key for the field
499 */
500 public void setKey(String key) {
501 this.key = key;
502 }
503
504 /***
505 * If there is a value specified for the indexedProperty field then
506 * <code>true</code> will be returned. Otherwise it will be
507 * <code>false</code>.
508 * @return Whether the Field is indexed.
509 */
510 public boolean isIndexed() {
511 return ((indexedListProperty != null && indexedListProperty.length() > 0));
512 }
513
514 /***
515 * Generate correct <code>key</code> value.
516 */
517 public void generateKey() {
518 if (this.isIndexed()) {
519 this.key = this.indexedListProperty + TOKEN_INDEXED + "." + this.property;
520 } else {
521 this.key = this.property;
522 }
523 }
524
525 /***
526 * Replace constants with values in fields and process the depends field
527 * to create the dependency <code>Map</code>.
528 */
529 void process(Map globalConstants, Map constants) {
530 this.hMsgs.setFast(false);
531 this.hVars.setFast(true);
532
533 this.generateKey();
534
535
536 for (Iterator i = constants.keySet().iterator(); i.hasNext();) {
537 String key = (String) i.next();
538 String key2 = TOKEN_START + key + TOKEN_END;
539 String replaceValue = (String) constants.get(key);
540
541 property = ValidatorUtils.replace(property, key2, replaceValue);
542
543 processVars(key2, replaceValue);
544
545 this.processMessageComponents(key2, replaceValue);
546 }
547
548
549 for (Iterator i = globalConstants.keySet().iterator(); i.hasNext();) {
550 String key = (String) i.next();
551 String key2 = TOKEN_START + key + TOKEN_END;
552 String replaceValue = (String) globalConstants.get(key);
553
554 property = ValidatorUtils.replace(property, key2, replaceValue);
555
556 processVars(key2, replaceValue);
557
558 this.processMessageComponents(key2, replaceValue);
559 }
560
561
562 for (Iterator i = hVars.keySet().iterator(); i.hasNext();) {
563 String key = (String) i.next();
564 String key2 = TOKEN_START + TOKEN_VAR + key + TOKEN_END;
565 Var var = this.getVar(key);
566 String replaceValue = var.getValue();
567
568 this.processMessageComponents(key2, replaceValue);
569 }
570
571 hMsgs.setFast(true);
572 }
573
574 /***
575 * Replace the vars value with the key/value pairs passed in.
576 */
577 private void processVars(String key, String replaceValue) {
578 Iterator i = this.hVars.keySet().iterator();
579 while (i.hasNext()) {
580 String varKey = (String) i.next();
581 Var var = this.getVar(varKey);
582
583 var.setValue(ValidatorUtils.replace(var.getValue(), key, replaceValue));
584 }
585
586 }
587
588 /***
589 * Replace the args key value with the key/value pairs passed in.
590 */
591 private void processMessageComponents(String key, String replaceValue) {
592 String varKey = TOKEN_START + TOKEN_VAR;
593
594 if (key != null && !key.startsWith(varKey)) {
595 for (Iterator i = hMsgs.values().iterator(); i.hasNext();) {
596 Msg msg = (Msg) i.next();
597 msg.setKey(ValidatorUtils.replace(msg.getKey(), key, replaceValue));
598 }
599 }
600
601 this.processArg(key, replaceValue);
602 }
603
604 /***
605 * Replace the arg <code>Collection</code> key value with the key/value
606 * pairs passed in.
607 */
608 private void processArg(String key, String replaceValue) {
609 for (int i = 0; i < this.args.length; i++) {
610
611 Map argMap = this.args[i];
612 if (argMap == null) {
613 continue;
614 }
615
616 Iterator iter = argMap.values().iterator();
617 while (iter.hasNext()) {
618 Arg arg = (Arg) iter.next();
619
620 if (arg != null) {
621 arg.setKey(
622 ValidatorUtils.replace(arg.getKey(), key, replaceValue));
623 }
624 }
625 }
626 }
627
628 /***
629 * Checks if the validator is listed as a dependency.
630 * @param validatorName Name of the validator to check.
631 * @return Whether the field is dependant on a validator.
632 */
633 public boolean isDependency(String validatorName) {
634 return this.dependencyList.contains(validatorName);
635 }
636
637 /***
638 * Gets an unmodifiable <code>List</code> of the dependencies in the same
639 * order they were defined in parameter passed to the setDepends() method.
640 * @return A list of the Field's dependancies.
641 */
642 public List getDependencyList() {
643 return Collections.unmodifiableList(this.dependencyList);
644 }
645
646 /***
647 * Creates and returns a copy of this object.
648 * @return A copy of the Field.
649 */
650 public Object clone() {
651 Field field = null;
652 try {
653 field = (Field) super.clone();
654 } catch(CloneNotSupportedException e) {
655 throw new RuntimeException(e.toString());
656 }
657
658 field.args = new Map[this.args.length];
659 for (int i = 0; i < this.args.length; i++) {
660 if (this.args[i] == null) {
661 continue;
662 }
663
664 Map argMap = new HashMap(this.args[i]);
665 Iterator iter = argMap.keySet().iterator();
666 while (iter.hasNext()) {
667 String validatorName = (String) iter.next();
668 Arg arg = (Arg) argMap.get(validatorName);
669 argMap.put(validatorName, arg.clone());
670 }
671 field.args[i] = argMap;
672 }
673
674 field.hVars = ValidatorUtils.copyFastHashMap(hVars);
675 field.hMsgs = ValidatorUtils.copyFastHashMap(hMsgs);
676
677 return field;
678 }
679
680 /***
681 * Returns a string representation of the object.
682 * @return A string representation of the object.
683 */
684 public String toString() {
685 StringBuffer results = new StringBuffer();
686
687 results.append("\t\tkey = " + key + "\n");
688 results.append("\t\tproperty = " + property + "\n");
689 results.append("\t\tindexedProperty = " + indexedProperty + "\n");
690 results.append("\t\tindexedListProperty = " + indexedListProperty + "\n");
691 results.append("\t\tdepends = " + depends + "\n");
692 results.append("\t\tpage = " + page + "\n");
693 results.append("\t\tfieldOrder = " + fieldOrder + "\n");
694
695 if (hVars != null) {
696 results.append("\t\tVars:\n");
697 for (Iterator i = hVars.keySet().iterator(); i.hasNext();) {
698 Object key = i.next();
699 results.append("\t\t\t");
700 results.append(key);
701 results.append("=");
702 results.append(hVars.get(key));
703 results.append("\n");
704 }
705 }
706
707 return results.toString();
708 }
709
710 /***
711 * Returns an indexed property from the object we're validating.
712 *
713 * @param bean The bean to extract the indexed values from.
714 * @throws ValidatorException If there's an error looking up the property
715 * or, the property found is not indexed.
716 */
717 Object[] getIndexedProperty(Object bean) throws ValidatorException {
718 Object indexedProperty = null;
719
720 try {
721 indexedProperty =
722 PropertyUtils.getProperty(bean, this.getIndexedListProperty());
723
724 } catch(IllegalAccessException e) {
725 throw new ValidatorException(e.getMessage());
726 } catch(InvocationTargetException e) {
727 throw new ValidatorException(e.getMessage());
728 } catch(NoSuchMethodException e) {
729 throw new ValidatorException(e.getMessage());
730 }
731
732 if (indexedProperty instanceof Collection) {
733 return ((Collection) indexedProperty).toArray();
734
735 } else if (indexedProperty.getClass().isArray()) {
736 return (Object[]) indexedProperty;
737
738 } else {
739 throw new ValidatorException(this.getKey() + " is not indexed");
740 }
741
742 }
743
744 /***
745 * Executes the given ValidatorAction and all ValidatorActions that it
746 * depends on.
747 * @return true if the validation succeeded.
748 */
749 private boolean validateForRule(
750 ValidatorAction va,
751 ValidatorResults results,
752 Map actions,
753 Map params,
754 int pos)
755 throws ValidatorException {
756
757 ValidatorResult result = results.getValidatorResult(this.getKey());
758 if (result != null && result.containsAction(va.getName())) {
759 return result.isValid(va.getName());
760 }
761
762 if (!this.runDependentValidators(va, results, actions, params, pos)) {
763 return false;
764 }
765
766 return va.executeValidationMethod(this, params, results, pos);
767 }
768
769 /***
770 * Calls all of the validators that this validator depends on.
771 * TODO ValidatorAction should know how to run its own dependencies.
772 * @param va Run dependent validators for this action.
773 * @param results
774 * @param actions
775 * @param pos
776 * @return true if all of the dependent validations passed.
777 * @throws ValidatorException If there's an error running a validator
778 */
779 private boolean runDependentValidators(
780 ValidatorAction va,
781 ValidatorResults results,
782 Map actions,
783 Map params,
784 int pos)
785 throws ValidatorException {
786
787 List dependentValidators = va.getDependencyList();
788
789 if (dependentValidators.isEmpty()) {
790 return true;
791 }
792
793 Iterator iter = dependentValidators.iterator();
794 while (iter.hasNext()) {
795 String depend = (String) iter.next();
796
797 ValidatorAction action = (ValidatorAction) actions.get(depend);
798 if (action == null) {
799 this.handleMissingAction(depend);
800 }
801
802 if (!this.validateForRule(action, results, actions, params, pos)) {
803 return false;
804 }
805 }
806
807 return true;
808 }
809
810 /***
811 * Run the configured validations on this field. Run all validations
812 * in the depends clause over each item in turn, returning when the first
813 * one fails.
814 * @param params A Map of parameter class names to parameter values to pass
815 * into validation methods.
816 * @param actions A Map of validator names to ValidatorAction objects.
817 * @return A ValidatorResults object containing validation messages for
818 * this field.
819 */
820 public ValidatorResults validate(Map params, Map actions)
821 throws ValidatorException {
822
823 if (this.getDepends() == null) {
824 return new ValidatorResults();
825 }
826
827 ValidatorResults allResults = new ValidatorResults();
828
829 Object bean = params.get(Validator.BEAN_PARAM);
830 int numberOfFieldsToValidate =
831 this.isIndexed() ? this.getIndexedProperty(bean).length : 1;
832
833 for (int fieldNumber = 0; fieldNumber < numberOfFieldsToValidate; fieldNumber++) {
834
835 Iterator dependencies = this.dependencyList.iterator();
836 while (dependencies.hasNext()) {
837 String depend = (String) dependencies.next();
838
839 ValidatorAction action = (ValidatorAction) actions.get(depend);
840 if (action == null) {
841 this.handleMissingAction(depend);
842 }
843
844 ValidatorResults results = new ValidatorResults();
845 boolean good =
846 validateForRule(action, results, actions, params, fieldNumber);
847
848 allResults.merge(results);
849
850 if (!good) {
851 return allResults;
852 }
853 }
854 }
855
856 return allResults;
857 }
858
859 /***
860 * Called when a validator name is used in a depends clause but there is
861 * no know ValidatorAction configured for that name.
862 * @param name The name of the validator in the depends list.
863 * @throws ValidatorException
864 */
865 private void handleMissingAction(String name) throws ValidatorException {
866 throw new ValidatorException("No ValidatorAction named " + name
867 + " found for field " + this.getProperty());
868 }
869
870 /***
871 * Returns a Map of String Msg names to Msg objects.
872 * @since Validator 1.2.0
873 * @return A Map of the Field's messages.
874 */
875 protected Map getMsgMap() {
876 return hMsgs;
877 }
878
879 /***
880 * Returns a Map of String Var names to Var objects.
881 * @since Validator 1.2.0
882 * @return A Map of the Field's variables.
883 */
884 protected Map getVarMap() {
885 return hVars;
886 }
887
888 }
889