1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 * { <key> : <expression> : <error_message> [ : <error_key> [ : <error_args> ] ] }
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 * <bean id="myValidator" class="org.springmodules.validation.valang.ValangValidatorFactoryBean">
61 * <property name="valang"><value><![CDATA[
62 * { age : ? is not null : 'Age is a required field.' : 'age_required' }
63 * { age : ? is null or ? >= 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 (? >= [T<d] and [T>d] > ?) :
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) <= 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 * ]]></value></property>
76 * </bean>
77 * </p>
78 * </pre>
79 * <p>Enums can be dynamically resolved either based on comparing an enum type to a literal delimitted by
80 * "['" + <enum> + "']" or it will be directly resovled if the complete path is specified.</p>
81 * <pre>
82 * <p>
83 * <bean id="myValidator" class="org.springmodules.validation.valang.ValangValidatorFactoryBean">
84 * <property name="valang"><value><![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 * ]]></value></property>
89 * </bean>
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 * <bean id="myValidator" class="org.springmodules.validation.valang.ValangValidatorFactoryBean">
98 * <property name="valang"><value><![CDATA[
99 * { age : ? > 18 WHERE lastName EQUALS 'Smith' : 'Age must be greater than 18 if your last name is Smith.' : 'valueDate_required' }
100 * ]]></value></property>
101 * </bean>
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
206
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 }