Monday, May 11, 2009

Validation of nested properties with Oval 1.3x

When I used Oval for the first time I didn't realize right away that Oval didn't validate nested properties.

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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValidateNestedProperty {

}
The second step is to wrap the OVal validator into our own Validator class like this :
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 List validate(Object objectToValidate) {
return doValidate(objectToValidate, new ArrayList() );
}

private List doValidate(Object target, List errors) {

List violations = 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 :

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;
}
}
Here's the business object we want to validate :

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();
List violations = 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();
List violations = 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: 0
testValidationWithCustomValidator - Number of errors: 1
The custom validator found 1 error . On the contrary, the OVal validator found none.

9 comments:

  1. Why don't you use OVal's @AssertValid annotation?

    ReplyDelete
  2. Because 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.

    ReplyDelete
  3. The ConstraintViolation.getCauses() method does return the underlying constraint violations.

    ReplyDelete
  4. How does it work for List or any other collection

    ReplyDelete
  5. Thanks for the post, I've added nested Collection and array support:

    It'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é

    ReplyDelete
  6. Thanks for the article. I modified it by using Oval Constraints instead of custom validator.
    Check out at My Blog

    ReplyDelete
  7. Hi All,

    I 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

    ReplyDelete
  8. class type="com.test.Employee" overwrite="false"

    field name="departments"

    notNull
    appliesTo
    .constraintTarget.CONTAINER.constraintTarget
    appliesTo
    notNull

    assertValid requireValidElements="true"
    appliesTo
    constraintTarget.VALUES.constraintTarget
    appliesTo
    assertValid
    field
    class

    ReplyDelete