JSR 303 - Bean Validation with OpenJPA - Part III

on July 25, 2011 0 comments
      
In the previous article, we have seen custom constraints. In this article we will see about validation groups.

Validation Groups:
Bean validation uses validation groups to determine what and when validation occurs. A validation group contains one or more constraints. There are no special interfaces to implement or annotations to apply in order to create a validation group. A validation group is denoted simply by a class definition. However, it is strongly recommended that simple interfaces are used. This is a best practice since it makes validation groups more usable in multiple environments. Whereas, if a class or entity definition were used as a validation group, it may pollute the object model of another application by bringing in domain classes and logic that do not make sense for the application. By default, if a validation group or multiple groups is not specified on an individual constraint, it will be validated using the javax.validation.groups.Default group.

Another advantage with validation groups is, instead of validating all the constraints at once, it will validate one group after the other, it will stop validation if one group couldn't be validated successfully.

Let's see how it can be implemented,
package com.sample.bean;

import java.util.Date;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class Book {

 @NotNull(groups = BookNotNull.class, message="Book Name should not be null")
 @Size(min = 2, max = 50, message = "Name length should be between 2 and 50")
 private String name;
 
 @NotNull(groups = BookNotNull.class, message="Book Author should not be null")
 private String author;
 
 @NotNull(groups = BookNotNull.class, message="Book Edition should not be null")
 private String edition;
 
 @NotNull(groups = BookNotNull.class, message="Release date should not be null")
 @Future(message = "Release date should not be past date")
 private Date releaseDate;

 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getAuthor() {
  return author;
 }
 public void setAuthor(String author) {
  this.author = author;
 }
 public String getEdition() {
  return edition;
 }
 public void setEdition(String edition) {
  this.edition = edition;
 }
 public Date getReleaseDate() {
  return releaseDate;
 }
 public void setReleaseDate(Date releaseDate) {
  this.releaseDate = releaseDate;
 }
 public interface BookNotNull {
 } 
}
In the above bean, @NotNull constraint is part of the BookNotNull validation group and @Size, @Future constraints belong to the Default group (which is specified by the JSR 303 API), as no special validation group is specified for them.

Now, we will test this using BookTest.java as shown below,
package com.bean.test;

import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import com.sample.bean.Book;
import com.sample.bean.Book.BookNotNull;

public class BookTest {
 
 private static Validator validator;

 public static void main(String args[]) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        Calendar cal = Calendar.getInstance();
        Book b = new Book();
        b.setAuthor("Satish");
        b.setEdition("first");
        b.setName("j");
        b.setReleaseDate(new Date("07/26/2011"));
        b.setRating(1);
        Set<ConstraintViolation<Book>> constraintViolations = validator.validate(b, BookNotNull.class, Default.class);
        int count = constraintViolations.size();
        System.out.println("Total voilations: "+count);        
        Iterator it = constraintViolations.iterator();
        while(it.hasNext()){
         ConstraintViolation cv=(ConstraintViolation)it.next();
         System.out.println(cv.getMessage());
        }        
    }
}
Above, we have specified BookNotNull and Default groups in validate(). So, first only the constraints which are under BookNotNull group will be validated. If there is any violations, processing stops there(constraints under default group will not be validated). If no violations occur, then constraints under Default group will be validated.

If you specify only BookNotNull group in validate(), only constraints under BookNotNull group will be validated.
   

JSR 303 - Bean Validation with OpenJPA - Part II

0 comments
  
In the previous article, we have seen how to use built-in constraints defined by Bean Validation Specification. In this article, we will see how to implement our own custom constraints.

If the built-in constraints do not meet your requirements, you can create your own custom validators and constraints. There are two ways of doing this, Implementing from scratch or Combine constraints from already existing constraints(Compound Constraints).

Implementing from scratch:
To implement a new constraint from scratch you first need to know how JSR-303 constraints work.

A constraint is basically a pair of an annotation and its associated validator class. When a bean is validated, it is being scanned for all the constraint annotations. Once such annotation is found its associated validator is created and initialized with the annotation (the annotation in a way serves as the configuration for its validator). How does the framework know which validator to instantiate? well... the annotation indicates it, and it's best explained by an example.

In this example, we will create a 'User' bean and validate the user name, whether it is reserved name or not. First, create a @ReservedUser annotation as shown below,
package com.bean.validators;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UserValidator.class)
@Documented
public @interface ReservedUser {

    String message() default "This UserName is reserved word."; 
    //String message() default "{username.reserved.message}";
    Class[] groups() default {};
    public abstract Class[] payload() default {};
} 
An annotation type is defined using the @interface keyword. All the defined attributes in above annotation are specified in the specification and mandatory for all constraint annotations. The specification of the Bean Validation API demands that all constraint annotations should define the below attributes:
  • message: This attribute can be used to set a custom error message that will be displayed if the constraint defined by the annotation is not met. If we want to set a message bundle key instead of a literal message, we should surround it with braces. So we can set message to either "This user name is reserved word." or "{username.reserved.message}". See the below NOTE for more information on this.
  • groups: This attribute can be used to associate a constraint with one or more validation processing groups. Validation processing groups can be used to influence the order in which constraints get validated, or to validate a bean only partially.
  • payload: This attribute can be used to attach extra meta information to a constraint. The Bean Validation standard does not define any standard metadata that can be used, but specific libraries can define their own metadata.
In addition, we annotate the annotation type with a couple of meta annotations:
  • @Target({ METHOD, FIELD, ANNOTATION_TYPE }): Says that methods, fields and annotation declarations may be annotated with @ReservedUser (but not type declarations).
  • @Retention(RUNTIME): Specifies that annotations of this type will be available at runtime by the means of reflection.
  • @Constraint(validatedBy = UserValidator.class): Specifies the validator to be used to validate elements annotated with @ReservedUser.
  • @Documented: Says that the use of @UpperCase will be contained in the JavaDoc of elements annotated with it.
NOTE: The specification define quite a powerful message interpolation mechanism for the error messages. The message can contain placeholders (surrounded by curly brackets) which can be replaced by the attributes defined in the annotation itself. Furthermore, the placeholders can also refer to keys defined in a resource bundle. In the later case, the placeholders will be replaced by their associated values in the bundle(see the commented line above). For example, as we did in the @ReservedUser annotation, it is considered a best practice to assign a default message value. This value actually consists of one parameter placeholder which refers to a key in a resource bundle ("username.reserved.message"). When the error message is resolved, the value of this key is looked up in the resource bundle and when found replaces the placeholder. By default, the validator will look for a resource bundle named ValidationMessages.properties in the classpath.
    Next, we need to implement a Constraint Validator, which is able to validate elements with the @ReservedUser annotation. To do so, we have to implement the interface ConstraintValidator as shown below,
    package com.bean.validators;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    public class UserValidator implements ConstraintValidator<ReservedUser, String> {
        public void initialize(ReservedUser constraintAnnotation) {
             //nothing to do
        }
        public boolean isValid(String username, ConstraintValidatorContext cvc) {
            if (username == null){
                return true;
            }else{
                if(username.equalsIgnoreCase("SATISH"))
                   return false;
                else
                   return true;
            }
        }
    }
    
    The ConstraintValidator interface specifies two type parameters, which we set in our implementation. The first specifies the annotation type to be validated by a ConstraintValidator (in our example ReservedUser), the second the type of elements, which the validator can handle (here String).

    The implementation of the validator is straightforward. The initialize() method gives us access to any attributes of the annotation (such as the min/max fields in case of the Size annotation), but as @ReservedUser doesn't define any such attributes, we have nothing to do here.

    What's interesting for us, is the isValid() method. It determines whether a given object is valid according to the @ReservedUser annotation or not.

    Next, create 'User.java' bean and define the custom constraint for username as shown below,
    package com.sample.bean;
    
    import com.bean.validators.ReservedUser;
    
    public class User {
     
     @ReservedUser(message = "Please use another username.")
     private String userName;
     private String userId;
     private int age;
     public String getUserName() {
      return userName;
     }
     public void setUserName(String userName) {
      this.userName = userName;
     }
     public String getUserId() {
      return userId;
     }
     public void setUserId(String userId) {
      this.userId = userId;
     }
     public int getAge() {
      return age;
     }
     public void setAge(int age) {
      this.age = age;
     }
    } 
    
    Now, create 'UserTest.java' program to test the validation as shown below,
    package com.bean.test;
    
    import static java.lang.System.out;
    import java.util.Calendar;
    import java.util.Iterator;
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    
    import com.sample.bean.User;
    
    public class UserTest {
     
     private static Validator validator;
    
     public static void main(String args[]) {
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            validator = factory.getValidator();
            Calendar cal = Calendar.getInstance();
            User u = new User();
            u.setUserName("satish");
            Set<ConstraintViolation<User>> constraintViolations = validator.validate(u);
            int count = constraintViolations.size();
            out.println("Total voilations: "+count);        
            Iterator it = constraintViolations.iterator();
            while(it.hasNext()){
                ConstraintViolation cv=(ConstraintViolation)it.next();
                out.println(cv.getMessage());
            }        
        }
    } 
    
    Thats it, you're done. You can test it by changing the name in setUserName().

    Compound Constraints:
    Sometimes there is no real need to create constraint entirely from scratch. It is often the case where a constraint is either a specific variation of another constraint, or a combination of other constraints. The specification acknowledges that and makes it even easier to define such constraints.

    So, let's compose a new constraint annotation @ValidUserName, that comprises the constraints @NotNull, @Size and @ReservedUser as shown below,
    package com.bean.validators;
    
    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import javax.validation.ReportAsSingleViolation;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    
    @NotNull
    @Size(min = 2, max = 14)
    @ReservedUser
    @Target( { METHOD, FIELD, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = {})
    @Documented
    @ReportAsSingleViolation
    public @interface ValidUserName {
    
        String message() default "This Username is not Valid.";
        Class<?>[] groups() default {};
        public abstract Class<? extends Payload>[] payload() default {};
    } 
    
    As you can see, no validator is associated with this annotation. Instead, it is annotated with the @ReservedUser annotation which holds the reserved username validation. By default, when the validation is performed, all the constraints are evaluated (in our case, @NotNull, @Size, @ReservedUser constraints) and register any encountered violations. Sometimes you'd like only one error message to be reported. This is where the @ReportAsSingleViolation annotation becomes useful. This annotation indicates that on any constraint violation of any of the constraints, only one violation will be reported and all other reported violations will then be ignored.

    Now, we have created a compound constraint @ValidUserName. We can define only this constraint for validating username instead of @NotNull, @Size and @ReservedUser as shown below,
    package com.sample.bean;
    
    import com.bean.validators.ValidUserName;
    
    public class User {
     
     @ValidUserName
     private String userName;
    
            //Other fields
    } 
    
    In the next article, we will see about Validation Groups.
         

    JSR 303 - Bean Validation with OpenJPA - Part I

    0 comments
        
    JSR 303:
    JSR-303 is a specification for Bean Validation. It is an attempt to come up with a standard declarative validation framework. The infrastructure defined by JSR-303 enables you to describe the constraints using constraint annotations on your bean and validate your bean using constraint validators.

    OpenJPA:
    A new feature defined by the JPA 2.0 specification is the ability to integrate with JSR-303 bean validation provider. With minimal effort, OpenJPA 2.0 can be coupled with JSR-303 validation provider to provide runtime data validation. By combining these two technologies, you get a standardized persistence solution with the added ability to perform standardized java bean validation.

    What is Bean Validation?
    Most applications, especially those that gather input from a user, contain a significant amount of code to perform data validation. It is typically custom code, fragmented and littered throughout the application. Worse, there may be duplicate validation code at the presentation (Web) , business (EJB), and persistence layers. The task of keeping duplicate code in sync can be especially problematic. A slight modification in the code at one layer can lead to an unforeseen breakage in another.

    The Bean Validation API was designed to provide a standardized method for validating data within Java beans. As an added feature, it seamlessly integrates with several JEE6 technologies including JPA 2.0, JCA 1.6, and JSF 2.0. Additionally, JEE6 complaint servers are required to support bean validation and must include a validation provider. While a JEE6 environment provides some simplified packaging and configuration benefits, bean validation works equally well in a JSE environment.

    While the JSR-303 specification is very feature rich and extensible, there are three core concepts that will be of most interest to the majority of users: constraints, constraint violation handling, and the validator itself. Constraints are a fundamental component of bean validation. Constraints can be placed on Java beans and/or fields(attributes) to constrain the data within the bean. A constraint can either be defined using annotations or XML. The bean validation specification defines a small set of constraints(Built-in constraints) that must be included with every validation provider. This small set covers most types of simple validation in addition to a powerful regular expression based validator. If the built-in constraints don't fit the bill, you can very easily build your own custom validators and constraints. Let's start by looking at some simple constraints and then move to creating some custom constraints.

    Pre-requisites:
    Before going to the examples, download the required jar file from here. At the time of writing this article, the latest version is 2.1.0. Download the apache-openjpa-2.1.0.zip file, extract it, and copy the openjpa-all-2.1.0.jar file and include it in your classpath.

    How to use Built-in Constraints:
    In the following example, I am going to explain how to use Built-in Constrains defined by the bean validation specification.

    Create a java bean class Book.java and define some simple built-in constraints as shown below,
    package com.sample.bean;
    
    import java.util.Date;
    import javax.validation.constraints.Future;
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Pattern;
    import javax.validation.constraints.Size;
    
    public class Book {
    
     @NotNull(message="Book Name should not be null")
     @Size(min = 2, max = 50, message = "Name length should be between 2 and 50")
     private String name;
     
     @NotNull(message="Book Author should not be null")
     private String author;
     
     @NotNull(message="Book Edition should not be null")
     private String edition;
     
     @NotNull(message="Release date should not be null")
     @Future(message = "Release date should not be past date")
     private Date releaseDate;
    
     @Min(1)
     @Max(5)
     private int rating;
     
     public String getName() {
      return name;
     }
     public void setName(String name) {
      this.name = name;
     }
     public String getAuthor() {
      return author;
     }
     public void setAuthor(String author) {
      this.author = author;
     }
     public String getEdition() {
      return edition;
     }
     public void setEdition(String edition) {
      this.edition = edition;
     }
     public Date getReleaseDate() {
      return releaseDate;
     }
     public void setReleaseDate(Date releaseDate) {
      this.releaseDate = releaseDate;
     }
     public int getRating() {
      return rating;
     }
     public void setRating(int rating) {
      this.rating = rating;
     } 
    }
    
    NOTE: It is possible to put the annotations on the fields or on the setter methods of the bean properties. Personally, I like putting them on the fields as it's easier to pick up when reading the code.
     
    Built-In Bean Validation Constraints:

    Constraint Description Example
    @AssertFalse The value of the field or property must be false.
    @AssertFalse
    boolean isUnsupported;
    @AssertTrue The value of the field or property must be true.
    @AssertTrue
    boolean isActive;
    @DecimalMax The value of the field or property must be a decimal value lower than or equal to the number in the value element.
    @DecimalMax("30.00")
    BigDecimal discount;
    @DecimalMin The value of the field or property must be a decimal value greater than or equal to the number in the value element.
    @DecimalMin("5.00")
    BigDecimal discount;
    @Digits The value of the field or property must be a number within a specified range. The integer element specifies the maximum integral digits for the number, and the fraction element specifies the maximum fractional digits for the number.
    @Digits(integer=6, fraction=2)
    BigDecimal price;
    @Future The value of the field or property must be a date in the future.
    @Future
    Date eventDate;
    @Max The value of the field or property must be an integer value lower than or equal to the number in the value element.
    @Max(10)
    int quantity;
    @Min The value of the field or property must be an integer value greater than or equal to the number in the value element.
    @Min(5)
    int quantity;
    @NotNull The value of the field or property must not be null.
    @NotNull
    String username;
    @Null The value of the field or property must be null.
    @Null
    String unusedString;
    @Past The value of the field or property must be a date in the past.
    @Past
    Date birthday;
    @Pattern The value of the field or property must match the regular expression defined in the regexp element.
    @Pattern(regexp="\\(\\d{3}\\)\\d{3}-\\d{4}")
    String phoneNumber;
    @Size The size of the field or property is evaluated and must match the specified boundaries. If the field or property is a String, the size of the string is evaluated. If the field or property is a Collection, the size of the Collection is evaluated. If the field or property is a Map, the size of the Map is evaluated. If the field or property is an array, the size of the array is evaluated. Use one of the optional max or min elements to specify the boundaries.
    @Size(min=2, max=240)
    String briefMessage;


    Create a java class BookTest.java to test the constraints defined in java bean as shown below,
    package com.bean.test;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Iterator;
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    import com.sample.bean.Book;
    
    public class BookTest {
     
     private static Validator validator;
    
     public static void main(String args[]) {
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            validator = factory.getValidator();
            Calendar cal = Calendar.getInstance();
            Book book = new Book();
            //book.setAuthor("Satish");
            //book.setEdition("first");
            //book.setName("java");
            //book.setReleaseDate(new Date("07/21/2011"));
            book.setRating(1);
            Set<ConstraintViolation<Book>> constraintViolations=validator.validate(book);
            int count = constraintViolations.size();
            System.out.println("Total voilations: "+count);        
            Iterator it = constraintViolations.iterator();
            while(it.hasNext()){
               ConstraintViolation cv=(ConstraintViolation)it.next();
               System.out.println(cv.getMessage());
            }        
        }
    }
    On line 19, we get a Validator object from the ValidatorFactory, we use this validator to validate the 'book' object. The validate() method returns a set of ConstraintViolation objects, which we can iterate through in order to see which validation errors occured.

    In the next article, we will see how to create our own custom constraints.