1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springmodules.validation.bean;
18
19 import java.lang.reflect.Array;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.Map.Entry;
27
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.springframework.beans.BeanWrapper;
31 import org.springframework.beans.BeanWrapperImpl;
32 import org.springframework.util.StringUtils;
33 import org.springframework.validation.Errors;
34 import org.springframework.validation.Validator;
35 import org.springmodules.validation.bean.conf.BeanValidationConfiguration;
36 import org.springmodules.validation.bean.conf.CascadeValidation;
37 import org.springmodules.validation.bean.conf.loader.BeanValidationConfigurationLoader;
38 import org.springmodules.validation.bean.conf.loader.xml.DefaultXmlBeanValidationConfigurationLoader;
39 import org.springmodules.validation.bean.converter.ErrorCodeConverter;
40 import org.springmodules.validation.bean.converter.ModelAwareErrorCodeConverter;
41 import org.springmodules.validation.bean.rule.ValidationRule;
42 import org.springmodules.validation.util.condition.Condition;
43
44
45
46
47
48
49
50
51 public class BeanValidator extends RuleBasedValidator {
52
53 private final static Logger logger = LoggerFactory.getLogger(BeanValidator.class);
54
55 private final static String PROPERTY_KEY_PREFIX = "[";
56
57 private final static String PROPERTY_KEY_SUFFIX = "]";
58
59 private BeanValidationConfigurationLoader configurationLoader;
60
61 private ErrorCodeConverter errorCodeConverter;
62
63 private boolean shortCircuitFieldValidation = true;
64
65
66
67
68
69
70 public BeanValidator() {
71 this(new DefaultXmlBeanValidationConfigurationLoader());
72 }
73
74
75
76
77
78
79
80 public BeanValidator(BeanValidationConfigurationLoader configurationLoader) {
81 this.configurationLoader = configurationLoader;
82 errorCodeConverter = new ModelAwareErrorCodeConverter();
83 }
84
85
86
87
88
89
90
91 public boolean supports(Class clazz) {
92 return configurationLoader.supports(clazz) || super.supports(clazz);
93 }
94
95
96
97
98
99
100
101 public void validate(Object obj, Errors errors) {
102
103
104 validateObjectGraphConstraints(obj, obj, errors, new HashSet());
105
106
107 super.validate(obj, errors);
108 }
109
110
111
112
113
114
115
116
117
118
119 public void setErrorCodeConverter(ErrorCodeConverter errorCodeConverter) {
120 this.errorCodeConverter = errorCodeConverter;
121 }
122
123
124
125
126
127
128 public void setConfigurationLoader(BeanValidationConfigurationLoader configurationLoader) {
129 this.configurationLoader = configurationLoader;
130 }
131
132
133
134
135
136
137
138
139 public void setShortCircuitFieldValidation(boolean shortCircuitFieldValidation) {
140 this.shortCircuitFieldValidation = shortCircuitFieldValidation;
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 protected void validateObjectGraphConstraints(Object root, Object obj, Errors errors, Set validatedObjects) {
158
159
160 if (obj == null) {
161 return;
162 }
163
164
165 if (validatedObjects.contains(obj)) {
166 if (logger.isDebugEnabled()) {
167 logger.debug("Skipping validation of object in path '" + errors.getObjectName() +
168 "' for it was already validated");
169 }
170 return;
171 }
172
173 if (logger.isDebugEnabled()) {
174 logger.debug("Validating object in path '" + errors.getNestedPath() + "'");
175 }
176
177
178 Class clazz = obj.getClass();
179 BeanValidationConfiguration configuration = configurationLoader.loadConfiguration(clazz);
180
181 if (configuration == null) {
182 return;
183 }
184
185
186 applyBeanValidation(configuration, obj, errors);
187 validatedObjects.add(obj);
188
189
190
191 CascadeValidation[] cascadeValidations = configuration.getCascadeValidations();
192 BeanWrapper wrapper = wrapBean(obj);
193 for (int i = 0; i < cascadeValidations.length; i++) {
194 CascadeValidation cascadeValidation = cascadeValidations[i];
195 Condition applicabilityCondition = cascadeValidation.getApplicabilityCondition();
196
197 if (!applicabilityCondition.check(obj)) {
198 continue;
199 }
200
201 String propertyName = cascadeValidation.getPropertyName();
202 Class propertyType = wrapper.getPropertyType(propertyName);
203 Object propertyValue = wrapper.getPropertyValue(propertyName);
204
205
206 if (propertyValue == null) {
207 continue;
208 }
209
210
211
212
213
214
215 if (propertyType.isArray()) {
216 validateArrayProperty(root, propertyValue, propertyName, errors, validatedObjects);
217 } else if (List.class.isAssignableFrom(propertyType) || Set.class.isAssignableFrom(propertyType)) {
218 validateListOrSetProperty(root, (Collection) propertyValue, propertyName, errors, validatedObjects);
219 } else if (Map.class.isAssignableFrom(propertyType)) {
220 validateMapProperty(root, ((Map) propertyValue), propertyName, errors, validatedObjects);
221 } else {
222
223
224 validatedSubBean(root, propertyValue, propertyName, errors, validatedObjects);
225 }
226 }
227 }
228
229
230
231
232
233
234
235 protected BeanWrapper wrapBean(Object bean) {
236 return new BeanWrapperImpl(bean);
237 }
238
239
240
241
242
243
244
245
246
247
248 protected void validateArrayProperty(
249 Object root,
250 Object array,
251 String propertyName,
252 Errors errors,
253 Set validatedObjects) {
254
255 for (int i = 0; i < Array.getLength(array); i++) {
256 String nestedPath = propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX;
257 errors.pushNestedPath(nestedPath);
258 validateObjectGraphConstraints(root, Array.get(array, i), errors, validatedObjects);
259 errors.popNestedPath();
260 }
261 }
262
263
264
265
266
267
268
269
270
271
272 protected void validateListOrSetProperty(
273 Object root,
274 Collection collection,
275 String propertyName,
276 Errors errors,
277 Set validatedObjects) {
278
279 int i = 0;
280 for (Iterator iter = collection.iterator(); iter.hasNext();) {
281 Object element = iter.next();
282 String nestedPath = propertyName + PROPERTY_KEY_PREFIX + i + PROPERTY_KEY_SUFFIX;
283 errors.pushNestedPath(nestedPath);
284 validateObjectGraphConstraints(root, element, errors, validatedObjects);
285 errors.popNestedPath();
286 i++;
287 }
288 }
289
290
291
292
293
294
295
296
297
298
299 protected void validateMapProperty(Object root, Map map, String propertyName, Errors errors, Set validatedObjects) {
300 for (Iterator entries = map.entrySet().iterator(); entries.hasNext();) {
301 Entry entry = (Entry) entries.next();
302 Object key = entry.getKey();
303 if (!(key instanceof String)) {
304
305
306 continue;
307 }
308 Object value = entry.getValue();
309 String nestedPath = propertyName + PROPERTY_KEY_PREFIX + String.valueOf(key) + PROPERTY_KEY_SUFFIX;
310 errors.pushNestedPath(nestedPath);
311 validateObjectGraphConstraints(root, value, errors, validatedObjects);
312 errors.popNestedPath();
313 }
314 }
315
316
317
318
319
320
321
322
323
324
325 protected void validatedSubBean(
326 Object root,
327 Object subBean,
328 String propertyName,
329 Errors errors,
330 Set validatedObjects) {
331
332 errors.pushNestedPath(propertyName);
333 validateObjectGraphConstraints(root, subBean, errors, validatedObjects);
334 errors.popNestedPath();
335 }
336
337
338
339
340
341
342
343
344
345 protected void applyBeanValidation(BeanValidationConfiguration configuration, Object obj, Errors errors) {
346 if (logger.isDebugEnabled()) {
347 logger.debug("Validating global rules...");
348 }
349 applyGlobalValidationRules(configuration, obj, errors);
350
351 if (logger.isDebugEnabled()) {
352 logger.debug("Validating properties rules...");
353 }
354 applyPropertiesValidationRules(configuration, obj, errors);
355
356 if (logger.isDebugEnabled()) {
357 logger.debug("Executing custom validator...");
358 }
359 applyCustomValidator(configuration, obj, errors);
360 }
361
362
363
364
365
366
367
368
369
370 protected void applyGlobalValidationRules(BeanValidationConfiguration configuration, Object obj, Errors errors) {
371 ValidationRule[] globalRules = configuration.getGlobalRules();
372 for (int i = 0; i < globalRules.length; i++) {
373 ValidationRule rule = globalRules[i];
374 if (rule.isApplicable(obj) && !rule.getCondition().check(obj)) {
375 String errorCode = errorCodeConverter.convertGlobalErrorCode(rule.getErrorCode(), obj.getClass());
376
377
378
379
380
381
382 if (StringUtils.hasLength(errors.getNestedPath())) {
383 String nestedPath = errors.getNestedPath();
384 String propertyName = nestedPath.substring(0, nestedPath.length() - 1);
385 errors.popNestedPath();
386 errors.rejectValue(propertyName, errorCode, rule.getErrorArguments(obj), rule.getDefaultErrorMessage());
387 errors.pushNestedPath(propertyName);
388 } else {
389 errors.reject(errorCode, rule.getErrorArguments(obj), rule.getDefaultErrorMessage());
390 }
391 }
392 }
393 }
394
395
396
397
398
399
400
401
402
403
404 protected void applyPropertiesValidationRules(BeanValidationConfiguration configuration, Object obj, Errors errors) {
405 String[] propertyNames = configuration.getValidatedProperties();
406 for (int i = 0; i < propertyNames.length; i++) {
407 String propertyName = propertyNames[i];
408 if (logger.isDebugEnabled()) {
409 logger.debug("Validating property '" + propertyName + "' rules...");
410 }
411 ValidationRule[] rules = configuration.getPropertyRules(propertyName);
412
413
414
415
416
417 validateAndShortCircuitRules(rules, propertyName, obj, errors);
418 }
419 }
420
421
422
423
424
425
426
427
428
429
430
431 protected void validateAndShortCircuitRules(ValidationRule[] rules, String propertyName, Object obj, Errors errors) {
432 for (int i = 0; i < rules.length; i++) {
433 ValidationRule rule = rules[i];
434 if (rule.isApplicable(obj) && !rule.getCondition().check(obj)) {
435 String errorCode = errorCodeConverter.convertPropertyErrorCode(rule.getErrorCode(), obj.getClass(), propertyName);
436 errors.rejectValue(propertyName, errorCode, rule.getErrorArguments(obj), rule.getDefaultErrorMessage());
437 if (shortCircuitFieldValidation) {
438 return;
439 }
440 }
441 }
442 }
443
444
445
446
447
448
449
450
451 protected void applyCustomValidator(BeanValidationConfiguration configuration, Object obj, Errors errors) {
452 Validator validator = configuration.getCustomValidator();
453 if (validator != null) {
454 if (validator.supports(obj.getClass())) {
455 validator.validate(obj, errors);
456 }
457 }
458 }
459
460 }