GWT 2.5 intègre désormais la validation des données selon la JSR303. Je propose donc de s’intéresser à cette nouvelle fonctionnalité en commençant par annoter un bean et à effectuer une validation côté client (javascript).
Pour réaliser cette petite POC, je vais partir du projet MyModule du post précédent.
Configuration de Maven
Le framework de validation de GWT 2.5 est basé sur Hibernate-Validator. Il va donc falloir ajouter cette librairie et ses sources à notre projet. Pourquoi les sources ? Parce que GWT a besoin des sources pour convertir du code Java en Javascript.
Si vous n’utilisez pas Maven, ça se passe ici…
Sinon, dans le fichier pom.xml, on commence donc par ajouter les dépendances ci-dessous :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <dependency> Â Â Â Â Â Â <groupId>org.hibernate</groupId> Â Â Â Â Â Â <artifactId>hibernate-validator</artifactId> Â Â Â Â Â Â <version>4.1.0.Final</version> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â Â <groupId>org.hibernate</groupId> Â Â Â Â Â Â <artifactId>hibernate-validator</artifactId> Â Â Â Â Â Â <version>4.1.0.Final</version> Â Â Â Â Â Â <classifier>sources</classifier> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â Â Â <groupId>log4j</groupId> Â Â Â Â Â Â Â <artifactId>log4j</artifactId> Â Â Â Â Â Â Â <version>1.2.16</version> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â Â Â <groupId>org.slf4j</groupId> Â Â Â Â Â Â Â <artifactId>slf4j-api</artifactId> Â Â Â Â Â Â Â <version>1.6.1</version> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â Â Â <groupId>org.slf4j</groupId> Â Â Â Â Â Â Â <artifactId>jcl-over-slf4j</artifactId> Â Â Â Â Â Â Â <version>1.6.1</version> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â Â Â <groupId>org.slf4j</groupId> Â Â Â Â Â Â Â <artifactId>slf4j-log4j12</artifactId> Â Â Â Â Â Â Â <version>1.6.1</version> Â Â Â </dependency> |
Remarque :
Si le fichier pom.xml a été généré automatiquement via l’archetype « gwt-maven-plugin », on va obtenir, au moment de la compilation, un message contenant « package javax.validation.constraints does not exist ». Cela est dû au fait que le projet de démo importe une version antérieure de l’API de validation au niveau des tests. Eclipse importe les bons fichiers, mais Maven plante lorsqu’il compile les classes de « test »… Une solution consiste donc à supprimer les lignes ci-dessous dans le fichier pom.xlm :
1 2 3 4 5 6 7 8 9 10 11 12 13 | <dependency> Â Â Â Â Â <groupId>javax.validation</groupId> Â Â Â Â Â <artifactId>validation-api</artifactId> Â Â Â Â Â <version>1.0.0.GA</version> Â Â Â Â Â <scope>test</scope> Â Â Â </dependency> Â Â Â <dependency> Â Â Â Â Â <groupId>javax.validation</groupId> Â Â Â Â Â <artifactId>validation-api</artifactId> Â Â Â Â Â <version>1.0.0.GA</version> Â Â Â Â Â <classifier>sources</classifier> Â Â Â Â Â <scope>test</scope> Â Â Â </dependency> |
La validation des données avec GWT 2.5
Pour utiliser le framework de validation fourni par GWT, nous allons procéder en trois étapes :
- définir les contraintes à vérifier sur les Java Beans
- implémenter l’usine abstraite AbstractGwtValidatorFactory
- lancer la validation lorsque l’utilisateur soumet une nouvelle instance de la classe Client
Dans la classe Client, commençons donc par rajouter une contrainte simple à l’attribut « nom » afin que celui-ci contienne au moins 4 caractères :
1 2 3 4 5 6 7 | public class Client implements Serializable {    //////////// ATTRIBUTES ////////////    private Integer id;    @Size(min = 4)    private String nom; ... } |
Voilà le première étape terminée. Passons maintenant à cette implémentation qui est très simple avec le mécanisme de differed binding de GWT : on commence par créer la classe ci-dessous dans la partie client et je la commente ensuite :
1 2 3 4 5 6 7 8 9 10 11 |
C’est toujours un peu le même mécanisme que l’on retrouve dans GWT : on crée une interface, les annotations vont permettre à GWT de déterminer certaines options pour générer les implémentations automatiques de ces interfaces, puis la méthode GWT.create va lier le bon morceau de code Javascript au moment adéquat.
On n’oublie pas de rajouter les lignes ci-dessous dans MyModule.gwt.xml :
1 2 3 4 5 |    <inherits name="org.hibernate.validator.HibernateValidator" />    <replace-with       class="com.acme.gwt.demo.client.validator.SimpleValidatorFactory">       <when-type-is class="javax.validation.ValidatorFactory" />    </replace-with> |
Puis on rajoute un petit algorithme de validation des données et d’affichage d’erreurs dans la classe ListClient :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | @UiHandler("clientPopup")    void onHide(HideEvent event) {       //Si on a cliqué sur OK : on sauvegarde le nouveau client       if (clientPopup.getHideButton() == clientPopup.getButtonById(PredefinedButton.OK.name())){          Client client = new Client(null, name.getCurrentValue(), date.getCurrentValue(), email.getCurrentValue());          //validation des données          Validator validator = Validation.buildDefaultValidatorFactory().getValidator();          Set<ConstraintViolation<Client>> violations = validator.validate(client);          if(!violations.isEmpty()){             clientPopup.show(); //empeche la popup de se fermer             StringBuilder builder = new StringBuilder();             for (ConstraintViolation<Client> violation : violations) {                builder.append(violation.getMessage());                    builder.append(" : <i>(");                    builder.append(violation.getPropertyPath().toString());                    builder.append(" = ");                    builder.append("" + violation.getInvalidValue());                    builder.append(")</i>");                    builder.append("<br/>");             }             //Affiche un message d'erreur avec les contraintes non respectées             Info.display("Contraintes non respectées !", builder.toString());          } else {             //persistance du nouveau client             service.ajouterClient(client, new AsyncCallback<Client>() {                public void onSuccess(Client clientPersistant) {                   //Ajout du nouveau client dans la grille                   store.add(clientPersistant);                   //réinitialisation de la popup                   name.reset();                   date.reset();                   email.reset();                }                public void onFailure(Throwable e) {                   e.printStackTrace();                }             });          }       }    } |
Le message d’erreur est généré automatiquement par GWT, et en français, s’il vous plait :
Personnalisation des contraintes et des messages
Comme on peut le constater dans la capture ci-dessus, le message n’est pas approprié puisqu’il affiche une valeur maximum que l’on n’a pas définit. On doit donc le modifier :
1 2 |
Ça marche (faites le test)… mais c’est moche ! Une chaîne de texte passée en dur comme ça c’est bien pour tester le framework de validation, mais en production, il va falloir pouvoir manipuler ça avec un fichier properties afin d’obtenir un joli code comme ça :
1 2 |
Pour une raison que j’expliquerai plus bas, nous allons commencer par ajouter les messages personnalisés dans les fichiers ValidationMessages.properties
1 | custom.name.size.message=The property must be between {min} |
et ValidationMessages_fr.properties
1 | custom.name.size.message=Le nom doit contenir au moins {min} caracteres |
On place ces deux fichiers dans le package com.acme.gwt.demo.client.validator, puis on va laisser Maven générer le bundle i18n en rajoutant les lignes ci-dessous dans le fichier pom.xml :
1 2 3 | <i18nConstantsWithLookupBundle> com.acme.gwt.demo.client.validator.ValidationMessages </i18nConstantsWithLookupBundle> |
et en lançant la commande :
L’interface est bien générée dans le package com.acme.gwt.demo.client.validator. Au passage, je rappelle la différence entre l’interface com.google.gwt.i18n.client.ConstantsWithLookup et l’interface com.google.gwt.i18n.client.Messages : la première génère des constantes javascript de manière dynamique, c’est-à -dire que les paramètres vont être lus une fois pour toute à la compilation, tandis que la seconde génère des méthodes javascript qui vont pouvoir recevoir des paramètres au moment de leur appel.
Toujours dans le même package, nous allons créer la classe CustomValidationMessagesResolver afin d’implémenter l’interface com.google.gwt.validation.client.UserValidationMessagesResolver.
1 2 3 4 5 6 7 | public class CustomValidationMessagesResolver extends       AbstractValidationMessageResolver implements       UserValidationMessagesResolver {    protected CustomValidationMessagesResolver() {       super((ConstantsWithLookup) GWT.create(ValidationMessages.class));    } } |
Le code source de la classe que l’on étend ici (AbstractValidationMessageResolver) se trouve ici. Attention à ce que la classe ValidationMessages soit bien celle que l’on vient de créer. C’est pour cela que j’ai souhaité démarré par cette étape. Il n’y aura pas de message d’erreur si, par exemple, on a importé la classe org.hibernate.validator.ValidationMessages…
Pour que notre implémentation de l’interface soit utilisée à la place de l’implémentation par défaut, on doit le préciser dans le fichier MyModule.gwt.xml :
1 2 3 4 5 6 7 8 9 10 11 | <!-- Validation -->    <inherits name="org.hibernate.validator.HibernateValidator" />    <replace-with       class="com.acme.gwt.demo.client.validator.SimpleValidatorFactory">       <when-type-is />    </replace-with>    <replace-with       class="com.google.gwt.sample.validation.client.CustomValidationMessagesResolver">       <when-type-is          class="com.google.gwt.validation.client.UserValidationMessagesResolver" />    </replace-with> |
On lance la compilation, et on obtient notre message personnalisé :
Liste des annotations commentées dans l’excellent article de J.M. Doudoux : http://jmdoudoux.developpez.com/cours/developpons/java/chap-validation_donnees.php#validation_donnees-2-4.