Friday, January 29, 2010

JSR 303 Bean Validation: @ValidateDateRange - A reusable constraint annotation to validate date ranges

In a previous blog-post, I have described my experience on JSR 303 Bean Validation, and in the process, this @ValidateDateRange constraint annotation came upon into my mind. I thought that it would be useful so it inspired me to create its implementation and eventually made this blog-post.

The @ValidateDateRange is a class-level constraint annotation that will validate a date range represented by two java.util.Date properties of an object. This can be reused in any class.
@ValidateDateRange(start="startDate", end="endDate")
public class MyEntity {
   private Date startDate;
   private Date endDate;
   ...
}

The annotation definition are as follows:
package blogspot.soadev.validator;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import java.util.Date;

import javax.validation.Constraint;
import javax.validation.ConstraintPayload;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {DateRangeValidator.class} )
@Documented

public @interface ValidateDateRange {
    String message() default "{end} should be later than {start}";
    String start();
    String end();
    Class[] groups() default {};
    Class[] payload() default {};       
}
And the implementing DateRangeValidator are as follows:
package blogspot.soadev.validator;

import java.lang.reflect.Field;
import java.util.Date;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DateRangeValidator implements ConstraintValidator{
    private String start;
    private String end;
    
    public void initialize(ValidateDateRange validateDateRange) {
        start = validateDateRange.start();
        end =  validateDateRange.end();
    }

    public boolean isValid(Object object,
                           ConstraintValidatorContext constraintValidatorContext) {
        try {
            Class clazz = object.getClass();
            Date startDate = null;
            Method startGetter = clazz.getMethod(getAccessorMethodName(start), new Class[0]);
            Object startGetterResult = startGetter.invoke(object, null);
            if (startGetterResult != null && startGetterResult instanceof Date){
                startDate = (Date) startGetterResult;
            }else{
                return false;
            }
            Date endDate = null;
            Method endGetter = clazz.getMethod(getAccessorMethodName(end), new Class[0]);
            Object endGetterResult = endGetter.invoke(object, null);
            if (endGetterResult == null){
                return true;
            }
            if (endGetterResult instanceof Date){
                endDate = (Date) endGetterResult;
            }
            return (startDate.before(endDate));           
        } catch (Throwable e) {
            System.err.println(e);
        }

        return false;
    }
    private String getAccessorMethodName(String property){
        StringBuilder builder = new StringBuilder("get");
        builder.append(Character.toUpperCase(property.charAt(0))); 
        builder.append(property.substring(1));
        return builder.toString();
    }
}

Related Posts


Cheers!

4 comments:

  1. Do you know any automatic way to interpolate (for example via resource bundle) the field names in the message? By this I mean, the user should not see 'code' such as "endDate should be later than startDate". Would you recommend just specifying a different message each time your constraint is used?

    ReplyDelete
  2. Hi Peter,
    The field names by default are being interpolated into the message (check the default message on the "ValidateDateRange" interface definition above)and you can also do the same on a resource bundle.
    FYI,
    Rommel Pino

    ReplyDelete
  3. Can not run in Weblogic 10.3.3?

    ReplyDelete
  4. Any thoughts on how you would approach validating a class that has more than one start and end date.

    ReplyDelete