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