Русский English Tags View Sergey Zolotaryov's profile on LinkedIn Sign-in
A better Spring validator for Oval
Permanent link 05-02-2008 anydoby java

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...

Add a comment

p
19-06-2008

Use @AssertValid. It does traverse as far as you Annotate containing attributes whit it.

myrddin22
12-05-2009

Indeed @AssertValid will validate as far as you want but you will not know which field is in error in your nested object . I just tried it and when I have 2 errors in my nested objects, I only get this output when writing the ConstraintViolations on sysout

[net.sf.oval.ConstraintViolation: net.sf.oval.constraint.AssertValid.violated]

For my part, I need to know all the fields in error in my object not just something telling that my nested object is not valid.

anydoby
12-05-2009

Hope my validator suits your needs :)

myrddin22
12-05-2009

Yes. Without it I could no longer use OVal in my project. Thanks.

test
09-09-2009

@AssertValid works fine, you only have to get the causes from the violation to get all errors.

net.sf.oval.ConstraintViolation[] causes = constraintViolation.getCauses();

superflybonzai
20-11-2009

A better way to go is to use

@AssertValid(errorCode = "<here describe your property>")
to document where the Valdation was violated and to override
protected void checkConstraintAssertValid
in your own Validator implementation. There you can add the violations to the parent list. Here is my Validator:

public class Validator extends net.sf.oval.Validator {

	@Override
	protected void checkConstraintAssertValid(
			List<ConstraintViolation> violations, AssertValidCheck check,
			Object validatedObject, Object valueToValidate,
			OValContext context, String[] profiles) throws OValException {
		if (valueToValidate == null) return;

		// ignore circular dependencies
		if (isCurrentlyValidated(valueToValidate)) return;

                // changed here access to private class variable to getter
		final List<ConstraintViolation> additionalViolations = getCollectionFactory().createList();
		validateInvariants(valueToValidate, additionalViolations, profiles);

		if (additionalViolations.size() != 0)
		{
			final String errorMessage = renderMessage(context, valueToValidate, check.getMessage(), check
					.getMessageVariables());

			violations.add(new ConstraintViolation(check, errorMessage, validatedObject, valueToValidate, context,
					additionalViolations));
//add the violations to parent list :-)
			violations.addAll(additionalViolations);
		}
	}

}

Thats it and should fulfill your reuirements.

Previous article Automatic generation of checksums and installation of source artifacts in Maven2 Next article Too long method size