Tutoriel pour implémenter le patron de conception MVP dans une application web avec Spring, GWT et Hibernate

Projet GWT MVP

Nguimgo Bertrand

Le but de cet article est de présenter le patron de conception MVP-GWT et de montrer comment l'implémenter pas à pas, en s'appuyant sur le framework gwt-maven-plugin.

Pour réagir à ce tutoriel, vous avez un espace de dialogue disponible pour apporter votre contribution ici: Commentez

- Titre : Tutoriel pour implémenter le patron de conception MVP dans une application web avec Spring, GWT et Hibernate

- Auteur : Nguimgo Bertrand

- Parution : 2 janvier 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Nguimgo Bertrand. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Cahier des charges

- I-A-1. Que va faire l'application ?

- I-A-1-a. Première partie : Partie cliente

- I-A-1-b. Deuxième partie : Partie serveur

- I-A-2. Présentation l'architecture

- I-A-2-a. Développement partie cliente

- I-A-2-b. Développement Partie serveur

- I-B. Installation des outils

- I-B-1. Prérequis

- I-B-2. Installation du plugin GWT

- I-B-3. Configuration du plugin GWT

- I-B-4. Création du squelette du projet

- I-B-4-a. Création du projet à partir d'Eclipse

- I-B-4-b. Configuration du projet

- I-B-4-c. Mise en place du Super Dev Mode

- I-B-4-d. Mise en place du débogage depuis le navigateur Chrome

- I-B-4-e. Installation et configuration du plugin de débogage SDBG

- II. Première partie : Partie cliente

- II-A. Organisation d'un projet GWT

- II-A-1. Le package client

- II-A-1-a. Définitions

- II-A-2. Le package server

- II-A-3. Le package shared

- II-B. Implémentation du pattern GWT-MVP

- II-B-1. Présentation du patron de conception GWT-MVP

- II-B-2. Implémentation de la Vue

- II-B-3. Implémentation de la ClientFactory

- II-B-4. Création de l'Activity

- II-B-5. Création de la Place

- II-B-6. Création de ActivityMapper

- II-B-7. Création de PlaceHistoryMapper

- II-B-8. Modification de l'EntryPoint

- II-B-9. Modification de BookManager.gwt.xml

- II-B-10. Ajout de maven-war-plugin

- II-B-11. Modification du build

- II-B-12. Exécution

- II-B-13. Intégration UiBinder et style CSS

- II-B-13-a. Intégration de l'UiBinder

- II-B-13-b. Modification de la vue

- II-B-13-c. Intégration feuille de style CSS

- II-B-14. Résumé des étapes du design MVP

- II-B-14-a. Étapes

- II-B-14-b. Structure du projet créé

- III. Deuxième partie : Partie serveur

- III-A. Mise en place des services RPC

- III-A-1. Création des interfaces RPC

- III-A-2. Implémentation des interfaces RPC

- III-A-2-a. Implémentation RPC côté client

- III-A-2-b. Implémentation RPC côté service

- III-A-2-c. Intégration de Spring et Hibernate

- III-B. Implémentation des services

- III-B-1. Configuration de la base de données

- III-B-2. Création des beans Entités

- III-B-3. Création de la couche DAO

- III-B-3-a. Mise en place de la généricité

- III-B-3-b. Implémentation de l'interface générique

- III-B-3-c. Utilisation de la classe générique

- III-B-4. Création de la couche de services

- III-B-4-a. Création du mapper DTO

- III-B-4-b. Mise en place du mapper Dozer

- III-B-4-c. Implémentation des services

- III-B-4-d. Liaison entre GWT et la couche de services

- III-B-4-e. Intégration de log4j

- III-B-5. Test de l'application

- III-B-5-a. Arborescence finale

- III-B-5-b. Test connexion avec erreurs

- III-B-5-c. Test connexion avec succès

- IV. Conclusion

- IV-A. Références et remerciements

- IV-A-1. Remerciements

- IV-A-2. Références

I - Introduction

Le but de cet article est de présenter le patron de conception MVP (Model View Presenter) de GWT (Google Web Toolkit) et de montrer pas à pas son implémentation à travers un projet maven complet, basé sur les Frameworks Spring et Hibernate. Je vais également mettre en pratique les notions d'Activity, Place, Presenter, HistoryMapper, PlaceController, ClientFactory et EventBus de GWT. L'implémentation de la couche DAO (couche d'accès aux données) sera basée sur l'utilisation de la généricité et la session utilisateur gérée par Hibernate-entityManager. Je mettrais aussi l'accent sur le développement des composants personnalisés en GWT. Pour la mise en place du pattern MVP, nous partons de zéro. Il existe cependant le framework GWTP qui a pour objectif de simplifier la mise place du patron de conception MVP. Mais nous n'allons pas l'utiliser dans le cadre de ce projet. Pour plus de détails sur l'utilisation de GWTP, vous pouvez consulter ce site : arcbees

Les sources complets des deux premières parties sont disponibles ici.

I-A - Cahier des charges

I-A-1 - Que va faire l'application ?

Cette application va offrir un portail web de gestion d'une bibliothèque. Un utilisateur pourra créer son compte, se connecter et procéder à l'emprunt d'un livre. La gestion de stock est prise en compte afin d'éviter les conflits des emprunts simultanés. Ce portail aura donc deux interfaces principales :

- une interface d'inscription ;

- une interface de connexion et d'administration.

On aura quatre types d'acteurs : les étudiants, les enseignants, les visiteurs et les administrateurs
Pour atteindre cet objectif, l'application sera développée en plusieurs parties.

I-A-1-a - Première partie : Partie cliente

La partie cliente est la partie visible de l'application. C'est donc l'interface utilisateur. Dans la partie cliente, je vais présenter comment est organisée une application GWT, en l'occurrence, l'organisation des packages et leurs rôles. L'objectif final dans la partie cliente est de permettre à l'utilisateur de s'inscrire et de se connecter en tant que : Etudiant, Enseignant, ou Visiteur.
Il y a deux profils : un profil utilisateur et un profil administrateur.
L'espace de gestion (quand on est connecté) offre la possibilité à un étudiant, enseignant, ou visiteur :

- d'emprunter un livre, accéder à la liste de ses emprunts ;

- de consulter l'état de l'emprunt ou même d'annuler son emprunt si celui-ci n'est pas encore validé. (Bref gérer l'historique de ses emprunts).

Un administrateur a la possibilité :

- d'ajouter des livres et des auteurs associés ;

- de valider ou non une série de demandes d'emprunts ;

- de gérer l'historique des emprunts.

Un administrateur sera averti automatiquement depuis son espace de gestion s'il y a une nouvelle demande d'emprunt qui est arrivée.

Le but dans cette partie pour un développeur, c'est d'apprendre à créer soi-même ses propres composants en GWT, et aussi, utiliser une seule page (c'est l'un des objectifs du Framework GWT) pour gérer toutes les étapes de l'inscription. L'inscription se fait après validation de chaque étape. Chaque inscription doit être unique. Le contrôle de l'unicité sera basé sur une table des utilisateurs comprenant une colonne d'adresses mails uniques.

I-A-1-b - Deuxième partie : Partie serveur

La partie serveur correspond à la partie métier, en général non accessible à l'utilisateur. C'est une boite noire qui est chargée de traiter les demandes utilisateurs. Son rôle est de fournir les services à la demande de l'utilisateur. Un développeur va apprendre dans cette partie à :

- implémenter les services RPC ;

- interfacer GWT et les autres couches de services ;

- développer la couche de services ;

- développer la couche d'accès aux données (grâce aux interfaces génériques) ;

- intégrer Spring et Hibernate dans une application ;

- créer soi-même un DTO (Data Transfer Object) ou utiliser le Framework Dozer.

I-A-2 - Présentation l'architecture

I-A-2-a - Développement partie cliente

Il s'agit du développement de l'interface utilisateur, ce que l'on verra à l'écran final.

À faire dans la partie cliente :

- mise en ?uvre du design-pattern GWT-MVP ;

- les termes tels que : Activity, Place, ClientFactory, PlaceHistoryMapper, ActivityMapper seront bien expliqués et mis en pratique dans la partie cliente.

I-A-2-b - Développement Partie serveur

La partie serveur est la partie invisible de l'utilisateur, c'est la plus grande partie des développements. Elle est composée de plusieurs couches :

Couche de services

C'est en général dans cette partie qu'il faut interfacer avec d'autres applications. Dans notre cas, nous aurons :

Contenu de la couche de services :

- gestion des services et appels asynchrones grâce à GWT-RPC ;

- mise en pratique du Framework Spring4gwt ;

- utilisation de Spring pour injecter les beans ;

- gestion des sessions utilisateurs grâce à HttpSession et HttpServletRequest ;

- gestion des transactions ;

- création des DTO pour le transfert des données vers l'interface utilisateur.

Couche d'accès aux données :

C'est dans cette couche qu'il faut créer les entités qui seront transformées en tables grâce au mapping Hibernate.

À faire dans les couches d'accès aux données :

- mise en place de Hibernate/JPA: EntityManager ;

- gestion des associations entre les tables grâce aux annotations JPA ;

- mise en place de la généricité au niveau de la couche DAO.

Mais avant toute chose, il faut qu'on s'assure que nos outils de développement sont en place et bien configurés.

I-B - Installation des outils

I-B-1 - Prérequis

L'environnement de développement

- JDK1.7 (fortement recommandé) ou supérieur ;

- Eclipse JEE version 4.3 ou supérieur. Personnellement, j'ai utilisé Eclipse-4.3 (Eclipse Kepler) ;

- Google Web toolkit : GWT-2.7.0 ;

- Google Plugin pour eclipse-4.3 (voir l'installation ci-dessous) ;

- gwt-maven-plugin ;

- Maven installé sur votre poste de développement.

I-B-2 - Installation du plugin GWT

Pour installer le plugin GWT sur Eclipse-4.3 :

- Lancer Eclipse

- Help => Install New Sofware …

- Clic sur Add …

- Entrer le nom et l'URL pour Eclipse-4.3 comme ci-dessous :
https://dl.google.com/eclipse/plugin/archive/3.8.0/4.3

Pour toutes les autres versions de Plugins pour Eclipse suivre ce lien :
https://developers.google.com/eclipse/docs/download

- Sélectionner Google Plugin for Eclipse (optionnellement, ajouter Developper Tools, et SDKs).

- Accepter la licence, puis la signature et laisser l'installation aller au bout.

- Redémarrer Eclipse.

I-B-3 - Configuration du plugin GWT

Pour vérifier si le plugin GWT est bien configuré

- Après redémarrage d'Eclipse, cliquer sur : Window => Preferences

- Dérouler le menu Google => cliquer sur Web Toolkit, vérifier que vous avez gwt-2.7.0 coché, sinon Cliquer Add et choisir le répertoire d'installation de GWT SDK.

- Si tout est OK, on a ce résultat :

I-B-4 - Création du squelette du projet

Nous allons nous appuyer sur le plugin gwt-maven-plugin comme base pour créer le squelette de notre projet. Ensuite nous allons réorganiser le résultat pour répondre à nos attentes. Car, comme vous allez le constater, ce plugin ne génère pas l'interface asynchrone pourtant obligatoire dans un projet GWT. Un premier test sera effectué pour valider l'environnement de développement.

I-B-4-a - Création du projet à partir d'Eclipse

Création d'un projet maven en utilisant l'archétype gwt-maven-plugin

- Lancer Eclipse.

- Cliquer sur Fichier => New Other …

- Dans la fenêtre qui s'ouvre, cliquer sur : Maven => Maven Project

- Décocher Create a simple project (skip archetype selection), puis => Next

- Dans Filter, saisir gwt => choisir gwt-maven-plugin => Next

Choix de l'Archetype gwt-maven-plugin

Renseigner tous les champs comme vous souhaitez, voir ci-dessous :

- Group Id : com.developpez.bnguimgo

- Artifact Id : gwtSpringHibernate5 (c'est le nom que portera votre projet)

- Version : 0.0.1-SNAPSHOT

- Package : com.developpez.bnguimgo.gwt

- Module : BookManager (NB : Obligatoire)

- Voir le résultat ci-dessous en image :

Configuration de l'archetype gwt-maven-plugin

Structure du projet créée :

Structure du projet crée par le plugin gwt-maven-plugin

Bien observer que le nom de mon package se termine par .gwt. Pas obligatoire, juste pour une bonne organisation. Vous verrez dans la suite que tout ce qui concerne GWT sera dans ce package.

I-B-4-b - Configuration du projet

Comme vous avez constaté, il manque des choses dans le projet généré :

Il faut ajouter de l'interface : GreetingServiceAsync

L'interface asynchrone GreetingServiceAsync.java n'a pas été générée (pourtant obligatoire). C'est une insuffisance dont je n'ai pas encore de réponse en utilisant le plugin gwt-maven-plugin. Cependant, le plugin nous a permis d'avoir une base de démarrage du projet. Nous allons donc compléter en ajoutant l'interface GreetingServiceAsync.java au même niveau (même package) que l'interface GreetingService.java avec ce contenu :

code de l'interface GreetingServiceAsync.java

1. import com.google.gwt.user.client.rpc.AsyncCallback;

3. public interface GreetingServiceAsync {

4. void greetServer(String input, AsyncCallback<String> callback)

5. throws IllegalArgumentException;

6. }

Configuration du pom.xml

Dans le pom.xml généré, au niveau du plugin gwt-maven-plugin, commenter le bloc executions :

commenter ce bloc ci-dessous

<!--     <executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test</goal>
<goal>generateAsync</goal>
</goals>
</execution>
</executions> -->

Nous n'avons pas besoin de ce bloc pour le moment. Si vous ne le faites pas, vous aurez une erreur à la compilation.

Mise en place du build maven

Le build maven permet de paramétrer le build afin d'exécuter les étapes choisies.

Configuration du build maven

- Clic droit sur le projet => run As => Run Configuration…

- Dans la fenêtre qui s'ouvre : double-clic sur Maven Build

- Dans le champ Name : gwtSpringHibernate

- Dans Base directory : clic sur Browse Workspace… et choisir le projet.

- Dans le champ Goals : clean install, cocher Skip Tests (pas de tests).

- Clic sur Apply => Run

- Vérifier que le build est OK avant de continuer.

Exécution du projet

- Clic droit sur le projet => run As => Web Application

- Si vous obtenez une erreur (Missing required argument 'module[s]'), c'est normal. Cela est dû au fait que le module BookManager est inaccessible Ce module est déclaré dans le fichier : com/developpez/bnguimgo/gwt/BookManager.gwt.xml. Nous allons le configurer.

- Refaire un clic droit sur le projet => Run As => Run Configuration…

- Dans le menu de gauche : clic sur BookManager.html

- Choisir l'onglet Arguments, puis ajouter un espace à la fin et insérer com.developpez.bnguimgo.gwt.BookManager

- Résultat configuration onglet Arguments :

Configuration du module BookManager dans l'onglet Arguments

Voici le résultat sur la console Eclipse après exécution :

Résultat console Eclipse

Pour exécuter l'application, saisir : http://127.0.0.1:8888/BookManager.html sur votre navigateur.

Résultat sur le navigateur :

Résultat de l'application en cours d'exécution

La configuration de base du projet est terminée.

I-B-4-c - Mise en place du Super Dev Mode

Le Super Dev Mode (SuperDevMode) est une des grandes nouveautés apportées par gwt-2.7.0, il remplace le Dev Mode et permet de tester directement une application en cours de développement dans un mini serveur intégré.

Avantages

- Avec le Super Dev Mode, plus besoin d'installer un plugin GWT sur un navigateur pour développer son application en GWT.

- Après modification du code dans Eclipse, un simple rafraîchissement permet de recompiler et de transformer le code Java en JavaScript exécutable directement sur un navigateur (par conséquent, plus besoin de la JVM).

- Compilation incrémentale : seul le module affecté par une modification est recompilé ce qui entraîne un gain de temps considérable dans le développement.

- Le débogage se fait depuis le navigateur (débogueur de Chrome via les SourceMaps). Je vais vous montrer dans la suite comment retrouver les sources depuis le navigateur Chrome, et comment déboguer.

Inconvénients
On ne peut plus déboguer la partie cliente directement dans Eclipse (les points d'arrêts dans Eclipse ne servent plus à rien), sauf à installer un nouveau plugin, par exemple le plugin SDBG.
C'est pourquoi dans la suite du projet, je vous montrerais comment mettre en place le débogage depuis Eclipse avec l'installation d'un Plugin Chrome (SDBG) dans Eclipse.

Mise en place du raccourci Super Dev Mode sur le Navigateur (optionnelle)

- Saisir l'URL : http://127.0.0.1:9876/ sur votre navigateur.

- Vous obtenez ce résultat :

Console SuperDevMode

Création raccourci sur la barre d'outils du navigateur

- Faire un Drag and Drop (glisser-Déplacer) du bouton Dev Mod On vers la barre des outils pour créer un raccourci.

- Faire un autre Drag and Drop du bouton Dev Mod Off vers la barre des outils pour créer un autre raccourci.

- Relancer l'application en cliquant sur : http://127.0.0.1:8888/BookManager.html

- En cliquant sur le raccourci Dev Mod On, que vous venez de créer, vous pouvez choisir le module à compiler.

Compilation depuis le navigateur

Notre projet compile. En GWT on peut directement déboguer la partie cliente (JavaScript) à partir du navigateur ou depuis Eclipse. Pour la partie serveur, le débogueur se fait uniquement dans Eclipse, car pas de JavaScript. Je vais vous présenter ci-dessous comment mettre en place chaque mode de débogage.

I-B-4-d - Mise en place du débogage depuis le navigateur Chrome

L'enjeu ici, c'est de montrer comment accéder au code source de l'application depuis le navigateur Chrome et d'y mettre un point d'arrêt pour déboguer.

Relancer l'application

- Relancer l'application en mode débogage.

- Clic droit sur le projet, puis Debug => Debug As … => choisir BookManager.html

- Appuyer sur la touche F12 de votre clavier (raccourci pour ouvrir les outils de développement du navigateur).

- Cliquer sur l'onglet Sources en bas du navigateur et développer les sources.

Cliquer comme ci-dessous sur le bouton :

Chemin d'accès au code source

Naviguer jusqu'à 127.0.0.1:9876 => sourcemaps/BookManager (voir capture) :

Code source depuis le navigateur grâce à Sourcemaps

Mise en place du point d'arrêt

- Cliquer sur la classe BookManager et mettre un point d'arrêt sur la ligne 112, de la méthode sendNameToServer(). On observe l'apparition du Breakpoint dans le menu de droite.

- Exécuter l'application en cliquant sur le bouton Send. Voir capture ci-dessous :

Débogage depuis le navigateur

Maintenant nous savons comment déboguer la partie cliente d'une application GWT depuis un navigateur Chrome. Si nous voulons faire la même chose depuis Eclipse (c'est d'ailleurs ce que je préfère, vous pouvez choisir ce qui vous plait), il nous faut installer le plugin SDBG.

I-B-4-e - Installation et configuration du plugin de débogage SDBG

Le débogueur SDBG n'est pas obligatoire pour la suite de ce tutoriel.

L'objectif, c'est de permettre le débogage dans Eclipse de l'interface cliente.

Suivre ce lien : Blog Iteconsulting, pour plus de détails sur le Super Dev Mode et le plugin SDBG.

SDBG est un plugin qui permet de créer un launcher Eclipse afin de lancer Chrome en mode de débogage. Grâce à ce plugin il sera donc possible de mettre les points d'arrêts dans Eclipse et de déboguer sans aucun souci.

Installation du Plugin SDBG

Télécharger le zip SDBG ici

Installation du plugin SDBG à partir de l'archive téléchargée

- Dans Eclipse, clic sur Help => Install New Software… => Add…

- Pour Name, entrer SDBG.

- Dans Location, cliquer sur Archive et renseigner le nom du fichier téléchargé SDBGxxx.zip ou entrer l'URL (http://sdbg.github.io/p2) si vous installez directement depuis le lien web.

Installation SDBG à partir d'un zip téléchargé

- Valider et cocher la case Source Map Debugger puis Next.

- Accepter la licence.

- Redémarrer Eclipse.

Configuration du plugin SDBG

- Clic droit sur le projet => Debug => Debug As… => Debug Configurations…

- Dans le menu de gauche, double clic sur Launch Chrome.

- Dans Name : entrer un nom, par exemple (DebugBookManger).

- Cocher URL et renseigner : http://127.0.0.1:8888/BookManager.html

- Dans Project : renseigner le nom du projet.

Configuration débogueur SDBG

- Enregistrer et lancer le débogage.

- Si vous obtenez une erreur, configurez le chemin d'accès à Chrome (répertoire installation Chrome) dans votre fichier eclipse.ini en ajoutant cette ligne à la fin : -Dchrome.location=Chemin_Installation\chrome.exe

- Dans mon cas, j'ai ceci : -Dchrome.location=D:\applications\Google\chrome.exe

Si le chemin d'accès à Chrome est mal renseigné ou manquant, vous aurez l'erreur ci-dessous :

Erreur chemin de Chrome manquant

- Redémarrer Eclipse et relancer l'application en mode débogage (Debug => Debug As…), quand vous obtenez sur la console Eclipse http://127.0.0.1:8888/BookManager.html, alors lancer le débogueur.

- Lancer la configuration DebugBookManager comme ci-dessous :

Exécution du débogueur SDBG

Débogage dans Eclipse de la partie cliente

- Pour tester, mettez un point d'arrêt dans la classe BooManager.java dans Eclipse (voir Schéma).

- Clic sur le bouton Send.

Mise en place point d'arrêt

Cette partie a permis de présenter le cahier des charges, d'installer les outils et même de générer une application test de démarrage.

Notre débogueur est en place, et nous pouvons passer aux choses sérieuses.

II - Première partie : Partie cliente

L'objectif dans cette partie sera de développer l'interface client en se basant sur le modèle MVP de GWT.

II-A - Organisation d'un projet GWT

Avant de commencer les développements, voyons comment est structuré un projet GWT.

Pour rappel, voici la structure de notre projet :

Structure packages GWT

Un projet typiquement GWT est construit autour de trois packages principaux :

- le package client ;

- le package server (ou serveur) ;

- le package shared (package partagé entre le client et le serveur).

II-A-1 - Le package client

Ce package contient les vues construites à base de Widgets ou composants fournis par GWT, donc pas de code métier. Son rôle est de fournir à l'utilisateur des moyens d'interaction avec l'application à travers des composants.

Après modification du code dans cette partie, pas besoin de recompiler l'application. Un simple rafraîchissement du navigateur est suffisant.

Dans cette couche, nous allons donc créer les Vues, la ClientFactory, le Presenter, la Place, l'Activity, l'EventBus et les Mappers, dont voici quelques définitions.

II-A-1-a - Définitions

Widget : c'est un composant en GWT, exemple : une zone de texte (TextBox). Pour séparer la vue de sa présentation, GWT offre un moyen appelé Uibinder.

Uibinder : c'est un moyen offert par GWT pour dessiner les interfaces graphiques à travers un fichier xml afin de mieux séparer la vue et sa présentation. Pour fabriquer les vues, on peut utiliser une factory ou ClientFactory.

ClientFactory : c'est la classe permettant de fabriquer les vues, de fournir le bus d'événements (ou EventBus), et la PlaceController. L'utilisation d'une factory n'est cependant pas obligatoire.

EventBus : quand un événement survient, tous ceux qui sont à l'écoute doivent être informés. C'est grâce à l'EventBus que tout cela est possible, par exemple la PlaceController peut être informée de la création d'une nouvelle Place.

PlaceController : c'est la classe utilisée par la ClientFactory pour naviguer entre les Places. Son rôle c'est de diffuser les Places dans l'EventBus.

Place : une Place représente une page de l'application. Elle correspond à l'endroit où on se trouve dans l'application. Donc à chaque nouvelle page, il faudra créer une nouvelle Place. Une Place doit toujours être associée à une PlaceTokeniZer qui permet de la sérialiser, c'est-à-dire transformer une Place en URL.

PlaceTokeniZer : c'est l'interface qui permet de sérialiser une Place, c'est-à-dire de transformer une Place en URL. Une URL en GWT est constituée du nom de la Place suivi des deux-points (:) et se termine par le Token (ou jeton) retourné par le PlaceTokeniZer. La gestion de l'historique de navigation entre les pages est assurée par la PlaceHistoryMapper.

PlaceHistoryMapper : c'est le gestionnaire de l'historique de navigation. Toutes les Places ou pages de l'application doivent être référencées dans cette interface afin d'être prises en compte par le Handle qui est PlaceHistoryHandler.

PlaceHistoryHandler : son rôle est de définir un mapping bidirectionnel entre la page et l'URL associée. Chaque page dynamique de l'application doit avoir un moyen qui permet à la vue de communiquer avec sa partie métier : c'est le Presenter (ou Présenteur).

Presenter : le Présenteur est une interface contenant des méthodes qui permettent à la vue de communiquer avec l'Activity (service traiteur).

Activity : une Activity (activité) représente ce que fait l'utilisateur dans une Place. C'est la classe chargée de traiter les demandes sollicitées par la vue, par exemple le clic sur un bouton. Ces demandes lui sont transmises grâce au présenteur. Chaque activité est liée à une Place c'est-à-dire une page. C'est l'ActivityMapper qui est chargée de mapper une Place à son Activity à travers la ClientFactory.

l'Activity ne doit pas contenir de composants, ni de logique de présentation. Son rôle est d'initialiser et de restaurer la page avant son affichage.

ActivityMapper : c'est l'interface chargée de créer une instance de l'activité en fonction de la Place qui est passée en paramètre. Dans les faits, c'est le gestionnaire des activités, l'ActivityManager qui récupère les Places diffusées dans l'EnventBus et la met à disposition de l'ActivityMapper.

ActivityManager : c'est le gestionnaire des activités. Il est à l'écoute de l'eventBus pour traquer les Places diffusées par la PlaceController afin de les transmettre à l'ActivityMapper.

Tous ces termes que vous venez de voir feront l'objet d'une mise en pratique lors du développement de la partie cliente de l'application.

II-A-2 - Le package server

C'est dans ce package qu'il faut coder la logique métier, les échanges avec les autres applications et les services transverses. Les services RPC seront implémentés dans cette couche.

Un service RPC est un service qui fonctionne en mode asynchrone (utilise le mode de communication en différé) comparativement à un mode de communication synchrone où les échanges d'informations sont directs.

Un exemple d'échanges asynchrones (mode non connecté) peut être l'envoi des méls, ou l'emprunt d'un livre à la bibliothèque.

Un exemple d'échanges synchrones (mode connecté) peut être l'appel téléphonique.

Toute modification du code dans le package server nécessite une recompilation du module.

II-A-3 - Le package shared

Ce package contient des modules ou classes qui sont à la fois nécessaires côté client et côté serveur. C'est-à-dire des classes utilisables par le client et le serveur. Nous allons y mettre toutes les interfaces asynchrones afin d'intercepter tous les appels asynchrones sollicités par l'utilisateur. À chaque appel asynchrone correspond un service RPC. Par exemple pour l'interface IBookServiceRpcAsync, il faudra aussi une interface IBookServiceRpc. L'implémentation de IBookServiceRpc se fera dans le package server. À chaque service RPC est associé une URL qui permet de garder le lien avec différentes implémentations. Exemple : @RemoteServiceRelativePath("rpc/bookServiceRpc")

On peut aussi mettre les interfaces asynchrones dans le module client, mais je trouve que ça peut créer des confusions.

Toute modification dans ce package doit faire l'objet d'une nouvelle compilation. Le rafraîchissement du navigateur ne suffit pas.

Dans la suite, tout module qui ne concerne pas GWT doit être créé dans un package différent (pas obligatoire, mais nécessaire pour une bonne séparation des modules du projet).

II-B - Implémentation du pattern GWT-MVP

II-B-1 - Présentation du patron de conception GWT-MVP

Le patron de conception MVP (Model View Presenter ou en français : Modèle Vue Présenteur) est un modèle de développement d'applications qui dérive du modèle MVC (Model View controller). Dans le pattern MVP, le modèle (partie des traitements ou services) est complètement séparé de la présentation ou de la vue (View). Dans ce pattern, le présenteur joue le rôle de contrôleur au sens strict du terme afin d'isoler toute communication directe entre la vue et le modèle.

Voici le rôle de chaque couche :

- Le Modèle assure la logique métier. C'est lui qui fait les traitements. Il ne contient aucun composant graphique.

- La Vue est l'interface par laquelle l'utilisateur agit sur l'application. Aucun traitement ne devrait avoir lieu dans la vue. Elle sert uniquement d'affichage et contient donc des composants graphiques.

- Le Présenteur assure la communication entre la vue et le modèle. Il connait toutes les méthodes de traitements et de mises à jour de la vue.

L'avantage de ce pattern est que l'implémentation de la vue peut être modifiée sans aucun impact sur la logique métier. Ce qui n'est pas possible avec le MVC, car dans ce modèle, le contrôleur et la vue manipulent le modèle, ce qui rend la séparation entre la vue et le modèle difficile.

Voici une image qui illustre le modèle MVP.

Schéma du pattern MVP

Pour illustrer l'implémentation de ce design, nous allons développer la page d'accueil WelcomeView.

Cette page contiendra :

- un bouton qui permet de déclencher l'inscription ;

- une zone de texte pour le login ;

- une zone de texte pour le mot de passe ;

- un bouton pour se connecter à son espace personnel.

Le résultat final devrait donner ceci :

Page d'accueil WelcomeView à développer

II-B-2 - Implémentation de la Vue

Note : je vous propose de créer un nouveau package view dans le package client. C'est dans ce package que nous allons créer toutes les vues.

Nous allons donc commencer par créer une interface IWelcomeView qui hérite des composants IsWidget de GWT nécessaires à notre vue.

Interface IWelcomeView.java

1. package com.developpez.bnguimgo.gwt.client.view;

2. import com.google.gwt.user.client.ui.IsWidget;

4. public interface IWelcomeView extends IsWidget {

6. void setPresenter(Presenter presenter);

7. void showError(String errorMessage);

8. void showSucces(String succesMessage);

10. public interface Presenter {

12. void goToCreateComptePlace(String token);

13. void findUserByEmail(String email, String password);

16. }

18. }

Explications

Méthodes exposées par la vue et le Présenteur

Notre vue doit contenir des composants tels que les boutons et les zones de texte. Autant mieux prendre cela en compte immédiatement par héritage de l'interface IsWidget.

IsWidget est une interface qui expose les composants GWT.

Dans cette interface, il y a la méthode setPresenter et l'interface interne Presenter. La présence simultanée de la méthode setPresenter et de l'interface Presenter permet d'établir une communication bidirectionnelle entre la vue et le présenteur. La méthode setPresenter(Presenter presenter) permet à la vue de récupérer le présenteur qui lui servira de communication avec son Activity. L'interface Presenter expose des méthodes qui permettent à son Activity de traiter les demandes de la vue : c'est à l'Activity d'implémenter ces méthodes.

Pour transmettre la page de création du compte, le présenteur aura besoin de la méthode goToCreateComptePlace(String token). La vue quant à elle, va transmettre au présenteur l'email et le mot de passe et attendre que le présenteur la redirige vers une nouvelle page. Le présenteur va se servir de la méthode findUserByEmail(String email, String password) pour demander à l'Activity d'aller contrôler l'existence de cet utilisateur en base de données. Si tout se passe bien, l'Activity renvoie l'utilisateur vers la nouvelle page, sinon, on reste à la page d'accueil.

Le Presenter est implémenté par l'Activity. C'est donc à l'activity que le présenteur transmet toutes les demandes utilisateurs : c'est l'Activity qui fait le boulot. Le Presenter sert juste à la vue de communiquer avec l'Activity.

Dans une communication unidirectionnelle, la vue n'a pas besoin du présenteur, et la méthode setPresenter(Presenter presenter) n'a plus sa raison d'être, mais dans ce cas tous les événements liés à la communication avec la vue doivent être préalablement enregistrés au démarrage de l'Activity.

La méthode showError(String errorMesse) est implémentée par la vue pour afficher les messages d'erreurs. Il en est de même de la méthode showSucces(String succesMesse) en cas de succès.

Les méthodes implémentées par la vue sont dans l'interface IWelcomeView alors que celles implémentées par l'Activity sont dans l'interface Presenter. N'oubliez pas que l'interface Presenter est incluse dans l'interface IWelcomeView, ce qui permet à la vue de s'assurer qu'elle dispose d'un moyen de communication avec son Activity et que le Présenteur connait les méthodes qui vont traiter les demandes de la vue. Nous verrons plus bas l'importance de cette organisation.

Implémentation de la vue

Je vous conseille de créer un package impl dans le package view. Nous allons créer la classe WelcomeViewImpl qui sera une implémentation de la vue IWelcomeView.
L'objectif de cette implémentation, c'est de construire la vue à travers les composants GWT.

WelcomeViewImpl.java

1. package com.developpez.bnguimgo.gwt.client.view.impl;

2. public class WelcomeViewImpl extends Composite implements IWelcomeView{

4. private Presenter presenter;

5. private Button createCompteButton = new Button("CREATION COMPTE");

6. private TextBox emailTextBox = new TextBox();

7. private PasswordTextBox passwordTextBox = new PasswordTextBox();

8. private Button connexionButton = new Button("CONNEXION");

9. private Label connexionStatus = new Label();

11. public WelcomeViewImpl() {

13. VerticalPanel welcomeVPanel = initComponents();

14. initWidget(welcomeVPanel); //création de la vue

15. // Ajout des Handles pour la gestion des événements

16. createCompteButton.addClickHandler(new ClickHandler(){

18. @Override

19. public void onClick(ClickEvent event) {

20. presenter.goToCreateComptePlace("CREATIONCOMPTE");

22. }

24. });

26. connexionButton.addClickHandler(new ClickHandler(){

28. @Override

29. public void onClick(ClickEvent event) {

30. presenter.findUserByEmail(emailTextBox.getValue(), passwordTextBox.getValue());

32. }

34. });

35. }

37. @Override

38. public void setPresenter(Presenter presenter) {

39. this.presenter = presenter;

40. }

42. @Override

43. public void showError(String errorMessage) {

44. connexionStatus.getElement().getStyle().setColor("red");

45. connexionStatus.setText(errorMessage);

46. }

47. @Override

48. public void showSucces(String succesMessage) {

49. connexionStatus.getElement().getStyle().setColor("blue");

50. connexionStatus.setText(succesMessage);

51. }

54. private VerticalPanel initComponents(){

56. HorizontalPanel createCompteHPanel = new HorizontalPanel();

57. createCompteHPanel.add(createCompteButton);

59. HorizontalPanel emailHPanel = new HorizontalPanel();

60. Label emailLabel = new Label("MAIL : ");

61. emailHPanel.add(emailLabel);

62. emailHPanel.add(emailTextBox);

64. HorizontalPanel passwordHPanel = new HorizontalPanel();

65. Label passwordLabel = new Label("PASS : ");

66. passwordHPanel.add(passwordLabel);

67. passwordHPanel.add(passwordTextBox);

69. HorizontalPanel connexionHPanel = new HorizontalPanel();

70. connexionHPanel.add(connexionButton);

72. VerticalPanel welcomeVPanel = new VerticalPanel();

73. welcomeVPanel.add(createCompteHPanel);

74. welcomeVPanel.add(connexionStatus);

75. welcomeVPanel.add(emailHPanel);

76. welcomeVPanel.add(passwordHPanel);

77. welcomeVPanel.add(connexionHPanel);

78. return welcomeVPanel;

80. }

82. }

N'oubliez pas de compléter les imports nécessaires.

Notre vue est à présent créée, dans la suite nous allons reconstruire cette même vue avec UiBinder. Pour obtenir une instance de cette vue, nous allons passer par la Factory. L'intérêt de créer la vue à travers une interface factory, c'est de fournir plusieurs implémentations. On peut par exemple construire la vue en fonction de la taille de l'écran (Smartphones, Mobiles, etc.). C'est l'interface ClientFactory qui sera chargée de fournir différentes implémentations de la vue, l'EventBus et la PlaceController.

II-B-3 - Implémentation de la ClientFactory

La ClientFactory, intégrée à chaque Activity, sera chargée de fournir différentes implémentations de la vue. Elle permet de rendre les vues réutilisables. Elle fournit aussi l'EventBus pour la gestion des événements, la PlaceController afin d'initialiser l'historique de navigation entre les pages. Je vous conseille de créer un package factory dans le package client.

La ClientFactory n'est pas toujours nécessaire, mais en cas d'absence, il faut utiliser un Framework d'injection de dépendance comme GIN pour obtenir une référence des objets comme l'EventBus.

Voici notre interface ClientFactory :

Interface ClientFactory.java

1. package com.developpez.bnguimgo.gwt.client.factory;

2. public interface ClientFactory {

4. EventBus getEventBus();

6. PlaceController getPlaceController();

8. IWelcomeView getWelcomeView();

10. Place getWhere();

12. void goTo(Place place);

14. }

Pour le moment, nous n'avons qu'une seule vue à afficher : WelcomeView. Dans la suite, nous ajouterons les autres vues dans cette même factory. Nous avons aussi ajouté deux méthodes : une pour indiquer où on se trouve : getWhere(), et une autre pour indiquer où on va : goTo(Place place). Ces deux méthodes sont nécessaires à la navigation.

Lors de l'import de l'EventBus, choisir le package import com.google.web.bindery.event.shared.EventBus, au lieu de : import com.google.gwt.event.shared.EventBus ; sinon, vous aurez une erreur lors de l'exécution.

Implémentation de la ClientFactory

Dans le même package factory, créer comme ci-dessous, la classe : ClientFactoryImpl

ClientFactoryImpl.java

1. package com.developpez.bnguimgo.gwt.client.factory;

2. public class ClientFactoryImpl implements ClientFactory {

4. protected PlaceController placeController;

5. protected EventBus eventBus;

7. public ClientFactoryImpl() {

8. super();

9. eventBus = new SimpleEventBus();

10. placeController = new PlaceController(eventBus);

11. }

13. @Override

14. public EventBus getEventBus() {

15. return eventBus;

16. }

18. @Override

19. public PlaceController getPlaceController() {

20. return placeController;

21. }

23. @Override

24. public IWelcomeView getWelcomeView() {

25. return new WelcomeViewImpl();

26. }

28. @Override

29. public Place getWhere() {

30. return getPlaceController().getWhere();

31. }

33. @Override

34. public void goTo(Place newPlace) {

35. getPlaceController().goTo(newPlace);

37. }

39. }

Ce qu'il faut retenir ici, c'est que grâce à la factory, on peut choisir la vue à afficher. Cette factory nous fournit la PlaceController pour faciliter la navigation entre les pages et l'EventBus pour gérer les événements. Grâce à la ClientFactory, on saura à tout moment où on se trouve et où on va.

La création d'une instance de la ClientFactory se fait via le deferred binding fourni par GWT. Il faut obligatoirement indiquer à GWT où se trouve la classe implémentant la factory. C'est pourquoi il faut ajouter dans le fichier BookManager.gwt.xml l'élément suivant :

configuration de la factory dans BookManager.gwt.xml

<replace-with class="com.developpez.bnguimgo.gwt.client.factory.ClientFactoryImpl">
<when-type-is class="com.developpez.bnguimgo.gwt.client.factory.ClientFactory"/>
</replace-with>

Nous verrons plus loin comment modifier l'Entry-point pour intégrer ces changements.

Intéressons-nous dès à présent à la logique métier. Rappelez-vous : toutes les demandes de la vue sont adressées au Presenter qui connait quelle méthode est capable de répondre à la demande. Toutes ces méthodes sont implémentées par l'activity (mais nous verrons encore plus loin qu'en réalité, l'activity transmet aussi la demande à un service RPC et attend en retour une réponse pour renvoyer à la vue).

II-B-4 - Création de l'Activity

Pour chaque activité, il y a un début et une fin. L'objectif primaire de notre activité ici, c'est de créer et d'afficher la page WelcomeView. Nous aurons besoin de la ClienFactory pour le faire. Ensuite, on doit se préparer à répondre aux exigences d'un utilisateur qui veut créer son compte : implémenter la méthode goToCreateComptePlace(String token), ou à celui qui veut se connecter : implémenter la méthode findUserByEmail(String email, String password). Dans l'architecture GWT, le démarrage d'une activité se fait par la méthode start(AcceptsOneWidget panel, EventBus eventBus) fournie par l'extension de AbstractActivity de GWT.

Je vous conseille de créer un package activity dans le package client. Toutes les autres activities seront créées dans le même package. Nous allons y créer la classe WelcomeActivity chargée de traiter les demandes de la vue.

WelcomeActivity.java

1. package com.developpez.bnguimgo.gwt.client.activity;

2. public class WelcomeActivity extends AbstractActivity implements IWelcomeView.Presenter {

4. private static Logger logger = Logger.getLogger(WelcomeActivity.class.getName());

5. private static IWelcomeView welcomeView;

6. private ClientFactory clientFactory;

8. public WelcomeActivity() {

9. super();

10. }

12. public WelcomeActivity(ClientFactory clientFactory){

13. this.clientFactory = clientFactory;//initialisation de la factory

14. }

16. @Override

17. public void start(AcceptsOneWidget panel, EventBus eventBus) {

18. logger.info("Démarrage de "+ WelcomeActivity.class.getName());

19. welcomeView = clientFactory.getWelcomeView();

20. welcomeView.setPresenter(this);

21. panel.setWidget(welcomeView.asWidget());

23. }

25. @Override

26. public void goToCreateComptePlace(String token) {

27. // TODO Auto-generated method stub

29. }

31. @Override

32. public void findUserByEmail(String email, String password) {

33. // TODO Auto-generated method stub

35. }

37. }

Explications :

Comme prévu, nous avons un objet factory pour fabriquer notre vue. Cet objet est initialisé dans le constructeur. Au démarrage, la vue est instanciée et un Presenter lui est associé afin de prévoir des échanges entre la vue et l'activity. Le tout est ajouté dans un Panel AcceptsOneWidget. Les autres méthodes seront implémentées ultérieurement. Rien ne nous oblige actuellement, on va attendre que l'utilisateur clique d'abord.

Question : Comment allons-nous afficher alors la page d'accueil si on n'a aucun bouton ? Nous pourrions utiliser la méthode goTo(welcomePlace) fournie par la ClientFactory, mais là, on cherche encore à afficher la page d'accueil, donc impossible.

Réponse : Au démarrage de l'application, on doit prendre soin d'indiquer vers quelle Place se diriger. Cette Place sera la page d'accueil de notre application. Vous comprenez qu'il nous faut créer une WelcomePlace pour la page d'accueil.

II-B-5 - Création de la Place

Je vous conseille de créer un package place dans le package client. Nous allons y créer la Place WelcomePlace.

WelcomePlace.java

1. package com.developpez.bnguimgo.gwt.client.place;

2. public class WelcomePlace extends Place {

4. private String token;

6. public WelcomePlace(String token) {

7. this.token = token;

8. }

9. public String getToken() {

10. return token;

11. }

13. public void setToken(String token) {

14. this.token = token;

15. }

17. //sans ce préfixe, c'est le nom de la classe qui est utilisé par défaut

18. @Prefix(value = "welcomePlace")

19. public static class Tokenizer implements PlaceTokenizer<WelcomePlace>{

21. @Override

22. public WelcomePlace getPlace(String token) {

23. return new WelcomePlace(token);

24. }

26. @Override

27. public String getToken(WelcomePlace place) {

28. return place.getToken();

29. }

31. }

32. }

Rappelez-vous qu'à chaque Activity correspond une Place.
Dans la Place construite, nous lui avons ajouté un moyen pour construire l'URL : c'est le rôle de la classe interne Tokenizer qui est une implémentation de PlaceTokenizer. Cette classe est chargée de construire l'URL de chaque page sous la forme : http://UrlServeur:port/page.html#nomPlace:token. Par exemple nous aurons dans notre cas : http://127.0.0.1:8888/BookManager.html#welcomePlace:token. Le token n'est pas obligatoire. Il sera remplacé par null s'il n'est pas renseigné. De même si le nom de la place n'est pas spécifié (c'est-à-dire pas de @Prefix sur Tokenizer), il sera remplacé par le nom de la classe correspondante.

Le token est un moyen de passer une variable dans l'URL lorsqu'on se dirige vers une page. Nous l'utiliserons pour gérer la variable de session afin de vérifier si une session est encore active.

On doit redéfinir deux méthodes lorsqu'on implémente PlaceTokenizer pour la construction de l'URL :

- une méthode getPlace(String token) qui permet de retourner la Place correspondante ;

- une méthode getToken(WelcomePlace place) qui affiche le token qui aurait été passé à l'URL.

Il nous faut maintenant établir un lien entre chaque Activity et la Place correspondante : c'est le rôle joué par ActivityMapper. Il en sera de même pour toutes Places à créer.

II-B-6 - Création de ActivityMapper

L'ActivityMapper a pour rôle d'établir le lien entre chaque Place et son Activity.
Je vous conseille de créer un package mapper dans le sous-package client.activity et d'y mettre la classe ActivityLibraryMapper.

ActivityLibraryMapper.java

1. package com.developpez.bnguimgo.gwt.client.activity.mapper;

2. public class ActivityLibraryMapper implements ActivityMapper {

4. private ClientFactory clientFactory;

6. public ActivityLibraryMapper() {

7. super();

8. }

10. public ActivityLibraryMapper(ClientFactory clientFactory) {

11. super();

12. this.clientFactory = clientFactory;

13. }

15. @Override

16. public Activity getActivity(Place place) {

18. if (place instanceof WelcomePlace) {

19. return new WelcomeActivity(clientFactory);

20. } else {

21. return null;

22. }

23. }

25. public ClientFactory getClientFactory() {

26. return this.clientFactory;

27. }

29. }

Pour chaque nouvelle Activity, nous devons l'ajouter dans la méthode : getActivity(Place place). C'est cette méthode qui fait le lien entre l'activité et la Place. Car elle retourne une activité en fonction de la Place passée en paramètre.

Il nous reste à établir aussi un lien entre l'URL et la Place : c'est le rôle de PlaceHistoryMapper. Il en sera de même pour toutes Places créées.

II-B-7 - Création de PlaceHistoryMapper

La PlaceHistoryMapper permet d'établir un lien bidirectionnel entre une Place et l'URL associée. Nous allons créer une interface PlaceHistoryLibraryMapper qui étend la classe PlaceHistoryMapper. Toutes les pages de l'application doivent être mappées dans cette interface.

Je vous conseille de créer cette interface dans le même package ci-dessus où se trouve ActivityLibraryMapper.

Interface PlaceHistoryLibraryMapper.java

1. package com.developpez.bnguimgo.gwt.client.activity.mapper;

2. @WithTokenizers(value = { WelcomePlace.Tokenizer.class})

3. public interface PlaceHistoryLibraryMapper extends PlaceHistoryMapper {

5. }

Grâce à l'annotation @WithTokenizers(value= { WelcomePlace.Tokenizer.class}), le lien est établi. Rappelez-vous que lors de la construction de la welcomePlace, nous lui avons associé une classe Tokenizer lui permettant de construire une URL avec un token associé. C'est cette classe qui est récupérée par annotation. Pour toute nouvelle Place, il faut juste l'ajouter dans cette annotation après une virgule.

Il nous faut maintenant modifier l'entry-point BookManager.java pour l'adapter à notre modèle MVP.

II-B-8 - Modification de l'EntryPoint

Notre entry-point avait été créée lors de la génération du projet. Il se trouve dans le package client et s'appelle : BookManager. Nous voulons désormais qu'il affiche notre propre page d'accueil et non celle par défaut offerte par GWT. Je vous conseille de modifier cette classe comme ci-dessous :

L'Entry-Point est la classe de démarrage d'une application GWT et contient obligatoirement la méthode onModuleLoad() chargée d'initialiser l'application. Dans notre cas, c'est la classe BookManager.java

Entry-Point BookManager.java

1. package com.developpez.bnguimgo.gwt.client;

2. public class BookManager implements EntryPoint {

4. private static final Logger logger = Logger.getLogger(BookManager.class.getName());

5. private Place welcomePlace = new WelcomePlace("");

6. // Le widget principal d'affichage

7. private SimplePanel display = new SimplePanel();

9. @Override

10. public void onModuleLoad() {

11. logger.info("The EntryPoint module is loading ..............");

12. ClientFactory clientFactory = GWT.create(ClientFactory.class);

13. EventBus eventBus = clientFactory.getEventBus();

14. //initialise la navigation vers une nouvelle Place

15. PlaceController placeController = clientFactory.getPlaceController();

16. //On crée l'ActivityManager pour gérer l'Activity associée à la Place demandée

17. ActivityMapper activityMapper = new ActivityLibraryMapper(clientFactory);

18. ActivityManager activityManager = new ActivityManager(activityMapper, eventBus);

19. activityManager.setDisplay(display);

20. //Création de PlaceHistoryHandler afin de gérer l'historique de navigation

21. PlaceHistoryLibraryMapper historyMapper = GWT.create(PlaceHistoryLibraryMapper.class);

22. //PlaceHistoryHandler assure le mapping bi-directionnel entre l'URL et la Place

23. PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);

24. historyHandler.register(placeController, eventBus, welcomePlace);

26. RootPanel.get().add(display);

27. //Premier chargement de l'historique de navigation

28. historyHandler.handleCurrentHistory();

29. logger.info("The application is ready ..............");

30. }

31. }

Expliquons tout ça

Au démarrage de l'application, la méthode onModuleLoad() est exécutée en premier. La clientFactory est créée via le deferred binding de GWT. Cette factory met à notre disposition l'eventBus pour se préparer à la gestion des événements. La même factory nous fournit la placeController pour initialiser éventuellement la navigation vers d'autres pages. Une instance d'activityMapper est alors créée en association avec la clientFactory et transmise à l'activityManager. L'ActivityManager enregistre alors l'ActivityMapper et l'eventBus.
Une instance de placeHistoryHandler est créée pour assurer le mapping bidirectionnel entre l'URL et la Place en se basant sur l'historique de navigation fourni par PlaceHistoryMapper. L'historyHandler enregistre enfin notre page d'accueil (welcomePlace), un moyen de navigation vers d'autres pages (placeController) et de gestion des événements (eventBus). Le tout est transmis au RootPanel pour affichage et un premier chargement de l'historique est enclenché.

II-B-9 - Modification de BookManager.gwt.xml

Dans ce fichier, il faut ajouter les extensions nécessaires à l'initialisation et au bon fonctionnement de l'application. Par exemple l'Activity, la Place, le Bootstrap pour profiter d'une feuille de style par défaut.

Voici les extensions nécessaires :

Déclarations à ajouter dans BookManager.gwt.xml

<inherits name="com.github.gwtbootstrap.Bootstrap"/>
<inherits name="com.google.gwt.activity.Activity" />
<inherits name="com.google.gwt.place.Place" />

Voici le contenu complet du fichier BookManager.gwt.xml

BookManager.gwt.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <module rename-to='BookManager'>

3. <!-- Inherit the core Web Toolkit stuff. -->

4. <inherits name='com.google.gwt.user.User' />

6. <!-- Inherit the default GWT style sheet.  You can change -->

7. <inherits name='com.google.gwt.user.theme.standard.Standard' />

9. <inherits name="com.github.gwtbootstrap.Bootstrap"/>

10. <inherits name="com.google.gwt.activity.Activity" />

11. <inherits name="com.google.gwt.place.Place" />

12. <!-- Specify the app entry point class. -->

13. <entry-point class='com.developpez.bnguimgo.gwt.client.BookManager' />

14. <!-- Factory nécessaire à la construction des vues -->

15. <replace-with class="com.developpez.bnguimgo.gwt.client.factory.ClientFactoryImpl">

16. <when-type-is class="com.developpez.bnguimgo.gwt.client.factory.ClientFactory"/>

17. </replace-with>

19. <!-- Specify the paths for translatable code -->

20. <source path='client' />

21. <source path='shared' />

23. </module>

II-B-10 - Ajout de maven-war-plugin

Pour indiquer au serveur où se trouve le war qui sera généré, il faut ajouter et configurer le plugin maven-war-plugin dans le pom.xml.

Modifier le pom.xml et ajouter le plugin maven-war-plugin

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<webappDirectory>${webappDirectory}</webappDirectory>
</configuration>
</plugin>

Dans l'élément properties ajouter la variable webappDirectory comme ci-dessous :
<webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>

Voici le contenu complet du fichier pom.xml :

Contenu du pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project

3. xmlns="http://maven.apache.org/POM/4.0.0"

4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

7. <modelVersion>4.0.0</modelVersion>

8. <groupId>com.developpez.bnguimgo</groupId>

9. <artifactId>gwtSpringHibernate5</artifactId>

10. <packaging>war</packaging>

11. <version>0.0.1-SNAPSHOT</version>

12. <name>GWT Maven Archetype</name>

14. <properties>

15. <!-- Convenience property to set the GWT version -->

16. <gwtVersion>2.7.0</gwtVersion>

18. <!-- GWT needs at least java 1.6 -->

19. <maven.compiler.source>1.7</maven.compiler.source>

20. <maven.compiler.target>1.7</maven.compiler.target>

21. <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>

23. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

24. </properties>

26. <dependencyManagement>

27. <dependencies>

28. <dependency>

29. <groupId>com.google.gwt</groupId>

30. <artifactId>gwt</artifactId>

31. <version>${gwtVersion}</version>

32. <type>pom</type>

33. <scope>import</scope>

34. </dependency>

35. </dependencies>

36. </dependencyManagement>

38. <dependencies>

39. <dependency>

40. <groupId>com.google.gwt</groupId>

41. <artifactId>gwt-servlet</artifactId>

42. <scope>runtime</scope>

43. </dependency>

44. <dependency>

45. <groupId>com.google.gwt</groupId>

46. <artifactId>gwt-user</artifactId>

47. <scope>provided</scope>

48. </dependency>

49. <dependency>

50. <groupId>com.google.gwt</groupId>

51. <artifactId>gwt-dev</artifactId>

52. <scope>provided</scope>

53. </dependency>

54. <dependency>

55. <groupId>com.github.gwtbootstrap</groupId>

56. <artifactId>gwt-bootstrap</artifactId>

57. <version>2.3.2.0</version>

58. </dependency>

59. <dependency>

60. <groupId>junit</groupId>

61. <artifactId>junit</artifactId>

62. <version>4.11</version>

63. <scope>test</scope>

64. </dependency>

65. </dependencies>

67. <build>

68. <!-- Output classes directly into the webapp, so that IDEs and "mvn process-classes" update them in DevMode -->

69. <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>

71. <plugins>

73. <!-- GWT Maven Plugin -->

74. <plugin>

75. <groupId>org.codehaus.mojo</groupId>

76. <artifactId>gwt-maven-plugin</artifactId>

77. <version>2.7.0</version>

78. <configuration>

79. <runTarget>BookManager.html</runTarget>

80. <modules>

81. <module>com.developpez.bnguimgo.gwt.BookManager</module>

82. </modules>

83. </configuration>

84. </plugin>

85. <plugin>

86. <groupId>org.apache.maven.plugins</groupId>

87. <artifactId>maven-war-plugin</artifactId>

88. <version>2.1.1</version>

89. <configuration>

90. <webappDirectory>${webappDirectory}</webappDirectory>

91. </configuration>

92. </plugin>

93. </plugins>

94. </build>

96. </project>

II-B-11 - Modification du build

Nous devons modifier le build pour indiquer au superDevMode où se trouve désormais le war après le build. Je vous propose d'ajouter ceci comme Arguments dans votre configuration du Run d'Eclipse :

-war C:\workspace\gwtSpringHibernate\target\gwtSpringHibernate-1.0.0-SNAPSHOT
-remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -startupUrl BookManager.html
-logLevel INFO -codeServerPort 9997 -port 8888 com.developpez.bnguimgo.gwt.BookManager

Configuration du build

II-B-12 - Exécution

Après avoir configuré les arguments comme indiqué ci-dessus, lancer le build de l'application :
Clic droit sur le projet => Run As => Maven Install (vérifier que le build est OK).

Exécution

Clic droit sur le projet => Run As => Web Application

Vous obtenez après le build cette URL : http://127.0.0.1:8888/BookManager.html

Le résultat sur un navigateur est le suivant :

Page d'accueil sans feuille de style

Nous pouvons dire que notre design MVP est complet. Mais avant d'aller plus loin, nous pouvons encore faire mieux en séparant la vue de sa présentation. Cela nous évitera de construire directement les composants sur la vue comme nous venons de faire. C'est dans ce contexte que Uibinder est nécessaire.

II-B-13 - Intégration UiBinder et style CSS

II-B-13-a - Intégration de l'UiBinder

UiBinder est un procédé fourni par GWT pour dessiner les interfaces graphiques à travers un fichier xml. L'intérêt est de séparer la vue de sa présentation. Le nommage du fichier Uibinder en GWT pour une vue suit une certaine logique qui est la suivante : nomDeLaVueImpl.ui.xml. Le nom du fichier Uibinder associé à la vue WelcomeViewImpl sera donc : WelcomeViewImpl.ui.xml. Un fichier ui.xml doit respecter la charte W3C pour la validation du CSS. Voici la structure de la page WelcomeViewImpl.ui.xml.

Fichier de présentation de la vue WelcomeViewImpl.ui.xml

1. <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">

2. <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'

3. xmlns:g='urn:import:com.google.gwt.user.client.ui'

4. xmlns:b='urn:import:com.github.gwtbootstrap.client.ui'>

6. <ui:style>

7. .gwt-PasswordTextBox {

8. color: black;

9. background-color: yellow;

10. }

11. </ui:style>

13. <g:HTMLPanel>

14. <div class="container">

15. <div class="content">

16. <h3>Créer votre compte ou connectez-vous</h3>

17. <p>

18. Votre compte utilisateur vous permettra d'accéder à vos services

19. en

20. ligne.

21. </p>

22. <br />

23. <g:Anchor ui:field="createCompte" addStyleNames="btn btn-primary btn-left">

24. Création

25. compte

26. </g:Anchor>

28. </div>

29. <div class="content">

30. <h3>Connexion à votre compte</h3>

31. <g:VerticalPanel>

32. <b:Label ui:field="connexionStatus" />

33. <b:Label ui:field="emailError" />

34. <b:Label ui:field="passwordError" />

35. <g:HorizontalPanel ui:field="emailHorizontalPanel"

36. title="click to enter email" verticalAlignment="middle" height="40px">

37. <b:Label text="Mail :" width="60px" height="20px"

38. styleName="control-label colorH3" />

39. <b:TextBox ui:field="emailTextBox" height="20px"></b:TextBox>

40. </g:HorizontalPanel>

41. <g:HorizontalPanel ui:field="passwordHorizontalPanel"

42. title="click to enter password" verticalAlignment="middle" height="40px">

43. <b:Label text="Pass :" width="60px" height="20px"

44. styleName="control-label colorH3" />

45. <b:PasswordTextBox ui:field="passwordTextBox"

46. height="20px" styleName="{style.gwt-PasswordTextBox}"></b:PasswordTextBox>

47. </g:HorizontalPanel>

48. <g:HorizontalPanel ui:field="buttonHorizontalPanel">

49. <b:Button ui:field="connexionButton" text="Connexion"

50. styleName="btn btn-primary btn-right" />

51. </g:HorizontalPanel>

53. </g:VerticalPanel>

54. </div>

55. <div class="content">

56. <p>

57. Tous droits réservés, copyright bnguimgo @ yahoo fr.

58. </p>

59. </div>

60. </div>

61. </g:HTMLPanel>

63. </ui:UiBinder>

Quelques explications :

- La déclaration xmlns:ui='urn:ui:com.google.gwt.uibinder' permet dans la suite d'intégrer une feuille de style interne ou externe, des images, des messages, etc.

- La déclaration de xmlns:g='urn:import:com.google.gwt.user.client.ui' permet d'intégrer les composants propres à GWT et sa feuille de style.

- La déclaration de xmlns:b='urn:import:com.github.gwtbootstrap.client.ui' permet d'intégrer les composants du Bootstrap et sa feuille de style.

- Les styles tels que container, content, btn, btn-primary, btn-left, etc. nous renvoient à la feuille de style BookManager.css ou au style bootstrap.css. Voir leurs intégrations un peu plus bas.

L'attribut ui:field n'est pas obligatoire. Mais tout composant susceptible d'être manipulé dans la vue doit avoir un attribut ui:field="nomDuChamp".

Pour intégrer un composant personnalisé, il faut suivre la même déclaration que ci-dessus. Nous verrons des exemples un peu plus loin.

Il faut maintenant modifier la vue pour prendre en compte le Template UiBinder correspondant à sa présentation. On va donc retirer tous les composants précédemment intégrés à la vue.

II-B-13-b - Modification de la vue

Voici notre nouvelle vue en association avec sa présentation fournie par l'interface interne WelcomeViewImplUiBinder :

Nouvelle page d'accueil séparée de sa présentation WelcomeViewImpl.java

1. package com.developpez.bnguimgo.gwt.client.view.impl;

3. public class WelcomeViewImpl extends Composite implements IWelcomeView {

5. interface WelcomeViewImplUiBinder extends UiBinder<Widget, WelcomeViewImpl> {

6. }

7. private static WelcomeViewImplUiBinder welcomeViewUiBinder = GWT.create(WelcomeViewImplUiBinder.class);

9. private Presenter presenter;

10. @UiField

11. TextBox emailTextBox;

12. @UiField

13. Label emailError;

14. @UiField

15. PasswordTextBox passwordTextBox;

16. @UiField

17. Label passwordError;

18. final DialogBox dialogBox = new DialogBox();

19. @UiField

20. Button connexionButton;

21. @UiField

22. Label connexionStatus;

24. public WelcomeViewImpl() {

25. initWidget(welcomeViewUiBinder.createAndBindUi(this));

26. initConnexionButtonClickhandle();

27. }

29. private void initConnexionButtonClickhandle(){

30. passwordTextBox.addKeyDownHandler(new KeyDownHandler(){

32. @Override

33. public void onKeyDown(KeyDownEvent event) {

34. if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {

35. connexionButton.click();

36. }

37. }

38. });

39. }

41. @Override

42. public void setPresenter(Presenter presenter) {

43. this.presenter = presenter;

44. }

46. @UiHandler(value = { "connexionButton" })

47. public void onConnexionButtonClick(ClickEvent event){

48. findUserByEmail();

49. }

51. private void findUserByEmail() {

52. if(validateField()){

53. presenter.findUserByEmail(emailTextBox.getValue(), passwordTextBox.getValue());

54. }

55. }

56. /**

57. * Valide instantanément la saisie de emailTextBox

58. * @param event

59. */

60. @UiHandler("emailTextBox")

61. void handleTitleChange(ValueChangeEvent<String> event) {

63. connexionStatus.setText("");

64. connexionStatus.setVisible(false);

66. if (Validator.isBlank(event.getValue())) {

67. emailError.setVisible(true);

68. emailError.setText("L'email est obligatoire");

69. emailError.setStyleName("errorMessage");

70. } else {

71. emailError.setText("");

72. emailError.setVisible(false);

73. }

74. }

76. /**

77. * Valide instantanément la saisie du champ passwordTextBox

78. * @param event

79. */

80. @UiHandler("passwordTextBox")

81. void handlePasswordChangeEvent(ValueChangeEvent<String> event) {

83. connexionStatus.setText("");

84. connexionStatus.setVisible(false);

86. if (Validator.isBlank(event.getValue())) {

87. passwordError.setVisible(true);

88. passwordError.setText("Le mot de passe est obligatoire");

89. passwordError.setStyleName("errorMessage");

90. } else {

91. passwordError.setText("");

92. passwordError.setVisible(false);

93. }

94. }

95. /**

96. * valide la saisie emailTextBox et passwordTextBox

97. * @return

98. */

99. private boolean validateField(){

100. boolean invalideFields = true;

101. initField();

102. if (Validator.isBlank(emailTextBox.getValue())) {

103. invalideFields = false;

104. emailError.setVisible(true);

105. emailError.setText("L'email est obligatoire");

106. emailError.setStyleName("errorMessage");

107. }

108. if (Validator.isBlank(passwordTextBox.getValue())) {

109. invalideFields = false;

110. passwordError.setVisible(true);

111. passwordError.setText("Le mot de passe est obligatoire");

112. passwordError.setStyleName("errorMessage");

113. }

114. return invalideFields;

115. }

117. private void initField(){

118. emailError.setText("");

119. emailError.setVisible(false);

120. passwordError.setText("");

121. passwordError.setVisible(false);

122. connexionStatus.setText("");

123. connexionStatus.setVisible(false);

125. }

127. @UiHandler("createCompte")

128. public void onCreateCompteClick(ClickEvent event) {

129. presenter.goToCreateComptePlace("CREATECOMPTE");

130. }

132. @Override

133. public void showError(String textError) {

134. connexionStatus.setVisible(true);

135. connexionStatus.setText(textError);

136. connexionStatus.setStyleName("errorMessage");

137. }

139. @Override

140. public void showSucces(String message) {

141. connexionStatus.setVisible(true);

142. connexionStatus.setText(message);

143. connexionStatus.setStyleName("succesMessage");

144. }

145. }

Nous avons ajouté quelques contrôles sur les champs de saisies.

Voici une classe de validation des champs vides à jouter dans le package utils de client :

Validateur des champs de saisies Validator.java

1. package com.developpez.bnguimgo.gwt.client.utils;

3. public class Validator {

5. public static boolean isBlank(String value) {

6. return (value == null || "".equals(value.trim()));

7. }

8. }

Toute annotation @UiField ou @UiHandler("nomDuComposant") oblige le compilateur à vérifier la présence du composant concerné dans le fichier .ui.xml correspondant à l'interface en cours.

Les différents composants de la vue qui se trouvent désormais dans le fichier WelcomeViewImpl.ui.xml sont injectés dans la vue grâce à l'annotation @UiField. GWT fournit également un moyen de gestion des événements grâce à l'annotation @UiHandler("nomDuComposant"). Chaque vue récupère sa présentation à travers une interface interne qui étend UiBinder.

UiBinder nous a permis de séparer la vue de sa présentation. Mais UiBinder ne remplace pas le style CSS. Cependant, Uibinder intègre parfaitement les styles associés à chaque composant de la vue.

II-B-13-c - Intégration feuille de style CSS

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Il existe quatre possibilités d'intégrer une feuille de style dans une application GWT :

1. Dans la page hôte ou page d'accueil html grâce à l'élément link. Exemple : dans notre cas c'est dans le fichier BookManager.html.

2. Dans le fichier de module .gwt.xml. Exemple : <stylesheet src='stylesheet.css' />

3. En utilisant un conteneur CssResource dans un ClientBundle (Nous verrons un cas pratique).

4. En utilisant une feuille de style interne. Nous avons un exemple de feuille de style interne ci-dessous dans WelcomeViewImpl.ui.xml :

Feuille de style interne

<ui:style>
.gwt-PasswordTextBox {
color: black;
background-color: yellow;
}
</ui:style>

Intégration d'une feuille de style en utilisant la CssResource.

Nous allons surcharger une feuille de style bootstrap téléchargée sur le web. Nous allons la modifier et l'intégrer à l'application comme une ressource CSS. Voici le fichier CSS (bootstrap.css) déjà modifié à télécharger.

Je vous propose de créer un package css dans le package client de gwt. Ajouter l'interface CssInjector.java comme ci-dessous :

Interface d'intégration du CSS

1. package com.developpez.bnguimgo.gwt.client.css;

3. public interface CssInjector extends ClientBundle {

5. public static final CssInjector INSTANCE = GWT.create(CssInjector.class);

7. @Source("bootstrap.css")

8. @CssResource.NotStrict

9. CssResource css();

10. }

Ajouter le fichier bootstrap.css précédemment téléchargé dans le même package que l'interface CssInjector.java.

L'intégration du fichier bootstrap.css se fait lors du démarrage de l'application dans l'entry-point de la classe BookManager.java avec cette ligne de code (à intégrer avant RootPanel.get().add(display)) :

Intégration d'une ressource CSS

//Préchargement des ressources CSS (optionnel)
CssInjector.INSTANCE.css().ensureInjected();

Lancer le build de l'application puis exécuter, et vous obtenez la page d'accueil avec le nouveau look.

Page d'accueil Welcomview avec feuille de style

II-B-14 - Résumé des étapes du design MVP

II-B-14-a - Étapes

Étapes de mise en place du patron de conception MVP

1. Création et implémentation de la Vue

2. Création et implémentation de la ClientFactory

3. Création des Activities

4. Création des Places

5. Création de l'ActivityMapper

6. Création de la PlaceHistoryMapper

7. Déclaration de la ClientFactory dans le fichier .gwt.xml

8. Création ou mise à jour de l'EntryPoint

II-B-14-b - Structure du projet créé

Voici l'organisation des packages que nous avons créés :

Structure packages avec pattern MVP intégré

Cette première partie nous a appris qu'un projet GWT est organisé autour de trois packages : le package client, server et shared. Nous avons aussi appris à mettre en place le patron de conception MVP et séparer la vue de sa présentation grâce à l'UiBinder.

Cependant, notre application est encore statique, elle ne fait rien pour le moment, sauf l'affichage de la page d'accueil. Pour la rendre un peu dynamique, nous allons lui ajouter des services pour faire quelques traitements, par exemple afficher la page d'administration en cas de clic sur le bouton CONNEXION après saisie de login et mot de passe. C'est l'objectif de la deuxième partie du tutoriel.

III - Deuxième partie : Partie serveur

III-A - Mise en place des services RPC

Le service RPC remplace les servlets dans une application GWT. Ce service fonctionne en mode asynchrone. C'est-à-dire que lorsque ce service commence les traitements, il libère la ressource qui peut être utilisée par d'autres processus. Il ne reprend le jeton de traitement que pour donner une réponse à l'utilisateur. Imaginons que vous ayez un fichier lourd à télécharger. Au lieu de bloquer toute la ressource pour le téléchargement, le service RPC ne le fera que le temps de commencer son traitement, la libère durant tout le temps de traitement, puis la reprend à nouveau pour signaler si le téléchargement est terminé avec ou sans succès.

Le service RPC ne fait pas partie du design MVP (Je n'ai pas trouvé cela sur le site de GWT).

Pour vérifier qu'un utilisateur a le droit de se connecter, il nous faut faire une recherche dans la base de données afin de vérifier les droits. En GWT, c'est le service RPC qui est chargé de répondre à ce genre de demande.

III-A-1 - Création des interfaces RPC

Rappelez-vous que nous avons une méthode findUserByEmail(String email, String password) dans WelcomeActivity qui n'est pas encore implémentée. Nous allons commencer son implémentation en RPC.

Le service RPC repose principalement sur deux interfaces :

- Une interface serveur pour recevoir les demandes et les traiter. Cette interface est une extension de l'interface RemoteService. Il faut également annoter cette interface pour indiquer avec quelle URL on peut déclencher les traitements. Dans notre exemple, nous aurons comme annotation : @RemoteServiceRelativePath("rpc/userServiceRpc"), où rpc est le sous-package du package shared, et userServiceRpc le nom du service à appeler.

- Une interface côté client pour répondre après les traitements. Cette interface doit obligatoirement porter le même nom que l'interface serveur suffixée par Async. Pour recevoir une réponse RPC, il faut absolument implémenter cette interface côté client. C'est ce que va faire la classe WelcomeActiviy afin d'indiquer si oui ou non l'utilisateur a le droit de se connecter. À tout appel RPC correspond un succès onSucces(), ou un échec onFailure().

Tout code qui se trouve dans le package shared ou même dans l'un de ses sous-packages est à la fois partagé par le client et le serveur.

Je vous recommande donc de créer un sous-package rpc dans le package shared pour une bonne séparation du code. C'est dans ce sous-package rpc, que nous allons créer nos deux interfaces : il s'agit de IUserServiceRpc et de IUserServiceRpcAsync.

Ces deux interfaces doivent se trouver dans le même package sinon, une erreur de compilation sera générée.

Commencer par créer l'interface IUserServiceRpc. Voici son contenu :

Interface de service rpc IUserServiceRpc.java

1. @RemoteServiceRelativePath("rpc/userServiceRpc")

2. public interface IUserServiceRpc extends RemoteService {

4. UserDTO findUserByEmailAndPassword(String email, String password) throws Exception;

5. }

On voit que l'implémentation de ce service nous renverra un UserDTO. Nous devons créer un UserDTO pour stocker les données d'un User.

Un DTO est une classe utilitaire qui permet de stocker les données d'une entité afin de les faire transiter d'une couche de l'application à l'autre.

Je vous invite à créer un package dto dans le package shared :

Classe DTO de stockage des données utilisateur UserDTO.java

1. package com.developpez.bnguimgo.dto;

3. public class UserDTO implements Serializable{

5. private static final long serialVersionUID = -443589941665403890L;

7. private Long id;

9. private String mail;

10. private String password;

11. private String userType;

14. public UserDTO() {

15. }

17. public UserDTO(Long id, String email, String password, String userType) {

18. this.id = id;

19. this.mail = email;

20. this.password = password;

21. this.userType = userType;

22. }

24. public Long getId() {

25. return id;

26. }

28. public void setId(Long id) {

29. this.id = id;

30. }

32. public String getMail() {

33. return mail;

34. }

36. public void setMail(String email) {

37. this.mail = email;

38. }

40. public String getPassword() {

41. return password;

42. }

44. public void setPassword(String password) {

45. this.password = password;

46. }

48. public String getUserType() {

49. return userType;

50. }

52. public void setUserType(String userType) {

53. this.userType = userType;

54. }

56. @Override

57. public String toString() {

58. return "UserDTO [id=" + id + ", mail=" + mail + ", password=" + "XXXX"

59. + ", userType=" + userType + "]";

60. }

61. }

Si vous n'avez pas encore créé l'interface IUserServiceRpcAsync, vous aurez une erreur du type : Missing asynchronous interface IUserServiceRpcAsync

Création de l'interface IUserServiceRpcAsync. Voici son contenu :

Interface asynchrone IUserServiceRpcAsync.java

1. public interface IUserServiceRpcAsync {

3. // Remarque, ne pas throws des exceptions dans cette interface,

4. // il faut lever des exceptions plutôt dans les callBacks

5. void findUserByEmailAndPassword(String email, String password, AsyncCallback<UserDTO> asyncCallback);

6. }

Cette interface doit être implémentée côté client. Il ne faut pas lever des exceptions dans cette interface, il est conseillé de le faire plutôt dans les callBacks des appels RPC.

Il nous reste à implémenter nos deux interfaces côté client (IUserServiceRpcAsync) et côté service(IUserServiceRpc).

III-A-2 - Implémentation des interfaces RPC

III-A-2-a - Implémentation RPC côté client

Commençons par implémenter l'interface IUserServiceRpcAsync dans WelcomeActivity.

Pour ce faire, il faut créer une instance du service RPC (IUserServiceRpcAsync) via le deferred binding de GWT. Ce service va donner l'accès à la méthode findUserByEmail(String email, String password, callback).

Ajouter donc la ligne ci-dessous comme attribut de classe dans WelcomeActivity :

Intégration de l'appel asynchrone dans WelcomeActivity.java

private IUserServiceRpcAsync userService = GWT.create(IUserServiceRpc.class);

Puis modifier la méthode findUserByEmail(String email, String password) de WelcomeActivity. La classe WelcomeActivity devient :

Implémentation l'appel asynchrone dans WelcomeActivity.java

1. package com.developpez.bnguimgo.gwt.client.activity;

3. import java.util.logging.Logger;

5. public class WelcomeActivity extends AbstractActivity implements IWelcomeView.Presenter {

7. private static Logger logger = Logger.getLogger(WelcomeActivity.class.getName());

8. private static IWelcomeView welcomeView;

9. private ClientFactory clientFactory;

10. private IUserServiceRpcAsync userService = GWT.create(IUserServiceRpc.class) ;

12. public WelcomeActivity() {

13. super();

14. }

16. public WelcomeActivity(ClientFactory clientFactory){

17. this.clientFactory = clientFactory;//initialisation de la factory

18. }

20. @Override

21. public void start(AcceptsOneWidget panel, EventBus eventBus) {

22. logger.info("Démarrage de "+ WelcomeActivity.class.getName());

23. welcomeView = clientFactory.getWelcomeView();

24. welcomeView.setPresenter(this);

25. panel.setWidget(welcomeView.asWidget());

26. }

28. @Override

29. public void goToCreateComptePlace(String token) {

30. // TODO Auto-generated method stub

32. }

34. @Override

35. public void findUserByEmail(String email, String password) {

36. userService.findUserByEmailAndPassword(email, password, new AsyncCallback<UserDTO>(){

38. @Override

39. public void onSuccess(UserDTO userDTO) {

41. if(null != userDTO){

42. GWT.log("WelcomeActivity, find user success, goTo UserDetailsPlace: "+userDTO.toString());

43. if(userDTO.getUserType().equalsIgnoreCase(UserType.ETUDIANT.name())){

44. welcomeView.showSucces("Interface étudiant en construction : "+userDTO.getUserType());

45. //                        clientFactory.goTo(new StudentPlace("STUDENT"));

46. } else if(userDTO.getUserType().equalsIgnoreCase(UserType.ENSEIGNANT.name())){

47. welcomeView.showSucces("Interface enseignant en construction : "+userDTO.getUserType());

48. //                        clientFactory.goTo(new StudentPlace("STUDENT")); //à Modifier quand EnseignantPlace sera construit

49. } else if(userDTO.getUserType().equalsIgnoreCase("ADMIN")){

50. welcomeView.showSucces("Interface admin en construction : "+userDTO.getUserType());

51. //                        clientFactory.goTo(new AdminPlace("ADMIN"));

52. }else {

53. welcomeView.showError("Erreur technique pour ce compte : "+userDTO.getUserType());

54. }

55. }else{

56. welcomeView.showError("Merci de vérifier votre Login et Mot de Passe");

57. }

58. }

60. @Override

61. public void onFailure(Throwable caught) {

62. welcomeView.showError("Erreur technique pour ce compte : "+caught.getMessage());

63. }

64. });

65. }

66. }

Dans un service asynchrone, il y a toujours les deux méthodes onSucces() et onFailure().

En cas de succès, nous devons afficher la page ADMIN ou la page ETUDIANT, selon le type utilisateur.

En cas d'échec, on affiche un message d'erreur à l'utilisateur pour l'informer qu'il ne peut pas se connecter.

Nous avons mis en commentaires les places puisqu'elles ne sont pas encore construites.

La classe UserType contient des énumérations permettant de distinguer le type d'utilisateur (ETUDIANT, ADMIN, ENSEIGNANT, VISITEUR). Ajoutez un package enumeration dans shared, car on aura besoin du type utilisateur à la fois côté client et côté service.

Classe Enumérant tous types utilisateurs UserType.java

1. package com.developpez.bnguimgo.gwt.shared.enumeration;

3. public enum UserType {

4. ETUDIANT,

5. ENSEIGNANT,

6. ADMIN,

7. VISITEUR;

8. }

III-A-2-b - Implémentation RPC côté service

Il manque à implémenter l'interface IUserServiceRpc côté service. Je vous propose de créer un package rpc dans le package server de GWT pour des besoins d'organisation. Dans ce package nous allons créer la classe UserRpcServiceImpl qui est une implémentation de IUserServiceRpc.

Implémentation RPC côté service de UserRpcServiceImpl.java

1. package com.developpez.bnguimgo.gwt.server.rpc;

3. //Remarque pas de extends RemoteServiceServlet ici, car fourni par le plugin spring4gwt

4. @Service("userServiceRpc")

5. public class UserRpcServiceImpl implements IUserServiceRpc{

7. @Autowired

8. private IUserService userService;

10. @Override

11. public UserDTO findUserByEmailAndPassword(String email, String password) throws Exception {

12. return userService.findUserByEmailAndPassword(email, password);

13. }

14. }

L'annotation @Service("userServiceRpc") ne compile pas encore et c'est normal. Il faut ajouter la dépendance Spring pour que ça compile. Cette annotation permet de déclarer que la classe UserRpcServiceImpl est un bean de service.

Nous avions annoté l'interface IUserServiceRpc pour qu'elle expose les services à travers l'URL : ("rpc/userServiceRpc"). C'est pour cette raison que nous devons annoter @Service("userServiceRpc"). Nous verrons dans la suite comment indiquer depuis le fichier web.xml la façon de mapper toutes les URL pour indiquer où retrouver tous les services.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Par exemple en ajoutant dans web.xml

Il manque beaucoup de choses pour que cette implémentation soit complète et stable. Nous avons injecté dans cette classe l'interface IUserService. Cette interface n'appartient pas du tout à GWT et c'est normal. Elle nous est fournie par Spring à travers l'annotation @Autowired.

L'interface IUserService assure alors la liaison entre GWT et toutes les autres applications non GWT. L'interface de services GWT est assurée par l'interface IUserServiceRpc et l'interface de services non GWT est assurée par IUserService.

Tout ce qui n'est pas GWT doit être créé hors du package GWT. C'est pour éviter les couplages entre les couches, on améliore ainsi la maintenance de l'application.

Nous devons donc créer l'interface IUserService hors des packages GWT (mais ce n'est pas une obligation). Je vous invite à créer un package service dans com.developpez.bnguimgo ;

Le package service contiendra tous les services qui ne sont pas directement liés à GWT.

Interface service hors GWT

1. package com.developpez.bnguimgo.service;

3. public interface IUserService {

5. UserDTO findUserByEmailAndPassword(String email, String password) throws Exception;

6. }

Nous avons ajouté une Exception pour prendre en compte la gestion des erreurs.
Il manque encore des choses pour terminer l'implémentation du service RPC. Comme nous avons choisi d'injecter l'interface IUserService par Spring, il faut intégrer Spring comme dépendance dans le pom.xml de notre projet.

III-A-2-c - Intégration de Spring et Hibernate

L'intégration de Spring qui va aider à injecter les beans, et à découpler aussi les couches.

Hibernate va servir d'ORM pour lier la couche d'accès aux données et la couche de services. Je profite pour vous dire qu'on va intégrer toutes les dépendances nécessaires au projet afin d'éviter des va-et-vient.

Configuration du pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

5. <modelVersion>4.0.0</modelVersion>

6. <groupId>com.developpez.bnguimgo</groupId>

7. <artifactId>gwtSpringHibernate</artifactId>

8. <packaging>war</packaging>

9. <version>1.0.0-SNAPSHOT</version>

10. <name>GWT MVP Tutorial</name>

11. <properties>

12. <gwtVersion>2.7.0</gwtVersion>

13. <springframeworkVersion>4.2.4.RELEASE</springframeworkVersion>

14. <hibernateVersion>5.0.1.Final</hibernateVersion>

15. <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>

16. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

17. </properties>

18. <dependencies>

19. <dependency>

20. <groupId>com.google.gwt</groupId>

21. <artifactId>gwt-servlet</artifactId>

22. <version>${gwtVersion}</version>

23. <scope>runtime</scope>

24. </dependency>

25. <dependency>

26. <groupId>com.google.gwt</groupId>

27. <artifactId>gwt-user</artifactId>

28. <version>${gwtVersion}</version>

29. <scope>provided</scope>

30. </dependency>

32. <!-- Nécessaire pour le superDevMode -->

33. <dependency>

34. <groupId>com.google.gwt</groupId>

35. <artifactId>gwt-codeserver</artifactId>

36. <version>${gwtVersion}</version>

37. </dependency>

38. <dependency>

39. <groupId>com.google.gwt</groupId>

40. <artifactId>gwt-dev</artifactId>

41. <version>${gwtVersion}</version>

42. </dependency>

43. <dependency>

44. <groupId>com.github.gwtbootstrap</groupId>

45. <artifactId>gwt-bootstrap</artifactId>

46. <version>2.3.2.0</version>

47. </dependency>

49. <!-- nécessaire pour la servlet ou service RPC: spring4gwt -->

50. <dependency>

51. <groupId>com.google.code</groupId>

52. <artifactId>spring4gwt</artifactId>

53. <version>0.0.1</version>

54. <!-- install or activate the lines below -->

55. <!--         <scope>system</scope> -->

56. <!--         <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/spring4gwt-0.0.1.jar</systemPath> -->

57. </dependency>

59. <!-- Guava pour la gestion des collections -->

60. <dependency>

61. <groupId>com.google.guava</groupId>

62. <artifactId>guava</artifactId>

63. <version>19.0</version>

64. </dependency>

66. <!-- Spring framework distribution -->

67. <dependency>

68. <groupId>org.springframework</groupId>

69. <artifactId>spring-core</artifactId>

70. <version>${springframeworkVersion}</version>

71. </dependency>

72. <dependency>

73. <groupId>org.springframework</groupId>

74. <artifactId>spring-beans</artifactId>

75. <version>${springframeworkVersion}</version>

76. </dependency>

77. <dependency>

78. <groupId>org.springframework</groupId>

79. <artifactId>spring-context</artifactId>

80. <version>${springframeworkVersion}</version>

81. </dependency>

82. <dependency>

83. <groupId>org.springframework</groupId>

84. <artifactId>spring-tx</artifactId>

85. <version>${springframeworkVersion}</version>

86. </dependency>

87. <dependency>

88. <groupId>org.springframework</groupId>

89. <artifactId>spring-web</artifactId>

90. <version>${springframeworkVersion}</version>

91. </dependency>

92. <dependency>

93. <groupId>org.springframework</groupId>

94. <artifactId>spring-orm</artifactId>

95. <version>${springframeworkVersion}</version>

96. </dependency>

97. <dependency>

98. <groupId>org.springframework</groupId>

99. <artifactId>spring-aspects</artifactId>

100. <version>${springframeworkVersion}</version>

101. </dependency>

102. <dependency>

103. <groupId>org.springframework</groupId>

104. <artifactId>spring-webmvc</artifactId>

105. <version>${springframeworkVersion}</version>

106. </dependency>

107. <dependency>

108. <groupId>org.springframework</groupId>

109. <artifactId>spring-test</artifactId>

110. <version>${springframeworkVersion}</version>

111. </dependency>

112. <dependency>

113. <groupId>org.aspectj</groupId>

114. <artifactId>aspectjtools</artifactId>

115. <version>1.8.7</version>

116. </dependency>

118. <!-- AOP dependency -->

119. <dependency>

120. <groupId>cglib</groupId>

121. <artifactId>cglib</artifactId>

122. <version>3.1</version>

123. <exclusions>

124. <exclusion>

125. <groupId>asm</groupId>

126. <artifactId>asm</artifactId>

127. </exclusion>

128. </exclusions>

129. </dependency>

130. <dependency>

131. <groupId>antlr</groupId>

132. <artifactId>antlr</artifactId>

133. <version>2.7.7</version>

134. </dependency>

136. <!-- Hibernate ORM -->

137. <dependency>

138. <groupId>org.hibernate.javax.persistence</groupId>

139. <artifactId>hibernate-jpa-2.1-api</artifactId>

140. <version>1.0.0.Final</version>

141. </dependency>

142. <dependency>

143. <groupId>org.hibernate</groupId>

144. <artifactId>hibernate-entitymanager</artifactId>

145. <version>${hibernateVersion}</version>

146. </dependency>

147. <dependency>

148. <groupId>org.hibernate</groupId>

149. <artifactId>hibernate-core</artifactId>

150. <version>${hibernateVersion}</version>

151. </dependency>

152. <dependency>

153. <groupId>org.hibernate</groupId>

154. <artifactId>hibernate-validator</artifactId>

155. <version>${hibernateVersion}</version>

156. </dependency>

157. <dependency>

158. <groupId>org.hibernate.common</groupId>

159. <artifactId>hibernate-commons-annotations</artifactId>

160. <version>${hibernateVersion}</version>

161. </dependency>

162. <dependency>

163. <groupId>com.fasterxml</groupId>

164. <artifactId>classmate</artifactId>

165. <version>1.2.0</version>

166. </dependency>

168. <dependency>

169. <groupId>javax.transaction</groupId>

170. <artifactId>jta</artifactId>

171. <version>1.1</version>

172. </dependency>

173. <dependency>

174. <groupId>dom4j</groupId>

175. <artifactId>dom4j</artifactId>

176. <version>1.6.1</version>

177. </dependency>

178. <dependency>

179. <groupId>org.javassist</groupId>

180. <artifactId>javassist</artifactId>

181. <version>3.20.0-GA</version>

182. </dependency>

183. <dependency>

184. <groupId>javax.validation</groupId>

185. <artifactId>validation-api</artifactId>

186. <version>1.1.0.Final</version>

187. </dependency>

188. <dependency>

189. <groupId>javax.el</groupId>

190. <artifactId>javax.el-api</artifactId>

191. <version>3.0.1-b04</version>

192. </dependency>

194. <!-- joda-time for manage local datetime -->

195. <dependency>

196. <groupId>joda-time</groupId>

197. <artifactId>joda-time</artifactId>

198. <version>2.9.1</version>

199. </dependency>

201. <dependency>

202. <groupId>c3p0</groupId>

203. <artifactId>c3p0</artifactId>

204. <version>0.9.1.2</version>

205. </dependency>

207. <!-- Databases Config -->

208. <dependency>

209. <groupId>com.h2database</groupId>

210. <artifactId>h2</artifactId>

211. <version>1.3.168</version>

212. </dependency>

214. <!-- Apache commons-logging -->

215. <dependency>

216. <groupId>commons-logging</groupId>

217. <artifactId>commons-logging</artifactId>

218. <version>1.1.1</version>

219. </dependency>

220. <dependency>

221. <groupId>org.apache.commons</groupId>

222. <artifactId>commons-lang3</artifactId>

223. <version>3.4</version>

224. </dependency>

226. <!-- SLF4J pour la gestion des logs -->

227. <dependency>

228. <groupId>org.slf4j</groupId>

229. <artifactId>slf4j-log4j12</artifactId>

230. <version>1.7.5</version>

231. </dependency>

233. <!-- Test -->

234. <dependency>

235. <groupId>junit</groupId>

236. <artifactId>junit</artifactId>

237. <version>4.7</version>

238. <scope>test</scope>

239. </dependency>

240. <dependency>

241. <groupId>org.ow2.asm</groupId>

242. <artifactId>asm</artifactId>

243. <version>5.0.4</version>

244. </dependency>

246. <!-- Dependance pour le mapper Dozer nécessaire à la création des DTOs -->

247. <dependency>

248. <groupId>net.sf.dozer</groupId>

249. <artifactId>dozer</artifactId>

250. <version>5.5.1</version>

251. </dependency>

252. <dependency>

253. <groupId>commons-beanutils</groupId>

254. <artifactId>commons-beanutils</artifactId> <!-- nécessaire pour faire fonctionner le Dozer -->

255. <version>1.9.2</version>

256. </dependency>

257. </dependencies>

259. <build>

260. <!-- Generate compiled stuff in the folder used for developing mode -->

261. <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>

262. <plugins>

263. <!-- GWT Maven Plugin -->

264. <plugin>

265. <groupId>org.codehaus.mojo</groupId>

266. <artifactId>gwt-maven-plugin</artifactId>

267. <version>${gwtVersion}</version>

268. <dependencies>

269. <dependency>

270. <groupId>org.codehaus.plexus</groupId>

271. <artifactId>plexus-utils</artifactId>

272. <version>3.0.22</version>

273. </dependency>

274. <dependency>

275. <groupId>org.codehaus.plexus</groupId>

276. <artifactId>plexus-compiler-api</artifactId>

277. <version>1.6</version>

278. </dependency>

279. <dependency>

280. <groupId>commons-io</groupId>

281. <artifactId>commons-io</artifactId>

282. <version>2.4</version>

283. </dependency>

284. </dependencies>

285. </plugin>

287. <!-- Copy static web files before executing gwt:run -->

288. <plugin>

289. <groupId>org.apache.maven.plugins</groupId>

290. <artifactId>maven-war-plugin</artifactId>

291. <version>2.1.1</version>

292. <configuration>

293. <webappDirectory>${webappDirectory}</webappDirectory>

294. </configuration>

295. </plugin>

296. <plugin>

297. <groupId>org.apache.maven.plugins</groupId>

298. <artifactId>maven-compiler-plugin</artifactId>

299. <version>2.3.2</version>

300. <configuration>

301. <source>1.7</source>

302. <target>1.7</target>

303. </configuration>

304. </plugin>

305. </plugins>

306. </build>

307. </project>

Quelques remarques sur le pom.xml

- La dépendance spring4gwt va permettre de mapper tous les appels RPC à travers la classe SpringGwtRemoteServiceServlet. On n'aura plus besoin d'étendre à chaque fois la classe RemoteServiceServlet lorsqu'on implémente un service RPC.

- Nous avons aussi ajouté une dépendance dozer qui va permettre de créer les DTOs pour le transfert des données entre les couches de l'application.

- La configuration et le mapping de la servlet springGwtRemoteServiceServlet se font dans le web.xml afin de rendre la servlet disponible au démarrage de l'application. Voir le contenu du fichier web.xml ci-dessous.

- Il faut à présent déclarer dans le fichier web.xml que c'est Spring qui fournit le Listener et que le mapping de nos servlets RPC est assuré par : springGwtRemoteServiceServlet.

Fichier web.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <!DOCTYPE web-app

3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

4. "http://java.sun.com/dtd/web-app_2_3.dtd">

6. <web-app>

8. <listener>

9. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

10. </listener>

12. <context-param>

13. <param-name>contextConfigLocation</param-name>

14. <param-value>classpath:spring/applicationContext.xml</param-value>

15. </context-param>

17. <!-- SpringGwt remote service servlet -->

18. <servlet>

19. <servlet-name>springGwtRemoteServiceServlet</servlet-name>

20. <servlet-class>org.spring4gwt.server.SpringGwtRemoteServiceServlet</servlet-class>

21. <load-on-startup>1</load-on-startup>

22. </servlet>

23. <servlet-mapping>

24. <servlet-name>springGwtRemoteServiceServlet</servlet-name>

25. <url-pattern>/BookManager/rpc/*</url-pattern>

26. </servlet-mapping>

28. <!-- Default page to serve -->

29. <welcome-file-list>

30. <welcome-file>BookManager.html</welcome-file>

31. </welcome-file-list>

32. </web-app>

Quelques explications :

La déclaration du listener ContextLoaderListener dans web.xml oblige Spring à inspecter le paramètre contextConfigLocation pour chercher où se trouve le fichier de configuration des beans applicationContext.xml.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Notez au passage cette configuration :

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Cette configuration indique que tous les services appelés par l'URL /BookManager/rpc/* seront interceptés et traités par le framework SpringGwtRemoteServiceServlet, nos services sont bien dans le package /rpc/* donc pas de soucis à se faire.

Afin de pouvoir indiquer dans quels packages se trouvent les beans annotés, il faut ajouter le fichier de configuration applicationContext.xml de Spring et dire à Spring quels sont les packages à scanner. Je vous invite donc à créer un dossier spring comme sous-dossier de resources et y ajouter le fichier de configuration des beans applicationContext.xml.

Ajouter ce fichier dans le répertoire : /src/main/resources/spring

Contenu du fichier applicationContext.xml

1. <beans xmlns="http://www.springframework.org/schema/beans"

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

3. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"

4. xmlns:tx="http://www.springframework.org/schema/tx"

5. xsi:schemaLocation="

6. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd

7. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd

8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd

9. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

11. <context:annotation-config />

12. <tx:annotation-driven />

13. <context:component-scan base-package="com.developpez.bnguimgo" />

14. <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">

15. <property name="persistenceUnitName" value="entityManagerUnit" />

16. </bean>

17. <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >

18. <property name="entityManagerFactory" ref="entityManagerFactory" />

19. </bean>

21. <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

22. <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />

23. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

25. <!-- Bean de mapping pour les DTOs -->

26. <bean id="org.dozer.mapper" class="org.dozer.DozerBeanMapper">

27. <property name="mappingFiles">

28. <list>

29. <value>dozer-mapping.xml</value>

30. </list>

31. </property>

32. </bean>

33. </beans>

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Le bean entityManagerFactory sera injecté par Spring localement (et non par un serveur d'application).
Ce bean est nécessaire pour obtenir une instance d'EntityManager pour des requêtes en base de données.
Le bean transactionManager permettra de gérer les transactions.
La valeur entityManagerUnit est fournie par le persistence-unit présent dans le fichier de configuration de la base de données persistence.xml.

Vous pouvez vérifier maintenant que les annotations @Service("userServiceRpc") et @Autowired fonctionnent correctement grâce aux dépendances de Spring que vous devez importer.

L'implémentation des services RPC est terminée.
Nous reviendrons sur le traitement de la réponse plus tard (après avoir fini la couche DAO).

III-B - Implémentation des services

Nous allons ici implémenter tous les services dont nous avons besoin pour notre application sans nous soucier si notre application est développée en GWT. Rien à voir avec le MVP, ni GWT. C'est au moment de consommer ces services que nous allons à nouveau avoir besoin de GWT dans sa partie RPC. C'est donc pour cette raison que nous avons créé l'interface IUserService en dehors des packages GWT. Les services qui seront fournis ici peuvent être consommés par une interface client en GWT, SpringMVC, Struts, JSP, JSF, etc. Il faudra juste changer l'implémentation de l'interface IUserServiceRpc par toute autre interface si on n'est pas en GWT. Mais dans notre cas, on restera en GWT.

Les services peuvent être séparés en deux couches :

1. Une couche de services proprement dite souvent appelée couche de business.

2. Une couche d'accès aux données ou DAO.

Pour implémenter les services, je propose qu'on commence par la couche d'accès aux données, car cette approche est plus rapide et plus facile à mettre en place.

III-B-1 - Configuration de la base de données

Nous allons utiliser une base de données intégrée H2 (HSQL). Et puisque nous allons faire nos requêtes en mode objet grâce à Hibernate, il faut donc configurer le provider en conséquence.

Pour la configuration de la base de données, je vous propose de créer un dossier nommé META-INF dans /src/main/resources/. Dans ce dossier nous allons y mettre le fichier de configuration de la base de données persistence.xml ci-dessous :

Fichier de configuration de la base de données persistence.xml

1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"

4. version="2.0">

5. <persistence-unit name="entityManagerUnit" transaction-type="RESOURCE_LOCAL">

7. <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

8. <properties>

9. <property name="hibernate.hbm2ddl.auto" value="create-update" />

10. <property name="hibernate.show_sql" value="true" />

11. <!--  Fichiers d'alimentation de la base de données au démarrage de l'application -->

12. <property name="hibernate.hbm2ddl.import_files" value="/import.sql" />

14. <!-- H2 DB Base de données intégrées -->

15. <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />

16. <property name="hibernate.connection.driver_class" value="org.h2.Driver" />

17. <property name="hibernate.connection.url" value="jdbc:h2:~/gwtSpringHibernate" />

18. <property name="hibernate.connection.username" value="sa" />

19. <property name="hibernate.connection.password" value="" />

20. <property name="hibernate.c3p0.min_size" value="5" />

21. <property name="hibernate.c3p0.max_size" value="20" />

22. <property name="hibernate.c3p0.timeout" value="300" />

23. <property name="hibernate.c3p0.max_statements" value="50" />

24. <property name="hibernate.c3p0.idle_test_period" value="3000" />

25. </properties>

26. </persistence-unit>

27. </persistence>

Remarque-1

L'attribut name="entityManagerUnit" correspond à la valeur de la propriété name="persistenceUnitName" du bean id="entityManagerUnit" déclaré dans fichier applicationContext.xml. Ce qui permet de lier les deux fichiers.

Remarque-2

La déclaration : permet de dire que nous allons utiliser Hibernate comme provider pour la persistance.

Remarque-3

La configuration permet de fournir un fichier sql à Hibernate afin d'initialiser si possible la base de données au moment du déploiement de l'application. Dans notre cas, cette déclaration va nous permettre d'initialiser les coordonnées d'un administrateur ou d'un simple utilisateur.

Il faut donc créer un fichier import.sql dans le dossier resources. Voici le contenu du fichier import.sql :

Fichier d'initialisation de la base de données à mettre dans le dossier resources import.sql

--INITIALISATION TABLE D'ADMINISTRATION
INSERT INTO USER_LOGIN(USER_ID, USER_MAIL, USER_PASSWORD, USER_TYPE) values(1,'admin@admin.com','admin', 'ADMIN');

--INITIALISATION TABLE UTILISATEURS
INSERT INTO USER_LOGIN(USER_ID, USER_MAIL, USER_PASSWORD, USER_TYPE) values (2, 'user@user.com', 'user', 'ETUDIANT');
COMMIT;

Pour toute initialisation de la base de données avec d'autres tables, il faut mettre à jour ce fichier.

Nos tables seront créées automatiquement par Hibernate grâce aux annotations sur chaque entité.

III-B-2 - Création des beans Entités

Une entité est une classe qui, selon la configuration, peut être transformée en une table dans une base de données. Ses propriétés ou attributs deviennent alors des colonnes.
Il nous faut créer une entité User pour stocker et récupérer après les données utilisateurs. Pour ce faire, créer un nouveau package entities dans le package com.developpez.bnguimgo et ajouter l'entité ci-dessous :

entité User.java

1. package com.developpez.bnguimgo.entities;

3. import java.io.Serializable;

5. import javax.persistence.Column;

6. import javax.persistence.Entity;

7. import javax.persistence.GeneratedValue;

8. import javax.persistence.GenerationType;

9. import javax.persistence.Id;

10. import javax.persistence.Table;

12. import com.developpez.bnguimgo.shared.dto.UserDTO;

14. @Entity

15. @Table(name = "USER_LOGIN")

16. public class User implements Serializable{

17. private static final long serialVersionUID = -443589941665403890L;

19. @Id

20. @GeneratedValue(strategy = GenerationType.AUTO)

21. @Column(name = "USER_ID")

22. private Long id;

24. @Column(name = "USER_MAIL", unique=true, insertable=true, updatable=true, nullable=false)

25. private String mail;

27. @Column(name = "USER_PASSWORD", insertable=true, updatable=true, nullable=false)

28. private String password;

30. @Column(name = "USER_TYPE", insertable=true, updatable=true, nullable=false)

31. private String userType;

33. public User() {

34. super();

35. }

37. public User(String email, String password, String userType) {

38. this.mail = email;

39. this.password = password;

40. this.userType = userType;

41. }

43. public User(Long id, String email) {

44. this.id = id;

45. this.mail = email;

46. }

48. public User(UserDTO userDTO) {

49. this.setId(userDTO.getId());

50. this.setMail(userDTO.getMail());

51. this.setPassword(userDTO.getPassword());

52. }

54. public Long getId() {

55. return id;

56. }

58. public void setId(Long id) {

59. this.id = id;

60. }

62. public String getMail() {

63. return mail;

64. }

66. public void setMail(String email) {

67. this.mail = email;

68. }

70. public String getPassword() {

71. return password;

72. }

74. public void setPassword(String password) {

75. this.password = password;

76. }

78. public String getUserType() {

79. return userType;

80. }

82. public void setUserType(String userType) {

83. this.userType = userType;

84. }

86. @Override

87. public String toString() {

88. return "User [id=" + id + ", mail=" + mail + ", password=" + "XXXX"

89. + ", userType=" + userType + "]";

90. }

91. }

La table qui sera créée à base de l'entité User aura le nom USER_LOGIN.

Plusieurs constructeurs ont été ajoutés et seront utilisés en fonction de chaque besoin.

III-B-3 - Création de la couche DAO

La couche DAO est la couche qui implémente l'accès à la base de données. Nous allons utiliser la généricité pour la plupart des requêtes communes. Les requêtes personnalisées seront implémentées par chaque service.

III-B-3-a - Mise en place de la généricité

La généricité consiste à créer et implémenter une interface générique afin de fournir une interface commune aux requêtes de mêmes types. Le type final sera alors spécifié par chaque classe qui étend la classe générique.
Il faut créer un package dao dans com.developpez.bnguimgo et y ajouter l'interface générique IGenericDAO :

Interface générique d'accès à la base de données IGenericDAO.java

1. package com.developpez.bnguimgo.dao;

2. /**

3. * @param <E> entité générique qui doit être spécifiée lors de l'utilisation concrète

4. * @param <PK> Clé générique, le type doit être spécifié lors de l'usage

5. */

6. public interface IGenericDAO<E, PK extends Serializable> {

8. /**

9. * Cherche un User par email et password

10. * @param email : Email du User

11. * @param password : mot de passe du User

12. * @return E : Renvoie un User

13. */

14. E findUserByEmailAndPassword(String email, String password);

16. }

Vous pouvez ajouter d'autres méthodes, mais pour l'instant, nous voulons chercher un utilisateur en lui passant un login et un mot de passe.

III-B-3-b - Implémentation de l'interface générique

Nous allons implémenter l'interface IGenericDAO et la déclarer comme un objet d'accès à la base de données grâce à l'annotation Spring @Repository("genericDAO"). Créer un package impl dans le package dao ci-dessus. Il faut y ajouter la classe GenericDAOImpl :

Implémentation de l'interface générique GenericDAOImpl.java

1. package com.developpez.bnguimgo.dao.impl;

3. @Repository("genericDAO")

4. public abstract class GenericDAOImpl<E, PK extends Serializable> implements IGenericDAO<E, PK> {

6. @Transient

7. protected Class<E> entityClass;

9. public GenericDAOImpl() {

10. super();

11. }

13. public GenericDAOImpl(Class<E> entityClass) {

14. this.entityClass = entityClass;

15. }

17. public Class<E> getEntityClass() {

18. return entityClass;

19. }

21. public void setEntityClass(Class<E> entityClass) {

22. this.entityClass = entityClass;

23. }

25. @Override

26. public E findUserByEmailAndPassword(String email, String password ) {

28. String queryString = "SELECT user FROM " + entityClass.getName() + " user WHERE user.mail LIKE :emailParam AND user.password LIKE :passwordParam";

29. TypedQuery<E> typedQuery = getEntityManager().createQuery(queryString, entityClass);

30. typedQuery.setParameter("emailParam", email);

31. typedQuery.setParameter("passwordParam", password);

32. List<E> resultList = typedQuery.getResultList();

33. if(resultList !=null && !resultList.isEmpty()){

34. return resultList.get(0);

35. }

36. return null;

37. }

39. /**

40. * Permet d'obtenir le EntityManager pour les requêtes en base de données

41. * @return EntityManager : Instance de EntityManager pour les requêtes en base de données

42. */

43. protected abstract EntityManager getEntityManager();

45. }

Quelques explications s'imposent :

L'annotation @Transient permet de ne pas persister l'attribut entityClass, car le type n'est pas connu (générique).
L'annotation @Repository("genericDAO") permet de déclarer genericDAO comme un bean d'accès à la base de données comparativement à un bean de service qui est annoté avec @Service.

Cette classe est abstraite, et impose donc que chaque implémentation fournisse elle-même l'instance EntityManager grâce à la méthode abstraite getEntityManager().

Enfin le plus important est l'implémentation finale de la méthode findUserByEmailAndPassword(String email, String password). Dans cette méthode, nous avons écrit une requête en mode Objet. De plus, notre requête est typée (grâce à TypedQuery) et paramétrée (emailParam, passwordParam).

TypedQuery ne marche qu'avec des requêtes de sélection (SELECT). Pour des requêtes de mise à jour (UPDATE) ou d'insertion (INSERT), utiliser plutôt Query.

III-B-3-c - Utilisation de la classe générique

Nous allons utiliser la classe générique pour développer la plupart des services d'accès à la base de données.

Créer l'interface IUserDAO qui est une extension de IGenericDAO. Créer dans le même package que IGenericDAO :

Interface IUserDAO.java

1. package com.developpez.bnguimgo.dao;

3. public interface IUserDAO extends IGenericDAO<User, Long> {

4. //Grâce à l'héritage, pas besoin d'une méthode dans cette interface

5. //Seules les méthodes propres à un User peuvent être ajoutées ici

6. }

Cette interface n'a aucune méthode, car par héritage nous aurons accès à findUserByEmailAndPassword(String email, String password) déjà implémentée par l'interface générique.

Grâce à la généricité, l'interface IUserDAO n'a aucune méthode à déclarer. Mais rien ne nous empêche d'ajouter des méthodes spécifiques au User.

Implémentation de l'interface IUserDAO

Créer dans le même package que GenericDAOImpl la classe UserDAOImpl qui est une implémentation de IUserDAO. Voici le code :

Implémentation UserDAOImpl.java

1. package com.developpez.bnguimgo.dao.impl;

3. @Repository("userDAO")

4. public class UserDAOImpl extends GenericDAOImpl<User, Long> implements IUserDAO{

6. @PersistenceContext(unitName = "entityManagerUnit")

7. protected EntityManager entityManager;

9. public UserDAOImpl() {

10. super(User.class); //Très important sinon, NullPointerException

11. }

13. public UserDAOImpl(Class<User> entityClass) {

14. super(entityClass);

15. }

17. @Override

18. protected EntityManager getEntityManager() {

19. return entityManager;

20. }

21. }

Notez ici l'injection du bean entityManager grâce à Spring. La méthode getEntityManager() permet de récupérer cette instance pour la manipulation d'un User.

Dans le constructeur UserDAOImpl(Class entityClass), la déclaration super(User.class), est obligatoire, sinon on aura un NullPointerException au runtime.

Le développement de la couche DAO est terminé. Passons maintenant à la couche de services ou couche de business.

III-B-4 - Création de la couche de services

Avant de développer la couche de services, pensons au stockage des données. En fait, il est conseillé de ne pas manipuler directement les entités. Mais plutôt de manipuler une image ou une copie de l'entité : c'est ce qu'on appelle un DTO. Un DTO a pour rôle de stocker tout ou une partie des données issues d'une entité afin de faire transiter ces données d'une couche à l'autre.

III-B-4-a - Création du mapper DTO

L'objectif ici est de transformer un User en UserDTO. Créer un package mapper dans com.developpez.bnguimgo et y ajouter la classe nouvelle classe DTOMapper suivante :

Transformation d'un User en UserDTO par le mapper DTOMapper.java

1. package com.developpez.bnguimgo.mapper;

3. public final class DTOMapper {

5. public static UserDTO mapToUserDTO(User user){

6. if(user !=null){

7. return new UserDTO(user.getId(), user.getMail(), user.getPassword(), user.getUserType());

8. }

9. return null;

10. }

11. }

Ci-dessous, nous verrons comment mettre en place le framework dozer pour mapper une entité en son DTO correspondant. Pour ce faire, je vais utiliser le framework dozer, et l'injecter grâce à Spring.

III-B-4-b - Mise en place du mapper Dozer

Le framework dozer permet de transformer une entité en son bean DTO correspondant. On peut décider lors de la configuration de ne récupérer qu'une partie de l'objet.
Contenu de dozer-mapping.xml, à mettre dans src/main/resources.

Configuration de dozer-mapping.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <mappings xmlns="http://dozer.sourceforge.net"

3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

4. xsi:schemaLocation="http://dozer.sourceforge.net

5. http://dozer.sourceforge.net/schema/beanmapping.xsd">

6. <mapping>

7. <class-a>com.developpez.bnguimgo.entities.User</class-a>

8. <class-b>com.developpez.bnguimgo.dto.UserDTO</class-b>

9. </mapping>

10. </mappings>

Ajouter la configuration ci-dessous dans applicationContext.xml si ce n'est déjà fait :

Ajout de la configuration dozer dans applicationContext.xml

<!-- Bean de mapping pour les DTOs -->
<bean id="org.dozer.mapper" class="org.dozer.DozerBeanMapper">
<property name="mappingFiles">
<list> <value>dozer-mapping.xml</value> </list>
</property>
</bean>

III-B-4-c - Implémentation des services

Nous allons enfin implémenter l'interface IUserService pour retourner les données utilisateurs depuis la base de données. Je vous propose de créer le package impl dans le package service de com.developpez.bnguimgo. Dans ce package, ajouter la classe UserServiceImpl avec l'implémentation suivante :

Implémentation du service UserServiceImpl.java

1. package com.developpez.bnguimgo.service.impl;

3. @Service("userService")

4. public class UserServiceImpl implements IUserService {

6. private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);

7. @Autowired

8. private IUserDAO userDAO;

9. @Autowired

10. private Mapper dozerMapper;

12. public UserServiceImpl(){

13. super();

14. }

15. @Override

16. public UserDTO findUserByEmailAndPassword(String email, String password) throws Exception {

17. User user = null;

18. try {

19. user = userDAO.findUserByEmailAndPassword(email, password);

20. if(null != user){

21. LOG.info("User found: {} ", user.toString());

22. //mapper manuel à commenter si utilisation de dozer

23. UserDTO userDTO = DTOMapper.mapToUserDTO(user);

24. //mapper dozer fourni par spring

25. //            UserDTO userDTO = dozerMapper.map(userDTO, UserDTO.class);

26. return userDTO;

27. } else{

28. LOG.error("Failed to find user with email ={} and paswword = {} ", email, password);

29. return null;

30. }

31. } catch(Exception e) {

32. LOG.error("Failed to find user with email ={} and paswword = {} ", email, password);

33. LOG.error("Failed to find user Or Update: ",e);

34. throw e;

35. }

36. }

37. }

L'annotation @Service("userService") permet de spécifier que c'est un bean de service et de rendre ce bean disponible au niveau des services. Les données seront stockées dans un DTO (UserDTO). Nous avons utilisé un DTO manuel développé par nous-mêmes (DTOMapper).

Vous devez choisir d'utiliser un DTO développé par vos soins, ou un DTO fourni par le framework Dozer. Si vous choisissez d'utiliser un DTO manuel, l'annotation @Autowired private Mapper dozerMapper; ne sert plus à rien. J'ai constaté lors de l'exécution que l'utilisation du dozer était lente.

Nous venons de terminer l'implémentation de la couche de services. Il faut rattacher maintenant la couche de services à GWT.
Nous verrons prochainement comment mettre en place les transactions dans la couche de services pour certains types de requêtes.

III-B-4-d - Liaison entre GWT et la couche de services

Voici un schéma qui montre comment rattacher la couche GWT avec la couche de services grâce à l'interface IUservice :

Schéma représentant le lien entre la couche gwt et la couche de services

III-B-4-e - Intégration de log4j

J'ai mis à disposition un fichier log4j.xml pour la gestion des logs à mettre dans /src/main/resources/ dont voici le contenu :

Fichier de logs à intégrer dans le répertoire resources log4j.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

3. <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">

5. <!-- levels : all|debug|info|warn|error|fatal|off|trace -->

7. <appender name="console" class="org.apache.log4j.ConsoleAppender">

8. <layout class="org.apache.log4j.PatternLayout">

9. <param name="ConversionPattern"

10. value="%X{codeapp} %d{yyyy-MM-dd HH:mm:ss} %-5p [%c{1}] : %m%n" />

11. </layout>

12. </appender>

14. <appender name="file-out" class="org.apache.log4j.RollingFileAppender">

15. <param name="file" value="gwtmvpproject.log" />

16. <param name="append" value="true" />

17. <param name="MaxFileSize" value="10MB" />

18. <param name="MaxBackupIndex" value="10" />

19. <layout class="org.apache.log4j.PatternLayout">

20. <param name="ConversionPattern"

21. value="[%X{idreq} %X{iduser} %X{codeenv}] %d{yyyy-MM-dd HH:mm:ss} %-5p [%c{1}] : %m%n" />

22. </layout>

23. </appender>

25. <logger name="org.springframework.web.context" additivity="false">

26. <level value="INFO" />

27. <appender-ref ref="console" />

28. </logger>

30. <logger name="com.developpez.bnguimgo" additivity="false">

31. <level value="INFO" />

32. <appender-ref ref="console" />

33. <appender-ref ref="file-out" />

34. </logger>

36. <root>

37. <priority value="info" />

38. <appender-ref ref="console" />

39. <appender-ref ref="file-out" />

40. </root>

41. </log4j:configuration>

III-B-5 - Test de l'application

III-B-5-a - Arborescence finale

Avant de commencer les tests, voici un premier aperçu des packages créés.

Structure du projet :

Structure globale des packages du projet

III-B-5-b - Test connexion avec erreurs

Entrez n'importe quels mail et mot de passe, puis cliquez sur connexion. Vous obtenez l'erreur suivante :

Test connexion avec Erreur

Le bouton CREATION COMPTE n'est pas encore implémenté. Seul le bouton CONNEXION réagit au clic.

III-B-5-c - Test connexion avec succès

Pour tester l'application, rappelez-vous que nous avons initialisé dans le fichier import.sql deux utilisateurs : un utilisateur (user) et un administrateur (admin). Nous allons utiliser ces informations pour tester en attendant de créer les utilisateurs directement depuis l'IHM.

Renseignez le champ Mail avec : admin@admin.com
Et le champ Pass avec : admin

Ou encore pour un simple utilisateur
Mail : user@user.com
Pass : user

Résultat :

Test connexion avec succès

Vous constatez que les styles sont bien appliqués.
Nous venons ainsi d'achever la deuxième partie. Nous avons appris à mettre en place un service RPC, à développer la couche de services ou couche de business, à mettre en place la couche d'accès aux données avec le mapping Hibernate et à lier ces couches les unes aux autres.

Il nous faut maintenant construire les pages à afficher pour la création des comptes utilisateurs, et aussi la page d'administration si le login et mot de passe sont corrects.

La création des pages d'inscription et d'administration fera l'objet d'une troisième partie de ce tutoriel.

Pour ceux et celles qui ont bien suivi ce tutoriel, vous pouvez déjà créer ces pages en vous inspirant de notre démarche de Création de la page d'accueil

Les sources complets pour ces deux premières parties sont disponibles ici.

La troisième partie consacrée aux développements des pages d'inscriptions et d'administration est en cours d'écriture.

IV - Conclusion

Conclusion

J'ai voulu à travers cet article mettre en place une structure complète et solide qui peut être utilisée comme socle de départ d'un projet de développement d'une application basée sur JAVA/JEE. Les couches sont bien séparées pour mieux isoler leurs différentes implémentations. Par exemple, la couche GWT est bien séparée de la couche de business ou couche de services. La couche de services elle-même bien séparée de la couche d'accès aux données. J'ai aussi essayé de mettre en place de la meilleure des façons possibles les recommandations d'implémentation du patron de conception-MVP de GWT basée sur les notions de Activity and Place.

IV-A - Références et remerciements

IV-A-1 - Remerciements

Je tiens à remercier Mickael Baron qui m'a encouragé sans hésitation à écrire ce tutoriel dès que j'ai exprimé une première intention.

La correction orthographique a été entièrement assurée par Fabien LE BRIS à qui j'adresse mes remerciements.

Je remercie également tous les membres du club qui ont mis en place des outils d'édition sans lesquels rien n'aurait été facile pour la rédaction de cet article. Je n'oublie pas non plus les guides à la rédaction des articles. Ces tutos ont été très encourageants pour acquérir une autonomie dans la rédaction d'un article sur www.developpez.com.

IV-A-2 - Références

Dans le cadre de la rédaction de ce tutoriel, j'ai consulté plusieurs articles dont voici quelques références :

Premier projet GWT

- https://moritan.developpez.com/tutoriels/java/gwt/premier/projet

Guide de mise en place du Super Dev Mode et du Plugin SDBG

- http://blog.lteconsulting.fr/2014/09/04/super-dev-mode.html

Design Pattern MVP (Activity and Places)

- http://www.gwtproject.org/doc/latest/DevGuideMvpActivitiesAndPlaces.html

- http://memotech.blogspot.fr/2013/05/architecture-mvp-model-view-presenter.html

Mise en place de la générécité et du framework spring4gwt pour intercepter les appels RPC

- http://www.javacodegeeks.com/2010/07/gwt-2-spring-3-jpa-2-hibernate-35.html

Mise en place des DTOs avec ou sans framework dozer

- http://www.gwtproject.org/articles/using_gwt_with_hibernate.html

Exemple d'un projet complet gwt-spring-Hibernate

- https://hugo.developpez.com/tutoriels/java/gwt/utilisation-gwt-avec-spring-et-hibernate/

- https://chieukam.wordpress.com/2012/08/02/gwt-spring-hibernate-maven-2-example/