Archive

Posts Tagged ‘JSR 303’

Custom JSR 303 Constraints

April 24th, 2012 No comments

In my previous blog post I talked about validating Spring Web applications using JSR 303 annotations. In that post, we used the out of the box JSR 303 constraints such as @NotEmpty and @Size. These out of the box constraints should be sufficient for most cases. However, there will be situations where you want to develop custom constraints. In this post, we will look at creating a custom constraint
that validates the ISBN number of a book in our Online Bookstore Admin application.

Each JSR 303 validation constraint consists of two parts. The first part is the constraint annotation itself.

package com.inflinx.blog.bookstore.constraint;

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

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy=IsbnValidator.class)
public @interface Isbn 
{
	String message() default "{Isbn.message}";

	Class[] groups() default {};

	Class[] payload() default {};
}

The JSR 303 specification requires each constraint annotation to define the following three attributes:

  • message – The error message that gets returned upon validation failure. Here we have defined the default value Isbn.message that acts as a resource bundle key. It is possible to just hard code a message by simply omitting the braces.
  • groups – This defines the constraint groups that this annotation needs to be associated with. Here we will use the default group.
  • Payload – This holds the additional metadata information that can be supplied by validation clients. Here we are using the default empty array.

The @Target, @Retention and @Documented annotations are needed for the annotation declaration. The @Constraint annotation declares the validator that we will be using to validate elements annotated with @Isbn constraint.

The next step in creating the custom constraint is defining the validator.

package com.inflinx.blog.bookstore.constraint;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.apache.commons.validator.routines.ISBNValidator;

public class IsbnValidator implements ConstraintValidator
{
	@Override
	public void initialize(Isbn isbn) { }

	@Override
	public boolean isValid(String value, ConstraintValidatorContext validatorContext) 
	{
		if(value == null || "".equals(value))
		{
			return true;
		}
		else
		{
			return ISBNValidator.getInstance().isValid(value);
		}
	}

}

Every constraint validator needs to implement the ConstraintValidator interface. The actual implementation of the IsbnValidator is straightforward. According to the specification, if the annotated element’s value is null or empty the validation should succeed. If you don’t want a null value, the element should be annotated with @NotNull annotation. The actual validation of the ISBN value is delegated to the Apache Validation API.

Now that we have the constraint defined, the next step is to use it in our Book Store application. To do that, we start out by creating a new property in the Book domain class and annotated it with @Isbn annotation. Here is the modified book class:

package com.inflinx.blog.bookstore.domain;

import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;

import com.inflinx.blog.bookstore.constraint.Isbn;

public class Book
{
	@NotEmpty
	private String name;
	
	@Size(min=1, max=50)
	private String description;
	
	@NotEmpty
	@Isbn
	private String isbn;
	
	public String getIsbn() {
		return isbn;
	}
	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}
	public String getName()
	{
		return name;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public String getDescription()
	{
		return description;
	}
	public void setDescription(String description)
	{
		this.description = description;
	}
	
	@Override
	public String toString()
	{
		return "Name: " + name + ", Description: " + description + ", ISBN: " + isbn;
	}
}

Then we need to modify the form.jsp file to add the ISBN form field.

 		<form:form method="post" action="book.html" commandName="book">
					<table>
						<tr>
							<td>Name:</td> <td><form:input path="name" /></td> <td><form:errors path="name" /></td>
						</tr>
						<tr>
							<td>Description:</td> <td><form:textarea path="description"/></td> <td><form:errors path="description" /></td>
						</tr>
						<tr>
							<td>ISBN:</td> <td><form:input path="isbn" /></td> <td><form:errors path="isbn" /></td>
						</tr>
						</table>
					<input type="submit" value="Create" />
		</form:form>

The final step in this process is to add a new validation key/value to the messages.properties file. Here is the new messages.properties file:

NotEmpty.book.name=Name is a required field
Size.book.description=Description must be between 1 and 50 characters
Isbn.book.isbn=Please enter a valid ISBN

Now, when you submit the form with an invalid ISBN value, you will see a validation error.

JSR 303 Bean Validation Using Spring 3

March 10th, 2010 27 comments

The JSR 303 specification provides a metadata model for JavaBean validation. The validation API provides a variety of annotations that makes validation easy at any layer. In this post I will demo Spring 3′s support for JSR 303 in the web layer. You can download the code here (eclipse project).

The first part of the post talks about creating a simple bookstore admin application using Spring MVC. The second part of the post will add JSR 303 validation. If you are an experienced Spring developer, you can safely skip to the second part. The final part will show how to customize the validation error messages.

1. Online Bookstore Admin Application

The admin application will be designed to allow administrators enter information about new books. Here are the steps that implement this requirement:

Step 1. Create a web project and add all the required jars
I will be using Eclipse’s and here is a list of the required jars that need to be in the classpath:

org.springframework.asm-3.0.1.RELEASE.jar
org.springframework.beans-3.0.1.RELEASE.jar
org.springframework.context-3.0.1.RELEASE.jar
org.springframework.context.support-3.0.1.RELEASE.jar
org.springframework.core-3.0.1.RELEASE.jar
org.springframework.expression-3.0.1.RELEASE.jar
org.springframework.test-3.0.1.RELEASE.jar
org.springframework.web-3.0.1.RELEASE.jar
org.springframework.web.servlet-3.0.1.RELEASE.jar
commons-collections-3.1.jar
hibernate-validator-4.0.2.GA.jar
jcl-over-slf4j-1.5.10.jar
joda-time-1.6.jar
jstl-1.2.jar
log4j.jar
slf4j-api-1.5.8.jar
slf4j-log4j12.jar
validation-api-1.0.0.GA.jar

Step 2. Create a web-Context.xml file under WEB-INF folder to hold web layer spring bean configuration. Here are the contents of the file:

		
		<?xml version="1.0" encoding="UTF-8"?>
		<beans xmlns="http://www.springframework.org/schema/beans"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			xmlns:context="http://www.springframework.org/schema/context"
			xmlns:mvc="http://www.springframework.org/schema/mvc"
			xsi:schemaLocation="
				http://www.springframework.org/schema/beans	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
				http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
			
			<!-- Enable annotation driven controllers, validation etc... -->
			<mvc:annotation-driven />
			
			<!-- Controllers package -->
			<context:component-scan base-package="com.inflinx.blog.bookstore.web.controller" />
		
			<!-- JSP page location -->
			<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
				<property name="suffix" value=".jsp"/>
			</bean>
			 	
		</beans>
			
	

Step 3. Add Spring MVC Dispatcher Servlet to web.xml file. In the declaration below, Spring MVC will handle all URL requests to html pages.

		
		<?xml version="1.0" encoding="UTF-8"?>
		<web-app version="2.5" 
			xmlns="http://java.sun.com/xml/ns/javaee" 
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
			xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

		  <servlet>
				<servlet-name>bookstore</servlet-name>
				<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
				<init-param>
					<param-name>contextConfigLocation</param-name>
					<param-value>/WEB-INF/web-Context.xml</param-value>
				</init-param>
				<load-on-startup>1</load-on-startup>
			</servlet>
		
			<servlet-mapping>
				<servlet-name>bookstore</servlet-name>
				<url-pattern>*.html</url-pattern>
			</servlet-mapping>
		
		</web-app>
		
	

Step 4. Create a Book domain object to hold information about a book:

		package com.inflinx.blog.bookstore.domain;

		public class Book
		{
			private String name;
			private String description;

			public String getName()
			{
				return name;
			}
			public void setName(String name)
			{
				this.name = name;
			}
			public String getDescription()
			{
				return description;
			}
			public void setDescription(String description)
			{
				this.description = description;
			}

			@Override
			public String toString()
			{
				return "Name: " + name + ", Description: " + description ;
			}
		}	
	

Step 5. Create a BookFormController to prepare the form and process form submission

		package com.inflinx.blog.bookstore.web.controller;
		
		import java.util.Map;
		
		import org.springframework.stereotype.Controller;
		import org.springframework.web.bind.annotation.RequestMapping;
		import org.springframework.web.bind.annotation.RequestMethod;
		
		import com.inflinx.blog.bookstore.domain.Book;
		
		@Controller
		@RequestMapping("/book.html")
		public class BookFormController
		{
			// Display the form on the get request
			@RequestMapping(method=RequestMethod.GET)
			public String showForm(Map model)
			{
				Book book  = new Book();
				model.put("book", book);
				return "form";
			}
			
		}
	
	

Step 6. Create form.jsp page with a form to capture book details:

	
		<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
		<html>
			<head></head>
			<body>
				<form:form method="post" action="book.html" commandName="book">
					<table>
						<tr>
							<td>Name:</td> <td><form:input path="name" /></td>
						</tr> 
						<tr>
							<td>Description:</td> <td><form:textarea path="description"/></td>
						</tr>
						</table>
					<input type="submit" value="Create" />
				</form:form>
			</body>
		</html>
	
	
	

The form will be submitted to the book.html page.

Step 7. Modify BookFormController to read the submitted data and process it.

		
		// Process the form. 
		@RequestMapping(method=RequestMethod.POST)
		public String processForm(Book book, Map model)
		{
			// Persistence logic to save the book will go here

			// Add the saved book to the model
			model.put("book", book);
			return "result";
		}	
	

Step 8. Create a result.jsp page to display the form submission result:

		<html>
			<body>
				${book.name} is stored in the database
			</body>
		</html>
	

Deploy the application and go to http://<yourserver:port>/bookstore/book.html. The page should present a form shown below:

Form

Form

Enter the book name “Test Book” and submit the form. You should see a screen that looks similar to image below:

Form Submission Result

Form Submission Result

 

2 Adding JSR 303 validation

Consider the following validation requirements to the book class:
a. Name and Description cannot be null or blank
b. Description cannot be more than 50 characters

Here are the steps to add the above validation requirements:

Step 1. Add @NotEmpty and @Size annotations to the name and description files of the Book class

		public class Book
		{
			@NotEmpty
			private String name;
			
			@Size(min=1, max=50)
			private String description;
			
			// Getters and setters here	
		}
	

NotEmpty annotation is not part of JSR 303 specification. It is part of Hibernate Validator framework which is the reference implementation for JSR 303.

Step 2. Modify BookFormController to have Spring validate the submitted data. This is done by adding a @Valid annotation before the book method parameter. The result of the validation can be accessed using the BindingResult method parameter.

		// Process the form. 
		@RequestMapping(method=RequestMethod.POST)
		public String processForm(@Valid Book book, BindingResult result, Map model)
		{
			if(result.hasErrors())
			{
				return "form";
			}
			// Persistence logic to save the book will go here

			// Add the saved book to the model
			model.put("book", book);
			return "result";
		} 	
 	

Step 3. Modify the form.jsp so that error messages can be displayed:

 		<form:form method="post" action="book.html" commandName="book">
					<table>
						<tr>
							<td>Name:</td> <td><form:input path="name" /></td> <td><form:errors path="name" /></td>
						</tr> 
						<tr>
							<td>Description:</td> <td><form:textarea path="description"/></td> <td><form:errors path="description" /></td>
						</tr>
						</table>
					<input type="submit" value="Create" />
		</form:form>
 	

Redeploy the application and when you submit an empty form, you should see validation failure messages next to the fields:

Validation Failed

Validation Failed

 

3 Customizing error messages

The validation failure messages in the image above are default to the framework. To make custom error messages appear follow these steps:

Step 1. Create a messages.properties file under the WEB-INF folder. Add the following messages:

   		NotEmpty.book.name=Name is a required field
		Size.book.description=Description must be between 1 and 50 characters
   	

Each message above follows this convention:

<CONSTRAINT_NAME>.<COMMAND_NAME>.<FIELD_NAME>

Step 2. Let Spring know about the newly created properties file. This is done by modifying the web-Context.xml file to include this bean declaration:

   	
			
	
    

Redeploy the application and submit an empty form. This time you should see custom error messages appear next to the fields:

Custom Validation Failed Messages

Custom Validation Failed Messages

 

Download the source code for this post here. The application is tested on Glassfish 3.0 and WebLogic 10.3.