At the beginning of the project I was working on, I had simple objects with simple types like String or int. But my objects evolved in a more complex way with nested objects in it. I was very disappointed when I ran my Oval validator and didn't see the outcome I expected.
I found the solution on this blog and customize it a little bit.
The solution consists of creating an annotation and a custom validator class which wrap the OVal validator.
Here's the implementation.
Firstable we have to create the annotation.
import java.lang.annotation.ElementType;The second step is to wrap the OVal validator into our own Validator class like this :
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValidateNestedProperty {
}
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import net.sf.oval.ConstraintViolation;
/**
* Default oval implementation does not validate nested property
*
* @author Julien Dechmann
*
*/
public class CustomOValValidator {
private net.sf.oval.Validator validator;
public CustomOValValidator() {
validator = new net.sf.oval.Validator();
}
/**
* Process the validation
* @param objectToValidate
*/
public Listvalidate(Object objectToValidate) {
return doValidate(objectToValidate, new ArrayList() );
}
private ListdoValidate(Object target, List errors) {
Listviolations = validator.validate(target);
if(violations != null)
errors.addAll(violations);
Field[] fields = getFields(target);
for (Field field : fields) {
ValidateNestedProperty validate = field.getAnnotation(ValidateNestedProperty.class);
if(validate!=null) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object nestedProperty;
try {
nestedProperty = field.get(target);
} catch (Exception ex) {
throw new RuntimeException("Reflexion error", ex);
}
if(nestedProperty!=null)
doValidate(nestedProperty, errors);
}
}
return errors;
}
/**
* Return the list of fields from an object
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public static Field[] getFields(Object target) {
Class clazz = target.getClass();
return clazz.getDeclaredFields();
}
}
Let's write a simple example.
NestedBusinessObject.java :
Here's the business object we want to validate :
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNull;
/**
* Nested business object
*/
public class NestedBusinessObject {
@NotNull
@NotEmpty
private String property;
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
BusinessObject.java
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNull;
public class BusinessObject {
@NotNull
private int id;
@NotNull
@NotEmpty
private String name;
@ValidateNestedProperty
private NestedBusinessObject nestedBusinessObject;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public NestedBusinessObject getNestedBusinessObject() {
return nestedBusinessObject;
}
public void setNestedBusinessObject(NestedBusinessObject nestedBusinessObject) {
this.nestedBusinessObject = nestedBusinessObject;
}
}
And the JUnit 4 test class :
import java.util.List;
import net.sf.oval.ConstraintViolation;
import org.junit.Before;
import org.junit.Test;
public class BuisinessObjectValidationTest {
private BusinessObject businessObject;
@Before
public void before() {
businessObject = new BusinessObject();
businessObject.setId(3455);
businessObject.setName("My business object");
//Since the property of the nestedBusinessObject
//is empty, the validation of the businessObject
//should return a ConstraintViolation
NestedBusinessObject nestedBusinessObject = new NestedBusinessObject();
nestedBusinessObject.setProperty("");
businessObject.setNestedBusinessObject(nestedBusinessObject);
}
/**
* Test with the OVal validator
*/
@Test
public void testValidationWithOvalValidator() {
net.sf.oval.Validator validator = new net.sf.oval.Validator();
Listviolations = validator.validate(businessObject);
System.out.println("testValidationWithOvalValidator " +
"- Number of errors: "+violations.size());
}
/**
* Test with the custom validator
*/
@Test
public void testValidationWithCustomValidator() {
CustomOValValidator validator = new CustomOValValidator();
Listviolations = validator.validate(businessObject);
System.out.println("testValidationWithCustomValidator " +
"- Number of errors: "+violations.size());
}
}
The output of the execution of this test is :
testValidationWithOvalValidator - Number of errors: 0The custom validator found 1 error . On the contrary, the OVal validator found none.
testValidationWithCustomValidator - Number of errors: 1
Why don't you use OVal's @AssertValid annotation?
ReplyDeleteBecause if you have a lot of properties in your nested object, with the AssertValid annotation, you can't know how many errors you have in it. For example if I have 2 errors in my nested object, the AssertValid annotation will only return 1 ConstraintViolation.
ReplyDeleteThe ConstraintViolation.getCauses() method does return the underlying constraint violations.
ReplyDeleteHow does it work for List or any other collection
ReplyDeleteThanks for the post, I've added nested Collection and array support:
ReplyDeleteIt's working OK so far (nested objects doesn't have references between them)
if (nestedProperty != null) {
if (nestedProperty instanceof Collection) {
// nestedProperty is a collection
Collection col = (Collection)nestedProperty;
for (Object object : col) {
doValidate(object, errors);
}
} if (nestedProperty.getClass().isArray()) {
// nestedProperty is an array
int length = Array.getLength(nestedProperty);
for (int i = 0; i < length; i++) {
Object o = Array.get(nestedProperty, i);
doValidate(o, errors);
}
} else {
// nestedProperty is other object
doValidate(nestedProperty, errors);
}
}
Regards,
José
Thanks for the article. I modified it by using Oval Constraints instead of custom validator.
ReplyDeleteCheck out at My Blog
Hi All,
ReplyDeleteI have case where in i have write a xml file contraint violation and i have scenario where in i need to check value of nested object in a collection how to do that plz help.
CONTAINER
VALUES
class type="com.test.Employee" overwrite="false"
ReplyDeletefield name="departments"
notNull
appliesTo
.constraintTarget.CONTAINER.constraintTarget
appliesTo
notNull
assertValid requireValidElements="true"
appliesTo
constraintTarget.VALUES.constraintTarget
appliesTo
assertValid
field
class