Handling Form Validation with Spring 3 MVC
Now lets dig a bit deeper into Spring. In this article we will learn to validate data that you have got from the forms. Let’s look at the task of validation a little more closely.
Scenario 1: We might need to validate e.g. that the email provided does indeed look like an email (x@x.x format to be simplistic). This could be done by just running some scripts on the email field itself. That ought to be simple. We could write some javascript to be run on the browser itself. And also write the same validation on server side [Why? Read here]. This is true for all isolated validations e.g. check that the data is not null, check that the data is certain length etc etc.
Scenario 2: Wish life was that simple. Since we were discussing email validations, lets say one of the validations requires us to check that the email is of certain domains (say partner organizations) so that the system can email certain privileged information also. Let’s say we need to check that the email is of format x@partner1.com, x@partner2.com or x@partner3.com. Trivial as it may be, this is an example of the type where the validation could not be run only on the data from the form itself. Whatever is the code for validation, it needs to know which domains are valid. And this data was not present in the data provided by end user in the form. If you flex your imagination a bit you could easily create use case where a business rules engine might be required (e.g. if the rules are too fluid) and / or you can throw in elements of internationalization (e.g. adult age is not 18 in all countries) or other complexities. These are non isolated validations and require accessing further information that is synchronously available. These validations could be coded in both server side and client side, although it is fair to say that it will lean more on server side.
Scenario 3: Again, wish life was that simple. Wile we are on the subject of validating emails, we might also need perhaps to check that the email is valid i.e. it is not a thisemail@doesnot.exist(I am not really sure that that email does not exist – or can not exist in future – but you get the idea I hope). We will need to send an email to that email id and perhaps ask the user to click and confirm. We need to interact asynchronously with some other system over SMTP. Again, lean on your imagination a bit and the whole pandora’s box will burst open. Soon you are integrating over REST, SOAP, JMS, File Servers, and handling issues of security and authentication over distributed systems. I would bet most of the systems would go for server side validation in this area and client side validation – though technically possible – would not be used too often.
Scenario 4: And we have not yet breached the subject of same domain objects being populated not only from web based forms but also from feed files, JMS messages etc thereby needing the same validation logic being applied on multiple channels. What started off as a puny little form with a handful of innocent looking textboxes at the beginning of this discussion, has morphed into a monster.
Fortunately, JSR 303 or Bean Validation [Read more here] is there to rescue. It solves Scenario 1 and 4 mentioned above out of the box. And it supports you to solve Scenario 2 and 3. I will recommend you to read the section marked “How does this standard benefit users? What problems will it solve?” at this link.
This specification for java was requested in 2006 and by late 2009 it was released. In other words, the implementations available have already had a chance to mature over year. Spring 3, being a first class open source citizen, allows you to use this standard solution rather than reinvent the wheel. And should you absolutely need to reinvent the wheel (all professional apps need to write that myAppSpecificKickAssValidator()) Spring allows for that as well. Let’s look at both scenarios one by one.
Add JSR 303 support to any Maven based project
The JSR 303 is an open api. You can find it at this link. Anyone can implement that. There are implementations by hibernate, and apache bval to name a few. Let’s go with the Hibernate implementation. You can see their jars at Maven Central at this link. The latest stable version at the time of writing this article is 4.3.0.Final. You just need to add this dependency in your pom. The api is also bundled with this implementation, hence you don’t need to add that either.
File: pom.xml
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> [...] <hibernate.validation.version>4.3.0.Final</hibernate.validation.version> [...] </properties> <!-- Hibernate validations --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>${hibernate.validation.version}</version> </dependency>
Just by adding this dependency you could go into your entity / form class and declare constraints. JSR 303 has a few standard constraints [listed here] which covers standard checks like NotNull. Hibernate adds a few more non standard custom constraints [listed here]. They are non standard, but quite handy e.g. a check for valid Email. Let’s introduce both these checks on our ContactFrm.java. If you don’t know where that came from, you most probably have not read the previous article of this series i.e. Handling Forms with Spring 3 MVC.
File: /org/academy/ui/spring3/forms/ContactFrm.java
package org.academy.ui.spring3.forms; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.Email; public class ContactFrm { @NotNull private String firstname; private String lastname; @Email private String email; private String telephone; // Getter and setters omitted for brevity. [...] }
Unit test
Until this point, our ContactFrm bean did not have any functionality in it and hence I did not bother to unit test it (although the TDD enthusiasts will shake their head at it). However, now just by adding a couple of annotations we have added functionality to the bean and that is unit testable (TDD enthusiasts can rejoice starting now). Let’s add a unit test.
File: /src/test/java/org/academy/ui/spring3/forms/ContactFrmTest.java
package org.academy.ui.spring3.forms; import static org.junit.Assert.*; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ContactFrmTest { private final static Logger logger = LoggerFactory .getLogger(ContactFrmTest.class); private static Validator validator; @BeforeClass public static void init() { validator = Validation.buildDefaultValidatorFactory().getValidator(); } @Test public void test() { Set<ConstraintViolation<ContactFrm>> errors; ContactFrm contactFrm = new ContactFrm(); errors = validator.validate(contactFrm); printErrors(errors); // We are expecting 1 error here. // Just the NotNull. assertEquals(1, errors.size()); errors.clear(); contactFrm.setFirstname("partha"); errors = validator.validate(contactFrm); printErrors(errors); // We are not expecting any errors here. assertEquals(0, errors.size()); errors.clear(); contactFrm.setEmail("this can not be a valid email"); errors = validator.validate(contactFrm); printErrors(errors); // We are expecting 1 errors here. assertEquals(1, errors.size()); errors.clear(); contactFrm.setEmail("this@mightbevalid.email"); errors = validator.validate(contactFrm); printErrors(errors); // We are not expecting any errors here. assertEquals(0, errors.size()); errors.clear(); } // Utility function to print out errors from validation. private void printErrors(Set<ConstraintViolation<ContactFrm>> errors) { if (errors.size() > 0) { for (ConstraintViolation<ContactFrm> error : errors) { logger.debug(error.getMessage()); } } else { logger.debug("There were no errors to print."); } } }
You will notice I did not need to use any Hibernate, Spring or JSR specific code to unit test. It is just a simple JUnit. In my mind, the fact that I could add validations on a POJO by just adding a couple of annotations and then I could unit test that with the standard unit testing framework without any tweaks, is a huge step deal. And we are just getting started.
Unit test – using Spring capabilities.
Of course it is reassuring that we could do complete validation and unit testing without relying on Spring. However we would miss the point of this exercise entirely if we don’t explore how easy it is to bring this all together using Spring, in our website.
We will start by adding all the required Spring dependencies to our project and stripping them off the commons logging as explained in the previous article.
File: /pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> [...] <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
Now, add the @RunWith and @ContextConfiguration magic in the unit test, as as explained in the previous article.
File: /src/test/java/org/academy/ui/spring3/forms/ContactFrmTest.java
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class ContactFrmTest { private final static Logger logger = LoggerFactory .getLogger(ContactFrmTest.class); @Autowired private Validator validator; // This is no more required as Spring does it for us. // @BeforeClass // public static void init() { // validator = Validation.buildDefaultValidatorFactory().getValidator(); // }
[The rest of the code omitted as it remains same.]
Now, all that remains is for us to tell Spring what it should Autowire in the validator. This we do using configuration rather than code.
File: /src/test/resources/org/academy/ui/spring3/forms/ContactFrmTest-context.xml
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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"> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> </beans>
There you are all set. You could now use “mvn -e clean install” to run this entire code and unit test it. If you were feeling too demanding you could use “mvn -e site” to create a nice HTML website which will report on the code coverage.
Read more:
- Home page of JSR 303
- Home page of Hibernate Validator
- Interview with lead developer of Hibernate Validation – Emmanuel Bernard
- Spring’s official documentation on validation
Reference: Handling Form Validation with Spring 3 MVC from our JCG partner Partho at the Tech for Enterprise blog
Spring MVC is good, but if we have JSF2 – not needed any Spring MVC and validations