Showing posts with label JSR 303. Show all posts
Showing posts with label JSR 303. Show all posts

Sunday, February 17, 2013

JSR 303 Custom Constraint Validator

In this article, we do not detail about JSR 303, for working with JSR 303, please read SpringMVC 3 + JSR 303 validation first.

Why we need a custom constraint validator, of course there are too much reasons for this question. Here I will show you 2 reasons:
  • I need a new validator to validate a field matches a specified pattern or not
  • I need to make the comparison between two fields (cross field validation)
So, let walk through these steps to realize this purpose

1. Import BeanUtils


<dependency>

    <groupId>commons-beanutils</groupId>

    <artifactId>commons-beanutils</artifactId>

    <version>1.8.3</version>

</dependency>

I use this util to get the value of a field in an object. This util is not important, you can use another one

2. Create a new properties used in Custom Constraint Validator

Create new ValidationMessages.properties file in folder src/main/resources
When <form:errors> renders error messages on form, it will look in this file, for more information, please read Message interpolation.

Note: by default, the normal Hibernate Validator still reads messages from application.properties

3. Create a Custom Constraint Validator

There are many ways to create a custom constraint validator, here I will show you two ways.

3.1 Custom Constraint Validator to validate a field

According to Hibernate, to create a custom constraint, the following three steps are required:
  • Create a constraint annotation
  • Implement a validator
  • Define a default error message

3.1.1 Constraint annotation

I do not detail about how to create an annotation in this article, for more detail, read it in oracle
@Target({ElementType.METHOD, ElementType.FIELD})

@Retention(RetentionPolicy.RUNTIME)

@Constraint(validatedBy=CodeValidatorImpl.class)

public @interface CodeValidator {

    String message() default "{code.not.correct}";



    Class<?>[] groups() default {};



    Class<? extends Payload>[] payload() default {};

}

  • Line 1, it means: this annotation is applied for a field
  • Line 2, it means: annotations of this type will be available at runtime by the means of reflection
  • Line 3 specifies the validator to be used to validate elements annotated with @CodeValidator
  • Line 5 defines the default message of this validator

3.1.2 Constraint validator

public class CodeValidatorImpl implements ConstraintValidator<CodeValidator, String> {



    @Override

    public void initialize(CodeValidator arg0) {

    }



    @Override

    public boolean isValid(String codeValue, ConstraintValidatorContext ctx) {

        return checkNull(codeValue, ctx) && checkMatched(codeValue, ctx);

    }



    private boolean checkNull(String codeValue, ConstraintValidatorContext ctx) {

        boolean isValid;



        if (codeValue == null || codeValue.equals("")) {

            isValid = false;

        } else {

            isValid = true;

        }

        

        if (!isValid) {

            ctx.disableDefaultConstraintViolation();

            ctx.buildConstraintViolationWithTemplate("{code.required}").addConstraintViolation();

        }

        

        return isValid;

    }

    

    private boolean checkMatched(String codeValue, ConstraintValidatorContext ctx) {

        Pattern pattern = Pattern.compile("[0-9a-zA-Z]*");

        

        Matcher matcher = pattern.matcher(codeValue);

        

        if (!matcher.matches()) {

            ctx.disableDefaultConstraintViolation();

            ctx.buildConstraintViolationWithTemplate("{code.contain.only}").addConstraintViolation();

            

            return false;

        } else {

            return true;

        }

    }

}

  • Line 1, ConstraintValidator<CodeValidator, String> type String means we use this validator to validate a String field.
  • Line 15, Make your validation in isValid function

3.1.3 Error message


In this example, I use ConstraintValidatorContext to create the custom error message
ctx.disableDefaultConstraintViolation();

ctx.buildConstraintViolationWithTemplate("{code.required}").addConstraintViolation();

These commands mean: Do not use the default message, let use the message with key = "code.required" in properties file (ValidationMessages.properties)

3.1.4 In Bean

Use the custom constraint as well as the normal constraint
public class User {



    @NotEmpty(message="user.firstName")

    private String firstName;

    

    @NotEmpty

    private String lastName;

    

    @CodeValidator

    private String code; 

....

}

3.2 Custom constraint for cross field validation

There are a few difference with the first way.

3.2.1 Constraint annotation

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Constraint(validatedBy=PasswordMatchImpl.class)

public @interface PasswordMatch {



    String message() default "{notmatch.password}";



    Class<?>[] groups() default {};



    Class<? extends Payload>[] payload() default {};

    

    /**

     * First field

     */

    String password();

    

    /**

     * Second field

     */

    String repassword();

}

  • Difference in Target
  • We require 2 fields in declaration of annotation

3.2.2 Constraint validator

public class PasswordMatchImpl implements ConstraintValidator<PasswordMatch, Object> {



    private String password;

    

    private String repassword;

    

    @Override

    public void initialize(PasswordMatch pm) {

        password = pm.password();

        repassword = pm.repassword();

    }



    @Override

    public boolean isValid(Object obj, ConstraintValidatorContext ctx) {

        

        try {

            // get field value

            Object pw = BeanUtils.getProperty(obj, password);

            Object rpw = BeanUtils.getProperty(obj, repassword);



            return pw != null && rpw != null && pw.equals(rpw);

        } catch (Exception ex) {

            return false;

        }

    }

}

  • ConstraintValidator<PasswordMatch, Object>, the argument type is Object
  • Initialize the attribute in initialize function (it's the name of field, not the value of field)
  • We get value of field through the obj parameter

3.2.3 In Bean

@PasswordMatch(password="password", repassword="repassword")
public class User {

    @NotEmpty(message="user.firstName")
    private String firstName;

...

}

  • We validate for an object, not for a field
  • The parameters are required

4. Error message in JSP

As normal validation, we use <form:errors> to show the error message
<tr>
    <td><form:label path="firstName">First Name</form:label></td>
    <td>
        <form:input path="firstName" />
        <form:errors path="firstName" class="errors" />
    </td>
</tr>


References:
Source code:

SpringMVC 3 + JSR 303 validation with Hibernate validator 4.x

Spring provides many ways to validate an object, I have posted an article about Spring's Validator Interface, now I will show you how to validate an object with JSR 303 in Spring 3.x by using Hibernate validator.

1. Import Hibernate Validator 4.x to project


<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.3.1.Final</version>
</dependency>

This is an implementation of JSR 303

2. Validation annotation

There is a few difference with the validation by using Spring's Validator Interface. We define the constraints in object such properties:
public class User {
    @NotEmpty(message="First Name is required")
    private String firstName;
    @NotEmpty
    private String lastName;    
    @CodeValidator
    private String code;    
    @NotEmpty
    private String password;    
    @NotEmpty
    private String repassword;

.... 

}

For more details about validation annotations, have a look at Hibernate Validator,

3. Support validation JSR 303

To support JSR 303 in Spring application we modify the *-servlet.xml to add

<mvc:annotation-driven/>
or
<bean id="validator"
          class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

A JSR-303 provider, such as Hibernate Validator, is expected to be present in the classpath and will be detected automatically. 

4. Controller

In controller, when you want to validate an object, you put the @Valid annotation in front of its place, the framework will detect and make validation for us.
@RequestMapping(value = "/modifyUser", method = RequestMethod.POST)
    public ModelAndView updateUser(@Valid @ModelAttribute("user") User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // show errors
            return new ModelAndView("modifyUser", "user", user);
        } else {
            // success
            return new ModelAndView("viewUser", "user", user);
        }   
    }

5. Configure ResourceBundle

  • Create new properties file in folder src/main/resources
  • Create new bean instance in *-servlet.xml to load resource file
<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="application" />
</bean>

Normally, the error message must follow [Constraint].[Bean name].[Property name]

For example, the application.properties contains:
NotEmpty.user.firstName = First Name is required
NotEmpty.user.lastName = Last Name is required
NotEmpty.user.password = Password is required
NotEmpty.user.repassword = Retype password is required

6. Prepare JSP page

Do not have anything new in JSR 303 for JSP, you do it as well as Spring's Validator Interface
<tr>
    <td><form:label path="firstName">First Name</form:label></td>
    <td>
        <form:input path="firstName" />
        <form:errors path="firstName" class="errors" />
    </td>
</tr>

References:
Source code:
  • For the demonstration, please download source code from HERE
  • My repository on Github