View Javadoc

1   /*
2    * Copyright 2004-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springmodules.validation.valang;
18  
19  import java.util.Collection;
20  import java.util.Map;
21  
22  import org.springframework.beans.BeanWrapper;
23  import org.springframework.beans.BeanWrapperImpl;
24  import org.springframework.beans.factory.InitializingBean;
25  import org.springframework.util.Assert;
26  import org.springframework.util.ClassUtils;
27  import org.springframework.util.StringUtils;
28  import org.springframework.validation.Errors;
29  import org.springframework.validation.Validator;
30  import org.springmodules.validation.util.date.DefaultDateParser;
31  import org.springmodules.validation.util.date.DefaultDateParser.DateModifier;
32  import org.springmodules.validation.valang.parser.SimpleValangBased;
33  import org.springmodules.validation.valang.parser.ValangParser;
34  import org.springmodules.validation.valang.predicates.ValidationRule;
35  
36  /**
37   * <p/>
38   * An implementation of <code>Validator</code> that takes a Valang syntax string
39   * to define the set of validation rules it will apply. This instance is thread-safe.
40   * <p/>
41   * The syntax of a Valang instruction is:
42   * <p/>
43   * <pre>
44   * <p/>
45   *  { &lt;key&gt; : &lt;expression&gt; : &lt;error_message&gt; [ : &lt;error_key&gt; [ : &lt;error_args&gt; ] ] }
46   * <p/>
47   * </pre>
48   * <p/>
49   * <p/>
50   * These instructions can be repeated and will be combined in a Validator
51   * instance. Each instruction will execute the expression on a target bean. If
52   * the expression fails the key will be rejected with the error message, error
53   * key and error arguments. If no error key is provided the key will be used as
54   * error key.
55   * <p/>
56   * Some examples of the Valang syntax:
57   * <p/>
58   * <pre>
59   * <p>
60   *      &lt;bean id=&quot;myValidator&quot; class=&quot;org.springmodules.validation.valang.ValangValidatorFactoryBean&quot;&gt;
61   *          &lt;property name=&quot;valang&quot;&gt;&lt;value&gt;&lt;![CDATA[
62   *          { age : ? is not null : 'Age is a required field.' : 'age_required' }
63   *          { age : ? is null or ? &gt;= minAge : 'Customers must be {0} years or older.' : 'not_old_enough' : minAge }
64   *          { valueDate : ? is not null : 'Value date is a required field.' : 'valueDate_required' }
65   *          { valueDate : ? is null or (? &gt;= [T&lt;d] and [T&gt;d] &gt; ?) :
66   *                  'Value date must be today.' : 'valueDate_today' }
67   *          { firstName : ? has text : 'First name is a required field.' : 'firstName_required' }
68   *          { firstName : ? has no text or length(firstName) &lt;= 50 :
69   *                  'First name must be no longer than {0} characters.' : 'firstName_length' : 50 }
70   *          { size : ? has length : 'Size is a required field.' }
71   *          { size : ? has no length or upper(?) in 'S', 'M', 'L', 'XL' :
72   *                  'Size must be either {0}, {1}, {2} or {3}.' : 'size_error' : 'S', 'M', 'L', 'XL' }
73   *          { lastName : ? has text and !(false) = true :
74   *                  'Last name is required and not false must be true.' }
75   *          ]]&gt;&lt;/value&gt;&lt;/property&gt;
76   *      &lt;/bean&gt;
77   * </p>
78   * </pre>
79   * <p>Enums can be dynamically resolved either based on comparing an enum type to a literal delimitted by 
80   * "['" + &lt;enum&gt; + "']" or it will be directly resovled if the complete path is specified.</p>
81   * <pre>
82   * <p>
83   *      &lt;bean id=&quot;myValidator&quot; class=&quot;org.springmodules.validation.valang.ValangValidatorFactoryBean&quot;&gt;
84   *          &lt;property name=&quot;valang&quot;&gt;&lt;value&gt;&lt;![CDATA[
85   *          { personType : ? EQUALS ['STUDENT'] : 'Person type must be student.' }
86   *          { personType : personType EQUALS ['org.springmodules.validation.example.PersonType.STUDENT'] : 'Person type must be student.' }
87   *          { personType : personType EQUALS ['org.springmodules.validation.example.Person$PersonType.STUDENT'] : 'Person type must be student.' }
88   *          ]]&gt;&lt;/value&gt;&lt;/property&gt;
89   *      &lt;/bean&gt;
90   * </p>
91   * </pre>
92   * <p>Where clauses are very similar to a SQL WHERE clause.  It lets you short circuit the validation in 
93   * case there are some rules that should only be applied after other criteria have been satisfied.  A where 
94   * clause doesn't generate any errors, and a where clause is optional.</p>
95   * <pre>
96   * <p>
97   *      &lt;bean id=&quot;myValidator&quot; class=&quot;org.springmodules.validation.valang.ValangValidatorFactoryBean&quot;&gt;
98   *          &lt;property name=&quot;valang&quot;&gt;&lt;value&gt;&lt;![CDATA[
99   *          { age : ? > 18 WHERE lastName EQUALS 'Smith' : 'Age must be greater than 18 if your last name is Smith.' : 'valueDate_required' }
100  *          ]]&gt;&lt;/value&gt;&lt;/property&gt;
101  *      &lt;/bean&gt;
102  * </p>
103  * </pre>
104  * <p/>
105  * Custom property editors can be registered using
106  * org.springmodules.validation.valang.CustomPropertyEditor.
107  * <p>A custom visitor can be registered to use custom functions in the Valang
108  * syntax.</p>
109  * 
110  * <p>If the class name is set it will be be used for bytecode generation 
111  * to avoid reflection.</p>
112  * 
113  * <p><strong>Note</strong>: By specifying the class name the <code>Validator</code> 
114  * will only be able to validate the class specified</p>
115  *
116  * @author Steven Devijver
117  * @see org.springmodules.validation.util.date.DefaultDateParser
118  * @see org.springframework.validation.Validator
119  * @since 23-04-2005
120  */
121 public class ValangValidator extends SimpleValangBased implements Validator, InitializingBean {
122 
123     private String valang = null;
124     private String className = null;
125     private Class<?> supportClass = null;
126     private Collection<CustomPropertyEditor> customPropertyEditors = null;
127     private Collection<ValidationRule> rules = null;
128 
129     /**
130      * Constructor
131      */
132     public ValangValidator() {
133         super();
134     }
135 
136     /**
137      * This property sets the Valang syntax.
138      *
139      * @param   valang      The Valang validation expression.
140      */
141     public void setValang(String valang) {
142         this.valang = valang;
143     }
144 
145     /**
146      * <p>Sets the class name to be used for bytecode generation 
147      * to avoid reflection.</p>
148      * 
149      * <p><strong>Note</strong>: By specifying this <code>Validator</code> 
150      * will only be able to validate the class specified</p>
151      *
152      * @param   className       The fully qualified class name to perform validation on.
153      */
154     public void setClassName(String className) {
155         this.className = className;
156     }
157 
158     /**
159      * Gets custom property editors.
160      */
161     private Collection<CustomPropertyEditor> getCustomPropertyEditors() {
162         return this.customPropertyEditors;
163     }
164 
165     /**
166      * Sets custom property editors on BeanWrapper instances (optional).
167      *
168      * @param customPropertyEditors the custom editors.
169      * @see BeanWrapper#registerCustomEditor(Class, String,
170      *      java.beans.PropertyEditor)
171      * @see BeanWrapper#registerCustomEditor(Class,
172      *      java.beans.PropertyEditor)
173      */
174     public void setCustomPropertyEditors(Collection<CustomPropertyEditor> customPropertyEditors) {
175         this.customPropertyEditors = customPropertyEditors;
176     }
177 
178     /**
179      * Gets Valang expression.
180      */
181     private String getValang() {
182         return this.valang;
183     }
184 
185     /**
186      * Gets list of validation rules.
187      */
188     public Collection<ValidationRule> getRules() {
189         return rules;
190     }
191 
192     /**
193      * Implementation of <code>InitializingBean</code>.
194      */
195     public void afterPropertiesSet() throws Exception {
196         Assert.hasLength(getValang(), "'valang' property must be set!");
197 
198         ValangParser parser = null;
199         
200         if (!StringUtils.hasText(className)) {
201             parser = createValangParser(valang);
202         } else {
203             parser = createValangParser(valang, className);
204 
205             // if className is set, this is the only supported class 
206             // for this validator
207             supportClass = ClassUtils.forName(className);
208         }
209         
210         rules = parser.parseValidation();
211     }
212 
213     /**
214      * What validation class is supported.
215      * Implementation of <code>Validator</code>.
216      */
217     public boolean supports(Class clazz) {
218         boolean result = true;
219         
220         if (supportClass != null) {
221             result = supportClass.isAssignableFrom(clazz);
222         }
223         
224         return result;
225     }
226 
227     /**
228      * <p>Validate the supplied <code>target</code> object, which must be
229      * of a {@link Class} for which the {@link #supports(Class)} method
230      * typically has (or would) return <code>true</code>.</p>
231      * 
232      * <p>Implementation of <code>Validator</code>.</p>
233      */
234     public void validate(Object target, Errors errors) {
235         Object bean = null;
236         BeanWrapper beanWrapper = null;
237         
238         if (!StringUtils.hasText(className)) {
239             beanWrapper = (target instanceof BeanWrapper) ? (BeanWrapper) target : new BeanWrapperImpl(target);
240 
241             if (getCustomPropertyEditors() != null) {
242                 for (CustomPropertyEditor customPropertyEditor : getCustomPropertyEditors()) {
243                     if (customPropertyEditor.getRequiredType() == null) {
244                         throw new IllegalArgumentException("[requiredType] is required on CustomPropertyEditor instances!");
245                     } else if (customPropertyEditor.getPropertyEditor() == null) {
246                         throw new IllegalArgumentException("[propertyEditor] is required on CustomPropertyEditor instances!");
247                     }
248     
249                     if (StringUtils.hasLength(customPropertyEditor.getPropertyPath())) {
250                         beanWrapper.registerCustomEditor(customPropertyEditor.getRequiredType(),
251                             customPropertyEditor.getPropertyPath(), customPropertyEditor.getPropertyEditor());
252                     } else {
253                         beanWrapper.registerCustomEditor(customPropertyEditor.getRequiredType(),
254                             customPropertyEditor.getPropertyEditor());
255                     }
256                 }
257             }
258             
259             bean = beanWrapper;
260         } else {
261             bean = target;
262         }
263         
264         for (ValidationRule rule : rules) {
265             rule.validate(bean, errors);
266         }
267     }
268     
269 }