Tuesday, 12 July 2011

Validating a Spring 3 MVC Web-App Using the Hibernate JSR 303 Validator

My last blog covered the Maven POM entries required to implement JSR 303 - Spring Bean Validation in a Spring 3 MVC web-app. Having set things up, the next step is to actually validate a command object and the easiest way of doing that is to use the Hibernate JSR 303 Validator's built-in set of constraints.

In this fictitious scenario, I have an application that allows a user to search for arbitrary events based upon date; the relevant section of the my input form will look like this:

This is a purposely clumsy input form, but it serves our purpose as it gives us lots of fields to validate.

There are four steps required to enable validation:
  • Add constraint annotations to your form’s command object.
  • Add an annotation to the handler method in your controller that deals with this form.
  • Modify your controller’s handler method, so that it re-displays your input form on validation failure.
  • Add some error processing tags to your JSP
Taking each of these steps in turn and in more detail, the first thing to note is that there are a whole bundle of built-in constraints available, which you can use to annotate your command object and they’re documented in the Hibernate JSR 303 documentation. My input form is backed by a command object that models a search for events called SearchCriteria:

public class SearchCriteria extends FrameworkBean {

 
@NotEmpty(message = "Event type may not be null")
 
@Size(min = 4, max = 4, message = "EventType must be length four")
 
private String eventType;

 
private String description;

 
@Min(value = 1, message = "Start day must be greater than 0")
 
@Max(value = 31, message = "Start day must be less than 32")
 
private int startDay;

 
@Min(value = 1, message = "Start month must be greater than 0")
 
@Max(value = 12, message = "Start month must be less than 12")
 
private int startMonth;

 
@Min(value = 1970, message = "Start year must be greater than 1970")
 
private int startYear;

 
@Min(value = 1, message = "End day must be greater than 0")
 
@Max(value = 31, message = "End day must be less than 32")
 
private int endDay;

 
@Min(value = 1, message = "End month must be greater than 0")
 
@Max(value = 12, message = "End month must be less than 12")
 
private int endMonth;

 
@Min(value = 1971, message = "End year must be greater than 1971")
 
private int endYear;

In the above code snippet, you can see that I’m applying the @NotEmpty and @Size constraints to the eventType attribute and the @Min and @Max constraints to the date component fields. Notice that in applying these constraints, I’ve added an optional message attribute to my annotations. This is the message that we’ll be displaying on the screen if validation fails.

The second step is to add an @Valid annotation to your controller’s handler method:

  @RequestMapping(value = SEARCH_PATH, method = RequestMethod.POST)
 
public String doSearch(@Valid SearchCriteria criteria, BindingResult bindingResult,
      Model model
) {  etc...

This lets Spring know that the criteria argument of the doSearch() method needs validating.

The next important point to remember is that your controller method is called irrespective of whether or not your input form fails validation. This means that the way that you find out about validation errors is by checking a BindingResult object, which brings us to the next step: modifying your controller’s handler method, so that it re-displays your input form on validation failure. This really means adding an if statement to your handler method:

  @RequestMapping(value = SEARCH_PATH, method = RequestMethod.POST)
 
public String doSearch(@Valid SearchCriteria criteria, BindingResult bindingResult,
      Model model
) {

   
logger.debug("Doing search with criteria: " + criteria);
    String view;

   
if (!bindingResult.hasErrors()) {
     
List<ViewEvent> eventList = dummyDao.getDummyData(criteria);
      updateModel
(criteria, eventList, bindingResult, model);
      view = getNextView
(eventList);
   
} else {
     
view = FORM_VIEW;
   
}

   
return view;
 
}

The controller handler method above directs the user back to the search criteria input form if the binding result contains errors. The big idea here is that we design our form so that it’ll display error information when validation fails.


As you can see in the screen shot above, the error text in the red ‘failure’ area of the screen has been taken from the message attribute of each JSR 303 annotation. In order to display that information, you can use Spring’s form:errors tag using some JSP code that looks something like:

<%-- The Spring:hasBindErrors and both divs are only here to do the red colouring --%>
 <spring:hasBindErrors name="searchCriteria">
  <div class="portlet-body">
   <div class="failure">
    <p><form:errors path="eventType" /></p>
    <p><form:errors path="startDay" /></p>
    <p><form:errors path="startMonth" /></p>
    <p><form:errors path="startYear" /></p>
    <p><form:errors path="endDay" /></p>
    <p><form:errors path="endMonth" /></p>
    <p><form:errors path="endYear" /></p>
   </div>
  </div>
 </spring:hasBindErrors>

It does appear that the validation is magically done by Spring and the Hibernate Validator after your user presses the Submit button and before your controller method is called; it’s something that you should need to have very little contact with. This may or may not be to your liking, I guess that it all depends upon how reliable this mechanism is and how it lets you know when you've done something wrong.

This is a simple overview on how to apply JSP 303 Bean Validation to your Spring 3 MVC web-app. There are some other details to look into that will make our application better. More on that next time...

No comments: