Validation des objets
But¶
- Les applications Java EE doivent manipuler des objets dont les attributs sont renseignés par des humains depuis l'IHM ou des services externes.
- Les humains font NÉCESSAIREMENT des erreurs.
- Les services externes sont potentiellement buggés.
-
Il est nécessaire de valider ses données tout au long de leur parcours :
- saisie HTML/javascript, SWING, ou autre
- stockage base
- C'est une opération normalement fastidieuse, répétitive, sujette à erreur
-
MAIS standardisée (Bean Validation (JSR 303)1) et relativement facile à utiliser à l'aide de quelques annotations:
@AssertFalse,@AssertTrue@DecimalMax,@DecimalMin@Future,@Past@Max,@Min@Null,@NotNull@Pattern@Digits@Size
Le chapitre «Declaring and validating bean constraints» de la documentation contient la liste des contraintes disponibles dans la section 2.3.1 : https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/chapter-bean-constraints.html.
Utilisation d'une annotation de validation¶
- Les contraintes de validation sont exprimées sous la forme d'annotations
-
Appliquées sur :
- types
- méthodes
- champs
- autres contraintes
- Prise en compte à l'exécution et pas à la compilation.
- Exemple :
import jakarta.validation.constraints.*;
public class PersonToday {
@NotNull
private String name;
@NotNull
private String surname;
@NotNull @Min(0)
private int age;
@Size(max = 2000)
private String currentJob;
}
Composition de contraintes existantes¶
- La composition de contraintes permet de créer des contraintes complexes composées de plusieurs contraintes validées individuellement
- S'exprime sous la forme d'une annotation
- Il n'y a pas d'implémentation à écrire
-
Une annotation est considérée comme étant une contrainte si :
- sa politique de rétention contient
RUNTIME - l'annotation est elle-même annotée avec
jakarta.validation.Constraint
- sa politique de rétention contient
@NotNull // Contrainte existante
@Size(min=7) // Contrainte existante
@Pattern(regexp = "...") // Contrainte existante
@Constraint(validatedBy={}) // Pas besoin d'implémenter du code de validation
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Email{
String message() default "Email incorrect"; // Message si pas valide
Class<?>[] groups() default {}; // Groupe de validation partiel
Class<? extends Payload>[] payload() default{}; // Metadonnée de la contrainte
}
Création d'une nouvelle contrainte¶
Contrainte NotNull¶
Déclaration de la contrainte :
// Cibles de l'annotation
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
//Quand l'annotation est utilisée
@Retention({RUNTIME})
// Inclusion dans la javadoc
@Documented
// Classe qui contient l'algo de validation
@Constraint(validatedBy = NotNullValidator.class)
public @interface NotNull {
// Message si pas valide
String message() default "{jakarta.validation.constraints.NotNull.message}";
// Groupe de validation partiel
Class<?>[] groups() default {};
// Metadonnée de la contrainte
Class<? extends Payload>[] payload() default {};
}
Définition de la contrainte :
// <Nom annotation, type de paramètre (pour spécialiser la contrainte)>
public class NotNullValidator implements ConstraintValidator<NotNull, Object>{
public void initialize(NotNull parameters){
} // appelé avant d'utiliser la contrainte
public boolean isValid(Object object,
ConstraintValidatorContext context){
return object != null;
} // Code de validation
}
Documentation de ConstraintValidator :
public interface ConstraintValidator<A extends Annotation,T>
Defines the logic to validate a given constraint A for a given object type T.
Implementations must comply to the following restriction:
T must resolve to a non parameterized type
or generic parameters of T must be unbounded wildcard types
Autre exemple de contrainte¶
Cette contrainte est à comparer avec la contrainte NotNull.
Déclaration de la contrainte :
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = AddressValidator.class)
public @interface Address {
String message() default "{address should be less than 500 aphanumerics chars}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
}
Définition de la contrainte :
public class AddressValidator implements ConstraintValidator <Address, String> {
@Override
public void initialize(Address address) { }
@Override
public boolean isValid(String s,
ConstraintValidatorContext constraintValidatorContext) {
// TO WRITE
return true;
}
}
Spécialisation de contraintes¶
Les contraintes peuvent contenir des arguments afin de spécifier leur comportement.
Utilisation de la contrainte :
@URL
private String resourceURL;
@NotNull @URL(protocol="http", host="www.iut.u-bordeaux1.fr");
private String httpServerUrl;
@URL(protocol="ftp", port=21)
private String ftpServerUrl;
Déclaration de la contrainte :
@Constraint(validatedBy={URLValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD,
ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface URL{
String message() default "URL incorrecte"; // Message si pas valide
Class<?>[] groups() default {}; // Groupe de validation partiel
Class<? extends Payload>[] payload() default{}; // Metadonnée de la contrainte
String protocol() default "";
String host() default "";
int port() default -1;
}
Implémentation de la contrainte :
public class URLValidator implements ConstraintValidator<URL, String>{
private String protocol;
private String host;
private int port;
public void initialize(URL url){
this.protocol = url.protocol();
this.host = url.host();
this.port = url.port();
}
public boolean isValid(String value, ConstraintValidatorContext context){
if (value == null || value.lenght ==0){
return true;
}
java.net.URL url;
try{
url = new java.net.URL(value);
} catch(MalformedURLException e){
return false;
}
if (protocol != null &&
protocol.length() > 0 &&
!url.getProtocol().equals(protocol)){
return false;
}
if (host != null &&
host.length() > 0 &&
!url.getHost().equals(host)){
return false;
}
if (port !=-1 && url.getPort() != port){
return false;
}
return true;
}
}
Utilisation¶
- Configuration :
META-INF/validation.xml - Bibliothèque :
annotations.jar
Illustration¶
Les exemples du projet permettent de comprendre le fonctionnement de la validation de dépendances (à ouvrir avec Intellij idea) :
- ajouter des contraintes prédéfinies (via des annotations)
- créer des contraintes
- tester le mécanisme de validation via des tests unitaires (classe
TestConstraints.javadeExemples/Validation)
Lisez attentivement le code pour bien intégrer l'utilisation de ce mécanisme.
Exercices¶
Objectif¶
Dans le cadre de cet exercice, on se propose d'implémenter les classes qui modélisent une application de gestion de projet (voir diagramme UML plus loin). Nous ne nous préoccupons que de la validation ; pas du reste.
Organisation¶
À partir de maintenant, vous devez utiliser un gestionnaire de sources pour stocker vos travaux et les partager
avec votre encadrant de TD.
La disposition des salles ne permettant pas toujours d'avoir une machine par étudiant, les TD se font en binôme.
Dans le cadre de cette matière nous utilisons un dépôt git hébergé par le serveur gitlab de l'iut https://gitlab-ce.iut.u-bordeaux.fr/ (pour les étudiants avec C. Johnen et R. Giot) et https://bitbucket.org/dashboard/ (pour les autres étudiants).
Vous avez déjà créé votre dépôt en local sur votre compte. Il faut maintenant le synchroniser avec le dépôt distant et le partager avec votre binôme et enseignant.
Le rôle de l'enseignant par rapport à ce dépôt gitLab doit être reporter, pour qu'il puisse télécharger vos projets JEE (la remise des projets se fait via gitLab).
Choisissez le compte d'un membre du binôme et créez un dépôt en respectant STRICTEMENT le nommage suivant : jee_td_NOM1_NOM2 (avec NOM1 et NOM2 les noms de famille des membres du binôme) et en partageant le dépôt avec votre encadrant de TD et votre binôme. Pensez à commiter et pousser plusieurs fois par séance votre travail sur le dépôt (nous ne le répéterons pas, mais regarder l'historique du dépôt fait parti de la notation TD). Chaque étudiant travaille dans un dossier portant son nom lorque la salle permet d'avoir 1 étudiant par machine.
Avant le TD2, créez un tag TD1 (nous ne le répéterons pas, pensez à créer systématiquement un tag avant chaque nouveau TD).
Travail à faire et terminer pour la séance suivante¶
- Dans votre dépôt créer un dossier validation dans lequel vous allez lancer la commande
mvn archetype:generate ...pour créer votre exercicevalidation. - Créez et ajouter les classes suivantes en utilisant les fonctionnalités de votre IDE (mettez les dans un paquetage java nommé
bean). C'est l'IDE qui génère le code de la classe avec ses getters/setters, pas vous (apprenez les fonctionnalités du menuCode).

- Ajoutez les dépendances à la bibliothèque de validation dans le pom.xml :
-
Écrivez les classes de validation
validation.Loginetvalidation.LoginValidatorqui permettent de valider un identifiant à l'aide des contraintes suivantes :- Taille minimum : 2 caractères
- Taille maximum : 8 caractères
- Utilisation uniquement des lettres de l'alphabet latin (majuscule ou minuscule)
À noter que cette validation est également implémentable à l'aide de l'annotation @Pattern que nous n'utiliserons pas pour cet exercice.
-
Pour chaque classe du projet, écrivez les annotations nécessaires à la validation des données (elles sont triviales à trouver). Le chapitre "Declaring and validating bean constraints" de la documentation contient la liste des contraintes disponibles dans la section 2.3.1 et 2.3.2 (https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/chapter-bean-constraints.html) :
- Gestion temporelle des dates
- Gestion des champs non nuls
- Gestion d'emails
-
Vérifiez votre implémentation en écrivant des tests unitaires :
- Utilisez plusieurs petites fonctions
- Regroupez les fonctions de manière thématique
private static Validator validator;
private Set<ConstraintViolation<Person>> violations;
@BeforeAll
public static void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
PersonConstraintTest.validator = factory.getValidator();
}
@Test
public void testNOk() {
Person p2 = new Person();
p2.setFirstName("21");
p2.setLastName("a");
p2.setEmail("21");
p2.setLogin(null);
violations = validator.validate(p2);
assertEquals(4, violations.size());
}
-
Vérifier votre travail avec
mvn test -
FACULTATIF Pour utiliser l'annotation @pattern, comme dans la classe suivante :
public class Person {
@NotNull
@Email(message = "email incorrect format")
private String email;
// firstName est un chaine de taille quelconque de lettres.
@Pattern(regexp="[a-zA-Z]*", message="firstName syntax error")
private String firstName;
// lastName est un chaine de 2 à 12 lettres.
@Pattern(regexp="[a-zA-Z]{2,12}", message="lastName syntax error")
private String lastName;
@Login
private String login;
private boolean isStudent;
}
Il faut inclure dans le fichier de configuration de maven (pom.xml) la dépendance suivante :
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.1</version>
</dependency>
À reternir (non exhaustif)¶
- Usage des annotations "Bean Validation" (JSR 303) (sauf
@Pattern). - Commandes de base de
git(git add,git commit,git pull,git push, ...). - Création de nouvelles contraintes.
-
https://www.jcp.org/en/jsr/detail?id=303 ↩