Ce tuto décrit comment valider un modèle de données manuellement avec JavaEE BeanValidation (JSR 303). L’intérêt ? Eviter une pile de
Environnement
Il faut Java 1.6, un IDE Eclipse et Maven.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.damienfremont.blog</groupId>
<artifactId>20150113-javaee-beanvalidator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<url>http://damienfremont.com</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>1.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Source code
La même Entité / Bean est utilisé pour tout le tuto. C’est un POJO autour de la personne, il contient ses 3 données de base : nom, prénom, date de naissance. Il en porte également la validation au travers des annotations JavaEE qui vont bien.
PersonModel.java
package com.damienfremont.blog;
import java.util.Calendar;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
public class PersonModel {
@NotNull
@Size(min = 1, max = 16)
private String firstName;
@NotNull
@Size(min = 1, max = 16)
private String lastName;
@NotNull
@Past
private Calendar birthDate;
public PersonModel(String firstName, String lastName, Calendar birthDate) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
}
@Null : Vérifier que la valeur du type concerné soit null
@NotNull : Vérifier que la valeur du type concerné soit non null
@Size : Vérifier que la taille de la donnée soit comprise en les valeurs min et max fournies
@Past : Vérifier que la date soit dans le passé (antérieure à la date courante)
Demo
PersonModelTest.java
package com.damienfremont.blog;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.assertj.core.api.Condition;
import org.junit.Test;
public class PersonModelTest {
@Test
public void test_WHEN_valid_GIVEN_valid_model_THEN_ok_no_errors() {
// GIVEN
PersonModel person = new PersonModel( //
"Kim", //
"Kardashian", //
new GregorianCalendar(1980, Calendar.OCTOBER, 21));
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
// WHEN
Set<ConstraintViolation<PersonModel>> constraintViolations = validator
.validate(person);
// THEN
assertThat(constraintViolations).isEmpty();
}
@Test
public void test_WHEN_valid_GIVEN_invalid_model_THEN_error() {
// GIVEN
PersonModel person = new PersonModel( //
null, //
"", //
null);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
// WHEN
Set<ConstraintViolation<PersonModel>> constraintViolations = validator
.validate(person);
// THEN
assertThat(constraintViolations) //
.hasSize(3) //
.haveExactly(2, notNullCondition) //
.haveExactly(1, notEmptyCondition);
}
Condition<ConstraintViolation<PersonModel>> notNullCondition = new Condition<ConstraintViolation<PersonModel>>() {
@Override
public boolean matches(ConstraintViolation<PersonModel> arg0) {
return arg0.getMessage().contains("may not be null");
}
};
Condition<ConstraintViolation<PersonModel>> notEmptyCondition = new Condition<ConstraintViolation<PersonModel>>() {
@Override
public boolean matches(ConstraintViolation<PersonModel> arg0) {
return arg0.getMessage().contains("size must be between");
}
};
}
Gestion des Exceptions
Il est facile de décliner cette solution pour créer des Exceptions, pour un back-end par exemple.
Source code
Ici, on veut qu’une Exception soir remontée si et seulement si il y a une erreur de validation.
ValidationByExceptionHandler.java
package com.damienfremont.blog;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
public class ValidationByExceptionHandler {
Validator jeeValidator = Validation.buildDefaultValidatorFactory()
.getValidator();
<T> void validate(T object) {
Set<ConstraintViolation<T>> errs = jeeValidator.validate(object);
if (errs.size() > 0) { // error
String msg = "Invalid Bean, constraint error(s) : ";
for (ConstraintViolation<T> err : errs) {
msg += err.getPropertyPath() + " " + err.getMessage() + ". ";
}
throw new IllegalArgumentException(msg);
}
}
}
Demo
ValidationByExceptionHandlerTest.java
package com.damienfremont.blog;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.junit.Test;
public class ValidationByExceptionHandlerTest {
@Test
public void test_WHEN_valid_GIVEN_valid_model_THEN_ok_no_errors() {
// GIVEN
PersonModel person = new PersonModel( //
"Kim", //
"Kardashian", //
new GregorianCalendar(1980, Calendar.OCTOBER, 21));
ValidationByExceptionHandler validator = new ValidationByExceptionHandler();
// WHEN
validator.validate(person);
// THEN
// nothing to do
}
@Test
public void test_WHEN_valid_GIVEN_invalid_model_THEN_error() {
// GIVEN
PersonModel person = new PersonModel( //
null, //
"", //
null);
ValidationByExceptionHandler validator = new ValidationByExceptionHandler();
// WHEN
try {
validator.validate(person);
fail();
} catch (IllegalArgumentException e) {
// THEN
assertThat(e.getMessage())
.contains("Invalid Bean, constraint error(s) : ")
.contains("birthDate may not be null.")
.contains("firstName may not be null.")
.contains("lastName size must be between 1 and 16.");
}
}
}
Gestion des Responses
Voici un exemple de gestion plus spécifique, pour un back-end HTTP.
Source code
Il faut mettre à jour la conf Maven pour gérer les Response JAX-RS. pom.xml
...
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.12</version>
</dependency>
</dependencies>
<build>
...
Ici, on veut construire le Response du server HTTP, dans les cas avec ou sans erreur de validation.
ValidationByResponseHandler.java
package com.damienfremont.blog;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.ws.rs.core.Response;
public class ValidationByResponseHandler {
Validator jeeValidator = Validation.buildDefaultValidatorFactory()
.getValidator();
<T> Response validate(T object) {
Set<ConstraintViolation<T>> errs = jeeValidator.validate(object);
if (errs.isEmpty()) { // no error
return Response.status(200).entity(object).build();
} else { // error
String msg = "Invalid Bean, constraint error(s) : ";
for (ConstraintViolation<T> err : errs) {
msg += err.getPropertyPath() + " " + err.getMessage() + ". ";
}
return Response.status(400).entity(msg).build();
}
}
}
Demo
ValidationByResponseHandlerTest.java
package com.damienfremont.blog;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.ws.rs.core.Response;
import org.junit.Test;
public class ValidationByResponseHandlerTest {
@Test
public void test_WHEN_valid_GIVEN_valid_model_THEN_ok_no_errors() {
// GIVEN
PersonModel person = new PersonModel( //
"Kim", //
"Kardashian", //
new GregorianCalendar(1980, Calendar.OCTOBER, 21));
ValidationByResponseHandler validator = new ValidationByResponseHandler();
// WHEN
Response response = validator.validate(person);
// THEN
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void test_WHEN_valid_GIVEN_invalid_model_THEN_error() {
// GIVEN
PersonModel person = new PersonModel( //
null, //
"", //
null);
ValidationByResponseHandler validator = new ValidationByResponseHandler();
// WHEN
Response response = validator.validate(person);
// THEN
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getEntity().toString())
.contains("Invalid Bean, constraint error(s) : ")
.contains("birthDate may not be null.")
.contains("firstName may not be null.")
.contains("lastName size must be between 1 and 16.");
}
}
Conclusion
Ce genre de solution permet de rester proche des standards JavaEE. Elle permet donc de faciliter la maintenance et la compréhension entre développeur par rapport à une solution custom parfois instable (
Toutefois, la validation manuelle n’a pratiquement plus sa place dans un server JavaEE. Les annotations @Valid la remplaçant déjà dans SpringMVC et JavaEE. Reste toutefois les petits projets, les batch et les servers non JavaEE.
@Path("/example")
public class MyExampleResourceImpl {
@POST
@Path("/")
public Response postExample(@Valid final Example example) {
// ....
}
}
http://stackoverflow.com/questions/14523201/hibernate-validator-how-to-work-with-valid-annotation http://stackoverflow.com/questions/14523201/hibernate-validator-how-to-work-with-valid-annotation
@Controller
@RequestMapping("/customer")
public class SignUpController {
@RequestMapping(value = "/signup", method = RequestMethod.POST)
public String addCustomer(@Valid Customer customer, BindingResult result) {
// ....
}
}
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/ http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/
Sources
https://github.com/DamienFremont/damienfremont.com-blog-labs/tree/master/20150113-javaee-beanvalidator https://github.com/DamienFremont/damienfremont.com-blog-labs/tree/master/20150113-javaee-beanvalidator
References
http://beanvalidation.org/ http://beanvalidation.org/
http://hibernate.org/validator/ http://hibernate.org/validator/
http://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean http://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean
http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html
http://www.jmdoudoux.fr/java/dej/chap-validation_donnees.htm http://www.jmdoudoux.fr/java/dej/chap-validation_donnees.htm