When I tried Oval's Spring validator for the first time I expected it to do magic. Well, it didn't :) I needed to validate a complex object graph, which my form represented. Of course default implementation does not traverse nested properties. Here is a simple solution to the problem:
package com.anydoby.oval;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells custom spring validator to follow the path and validate a nested property
* object.
* @author SergeyZ
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValidateNestedProperty {
}
Annotate all nested fields which you want the validator to traverse. Use this validator class for form processing:
package com.anydoby.oval;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import net.sf.oval.exception.ValidationFailedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* Implements spring's validator. Uses Oval {@link net.sf.oval.Validator} for actually validating
* the objects. Supports validating nested complex properties, which should be marked with
* {@link ValidateNestedProperty}.
*
* @author SergeyZ
*
*/
public class SpringOvalValidator implements Validator, InitializingBean {
private net.sf.oval.Validator validator;
public SpringOvalValidator(net.sf.oval.Validator validator) {
this.validator = validator;
}
@SuppressWarnings("unchecked")
public boolean supports(Class clazz) {
return true;
}
public void validate(Object target, Errors errors) {
doValidate(target, errors, "");
}
@SuppressWarnings("unchecked")
private void doValidate(Object target, Errors errors, String fieldPrefix) {
try {
for (ConstraintViolation violation : validator.validate(target)) {
OValContext ctx = violation.getContext();
String errorCode = violation.getErrorCode();
String errorMessage = violation.getMessage();
if (ctx instanceof FieldContext) {
String fieldName = fieldPrefix + ((FieldContext)ctx).getField().getName();
errors.rejectValue(fieldName, errorCode, errorMessage);
} else {
errors.reject(errorCode, errorMessage);
}
}
try {
Field[] fields = getFields(target);
for (Field field : fields) {
String name = field.getName();
ValidateNestedProperty validate = field.getAnnotation(ValidateNestedProperty.class);
if (validate != null) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object nestedProperty = field.get(target);
if (nestedProperty != null) {
doValidate(nestedProperty, errors, name + ".");
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} catch (final ValidationFailedException ex) {
errors.reject(ex.getMessage());
}
}
@SuppressWarnings("unchecked")
private Field[] getFields(Object target) {
Class clazz = target.getClass();
List<Field> fields = doGetFields(clazz);
return fields.toArray(new Field[fields.size()]);
}
@SuppressWarnings("unchecked")
private List<Field> doGetFields(Class clazz) {
ArrayList<Field> list = new ArrayList<Field>();
Field[] fields = clazz.getDeclaredFields();
list.addAll(Arrays.asList(fields));
if (clazz.getSuperclass() != null) {
list.addAll(doGetFields(clazz.getSuperclass()));
}
return list;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(validator, "Property [validator] is not set");
}
public net.sf.oval.Validator getValidator() {
return validator;
}
public void setValidator(net.sf.oval.Validator validator) {
this.validator = validator;
}
}
For the lazy Maven2 users:
<repositories>
<repository>
<id>anydoby.com</id>
<url>http://anydoby.com/maven</url>
</repository>
</repositories>
and dependency:
<dependency>
<groupId>com.anydoby</groupId>
<artifactId>oval-extensions</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
Good luck...
