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, 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.
- 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 coté 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. A 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 :
2.
3.
4.
5.
6.
@
RemoteServiceRelativePath
(
"
rpc/userServiceRpc
"
)
public
interface
IUserServiceRpc extends
RemoteService {
UserDTO findUserByEmailAndPassword
(
String email, String password) throws
Exception;
}
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:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
package
com.developpez.bnguimgo.dto;
public
class
UserDTO implements
Serializable{
private
static
final
long
serialVersionUID =
-
443589941665403890L
;
private
Long id;
private
String mail;
private
String password;
private
String userType;
public
UserDTO
(
) {
}
public
UserDTO
(
Long id, String email, String password, String userType) {
this
.id =
id;
this
.mail =
email;
this
.password =
password;
this
.userType =
userType;
}
public
Long getId
(
) {
return
id;
}
public
void
setId
(
Long id) {
this
.id =
id;
}
public
String getMail
(
) {
return
mail;
}
public
void
setMail
(
String email) {
this
.mail =
email;
}
public
String getPassword
(
) {
return
password;
}
public
void
setPassword
(
String password) {
this
.password =
password;
}
public
String getUserType
(
) {
return
userType;
}
public
void
setUserType
(
String userType) {
this
.userType =
userType;
}
@
Override
public
String toString
(
) {
return
"
UserDTO
[id=
"
+
id +
"
,
mail=
"
+
mail +
"
,
password=
"
+
"
XXXX
"
+
"
,
userType=
"
+
userType +
"
]
"
;
}
}
Si vous n'avez pas encore crée l'interface IUserServiceRpcAsync, vous aurez une erreur du type : Missing asynchronous interface IUserServiceRpcAsync
Création de l'interface IUserServiceRpcAsync. Voici son contenu :
2.
3.
4.
5.
6.
7.
public
interface
IUserServiceRpcAsync {
//
Remarque,
ne
pas
throws
des
exceptions
dans
cette
interface,
//
il
lever
des
exceptions
plutôt
dans
les
callBacks
void
findUserByEmailAndPassword
(
String email, String password, AsyncCallback<
UserDTO>
asyncCallback);
}
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-1. 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 :
private
IUserServiceRpcAsync userService =
GWT.create
(
IUserServiceRpc.class
);
Puis modifier la méthode findUserByEmail(String email, String password) de WelcomeActivity. La classe WelcomeActivity devient :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
package
com.developpez.bnguimgo.gwt.client.activity;
import
java.util.logging.Logger;
public
class
WelcomeActivity extends
AbstractActivity implements
IWelcomeView.Presenter {
private
static
Logger logger =
Logger.getLogger
(
WelcomeActivity.class
.getName
(
));
private
static
IWelcomeView welcomeView;
private
ClientFactory clientFactory;
private
IUserServiceRpcAsync userService =
GWT.create
(
IUserServiceRpc.class
) ;
public
WelcomeActivity
(
) {
super
(
);
}
public
WelcomeActivity
(
ClientFactory clientFactory){
this
.clientFactory =
clientFactory;//
initialisation
de
la
factory
}
@
Override
public
void
start
(
AcceptsOneWidget panel, EventBus eventBus) {
logger.info
(
"
Démarrage
de
"
+
WelcomeActivity.class
.getName
(
));
welcomeView =
clientFactory.getWelcomeView
(
);
welcomeView.setPresenter
(
this
);
panel.setWidget
(
welcomeView.asWidget
(
));
}
@
Override
public
void
goToCreateComptePlace
(
String token) {
//
TODO
Auto-generated
method
stub
}
@
Override
public
void
findUserByEmail
(
String email, String password) {
userService.findUserByEmailAndPassword
(
email, password, new
AsyncCallback<
UserDTO>
(
){
@
Override
public
void
onSuccess
(
UserDTO userDTO) {
if
(
null
!
=
userDTO){
GWT.log
(
"
WelcomeActivity,
find
user
success,
goTo
UserDetailsPlace:
"
+
userDTO.toString
(
));
if
(
userDTO.getUserType
(
).equalsIgnoreCase
(
UserType.ETUDIANT.name
(
))){
welcomeView.showSucces
(
"
Interface
étudiant
en
construction
:
"
+
userDTO.getUserType
(
));
//
clientFactory.goTo(new
StudentPlace("STUDENT"));
}
else
if
(
userDTO.getUserType
(
).equalsIgnoreCase
(
UserType.ENSEIGNANT.name
(
))){
welcomeView.showSucces
(
"
Interface
enseignant
en
construction
:
"
+
userDTO.getUserType
(
));
//
clientFactory.goTo(new
StudentPlace("STUDENT"));
//à
Modifier
quand
EnseignantPlace
sera
construit
}
else
if
(
userDTO.getUserType
(
).equalsIgnoreCase
(
"
ADMIN
"
)){
welcomeView.showSucces
(
"
Interface
admin
en
construction
:
"
+
userDTO.getUserType
(
));
//
clientFactory.goTo(new
AdminPlace("ADMIN"));
}
else
{
welcomeView.showError
(
"
Erreur
technique
pour
ce
compte
:
"
+
userDTO.getUserType
(
));
}
}
else
{
welcomeView.showError
(
"
Merci
de
vérifier
votre
Login
et
Mot
de
Passe
"
);
}
}
@
Override
public
void
onFailure
(
Throwable caught) {
welcomeView.showError
(
"
Erreur
technique
pour
ce
compte
:
"
+
caught.getMessage
(
));
}
}
);
}
}
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 besion du type utilisateur à la fois côté client et côté service.
2.
3.
4.
5.
6.
7.
8.
9.
package
com.developpez.bnguimgo.gwt.shared.enumeration;
public
enum
UserType {
ETUDIANT,
ENSEIGNANT,
ADMIN,
VISITEUR;
}
III-A-2-2. 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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
package
com.developpez.bnguimgo.gwt.server.rpc;
//
Remarque
pas
de
extends
RemoteServiceServlet
ici,
car
fourni
par
le
plugin
spring4gwt
@
Service
(
"
userServiceRpc
"
)
public
class
UserRpcServiceImpl implements
IUserServiceRpc{
@
Autowired
private
IUserService userService;
@
Override
public
UserDTO findUserByEmailAndPassword
(
String email, String password) throws
Exception {
return
userService.findUserByEmailAndPassword
(
email, password);
}
}
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 comment mapper toutes les URLs pour indiquer comment retrouver tous les services.
- <url-pattern>/BookManager/rpc/*</url-pattern>
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 assurée par IUserService.
Tout ce qui n'est pas GWT doit être crée 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
2.
3.
4.
5.
6.
7.
package
com.developpez.bnguimgo.service;
public
interface
IUserService {
UserDTO findUserByEmailAndPassword
(
String email, String password) throws
Exception;
}
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-3. 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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<
project
xmlns
=
"
http://maven.apache.org/POM/4.0.0
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xsi
:
schemaLocation
=
"
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd
"
>
<
modelVersion
>
4.0.0<
/
modelVersion
>
<
groupId
>
com.developpez.bnguimgo<
/
groupId
>
<
artifactId
>
gwtSpringHibernate<
/
artifactId
>
<
packaging
>
war<
/
packaging
>
<
version
>
1.0.0-SNAPSHOT<
/
version
>
<
name
>
GWT MVP Tutorial<
/
name
>
<
properties
>
<
gwtVersion
>
2.7.0<
/
gwtVersion
>
<
springframeworkVersion
>
4.2.4.RELEASE<
/
springframeworkVersion
>
<
hibernateVersion
>
5.0.1.Final<
/
hibernateVersion
>
<
webappDirectory
>
${project.build.directory}/${project.build.finalName}<
/
webappDirectory
>
<
project
.
build
.
sourceEncoding
>
UTF-8<
/
project
.
build
.
sourceEncoding
>
<
/
properties
>
<
dependencies
>
<
dependency
>
<
groupId
>
com.google.gwt<
/
groupId
>
<
artifactId
>
gwt-servlet<
/
artifactId
>
<
version
>
${gwtVersion}<
/
version
>
<
scope
>
runtime<
/
scope
>
<
/
dependency
>
<
dependency
>
<
groupId
>
com.google.gwt<
/
groupId
>
<
artifactId
>
gwt-user<
/
artifactId
>
<
version
>
${gwtVersion}<
/
version
>
<
scope
>
provided<
/
scope
>
<
/
dependency
>
<!--
Nécessaire
pour
le
superDevMode
-->
<
dependency
>
<
groupId
>
com.google.gwt<
/
groupId
>
<
artifactId
>
gwt-codeserver<
/
artifactId
>
<
version
>
${gwtVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
com.google.gwt<
/
groupId
>
<
artifactId
>
gwt-dev<
/
artifactId
>
<
version
>
${gwtVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
com.github.gwtbootstrap<
/
groupId
>
<
artifactId
>
gwt-bootstrap<
/
artifactId
>
<
version
>
2.3.2.0<
/
version
>
<
/
dependency
>
<!--
néssaire
pour
la
servlet
ou
service
RPC:
spring4gwt
-->
<
dependency
>
<
groupId
>
com.google.code<
/
groupId
>
<
artifactId
>
spring4gwt<
/
artifactId
>
<
version
>
0.0.1<
/
version
>
<!--
install
or
activate
the
lines
below
-->
<!--
<scope>system</scope>
-->
<!--
<systemPath>${basedir}/src/main/webapp/WEB-INF/lib/spring4gwt-0.0.1.jar</systemPath>
-->
<
/
dependency
>
<!--
Guava
pour
la
gestion
des
collections
-->
<
dependency
>
<
groupId
>
com.google.guava<
/
groupId
>
<
artifactId
>
guava<
/
artifactId
>
<
version
>
19.0<
/
version
>
<
/
dependency
>
<!--
Spring
framework
distribution
-->
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-core<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-beans<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-context<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-tx<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-web<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-orm<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-aspects<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-webmvc<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-test<
/
artifactId
>
<
version
>
${springframeworkVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.aspectj<
/
groupId
>
<
artifactId
>
aspectjtools<
/
artifactId
>
<
version
>
1.8.7<
/
version
>
<
/
dependency
>
<!--
AOP
dependency
-->
<
dependency
>
<
groupId
>
cglib<
/
groupId
>
<
artifactId
>
cglib<
/
artifactId
>
<
version
>
3.1<
/
version
>
<
exclusions
>
<
exclusion
>
<
groupId
>
asm<
/
groupId
>
<
artifactId
>
asm<
/
artifactId
>
<
/
exclusion
>
<
/
exclusions
>
<
/
dependency
>
<
dependency
>
<
groupId
>
antlr<
/
groupId
>
<
artifactId
>
antlr<
/
artifactId
>
<
version
>
2.7.7<
/
version
>
<
/
dependency
>
<!--
Hibernate
ORM
-->
<
dependency
>
<
groupId
>
org.hibernate.javax.persistence<
/
groupId
>
<
artifactId
>
hibernate-jpa-2.1-api<
/
artifactId
>
<
version
>
1.0.0.Final<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.hibernate<
/
groupId
>
<
artifactId
>
hibernate-entitymanager<
/
artifactId
>
<
version
>
${hibernateVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.hibernate<
/
groupId
>
<
artifactId
>
hibernate-core<
/
artifactId
>
<
version
>
${hibernateVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.hibernate<
/
groupId
>
<
artifactId
>
hibernate-validator<
/
artifactId
>
<
version
>
${hibernateVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.hibernate.common<
/
groupId
>
<
artifactId
>
hibernate-commons-annotations<
/
artifactId
>
<
version
>
${hibernateVersion}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
com.fasterxml<
/
groupId
>
<
artifactId
>
classmate<
/
artifactId
>
<
version
>
1.2.0<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
javax.transaction<
/
groupId
>
<
artifactId
>
jta<
/
artifactId
>
<
version
>
1.1<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
dom4j<
/
groupId
>
<
artifactId
>
dom4j<
/
artifactId
>
<
version
>
1.6.1<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.javassist<
/
groupId
>
<
artifactId
>
javassist<
/
artifactId
>
<
version
>
3.20.0-GA<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
javax.validation<
/
groupId
>
<
artifactId
>
validation-api<
/
artifactId
>
<
version
>
1.1.0.Final<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
javax.el<
/
groupId
>
<
artifactId
>
javax.el-api<
/
artifactId
>
<
version
>
3.0.1-b04<
/
version
>
<
/
dependency
>
<!--
joda-time
for
manage
local
datetime
-->
<
dependency
>
<
groupId
>
joda-time<
/
groupId
>
<
artifactId
>
joda-time<
/
artifactId
>
<
version
>
2.9.1<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
c3p0<
/
groupId
>
<
artifactId
>
c3p0<
/
artifactId
>
<
version
>
0.9.1.2<
/
version
>
<
/
dependency
>
<!--
Databases
Config
-->
<
dependency
>
<
groupId
>
com.h2database<
/
groupId
>
<
artifactId
>
h2<
/
artifactId
>
<
version
>
1.3.168<
/
version
>
<
/
dependency
>
<!--
Apache
commons-logging
-->
<
dependency
>
<
groupId
>
commons-logging<
/
groupId
>
<
artifactId
>
commons-logging<
/
artifactId
>
<
version
>
1.1.1<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.apache.commons<
/
groupId
>
<
artifactId
>
commons-lang3<
/
artifactId
>
<
version
>
3.4<
/
version
>
<
/
dependency
>
<!--
SLF4J
pour
la
gestion
des
logs
-->
<
dependency
>
<
groupId
>
org.slf4j<
/
groupId
>
<
artifactId
>
slf4j-log4j12<
/
artifactId
>
<
version
>
1.7.5<
/
version
>
<
/
dependency
>
<!--
Test
-->
<
dependency
>
<
groupId
>
junit<
/
groupId
>
<
artifactId
>
junit<
/
artifactId
>
<
version
>
4.7<
/
version
>
<
scope
>
test<
/
scope
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.ow2.asm<
/
groupId
>
<
artifactId
>
asm<
/
artifactId
>
<
version
>
5.0.4<
/
version
>
<
/
dependency
>
<!--
Dependance
pour
le
mapper
Dozer
nécessaire
à
la
création
des
DTOs
-->
<
dependency
>
<
groupId
>
net.sf.dozer<
/
groupId
>
<
artifactId
>
dozer<
/
artifactId
>
<
version
>
5.5.1<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
commons-beanutils<
/
groupId
>
<
artifactId
>
commons-beanutils<
/
artifactId
>
<!--
nécessaire
pour
faire
fonctionner
le
Dozer
-->
<
version
>
1.9.2<
/
version
>
<
/
dependency
>
<
/
dependencies
>
<
build
>
<!--
Generate
compiled
stuff
in
the
folder
used
for
developing
mode
-->
<
outputDirectory
>
${webappDirectory}/WEB-INF/classes<
/
outputDirectory
>
<
plugins
>
<!--
GWT
Maven
Plugin
-->
<
plugin
>
<
groupId
>
org.codehaus.mojo<
/
groupId
>
<
artifactId
>
gwt-maven-plugin<
/
artifactId
>
<
version
>
${gwtVersion}<
/
version
>
<
dependencies
>
<
dependency
>
<
groupId
>
org.codehaus.plexus<
/
groupId
>
<
artifactId
>
plexus-utils<
/
artifactId
>
<
version
>
3.0.22<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.codehaus.plexus<
/
groupId
>
<
artifactId
>
plexus-compiler-api<
/
artifactId
>
<
version
>
1.6<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
commons-io<
/
groupId
>
<
artifactId
>
commons-io<
/
artifactId
>
<
version
>
2.4<
/
version
>
<
/
dependency
>
<
/
dependencies
>
<
/
plugin
>
<!--
Copy
static
web
files
before
executing
gwt:run
-->
<
plugin
>
<
groupId
>
org.apache.maven.plugins<
/
groupId
>
<
artifactId
>
maven-war-plugin<
/
artifactId
>
<
version
>
2.1.1<
/
version
>
<
configuration
>
<
webappDirectory
>
${webappDirectory}<
/
webappDirectory
>
<
/
configuration
>
<
/
plugin
>
<
plugin
>
<
groupId
>
org.apache.maven.plugins<
/
groupId
>
<
artifactId
>
maven-compiler-plugin<
/
artifactId
>
<
version
>
2.3.2<
/
version
>
<
configuration
>
<
source
>
1.7<
/
source
>
<
target
>
1.7<
/
target
>
<
/
configuration
>
<
/
plugin
>
<
/
plugins
>
<
/
build
>
<
/
project
>
- La dépendance spring4gwt va permettre de mapper tous les appels RPC à travers la classe SpringGwtRemoteServiceServlet. On 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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<!
DOCTYPE
web
-
app
PUBLIC
"
-//Sun
Microsystems,
Inc.//DTD
Web
Application
2.3//EN
"
"
http://java.sun.com/dtd/web-app_2_3.dtd
"
>
<
web-app
>
<
listener
>
<
listener-class
>
org.springframework.web.context.ContextLoaderListener<
/
listener-class
>
<
/
listener
>
<
context-param
>
<
param-name
>
contextConfigLocation<
/
param-name
>
<
param-value
>
classpath:spring/applicationContext.xml<
/
param-value
>
<
/
context-param
>
<!--
SpringGwt
remote
service
servlet
-->
<
servlet
>
<
servlet-name
>
springGwtRemoteServiceServlet<
/
servlet-name
>
<
servlet-class
>
org.spring4gwt.server.SpringGwtRemoteServiceServlet<
/
servlet-class
>
<
load-on-startup
>
1<
/
load-on-startup
>
<
/
servlet
>
<
servlet-mapping
>
<
servlet-name
>
springGwtRemoteServiceServlet<
/
servlet-name
>
<
url-pattern
>
/BookManager/rpc/*<
/
url-pattern
>
<
/
servlet-mapping
>
<!--
Default
page
to
serve
-->
<
welcome-file-list
>
<
welcome-file
>
BookManager.html<
/
welcome-file
>
<
/
welcome-file-list
>
<
/
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
Notez au passage cette configuration :
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 d'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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
<
beans
xmlns
=
"
http://www.springframework.org/schema/beans
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xmlns
:
p
=
"
http://www.springframework.org/schema/p
"
xmlns
:
aop
=
"
http://www.springframework.org/schema/aop
"
xmlns
:
context
=
"
http://www.springframework.org/schema/context
"
xmlns
:
tx
=
"
http://www.springframework.org/schema/tx
"
xsi
:
schemaLocation
=
"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
"
>
<
context
:
annotation-config
/
>
<
tx
:
annotation-driven
/
>
<
context
:
component-scan
base-package
=
"
com.developpez.bnguimgo
"
/
>
<
bean
id
=
"
entityManagerFactory
"
class
=
"
org.springframework.orm.jpa.LocalEntityManagerFactoryBean
"
>
<
property
name
=
"
persistenceUnitName
"
value
=
"
entityManagerUnit
"
/
>
<
/
bean
>
<
bean
id
=
"
transactionManager
"
class
=
"
org.springframework.orm.jpa.JpaTransactionManager
"
>
<
property
name
=
"
entityManagerFactory
"
ref
=
"
entityManagerFactory
"
/
>
<
/
bean
>
<
bean
class
=
"
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
"
/
>
<
bean
class
=
"
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
"
/
>
<
bean
class
=
"
org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
"
/
>
<!--
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
>
<
/
beans
>
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érifiez 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 que nous avons besoin pour notre application sans se sourcier 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.
- Une couche de services proprement dite souvent appelée couche de business
- 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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
<
persistence
xmlns
=
"
http://java.sun.com/xml/ns/persistence
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xsi
:
schemaLocation
=
"
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd
"
version
=
"
2.0
"
>
<
persistence-unit
name
=
"
entityManagerUnit
"
transaction-type
=
"
RESOURCE_LOCAL
"
>
<
provider
>
org.hibernate.jpa.HibernatePersistenceProvider<
/
provider
>
<
properties
>
<
property
name
=
"
hibernate.hbm2ddl.auto
"
value
=
"
create-update
"
/
>
<
property
name
=
"
hibernate.show_sql
"
value
=
"
true
"
/
>
<!--
Fichiers
d'alimentation
de
la
base
de
données
au
démarrage
de
l'application
-->
<
property
name
=
"
hibernate.hbm2ddl.import_files
"
value
=
"
/import.sql
"
/
>
<!--
H2
DB
Base
de
données
intégrées
-->
<
property
name
=
"
hibernate.dialect
"
value
=
"
org.hibernate.dialect.H2Dialect
"
/
>
<
property
name
=
"
hibernate.connection.driver_class
"
value
=
"
org.h2.Driver
"
/
>
<
property
name
=
"
hibernate.connection.url
"
value
=
"
jdbc:h2:~/gwtSpringHibernate
"
/
>
<
property
name
=
"
hibernate.connection.username
"
value
=
"
sa
"
/
>
<
property
name
=
"
hibernate.connection.password
"
value
=
"
"
/
>
<
property
name
=
"
hibernate.c3p0.min_size
"
value
=
"
5
"
/
>
<
property
name
=
"
hibernate.c3p0.max_size
"
value
=
"
20
"
/
>
<
property
name
=
"
hibernate.c3p0.timeout
"
value
=
"
300
"
/
>
<
property
name
=
"
hibernate.c3p0.max_statements
"
value
=
"
50
"
/
>
<
property
name
=
"
hibernate.c3p0.idle_test_period
"
value
=
"
3000
"
/
>
<
/
properties
>
<
/
persistence-unit
>
<
/
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 cordonné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 :
--
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 utilisateur.
Pour ce faire, créer un nouveau package entities dans le package com.developpez.bnguimgo et ajouter l'entité ci-dessous:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
package
com.developpez.bnguimgo.entities;
import
java.io.Serializable;
import
javax.persistence.Column;
import
javax.persistence.Entity;
import
javax.persistence.GeneratedValue;
import
javax.persistence.GenerationType;
import
javax.persistence.Id;
import
javax.persistence.Table;
import
com.developpez.bnguimgo.shared.dto.UserDTO;
@
Entity
@
Table
(
name =
"
USER_LOGIN
"
)
public
class
User implements
Serializable{
private
static
final
long
serialVersionUID =
-
443589941665403890L
;
@
Id
@
GeneratedValue
(
strategy =
GenerationType.AUTO)
@
Column
(
name =
"
USER_ID
"
)
private
Long id;
@
Column
(
name =
"
USER_MAIL
"
, unique=
true
, insertable=
true
, updatable=
true
, nullable=
false
)
private
String mail;
@
Column
(
name =
"
USER_PASSWORD
"
, insertable=
true
, updatable=
true
, nullable=
false
)
private
String password;
@
Column
(
name =
"
USER_TYPE
"
, insertable=
true
, updatable=
true
, nullable=
false
)
private
String userType;
public
User
(
) {
super
(
);
}
public
User
(
String email, String password, String userType) {
this
.mail =
email;
this
.password =
password;
this
.userType =
userType;
}
public
User
(
Long id, String email) {
this
.id =
id;
this
.mail =
email;
}
public
User
(
UserDTO userDTO) {
this
.setId
(
userDTO.getId
(
));
this
.setMail
(
userDTO.getMail
(
));
this
.setPassword
(
userDTO.getPassword
(
));
}
public
Long getId
(
) {
return
id;
}
public
void
setId
(
Long id) {
this
.id =
id;
}
public
String getMail
(
) {
return
mail;
}
public
void
setMail
(
String email) {
this
.mail =
email;
}
public
String getPassword
(
) {
return
password;
}
public
void
setPassword
(
String password) {
this
.password =
password;
}
public
String getUserType
(
) {
return
userType;
}
public
void
setUserType
(
String userType) {
this
.userType =
userType;
}
@
Override
public
String toString
(
) {
return
"
User
[id=
"
+
id +
"
,
mail=
"
+
mail +
"
,
password=
"
+
"
XXXX
"
+
"
,
userType=
"
+
userType +
"
]
"
;
}
}
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-1. 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
package
com.developpez.bnguimgo.dao;
/**
*
@param
<E>
entité
générique
qui
doit
être
spécifiée
lors
de
l
'
utilisation
concrète
*
@param
<PK>
Clé
générique
,
le
type
doit
être
spécifié
lors
de
l
'
usage
*/
public
interface
IGenericDAO<
E, PK extends
Serializable>
{
/**
*
Cherche
un
User
par
email
et
password
*
@param
email
:
Email
du
User
*
@param
password
:
mot
de
passe
du
User
*
@return
E
:
Renvoie
un
User
*/
E findUserByEmailAndPassword
(
String email, String password);
}
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-2. Implémentation de l'interface générique▲
Nous allons implémenter l'interface IGenericDAO et la déclarer que en même temps que c'est 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
package
com.developpez.bnguimgo.dao.impl;
@
Repository
(
"
genericDAO
"
)
public
abstract
class
GenericDAOImpl<
E, PK extends
Serializable>
implements
IGenericDAO<
E, PK>
{
@
Transient
protected
Class<
E>
entityClass;
public
GenericDAOImpl
(
) {
super
(
);
}
public
GenericDAOImpl
(
Class<
E>
entityClass) {
this
.entityClass =
entityClass;
}
public
Class<
E>
getEntityClass
(
) {
return
entityClass;
}
public
void
setEntityClass
(
Class<
E>
entityClass) {
this
.entityClass =
entityClass;
}
@
Override
public
E findUserByEmailAndPassword
(
String email, String password ) {
String queryString =
"
SELECT
user
FROM
"
+
entityClass.getName
(
) +
"
user
WHERE
user.mail
LIKE
:emailParam
AND
user.password
LIKE
:passwordParam
"
;
TypedQuery<
E>
typedQuery =
getEntityManager
(
).createQuery
(
queryString, entityClass);
typedQuery.setParameter
(
"
emailParam
"
, email);
typedQuery.setParameter
(
"
passwordParam
"
, password);
List<
E>
resultList =
typedQuery.getResultList
(
);
if
(
resultList !
=
null
&
&
!
resultList.isEmpty
(
)){
return
resultList.get
(
0
);
}
return
null
;
}
/**
*
Permet
d
'
obtenir
le
EntityManager
pour
les
requêtes
en
base
de
données
*
@return
EntityManager
:
Instance
de
EntityManager
pour
les
requêtes
en
base
de
données
*/
protected
abstract
EntityManager getEntityManager
(
);
}
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-3. Utilisation de la classe générique▲
Nous allons utiliser la classe générique pour développer la plus part 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
2.
3.
4.
5.
6.
7.
package
com.developpez.bnguimgo.dao;
public
interface
IUserDAO extends
IGenericDAO<
User, Long>
{
//
Grâce
à
l'héritage,
pas
besoin
d'une
méthode
dans
cette
interface
//
Seules
les
méthodes
propres
à
un
User
peuvent
être
ajoutées
ici
}
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écifique 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
package
com.developpez.bnguimgo.dao.impl;
@
Repository
(
"
userDAO
"
)
public
class
UserDAOImpl extends
GenericDAOImpl<
User, Long>
implements
IUserDAO{
@
PersistenceContext
(
unitName =
"
entityManagerUnit
"
)
protected
EntityManager entityManager;
public
UserDAOImpl
(
) {
super
(
User.class
); //
Très
important
sinon,
NullPointerException
}
public
UserDAOImpl
(
Class<
User>
entityClass) {
super
(
entityClass);
}
@
Override
protected
EntityManager getEntityManager
(
) {
return
entityManager;
}
}
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 service ou couche de business
III-B-4. Création de la couche de service▲
Avant de développer la couche de service, 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-1. 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:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
package
com.developpez.bnguimgo.mapper;
public
final
class
DTOMapper {
public
static
UserDTO mapToUserDTO
(
User user){
if
(
user !
=
null
){
return
new
UserDTO
(
user.getId
(
), user.getMail
(
), user.getPassword
(
), user.getUserType
(
));
}
return
null
;
}
}
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-2. 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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<
mappings
xmlns
=
"
http://dozer.sourceforge.net
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xsi
:
schemaLocation
=
"
http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd
"
>
<
mapping
>
<
class-a
>
com.developpez.bnguimgo.entities.User<
/
class-a
>
<
class-b
>
com.developpez.bnguimgo.dto.UserDTO<
/
class-b
>
<
/
mapping
>
<
/
mappings
>
Ajouter la configuration ci-dessous dans applicationContext.xml si ce n'est déjà fait :
<
!
-
-
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-3. Implémentation des services▲
Nous allons en fin implémenter l'interface IUserService pour retourner les données utilisateur 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package
com.developpez.bnguimgo.service.impl;
@
Service
(
"
userService
"
)
public
class
UserServiceImpl implements
IUserService {
private
static
final
Logger LOG =
LoggerFactory.getLogger
(
UserServiceImpl.class
);
@
Autowired
private
IUserDAO userDAO;
@
Autowired
private
Mapper dozerMapper;
public
UserServiceImpl
(
){
super
(
);
}
@
Override
public
UserDTO findUserByEmailAndPassword
(
String email, String password) throws
Exception {
User user =
null
;
try
{
user =
userDAO.findUserByEmailAndPassword
(
email, password);
if
(
null
!
=
user){
LOG.info
(
"
User
found:
{}
"
, user.toString
(
));
//
mapper
manuel
à
commenter
si
utilisation
de
dozer
UserDTO userDTO =
DTOMapper.mapToUserDTO
(
user);
//
mapper
dozer
fourni
par
spring
//
UserDTO
userDTO
=
dozerMapper.map(userDTO,
UserDTO.class);
return
userDTO;
}
else
{
LOG.error
(
"
Failed
to
find
user
with
email
={}
and
paswword
=
{}
"
, email, password);
return
null
;
}
}
catch
(
Exception e) {
LOG.error
(
"
Failed
to
find
user
with
email
={}
and
paswword
=
{}
"
, email, password);
LOG.error
(
"
Failed
to
find
user
Or
Update:
"
,e);
throw
e;
}
}
}
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ême (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 service. Il faut rattacher maintenant la couche de service à GWT.
Nous verrons prochainement comment mettre en place les transactions dans la couche de service pour certains types de requêtes.
III-B-4-4. Liaison entre GWT et la couche service▲
Voici un schéma qui montre comment rattacher la couche GWT avec la couhe de service grâce à l'interface IUservice :
III-B-4-5. 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<!
DOCTYPE
log4j
:
configuration
SYSTEM
"
log4j.dtd
"
>
<
log4j
:
configuration
xmlns
:
log4j
=
"
http://jakarta.apache.org/log4j/
"
debug
=
"
true
"
>
<!--
levels
:
all|debug|info|warn|error|fatal|off|trace
-->
<
appender
name
=
"
console
"
class
=
"
org.apache.log4j.ConsoleAppender
"
>
<
layout
class
=
"
org.apache.log4j.PatternLayout
"
>
<
param
name
=
"
ConversionPattern
"
value
=
"
%X{codeapp}
%d{yyyy-MM-dd
HH:mm:ss}
%-5p
[%c{1}]
:
%m%n
"
/
>
<
/
layout
>
<
/
appender
>
<
appender
name
=
"
file-out
"
class
=
"
org.apache.log4j.RollingFileAppender
"
>
<
param
name
=
"
file
"
value
=
"
gwtmvpproject.log
"
/
>
<
param
name
=
"
append
"
value
=
"
true
"
/
>
<
param
name
=
"
MaxFileSize
"
value
=
"
10MB
"
/
>
<
param
name
=
"
MaxBackupIndex
"
value
=
"
10
"
/
>
<
layout
class
=
"
org.apache.log4j.PatternLayout
"
>
<
param
name
=
"
ConversionPattern
"
value
=
"
[%X{idreq}
%X{iduser}
%X{codeenv}]
%d{yyyy-MM-dd
HH:mm:ss}
%-5p
[%c{1}]
:
%m%n
"
/
>
<
/
layout
>
<
/
appender
>
<
logger
name
=
"
org.springframework.web.context
"
additivity
=
"
false
"
>
<
level
value
=
"
INFO
"
/
>
<
appender-ref
ref
=
"
console
"
/
>
<
/
logger
>
<
logger
name
=
"
com.developpez.bnguimgo
"
additivity
=
"
false
"
>
<
level
value
=
"
INFO
"
/
>
<
appender-ref
ref
=
"
console
"
/
>
<
appender-ref
ref
=
"
file-out
"
/
>
<
/
logger
>
<
root
>
<
priority
value
=
"
info
"
/
>
<
appender-ref
ref
=
"
console
"
/
>
<
appender-ref
ref
=
"
file-out
"
/
>
<
/
root
>
<
/
log4j
:
configuration
>
III-B-5. Test de l'application▲
III-B-5-1. Arborescence finale▲
Avant de commencer les tests, voici un premier aperçu des packages crées.
Structure du projet :
III-B-5-2. Test connexion avec erreurs▲
Entrez n'importe quel mail et mot de passe, puis cliquez sur connexion. Vous obtenez l'erreur suivante :
Le bouton CREATION COMPTE n'est pas encore implémenté. Seul le bouton CONNEXION réagit au clic
III-B-5-3. 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 :
Vous constatez que les styles sont bien appliqués.
Nous venons ainsi d'achéver la deuxième partie. Nous avons appris à mettre en place un service RPC, à développer la couche de service ou couche de business, à mettre en place la couche d'accès aux données avec le mapping Hibernate et sur à lier ces couches 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 feront l'objet d'une troixiè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'accueilVoir la création page accueil
Les sources complètes pour ces deux premières parties sont disponibles iciCode source.
La troixième partie consacrée aux développements des pages d'inscriptions et d'administration est en cours d'écriture