Handling Form Validation with Spring 3 MVC
Join the DZone community and get the full member experience.
Join For FreeThis article is a part of a series on Spring 3. The earlier articles of this series were
Hello World with Spring 3 MVC
Handling Forms with Spring 3 MVC
Unit testing and Logging with Spring 3
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.
That's it for this article. Happy coding.
Published at DZone with permission of Partha Bhattacharjee, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments