Vous êtes sur la page 1sur 572

Référence

Java EE 6 ™

et GlassFish 3 ™

Antonio Goncalves

Réseaux
et télécom

Programmation

Développement
web

Sécurité

Système
d’exploitation

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Java EE 6
et GlassFish 3
Antonio Goncalves

Traduit par Éric Jacoboni,


avec la contribution technique de Éric Hébert

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Pearson Education France a apporté le plus grand soin à la réalisation de ce livre afin de
vous fournir une information complète et fiable. Cependant, Pearson Education France
n’assume de responsabilités, ni pour son utilisation, ni pour les contrefaçons de brevets
ou atteintes aux droits de tierces personnes qui pourraient résulter de cette utilisation.

Les exemples ou les programmes présents dans cet ouvrage sont fournis pour
illustrer les descriptions théoriques. Ils ne sont en aucun cas destinés à une utilisation
commerciale ou professionnelle.

Pearson Education France ne pourra en aucun cas être tenu pour responsable des
préjudices ou dommages de quelque nature que ce soit pouvant résulter de l’utilisation
de ces exemples ou programmes.

Tous les noms de produits ou marques cités dans ce livre sont des marques déposées par
leurs ­propriétaires respectifs.

Publié par Pearson Education France Titre original : Beginning Java™ EE6
47 bis, rue des Vinaigriers Platform with GlassFish™ 3
75010 PARIS
Tél. : 01 72 74 90 00
www.pearson.fr Traduction : Éric Jacoboni, avec la
contribution de Éric Hébert
Mise en pages : TyPAO

ISBN : 978-2-7440-4157-0 ISBN original : 978-1-4302-1954-5


Copyright © 2010 Pearson Education France Copyright © 2009 by Antonio Goncalves
Tous droits réservés Tous droits réservés

Édition originale publiée par Apress


2855 Telegraph Avenue
Berkeley, CA 94705
www.apress.com

Aucune représentation ou reproduction, même partielle, autre que celles prévues à l’article L. 122-5 2˚ et 3˚ a)
du code de la propriété intellectuelle ne peut être faite sans l’autorisation expresse de Pearson Education France
ou, le cas échéant, sans le respect des modalités prévues à l’article L. 122-10 dudit code.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from Pearson Education, Inc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières

Avant-propos ...................................................................................................................... XIII

À propos de l’auteur ......................................................................................................... XIV

Remerciements ................................................................................................................... XV

Introduction......................................................................................................................... 1

1 Tour d’horizon de Java EE 6 ............................................................................................ 5


Présentation de Java EE. ....................................................................................................... 6
Un peu d’histoire............................................................................................................ 6
Standards. ......................................................................................................................... 9
Architecture. .................................................................................................................... 9
Composants...................................................................................................................... 10
Conteneurs. ...................................................................................................................... 11
Services............................................................................................................................. 12
Protocoles réseau............................................................................................................ 14
Paquetages........................................................................................................................ 15
Java Standard Edition.................................................................................................... 16
Spécifications de Java EE 6 ................................................................................................. 16
Nouveautés de Java EE 6...................................................................................................... 18
Plus léger. ......................................................................................................................... 19
Élagage.............................................................................................................................. 19
Profils................................................................................................................................. 20
Plus simple d’utilisation .............................................................................................. 21
Plus riche.......................................................................................................................... 22
Plus portable.................................................................................................................... 24
L’application CD-Bookstore . .............................................................................................. 24
Configuration de l’environnement de travail .................................................................... 26
JDK 1.6............................................................................................................................. 26
Maven 2. ........................................................................................................................... 27
JUnit 4............................................................................................................................... 34

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
IV Java EE 6 et GlassFish 3 

Derby 10.5........................................................................................................................ 39
GlassFish v3. ................................................................................................................... 42
Installation de GlassFish............................................................................................... 48
Résumé..................................................................................................................................... 50

2 Persistance en Java .............................................................................................................. 51


Résumé de la spécification JPA. .......................................................................................... 52
Historique de la spécification...................................................................................... 53
Nouveautés de JPA 2.0.................................................................................................. 54
Implémentation de référence ...................................................................................... 54
Comprendre les entités.......................................................................................................... 55
ORM = Object-Relational Mapping.......................................................................... 55
Interrogation des entités................................................................................................ 57
Méthodes de rappel et écouteurs ............................................................................... 59
Récapitulatif............................................................................................................................ 60
Écriture de l’entité Book............................................................................................... 61
Écriture de la classe Main. ........................................................................................... 62
Unité de persistance pour la classe Main. ................................................................ 63
Compilation avec Maven.............................................................................................. 64
Exécution de la classe Main avec Derby.................................................................. 66
Écriture de la classe BookTest..................................................................................... 67
Unité de persistance pour la classe BookTest ......................................................... 69
Exécution de la classe BookTest avec Derby intégré ............................................ 70
Résumé..................................................................................................................................... 71

3 ORM : Object-Relational Mapping. ................................................................................ 73


Association d’une entité........................................................................................................ 73
Configuration par exception. ....................................................................................... 76
Associations élémentaires..................................................................................................... 77
Tables................................................................................................................................. 78
Clés primaires.................................................................................................................. 80
Attributs............................................................................................................................ 85
Types d’accès................................................................................................................... 92
Collections de types de base........................................................................................ 95
Association des types de base. .................................................................................... 97
Associations avec XML........................................................................................................ 99

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Table des matières V

Objets intégrables................................................................................................................... 102


Types d’accès d’une classe intégrable....................................................................... 104
Correspondance des relations............................................................................................... 106
Relations dans les bases de données relationnelles. .............................................. 107
Relations entre entités .................................................................................................. 109
Chargement des relations. ............................................................................................ 121
Tri des relations............................................................................................................... 122
Traduction de l’héritage........................................................................................................ 126
Stratégies d’héritage. ..................................................................................................... 127
Type de classes dans une hiérarchie d’héritage. ..................................................... 135
Résumé..................................................................................................................................... 139

4 Gestion des objets persistants............................................................................................ 141


Interrogation d’une entité...................................................................................................... 142
Le gestionnaire d’entités....................................................................................................... 145
Obtenir un gestionnaire d’entités. .............................................................................. 147
Contexte de persistance. ............................................................................................... 149
Manipulation des entités............................................................................................... 150
L’API de cache................................................................................................................ 162
JPQL......................................................................................................................................... 163
Select.................................................................................................................................. 165
From................................................................................................................................... 167
Where................................................................................................................................. 167
Order By. .......................................................................................................................... 169
Group By et Having....................................................................................................... 169
Suppressions multiples . ............................................................................................... 170
Mises à jour multiples................................................................................................... 171
Requêtes................................................................................................................................... 171
Requêtes dynamiques.................................................................................................... 174
Requêtes nommées......................................................................................................... 175
Requêtes natives. ............................................................................................................ 178
Concurrence. ........................................................................................................................... 179
Gestion de version.......................................................................................................... 182
Verrouillage optimiste................................................................................................... 183
Verrouillage pessimiste................................................................................................. 185
Résumé..................................................................................................................................... 186

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
VI Java EE 6 et GlassFish 3 

5 Méthodes de rappel et écouteurs....................................................................................... 187


Cycle de vie d’une entité....................................................................................................... 187
Méthodes de rappel................................................................................................................ 189
Écouteurs (listeners). ............................................................................................................. 192
Résumé..................................................................................................................................... 198

6 Enterprise Java Beans. ........................................................................................................ 199


Introduction aux EJB............................................................................................................. 200
Types d’EJB..................................................................................................................... 201
Anatomie d’un EJB . ..................................................................................................... 202
Conteneur d’EJB. ........................................................................................................... 204
Conteneur intégré........................................................................................................... 205
Injection de dépendances et JNDI.............................................................................. 206
Méthodes de rappel et intercepteurs.......................................................................... 207
Tour d’horizon de la spécification EJB............................................................................... 209
Historique......................................................................................................................... 209
Nouveautés d’EJB 3.1................................................................................................... 210
EJB Lite. ........................................................................................................................... 211
Récapitulatif............................................................................................................................ 213
L’entité Book.................................................................................................................... 214
Le bean de session sans état BookEJB...................................................................... 215
Unité de persistance pour le BookEJB...................................................................... 216
La classe Main. ............................................................................................................... 217
Compilation et assemblage avec Maven................................................................... 218
Déploiement sur GlassFish.......................................................................................... 220
Exécution de la classe Main avec Derby.................................................................. 221
La classe BookEJBTest.................................................................................................. 221
Résumé..................................................................................................................................... 223

7 Beans de session et service timer....................................................................................... 225


Beans de session..................................................................................................................... 226
Beans sans état................................................................................................................ 226
Beans avec état................................................................................................................ 229
Singletons......................................................................................................................... 232
Modèle des beans de session....................................................................................... 239
Appels asynchrones. ...................................................................................................... 252

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Table des matières VII

Conteneurs intégrés . ..................................................................................................... 254


Le service timer...................................................................................................................... 256
Expressions calendaires................................................................................................ 257
Création automatique d’un timer................................................................................ 260
Création d’un timer par programme ......................................................................... 261
Résumé..................................................................................................................................... 263

8 Méthodes de rappel et ­intercepteurs................................................................................ 265


Cycles de vie des beans de session...................................................................................... 266
Beans sans état et singletons........................................................................................ 266
Beans avec état................................................................................................................ 267
Méthodes de rappel........................................................................................................ 269
Intercepteurs............................................................................................................................ 272
Intercepteurs autour des appels................................................................................... 274
Intercepteurs de méthode.............................................................................................. 277
Intercepteur du cycle de vie......................................................................................... 278
Chaînage et exclusion d’intercepteurs . .................................................................... 280
Résumé..................................................................................................................................... 282

9 Transactions et sécurité....................................................................................................... 283


Transactions. ........................................................................................................................... 283
ACID.................................................................................................................................. 284
Transactions locales....................................................................................................... 285
XA et transactions distribuées..................................................................................... 286
Support des transactions avec les EJB................................................................................ 288
Transactions gérées par le conteneur ........................................................................ 289
Transactions gérées par le bean. ................................................................................. 296
Sécurité. ................................................................................................................................... 298
Principal et rôle............................................................................................................... 299
Authentification et habilitation. .................................................................................. 300
Gestion de la sécurité dans EJB........................................................................................... 301
Sécurité déclarative........................................................................................................ 301
Sécurité par programmation. ....................................................................................... 305
Résumé..................................................................................................................................... 307

10 JavaServer Faces................................................................................................................... 309


Introduction à JSF.................................................................................................................. 310

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
VIII Java EE 6 et GlassFish 3 

FacesServlet et faces-config.xml................................................................................. 311


Pages et composants...................................................................................................... 311
Moteurs de rendu............................................................................................................ 313
Convertisseurs et validateurs....................................................................................... 313
Beans gérés et navigation............................................................................................. 314
Support d’Ajax................................................................................................................ 315
Résumé des spécifications de l’interface web .................................................................. 316
Bref historique des interfaces web............................................................................. 316
JSP 2.2, EL 2.2 et JSTL 1.2......................................................................................... 317
JSF 2.0............................................................................................................................... 317
Nouveautés de JSF 2.0.................................................................................................. 318
Implémentation de référence....................................................................................... 318
Récapitulatif............................................................................................................................ 318
L’entité Book.................................................................................................................... 320
L’EJB BookEJB. ............................................................................................................. 320
Le bean géré BookController. ..................................................................................... 321
La page newBook.xhtml................................................................................................ 322
La page listBooks.xhtml................................................................................................ 325
Configuration avec web.xml......................................................................................... 327
Compilation et assemblage avec Maven................................................................... 328
Déploiement dans GlassFish. ...................................................................................... 329
Exécution de l’application............................................................................................ 329
Résumé..................................................................................................................................... 330

11 Pages et composants. ............................................................................................................ 331


Pages web................................................................................................................................ 332
HTML................................................................................................................................ 332
XHTML............................................................................................................................ 334
CSS. ................................................................................................................................... 335
DOM.................................................................................................................................. 338
JavaScript. ........................................................................................................................ 338
Java Server Pages. .................................................................................................................. 341
Directives.......................................................................................................................... 342
Scripts................................................................................................................................ 343
Actions.............................................................................................................................. 344
Récapitulatif..................................................................................................................... 345

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Table des matières IX

Langage d’expressions (EL)................................................................................................. 347


La bibliothèque de marqueurs standard de JSP (JSTL)................................................... 349
Actions fondamentales.................................................................................................. 350
Actions de formatage..................................................................................................... 351
Actions SQL.................................................................................................................... 353
Actions XML................................................................................................................... 355
Fonctions. ......................................................................................................................... 357
Facelets..................................................................................................................................... 358
JavaServer Faces..................................................................................................................... 360
Cycle de vie. .................................................................................................................... 361
Composants HTML standard. ..................................................................................... 363
Gestion des ressources.................................................................................................. 374
Composants composites. .............................................................................................. 375
Objets implicites............................................................................................................. 381
Résumé..................................................................................................................................... 383

12 Traitement et navigation..................................................................................................... 385


Le modèle MVC..................................................................................................................... 385
FacesServlet..................................................................................................................... 387
FacesContext.................................................................................................................... 389
Configuration de Faces. ................................................................................................ 390
Beans gérés.............................................................................................................................. 390
Écriture d’un bean géré................................................................................................. 391
Modèle d’un bean géré.................................................................................................. 391
Navigation........................................................................................................................ 396
Gestion des messages.................................................................................................... 400
Conversion et validation........................................................................................................ 402
Convertisseurs................................................................................................................. 404
Convertisseurs personnalisés....................................................................................... 406
Validateurs........................................................................................................................ 407
Validateurs personnalisés. ............................................................................................ 408
Ajax. ......................................................................................................................................... 410
Concepts généraux......................................................................................................... 410
Ajax et JSF....................................................................................................................... 412
Récapitulatif..................................................................................................................... 413
Résumé..................................................................................................................................... 418

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
X Java EE 6 et GlassFish 3 

13 Envoi de messages................................................................................................................. 419


Présentation des messages.................................................................................................... 420
JMS.................................................................................................................................... 420
MDB.................................................................................................................................. 421
Résumé de la spécification des messages . ........................................................................ 421
Bref historique des messages. ..................................................................................... 422
JMS 1.1............................................................................................................................. 422
EJB 3.1.............................................................................................................................. 422
Implémentation de référence....................................................................................... 423
Envoi et réception d’un message. ........................................................................................ 423
Java Messaging Service......................................................................................................... 425
Point à point..................................................................................................................... 427
Publication-abonnement............................................................................................... 428
API JMS. .......................................................................................................................... 429
Sélecteurs.......................................................................................................................... 442
Mécanismes de fiabilité ............................................................................................... 443
MDB : Message-Driven Beans. ........................................................................................... 446
Création d’un MDB....................................................................................................... 447
Le modèle des MDB...................................................................................................... 447
MDB comme consommateur....................................................................................... 452
MDB comme producteur.............................................................................................. 453
Transactions..................................................................................................................... 455
Gestion des exceptions.................................................................................................. 456
Récapitulatif............................................................................................................................ 457
OrderDTO. ....................................................................................................................... 457
OrderSender..................................................................................................................... 458
OrderMDB. ...................................................................................................................... 459
Compilation et assemblage avec Maven................................................................... 459
Création des objets administrés. ................................................................................. 461
Déploiement du MDB dans GlassFish...................................................................... 461
Exécution de l’exemple................................................................................................. 462
Résumé..................................................................................................................................... 462

14 Services web SOAP............................................................................................................... 465


Présentation des services web.............................................................................................. 466
UDDI................................................................................................................................. 467

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Table des matières XI

WSDL................................................................................................................................ 467
SOAP................................................................................................................................. 468
Protocole de transport.................................................................................................... 468
XML. ................................................................................................................................. 468
Résumé de la spécification des services web . .................................................................. 468
Bref historique des services web................................................................................ 469
Spécifications Java EE. ................................................................................................. 469
Implémentation de référence....................................................................................... 471
Appel d’un service web ........................................................................................................ 471
JAXB : Java Architecture for XML Binding..................................................................... 473
Liaison............................................................................................................................... 476
Annotations...................................................................................................................... 478
La partie immergée de l’iceberg.......................................................................................... 481
WSDL................................................................................................................................ 481
SOAP................................................................................................................................. 484
JAX-WS : Java API for XML-Based Web Services......................................................... 485
Le modèle JAX-WS....................................................................................................... 486
Appel d’un service web ............................................................................................... 494
Récapitulatif............................................................................................................................ 496
La classe CreditCard..................................................................................................... 497
Le service web CardValidator..................................................................................... 497
Compilation et assemblage avec Maven................................................................... 498
Déploiement dans GlassFish. ...................................................................................... 499
Le consommateur du service web ............................................................................. 501
Création des artefacts du consommateur et assemblage avec Maven............... 502
Exécution de la classe Main. ....................................................................................... 505
Résumé..................................................................................................................................... 505

15 Services web REST............................................................................................................... 507


Présentation des services web REST.................................................................................. 507
Ressources........................................................................................................................ 508
URI..................................................................................................................................... 508
Représentations............................................................................................................... 509
WADL. .............................................................................................................................. 510
HTTP................................................................................................................................. 510
Spécification des services web REST................................................................................. 516

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
XII Java EE 6 et GlassFish 3 

Historique rapide de REST.......................................................................................... 516


JAX-RS 1.1...................................................................................................................... 517
Nouveautés de JAX-RS 1.1. ........................................................................................ 517
Implémentation de référence....................................................................................... 517
L’approche REST................................................................................................................... 518
Du Web aux services web . .......................................................................................... 518
Pratique de la navigation sur le Web. ........................................................................ 519
Interface uniforme.......................................................................................................... 519
Accessibilité..................................................................................................................... 520
Connectivité..................................................................................................................... 521
Sans état............................................................................................................................ 522
JAX-RS : Java API for RESTful Web Services................................................................. 523
Le modèle JAX-RS........................................................................................................ 523
Écriture d’un service REST......................................................................................... 524
Définition des URI......................................................................................................... 525
Extraction des paramètres. ........................................................................................... 526
Consommation et production des types de contenus . .......................................... 528
Fournisseurs d’entités.................................................................................................... 530
Méthodes ou interface uniforme................................................................................. 532
Informations contextuelles........................................................................................... 534
Gestion des exceptions.................................................................................................. 536
Cycle de vie. .................................................................................................................... 537
Récapitulatif............................................................................................................................ 537
L’entité Book.................................................................................................................... 538
Le service REST BookResource................................................................................. 539
Configuration avec web.xml......................................................................................... 542
Compilation et assemblage avec Maven................................................................... 543
Déploiement dans GlassFish. ...................................................................................... 544
Exécution de l’exemple................................................................................................. 545
Résumé..................................................................................................................................... 546

Index................................................................................................................................................... 547

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Avant-propos

Bien que Java EE 5 soit unanimement considérée comme la version la plus impor-
tante de Java EE, Java EE  6 fournit de nombreuses fonctionnalités supplémen-
taires. La création d’applications d’entreprise est, en effet, encore améliorée grâce à
EJB 3.1, JPA 2.0, JAX-RS et JSF 2.0. La plate-forme Java EE est désormais arrivée
à un niveau de maturité permettant de faire côtoyer légèreté et puissance.
Vous pourriez évidemment utiliser votre navigateur favori pour parcourir les nom-
breux blogs, wikis et articles consacrés à Java EE 6, mais je vous conseille plutôt
de commencer par lire ce livre : il est concis, pragmatique et il distille l’expérience
complète de l’auteur sur le sujet.
Cet ouvrage utilise GlassFish comme serveur d’applications sous-jacent pour plu-
sieurs raisons : GlassFish V3 est l’implémentation de référence et est donc en phase
avec Java EE 6. Par ailleurs, l’expérience acquise en utilisant une implémentation
de référence et les technologies les plus récentes est adaptable au monde de l’entre-
prise. Ce que vous apprendrez sera directement exploitable en production.
Antonio Goncalves est un exemple rare de développeur où se mêlent passion pour
Java et expérience professionnelle de Java EE. Son travail de consultant, combiné
à sa participation au Java User Group de Paris ainsi, bien sûr, que son rôle dans
les différents groupes d’experts Java EE 6 en font l’auteur idéal de Java EE 6 et
GlassFish 3.
Après la lecture de ce livre, vous aurez compris que la plus grande richesse de Java
EE n’est pas la somme de ses fonctionnalités mais la communauté qui l'a créé, ainsi
que sa nature même de standard qui vous permet de choisir ou de modifier l'implé-
mentation en fonction de vos besoins. La liberté n'est pas simplement représentée
par l'open-source, mais également par les standards ouverts.

Alexis Moussine-Pouchkine
Équipe GlassFish, Sun Microsystems.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
À propos de l’auteur

Antonio Goncalves est un architecte logiciel ; il vit à Paris. S’intéressant au déve-


loppement en Java depuis la fin des années 1990, il a travaillé dans diverses sociétés
de différents pays, pour lesquelles il intervient désormais en tant que consultant en
architecture logicielle Java EE. Son expérience lui a permis d’acquérir une grande
connaissance des serveurs d’applications comme WebLogic, JBoss et, bien sûr,
GlassFish. C’est un ardent défenseur des logiciels open-source – il est membre de
l’OSSGTP (Open Source Get Together Paris). Il est également l’un des initiateurs et
des animateurs du Paris Java User Group.
Antonio a écrit son premier livre sur Java EE 5 en 2007. Depuis, il a rejoint le JCP
et est l’un des experts de plusieurs JSR (Java EE 6, JPA 2.0 et EJB 3.1). Au cours de
ces dernières années, Antonio est intervenu dans plusieurs conférences internatio-
nales consacrées essentiellement à Java EE – notamment JavaOne, The Server Side
Symposium, Devoxx et Jazoon. Il a également publié de nombreux articles tech-
niques, aussi bien pour des sites web (DevX) que pour des magazines spécialisés
(Programmez, Linux Magazine).
Antonio possède le diplôme d’ingénieur en informatique du CNAM (Conservatoire
national des arts et métiers) de Paris et une maîtrise en conception orientée objet de
l’université de Brighton.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Remerciements

Écrire un livre sur une nouvelle spécification comme Java EE 6 est une tâche énorme
qui mobilise les talents de plusieurs personnes. Avant tout, je remercie Steve Anglin,
d’Apress, de m’avoir donné l’opportunité de participer à la collection "Beginning
Series" d’Apress, que j’apprécie beaucoup en tant que lecteur. Tout au long de mon
travail de rédaction, j’ai été en contact avec Candace English et Tom Welsh, qui
ont relu l’ouvrage et m’ont rassuré lorsque j’avais des doutes sur le fait de pouvoir
le terminer en temps voulu. Je remercie également les relecteurs techniques, Jim
Farley et Sumit Pal, qui ont fait un excellent travail en me suggérant de nombreuses
améliorations. Enfin, j’ai admiré le travail remarquable d’Ami Knox, qui a produit la
dernière version de l’édition. Merci également à Alexis Midon et Sebastien Auvray,
les coauteurs du Chapitre 15, consacré aux services web REST : Alex est un infor-
maticien passionné, fan de REST, et Sébastien est un développeur talentueux qui
utilise REST de façon pragmatique. Merci à eux pour leur aide précieuse.
Je remercie tout spécialement Alexis Moussine-Pouchkine, qui a gentiment accepté
d’écrire la préface et la section sur GlassFish. Il m’a également été d’un grand
secours pour contacter les bonnes personnes pour des sujets particuliers : je pense
à Ryan Lubke pour JSF 2.0, à Paul Sandoz pour JAX-RS 1.1 et à François Orsini
pour Derby.
Merci à Damien Gouyette pour son aide sur JSF 2.0. Damien a une grande expé-
rience en développement web et en JSF en particulier (au fait, merci à celui qui gère
le dépôt SVN). Merci également à Arnaud Héritier pour avoir écrit la section sur
Maven et pour m’avoir aidé à résoudre certains problèmes avec Maven, ainsi qu’à
Nicolas de Loof pour sa relecture technique sur le sujet.
Sébastien Moreno m’a aidé pour JUnit et a relu le manuscrit complet avec David
Dewalle et Pascal Graffion – tout cela dans un délai imparti très serré. Je les remer-
cie beaucoup pour leur travail.
Je remercie les correcteurs, Denise Green et Stefano Costa, pour avoir tenté de donner
une touche shakespearienne à ce livre.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

XVI Java EE 6 et GlassFish 3 

Les diagrammes de cet ouvrage ont été réalisés avec l’extension Visual Paradigm
d’IntelliJ IDEA. Je remercie d’ailleurs Visual Paradigm et JetBrains pour m’avoir
offert une licence gratuite de leurs excellents produits.
Je n’aurais pas pu écrire ce livre sans l’aide de la communauté Java : tous ceux qui
ont pris sur leur temps libre pour m’aider dans leurs courriers électroniques, dans les
listes de diffusion ou sur les forums de discussion. La liste de diffusion des groupes
d’experts JCP est, évidemment, la première qui me vient à l’esprit : merci à Roberto
Chinnici, Bill Shannon, Kenneth Saks, Linda DeMichiel, Michael Keith, Reza Rah-
man, Adam Bien, etc.
Un gros bisou à ma fille, Éloïse : les interruptions dues à son espièglerie m’ont aidé
alors que je passais mes week-ends à écrire.
Un livre est le produit d’un nombre incalculable de personnes que je voudrais remer-
cier pour leur contribution, que ce soit pour un avis technique, une bière dans un bar,
un extrait de code... Merci donc à Jean-Louis Dewez, Frédéric Drouet, les geeks du
JUG de Paris, T. Express, les membres d’OSSGTP, les Cast Codeurs, FIP, Marion,
Les Connards, Vitalizen, La Fontaine, Ago, Laure, La Grille, les Eeckman, Yaya,
Rita, os Navalhas, La Commune Libre d’Aligre, etc.
Merci à tous !

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Introduction

Aujourd’hui, les applications doivent accéder à des données, appliquer une logique
métier, ajouter des couches de présentation et communiquer avec des systèmes
externes. Les entreprises tentent de réaliser toutes ces opérations à moindre coût, en
se servant de technologies standard et robustes supportant des charges importantes.
Si vous êtes dans cette situation, ce livre est fait pour vous.
Java Enterprise Edition est apparu à la fin des années 1990 et a doté le langage
Java d’une plate-forme logicielle fiable, prête pour les besoins des entreprises. Cri-
tiqué à chaque nouvelle version, mal compris ou mal utilisé, en compétition avec
les frameworks open-source, J2EE a été considéré comme une technologie lourde.
Java EE a bénéficié de toutes ces critiques pour s’améliorer : son but actuel est la
simplicité.
Si vous faites partie de ceux qui pensent "que les EJB sont nuls et qu’il faut s’en
débarrasser", lisez ce livre et vous changerez d’avis. Les EJB (Enterprise Java
Beans), comme toutes les technologies de Java EE, sont des composants puissants.
Si, au contraire, vous êtes un fan de Java  EE, cet ouvrage vous montrera que la
plate-forme a trouvé son équilibre grâce à une simplification du développement,
à de nouvelles spécifications, à un modèle de composants EJB plus légers, à des
profils et à l’élagage. Si vous débutez avec Java EE, ce livre vous guidera dans votre
apprentissage des spécifications les plus importantes au moyen d’exemples et de
diagrammes très simples à comprendre.
Les standards ouverts sont l’une des forces principales de Java EE. Plus que jamais,
les applications écrites avec JPA, EJB, JSF, JMS, les services web SOAP ou REST
sont portables entre les différents serveurs d’applications. Comme vous pourrez le
constater, la plupart des implémentations de référence de Java EE  6 (GlassFish,
EclipseLink, Mojarra, OpenMQ, Metro et Jersey) sont distribuées sous les termes
d’une licence open-source.
Ce livre explore les innovations de cette nouvelle version et examine les différentes
spécifications et la façon de les assembler pour développer des applications. Java

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
2 Java EE 6 et GlassFish 3 

EE 6 est formé d’environ 30 spécifications et c’est une version importante pour la
couche métier (EJB 3.1, JPA 2.0), la couche web (Servlet 3.0, JSF 2.0) et l’interopé-
rabilité (services web SOAP et REST). Ce livre présente une grande partie de toutes
ces spécifications, utilise le JDK 1.6 et certains patrons de conception bien connus,
ainsi que le serveur d’applications GlassFish, la base de données Derby, JUnit et
Maven. Il est abondamment illustré de diagrammes UML, de code Java et de copies
d’écran.

Structure du livre

Ce livre ne se veut pas une référence exhaustive de Java EE 6. Il s’intéresse essen-
tiellement aux spécifications les plus importantes et aux nouvelles fonctionnalités de
cette version. Sa structure respecte le découpage de l’architecture d’une application.

Présentation
Chapitre 10 : JavaServer Faces
Chapitre 11 : Pages et composants
Chapitre 12 : Traitements et navigation

Logique métier
Chapitre 6 : Enterprise Java Beans
Interopérabilité
Chapitre 7 : Beans de session et service timer
Chapitre 8 : Méthodes de rappel et intercepteurs Chapitre 13 : Envoi de messages
Chapitre 9 : Transactions et sécurité Chapitre 14 : Services web SOAP
Chapitre 15 : Services web REST

Persistance
Chapitre 2 : Persistance en Java
Chapitre 3 : ORM : ObjectRelational Mapping
Chapitre 4 : Gestion des objets persistants
Chapitre 5 : Méthodes de rappel et écouteurs

Le Chapitre 1 présente brièvement l’essentiel de Java EE 6 et les outils que nous
utiliserons dans ce livre (JDK, Maven, JUnit, Derby et GlassFish).
La couche de persistance est décrite du Chapitre 2 au Chapitre 5 et s’intéresse prin-
cipalement à JPA 2.0. Après un survol général et quelques exemples au Chapitre 2,
le Chapitre  3 s’intéresse à l’association objet-relationnel (ORM). Le Chapitre  4
montre comment gérer et interroger les entités, tandis que le 5 présente leur cycle de
vie, les méthodes de rappel et les écouteurs.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Introduction   3

Pour développer une couche métier transactionnelle avec Java EE 6, on utilise natu-
rellement les EJB, qui seront décrits du Chapitre  6 au Chapitre  9. Le Chapitre  6
passe en revue la spécification, son histoire et donne des exemples de code, tandis
que le 7 s’intéresse plus particulièrement aux beans de session et à leur modèle de
programmation, ainsi qu’au nouveau service timer. Le Chapitre 8 est consacré au
cycle de vie des EJB et aux intercepteurs, tandis que le 9 explique tout ce qui a trait
aux transactions et à la sécurité.
Du Chapitre 10 au Chapitre 12, vous apprendrez à développer une couche de pré-
sentation avec JSF 2.0. Après une présentation de la spécification au Chapitre 10,
le Chapitre 11 s’intéresse à la construction d’une page web avec JSF et Facelets.
Le 12, quant à lui, explique comment interagir avec un "backend" EJB et comment
naviguer entre les pages.
Enfin, les derniers chapitres vous présenteront différents moyens d’échanger des
informations avec d’autres systèmes. Le Chapitre 13 montre comment échanger des
messages asynchrones avec JMS (Java Message Service) et les MDB (Message-Dri-
ven Beans) ; le Chapitre 14 s’intéresse aux services web SOAP tandis que le 15 est
consacré aux services web REST.

Téléchargement et exécution du code

Les exemples de ce livre sont conçus pour être compilés avec le JDK 1.6, déployés
sur le serveur d’applications GlassFish  V3 et stockés dans la base de données
Derby. Le Chapitre 1 explique comment installer tous ces logiciels et chaque cha-
pitre montre comment compiler, déployer, exécuter et tester les composants selon la
technologie employée. Tous les codes des exemples ont été testés sur la plate-forme
Windows, mais ni sur Linux ni sur OS X. Les codes sources de ces exemples sont
disponibles sur le site des éditions Pearson (www.pearson.fr), sur la page consacrée
à cet ouvrage.

Contacter l’auteur

Pour toute question sur le contenu de ce livre, sur le code ou tout autre sujet, contac-
tez-moi à l’adresse antonio.goncalves@gmail.com. Vous pouvez également visiter
mon site web, http://www. antoniogoncalves.org.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
1
Tour d’horizon de Java EE 6

De nos jours, les entreprises évoluent dans une compétition à l’échelle mondiale.
Elles ont besoin pour résoudre leurs besoins métiers d’applications qui deviennent
de plus en plus complexes. À notre époque de mondialisation, les sociétés sont pré-
sentes sur les différents continents, fonctionnent 24 heures sur 24, 7 jours sur 7 via
Internet et dans de nombreux pays  ; leurs systèmes doivent être internationalisés
et savoir traiter plusieurs monnaies et des fuseaux horaires différents – tout ceci
en réduisant les coûts, en améliorant le temps de réponse des services, en stockant
les données sur des supports fiables et sécurisés et en offrant différentes interfaces
­graphiques à leurs clients, employés et fournisseurs.
La plupart des sociétés doivent combiner ces défis innovants avec leur système
d’information existant tout en développant en même temps des applications B2B
(business to business) pour communiquer avec leurs partenaires. Il n’est pas rare
non plus qu’une société doive coordonner des données stockées à divers endroits,
traitées par plusieurs langages de programmation et acheminées via des protocoles
différents. Évidemment, ceci ne doit pas faire perdre d’argent, ce qui signifie qu’il
faut empêcher les pannes du système et toujours rester disponible, sécurisé et évo-
lutif. Les applications d’entreprise doivent faire face aux modifications et à la com-
plexité tout en étant robustes. C’est précisément pour relever ces défis qu’a été créé
Java Enterprise Edition (Java EE).
La première version de Java EE (connue sous le nom de J2EE) se concentrait sur les
problèmes que devaient résoudre les sociétés en 1999 : les composants distribués.
Depuis, les logiciels ont dû s’adapter à de nouvelles solutions techniques comme les
services web SOAP ou REST. La plate-forme a donc évolué pour tenir compte de
ces besoins en proposant plusieurs mécanismes standard sous forme de spécifica-
tions. Au cours des années, Java EE a évolué pour devenir plus riche, plus léger, plus
simple d’utilisation et plus portable.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
6 Java EE 6 et GlassFish 3 

Ce chapitre fait un tour d’horizon de Java EE. Après une présentation rapide de son
architecture interne, il présentera les nouveautés de Java EE 6. La seconde partie du
chapitre est consacrée à la mise en place de votre environnement de développement
pour que vous puissiez vous-même mettre en œuvre les extraits de code présentés
dans ce livre.

Présentation de Java EE

Lorsque l’on veut traiter une collection d’objets, on ne commence pas par dévelop-
per sa propre table de hachage : on utilise l’API des collections. De même, lorsque
l’on a besoin d’une application transactionnelle, sécurisée, interopérable et distri-
buée, on ne développe pas des API de bas niveau : on utilise la version entreprise de
Java (Java EE). Tout comme l’édition standard de Java (Java SE, Standard Edition)
permet de traiter les collections, Java EE fournit des moyens standard pour traiter les
transactions via Java Transaction API (JTA), les messages via Java Message Service
(JMS) ou la persistance via Java Persistence API (JPA). Java EE est un ensemble de
spécifications pour les applications d’entreprise ; il peut donc être considéré comme
une extension de Java  SE destinée à faciliter le développement d’applications
­distribuées, robustes, puissantes et à haute disponibilité.
Java EE 6 est une version importante. Non seulement elle marche dans les pas de
Java EE  5 pour fournir un modèle de développement simplifié, mais elle ajoute
également de nouvelles spécifications et apporte des profils et de l’élagage pour
alléger ce modèle. La sortie de Java EE 6 coïncide avec le dixième anniversaire de la
plate-forme entreprise : elle combine donc les avantages du langage Java avec l’ex-
périence accumulée au cours de ces dix dernières années. En outre, elle tire profit
du dynamisme de la communauté open-source et de la rigueur du JCP. Désormais,
Java EE est une plate-forme bien documentée, avec des développeurs expérimentés,
une communauté d’utilisateurs importante et de nombreuses applications déployées
sur les serveurs d’entreprises. C’est un ensemble d’API permettant de construire des
applications multi-tier reposant sur des composants logiciels standard ; ces compo-
sants sont déployés dans différents conteneurs offrant un ensemble de services.

Un peu d’histoire

Dix ans permettent de se faire une idée de l’évolution de Java EE (voir Figure 1.1),
qui s’est d’abord appelé J2EE. La première version, J2EE 1.2, a été initialement déve-
loppée par Sun et est apparue en 1999 sous la forme d’une spécification contenant dix

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  7

Java Specification Requests (JSR). À cette époque, on parlait beaucoup de CORBA :


c’est la raison pour laquelle J2EE 1.2 a été créé en ayant à l’esprit la création de sys-
tèmes distribués. Les Enterprise Java Beans (EJB) introduits alors permettaient de
manipuler des objets service distants avec ou sans état et, éventuellement, des objets
persistants (beans entités). Ils étaient construits selon un modèle distribué et tran-
sactionnel utilisant le protocole sous-jacent RMI-IIOP (Remote Method Invocation-
Internet Inter-ORB Protocol). La couche web utilisait les servlets et les JavaServer
Pages (JSP) et les messages étaient envoyés via JMS.

Figure 1.1 Profil Web


EoD
Historique de J2EE/Java EE. Facilité de Java EE 6
développement
Java EE 5
Services
web
J2EE 1.4
Robuste, Élagage
évolutif Conteneur
Application intégrable
d'entreprise J2EE 1.3 JAX-RS
J2EE 1.2 Validation
des beans

Servlet Annotations
JSP Injection
Project JPE Profil web
EJB Services web JPA
JMS EJB CMP Gestion WS-*
RMI/IIOP JCA Déploiement JSF

Mai 1998 Dec 1999 Sept 2001 Nov 2003 Mai 2006 Q3 2009
10 specs 13 specs 20 specs 23 specs 28 specs

INFO

CORBA est apparu vers 1988, précisément parce que les systèmes d’entreprise commençaient
à être distribués (Tuxedo, CICS, par exemple). Les EJB puis J2EE lui ont emboîté le pas, mais dix
ans plus tard. Lorsque J2EE est apparu pour la première fois, CORBA avait déjà gagné son sta-
tut industriel, mais les sociétés commençaient à utiliser des solutions "tout-Java" et l’approche
neutre de CORBA vis-à-vis des langages de programmation est donc devenu redondante.

À partir de J2EE 1.3, la spécification a été développée par le Java Community Pro-
cess (JCP) en réponse à la JSR 58. Le support des beans entité est devenu obliga-
toire et les EJB ont introduit les descripteurs de déploiement XML pour stocker les
métadonnées (qui étaient jusqu’alors sérialisées dans un fichier avec EJB 1.0). Cette
version a également réglé le problème du surcoût induit par le passage des para-
mètres par valeur avec les interfaces distantes en introduisant les interfaces locales

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
8 Java EE 6 et GlassFish 3 

et en passant les paramètres par référence. J2EE Connector Architecture (JCA) a été
ajoutée afin de connecter J2EE aux EIS (Enterprise Information Systems).

INFO

JCP est une organisation ouverte créée en 1998 afin de définir les évolutions de la plate-
forme Java. Lorsqu’il identifie le besoin d’un nouveau composant ou d’une nouvelle API,
l’initiateur (appelé "leader de la spécification") crée une JSR et forme un groupe d’experts.
Ce groupe, formé de représentants de diverses sociétés ou organisations, ainsi que de per-
sonnes privées, est responsable du développement de la JSR et doit délivrer : 1) une spé-
cification qui explique les détails et définit les bases de la JSR ; 2) une implémentation de
référence (RI, Reference Implementation) ; et 3) un kit de test de compatibilité (TCK, Tech-
nology Compatibility Kit), c’est-à-dire un ensemble de tests que devront satisfaire toutes les
implémentations avant de pouvoir prétendre qu’elles sont conformes à la spécification. Une
fois qu’elle a été acceptée par le comité exécutif (EC, Executive Committee), la spécification
est fournie à la communauté pour être implémentée. En réalité, Java  EE est une JSR qui
­chapeaute d’autres JSR et c’est la raison pour laquelle on parle souvent de JSR umbrella.

J2EE 1.4 (JSR 151), qui est sorti en 2003, a ajouté vingt spécifications et le support
des services web. EJB 2.1 permettait ainsi d’invoquer des beans de session à partir
de SOAP/HTTP. Un service de temporisation a également été ajouté pour permettre
aux EJB d’être appelés à des moments précis ou à intervalles donnés. Cette version
fournissait un meilleur support pour l’assemblage et le déploiement des applications.
Bien que ses supporters lui aient prédit un grand avenir, toutes les promesses de
J2EE ne se sont pas réalisées. Les systèmes créés grâce à lui étaient trop complexes
et les temps de développement, souvent sans commune mesure avec les exigences
de l’utilisateur. J2EE était donc considéré comme un modèle lourd, difficile à tester,
à déployer et à exécuter. C’est à cette époque que des frameworks comme Struts,
Spring ou Hibernate ont commencé à émerger et à proposer une nouvelle approche
dans le développement des applications. Heureusement, Java EE 5 (JSR 244) fit son
apparition au deuxième trimestre de 2006 et améliora considérablement la situa-
tion. Il s’inspirait des frameworks open-source en revenant à un bon vieil objet Java
(POJO, Plain Old Java Object). Les métadonnées pouvaient désormais être définies
grâce à des annotations et les descripteurs XML devenaient facultatifs. Du point de
vue des développeurs, EJB 3 et la nouvelle spécification JPA représentèrent donc
plus un bond prodigieux qu’une évolution de la plate-forme. JSF (JavaServer Faces)
fit également son apparition comme framework standard de la couche présentation
et JAX-WS 2.0 remplaça JAX-RPC comme API pour les services web SOAP.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  9

Aujourd’hui, Java EE 6 (JSR 316) poursuit sur cette voie en appliquant les concepts
d’annotation, de programmation POJO et la politique "convention plutôt que confi-
guration" à toute la plate-forme, y compris la couche web. Il fournit également un
grand nombre d’innovations comme la toute nouvelle API JAX-RS 1.1, simplifie
des API matures comme EJB 3.1 et en enrichit d’autres comme JPA 2.0 ou le service
de temporisation. Les thèmes principaux de Java EE 6 sont la portabilité (en standar-
disant le nommage JNDI, par exemple), la dépréciation de certaines spécifications
(via l’élagage) et la création de sous-ensembles de la plate-forme au moyen de pro-
fils. Dans ce livre, nous présenterons toutes ces améliorations et montrerons com-
ment Java Enterprise Edition est devenu à la fois bien plus simple et bien plus riche.

Standards

Java EE repose sur des standards. C’est une spécification centrale qui chapeaute un cer-
tain nombre d’autres JSR. Vous pourriez vous demander pourquoi les standards sont si
importants puisque certains des frameworks Java les plus utilisés (Struts, Spring, etc.)
ne sont pas standardisés. La raison est que les standards, depuis l’aube des temps,
facilitent la communication et les échanges – des exemples de standards bien connus
concernent les langues, la monnaie, le temps, les outils, les trains, les unités de mesure,
l’électricité, le téléphone, les protocoles réseau et les langages de programmation.
Quand Java est apparu, le développement d’une application web ou d’entreprise passait
généralement par l’utilisation d’outils propriétaires : on créait son propre framework
ou l’on s’enfermait en choisissant un framework commercial propriétaire. Puis vint
l’époque des frameworks open-source, qui ne reposent pas toujours sur des stan-
dards ouverts. Vous pouvez donc utiliser une solution open-source qui vous enferme
dans une seule implémentation ou en choisir une qui implémente les standards et qui
sera alors portable. Java EE fournit des standards ouverts implémentés par plusieurs
frameworks commerciaux (WebLogic, Websphere, MQSeries, etc.) ou open-source
(GlassFish, JBoss, Hibernate, Open JPA, Jersey, etc.) pour gérer les transactions, la
sécurité, les objets à état, la persistance des objets, etc. Aujourd’hui plus que jamais
dans l’histoire de Java EE, votre application peut être déployée sur n’importe quel
serveur d’applications conforme, moyennant quelques modifications mineures.

Architecture

Java EE est un ensemble de spécifications implémentées par différents conteneurs.


Ces conteneurs sont des environnements d’exécution Java EE qui fournissent cer-

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
10 Java EE 6 et GlassFish 3 

tains services aux composants qu’ils hébergent : gestion du cycle de vie, injection de
dépendances, etc. Les composants doivent respecter des contrats bien définis pour
communiquer avec l’infrastructure de Java EE et avec les autres composants, et ils
doivent être assemblés en respectant un certain standard (fichiers archives) avant
d’être déployés. Java EE étant un surensemble de la plate-forme Java SE, les API de
cette dernière peuvent donc être utilisées par n’importe quel composant de Java EE.
La Figure 1.2 présente les relations logiques qui relient les conteneurs. Les flèches
représentent les protocoles utilisés par un conteneur pour accéder à un autre. Le
conteneur web, par exemple, héberge les servlets qui peuvent accéder aux EJB via
le protocole RMI-IIOP.

Figure 1.2
<<executionEnvironment>> <<executionEnvironment>>
Conteneurs standard Conteneur web Conteneur EJB

de Java EE. <<component>>


Servlet RMI / IIOP <<component>>
EJB
<<component>>
JSF

SSL
HTTP RMI / IIOP
HTTP SSL
RMI / IIOP

<<executionEnvironment>> Conteneur
Conteneur Applets d'applications client

<<component>> <<component>>
Applet Application

Composants

L’environnement d’exécution de Java EE définit quatre types de composants que


doivent supporter toutes les implémentations :
■■ Les applets sont des applications graphiques exécutées dans un navigateur web.
Elles utilisent l’API Swing pour fournir des interfaces utilisateurs puissantes.
■■ Les applications sont des programmes exécutés sur un client. Il s’agit le plus
souvent d’interfaces graphiques ou de programmes non interactifs qui ont accès
à toutes les fonctionnalités de la couche métier de Java EE.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  11

■■ Les applications web (composées de servlets, de filtres de servlet, d’écouteurs


d’événements web, de pages JSP et de JSF) s’exécutent dans un conteneur web
et répondent aux requêtes HTTP envoyées par les clients web. Les servlets
­permettent également de mettre en place des services web SOAP et REST.
■■ Les EJB (Enterprise Java Beans) sont des composants permettant de traiter
la logique métier en modèle transactionnel. On peut y accéder localement et à
­distance via RMI (ou HTTP pour les services web SOAP et REST).

Conteneurs

L’infrastructure de Java EE est découpée en domaines logiques appelés conteneurs


(voir Figure 1.2). Chacun d’eux joue un rôle spécifique, supporte un ensemble d’API
et offre des services à ses composants (sécurité, accès aux bases de données, gestion
des transactions, injection de ressources, etc.).
Les conteneurs cachent les aspects techniques et améliorent la portabilité. Selon le
type d’application que vous voudrez construire, vous devrez comprendre les possibi-
lités et les contraintes de chaque conteneur. Si, par exemple, vous devez développer
une couche de présentation web, vous écrirez une application JSF et la déploierez
dans un conteneur web, non dans un conteneur EJB. Si, en revanche, vous voulez
qu’une application web appelle une couche métier, vous devrez sûrement utiliser à
la fois un conteneur web et un conteneur EJB.
La plupart des navigateurs web fournissent des conteneurs d’applets pour exécuter
les composants applets. Lorsque vous développez des applets, vous pouvez donc
vous concentrer sur l’aspect visuel de l’application puisque le conteneur vous four-
nit un environnement sécurisé grâce à un modèle de protection appelé "bac à sable" :
le code qui s’exécute dans ce bac à sable n’est pas autorisé à en sortir, ce qui signifie
que le conteneur empêchera un code téléchargé sur votre machine locale d’accéder
aux ressources de votre système, comme les processus ou les fichiers.
Le conteneur d’applications client (ACC, application client container) contient un
ensemble de classes et de bibliothèques Java ainsi que d’autres fichiers afin d’ajou-
ter l’injection, la gestion de la sécurité et le service de nommage aux applications
Java SE (applications Swing, traitements non interactifs ou, simplement, une classe
avec une méthode main()). ACC communique avec le conteneur EJB en utilisant
le protocole RMI-IIOP et avec le conteneur web via le protocole HTTP (pour les
services web, notamment).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
12 Java EE 6 et GlassFish 3 

Le conteneur web (ou conteneur de servlets) fournit les services sous-jacents per-
mettant de gérer et d’exécuter les composants web (servlets, JSP, filtres, écouteurs,
pages JSF et services web). Il est responsable de l’instanciation, de l’initialisation et
de l’appel des servlets et du support des protocoles HTTP et HTTPS. C’est lui qu’on
utilise pour servir les pages web aux navigateurs des clients.
Le conteneur EJB est responsable de la gestion de l’exécution des beans entreprise
contenant la couche métier de votre application Java EE. Il crée de nouvelles ins-
tances des EJB, gère leur cycle de vie et fournit des services comme les transactions,
la sécurité, la concurrence, la distribution, le nommage ou les appels asynchrones.

Services

Les conteneurs fournissent les services sous-jacents à leurs composants. En tant


que développeur, vous pouvez donc vous concentrer sur l’implémentation de la
logique métier au lieu de résoudre les problèmes techniques auxquels sont exposées
les applications d’entreprise. La Figure 1.3 montre les services fournis par chaque
conteneur.

Conteneur web Conteneur EJB


JSP JSF Servlet RMI/IIOP EJB
Métadonnées WS
Métadonnées WS

Services web
Services web

Connecteurs
Connecteurs

JAX-RPC
JAX-RPC

JavaMail
JavaMail

JAX-WS
JAX-WS

JAX-RS

JASP C
JAX-RS

Gestion
JASP C

Gestion

JACC
JAXR
JACC
JAXR

JSTL

JMS
JMS

JSF

JPA
JTA
JPA
JTA

SAAJ SAAJ
Java SE Java SE

HTTP HTTP SSL RMI/IIOP


SSL
Conteneur
d'applications client
Application cliente
Conteneur
Métadonnées WS

Applets JDBC
Services web
JAX-RPC
JAX-WS

Applet
Gestion

JDBC
JAXR
JMS

JPA

Base de
Java SE données

SAAJ
Java SE

Figure 1.3
Services fournis par les conteneurs.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  13

Les conteneurs web et EJB, par exemple, fournissent des connecteurs pour accé-
der à EIS, alors que le conteneur d’applet ou ACC ne le font pas. Java EE offre les
­services suivants :
■■ JTA. Ce service offre une API de démarcation des transactions utilisée par le
conteneur et l’application. Il sert également d’interface entre le gestionnaire de
transactions et un gestionnaire de ressource au niveau SPI (Service Provider
Interface).
■■ JPA. Fournit l’API pour la correspondance modèle objet-modèle relationnel
(ORM, Object-Relational Mapping). JPQL (Java Persistence Query Language)
permet d’interroger les objets stockés dans la base de données sous-jacente.
■■ JMS. Permet aux composants de communiquer de façon asynchrone par passage
de messages. Ce service fournit un système d’envoi de message fiable, point à
point (P2P) ainsi que le modèle publication-abonnement.
■■ JNDI (Java Naming and Directory Interface). Cette API, incluse dans Java SE,
permet d’accéder aux systèmes d’annuaires et de nommage. Votre application
peut l’utiliser pour associer des noms à des objets puis les retrouver dans un
annuaire. Avec elle, vous pouvez parcourir des sources de données, des fabriques
JMS, des EJB et d’autres ressources. Omniprésente dans le code jusqu’à J2EE 1.4,
JNDI est désormais utilisée de façon plus transparente grâce à l’injection.
■■ JavaMail. Le but de cette API consiste à simplifier l’envoi de courrier élec­
tronique par les applications.
■■ JAF (JavaBeans Activation Framework). Cette API, incluse dans Java SE, est
un framework pour la gestion des données des différents types MIME. Elle est
utilisée par JavaMail.
■■ XML. La plupart des composants Java EE peuvent éventuellement être déployés
à l’aide de descripteurs de déploiements XML, et les applications doivent sou-
vent manipuler des documents XML. JAXP (Java API for XML Processing) per-
met d’analyser des documents XML à l’aide des API SAX et DOM, ainsi que
pour XSLT. StAX (Streaming API for XML) est une API d’analyse XML par flux.
■■ JCA. Les connecteurs permettent d’accéder à EIS à partir d’un composant
Java EE, que ce soient des bases de données, des mainframes ou des programmes
ERP (Enterprise Resource Planning).
■■ Sécurité. JAAS (Java Authentication and Authorization Service) fournit les ser-
vices permettant d’authentifier les utilisateurs et d’assurer le contrôle de leurs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

14 Java EE 6 et GlassFish 3 

droits d’accès. JACC (Java Authorization Service Provider Contract for Containers)
définit un contrat entre un serveur d’application Java  EE et un fournisseur de
service d’autorisation, ce qui permet de greffer des fournisseurs de services d’au-
torisation personnalisés dans n’importe quel produit Java EE.
■■ Services web. Java EE reconnaît les services web SOAP et REST. JAX-WS
(Java API for XML Web Services) remplace JAX-RPC (Java API for XML-based
RPC) et fournit le support des protocoles SOAP et HTTP. JAX-RS (Java API for
RESTful Web Services) fournit le support des services web reposant sur REST.
■■ Gestion. Java EE définit des API pour gérer les conteneurs et les serveurs à
l’aide d’un bean d’entreprise spécialisé. L’API JMX (Java Management Exten-
sions) fournit également une aide à la gestion.
■■ Déploiement. La spécification de déploiement Java EE définit un contrat entre
les outils de déploiement et les produits Java EE afin de standardiser le déploie-
ment des applications.

Protocoles réseau

Comme le montre la Figure 1.3 (voir la section "Platform Overview" de la spécifica-


tion Java EE 6), les composants déployés dans les conteneurs peuvent être invoqués
via différents protocoles. Une servlet déployée dans un conteneur web, par exemple,
peut être appelée avec HTTP ou comme un service web avec un point terminal EJB
déployé dans un conteneur EJB. Voici la liste des protocoles reconnus par Java EE :
■■ HTTP. HTTP est le protocole du Web, omniprésent dans les applications
modernes. L’API côté client est définie par le paquetage java.net de Java SE.
L’API côté serveur de HTTP est définie par les servlets, les JSP et les interfaces
JSF, ainsi que par les services web SOAP et REST. HTTPS est une combinaison
de HTTP et du protocole SSL (Secure Sockets Layer).
■■ RMI-IIOP. RMI (Remote Method Invocation) permet d’appeler des objets dis-
tants indépendamment du protocole sous-jacent – avec Java SE, le protocole natif
est JRMP (Java Remote Method Protocol). RMI-IIOP est une extension de RMI
permettant de l’intégrer à CORBA. IDL (Java interface description language)
permet aux composants des applications Java EE d’invoquer des objets CORBA
externes à l’aide du protocole IIOP. Les objets CORBA peuvent avoir été écrits
dans différents langages (Ada, C, C++, Cobol, etc.) et, bien sûr, en Java.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  15

Paquetages

Pour être déployés dans un conteneur, les composants doivent d’abord être empa-
quetés dans une archive au format standard. Java SE définit les fichiers jar (Java
Archive), qui permettent de regrouper plusieurs fichiers (classes Java, descripteurs
de déploiement ou bibliothèques externes) dans un seul fichier compressé (reposant
sur le format ZIP). Java EE définit différents types de modules ayant leur propre
format de paquetage reposant sur ce format jar commun.
Un module d’application client contient des classes Java et d’autres fichiers de res-
sources empaquetés dans un fichier jar. Ce fichier peut être exécuté dans un environ-
nement Java SE ou dans un conteneur d’application client. Comme tous les autres
formats d’archive, le fichier jar contient éventuellement un répertoire META-INF décri-
vant l’archive. Le fichier META-INF/MANIFEST.MF, notamment, permet de décrire les
données du paquetage. S’il est déployé dans un ACC, le descripteur de déploiement
peut éventuellement se trouver dans le fichier META-INF/application-client.xml.
Un module EJB contient un ou plusieurs beans de session et/ou des beans pilotés par des
messages (MDB, Message Driven Bean) assemblés dans un fichier jar (souvent appelé
fichier jar EJB). Ce fichier peut éventuellement contenir un descripteur de déploiement
META-INF/ejb-jar.xml et ne peut être déployé que dans un conteneur EJB.

Un module d’application web contient des servlets, des JSP, des pages JSF, des
services web ainsi que tout autre fichier web associé (pages HTML et XHTML,
feuilles de style CSS, scripts JavaScript, images, vidéos, etc.). Depuis Java EE 6,
un module d’application web peut également contenir des beans EJB Lite (un sous-
ensemble de l’API des EJB que nous décrirons au Chapitre 6). Tous ces composants
sont assemblés dans un fichier jar portant l’extension .war (souvent désigné sous le
terme de fichier war, ou Web Archive). L’éventuel descripteur de déploiement est
défini dans le fichier WEB-INF/web.xml. Si le fichier war contient des beans EJB Lite,
l’archive peut également contenir un descripteur de déploiement décrit par WEB-
INF/ejb-jar.xml. Les fichiers .class Java se trouvent dans le répertoire WEB-INF/
classes et les fichiers jar dépendants sont dans le répertoire WEB-INF/lib.

Un module entreprise peut contenir zéro ou plusieurs modules d’applications web,


zéro ou plusieurs modules EJB et d’autres bibliothèques classiques ou externes.
Toutes ces composantes sont assemblées dans une archive entreprise (un fichier jar
portant l’extension .ear) afin que le déploiement de ces différents modules se fasse
simultanément et de façon cohérente. Le descripteur de déploiement facultatif d’un
module entreprise est défini dans le fichier META-INF/application.xml. Le réper-
toire spécial lib sert à partager les bibliothèques entre les modules.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
16 Java EE 6 et GlassFish 3 

Java Standard Edition

Il est important de bien comprendre que Java EE est un surensemble de Java SE


(Java Standard Edition). Ceci signifie donc que toutes les fonctionnalités du langage
Java et toutes ses API sont également disponibles dans Java EE.
Java SE 6 est apparu officiellement le 11 décembre 2006. Cette version a été déve-
loppée sous le contrôle de la JSR 270 ; elle apporte de nombreuses fonctionnalités
supplémentaires et poursuit l’effort de simplification de la programmation initié
par Java SE 5 (autoboxing, annotations, généricité, énumérations, etc.). Java SE 6
fournit de nouveaux outils de diagnostic, de gestion et de surveillance des applica-
tions ; il améliore l’API JMX et simplifie l’exécution des langages de scripts dans
la machine virtuelle Java (JVM, Java Virtual Machine). Le but de cet ouvrage n’est
pas de présenter Java SE 6 : consultez l’abondante documentation disponible sur le
langage si vous pensez ne pas assez le maîtriser. Un bon point de départ est le livre
de Dirk Louis et Peter Müller, Java SE 6 (Pearson, 2007).

Spécifications de Java EE 6

Java EE 6 est une spécification centrale définie par la JSR 316 qui contient vingt-
huit autres spécifications. Un serveur d’application souhaitant être compatible avec
Java EE 6 doit donc implémenter toutes ces spécifications. Les Tableaux 1.1 à 1.5
énumèrent toutes leurs versions et leurs numéros de JSR. Certaines spécifications
ont été élaguées, ce qui signifie qu’elles seront peut-être supprimées de Java EE 7.

Tableau 1.1 : Spécification de Java Enterprise Edition

Spécification Version JSR URL


Java EE 6.0 316 http://jcp.org/en/jsr/detail?id=316

Tableau 1.2 : Spécifications des services web

Spécification Version JSR URL Élaguée


JAX-RPC 1.1 101 http://jcp.org/en/jsr/detail?id=101 X
JAX-WS 2.2 224 http://jcp.org/en/jsr/detail?id=224

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  17

Tableau 1.2 : Spécifications des services web (suite)

Spécification Version JSR URL Élaguée


JAXB 2.2 222 http://jcp.org/en/jsr/detail?id=222
JAXM 1.0 67 http://jcp.org/en/jsr/detail?id=67
StAX 1.0 173 http://jcp.org/en/jsr/detail?id=173
Services web 1.2 109 http://jcp.org/en/jsr/detail?id=109
Métadonnées des 1.1 181 http://jcp.org/en/jsr/detail?id=181
services web
JAX-RS 1.0 311 http://jcp.org/en/jsr/detail?id=311
JAXR 1.1 93 http://jcp.org/en/jsr/detail?id=93 X

Tableau 1.3 : Spécifications web

Spécification Version JSR URL Élaguée


JSF 2.0 314 http://jcp.org/en/jsr/detail?id=314
JSP 2.2 245 http://jcp.org/en/jsr/detail?id=245
JSTL (JavaServer 1.2 52 http://jcp.org/en/jsr/detail?id=52
Pages Standard Tag
Library)
Servlet 3.0 315 http://jcp.org/en/jsr/detail?id=315
Expression Language 1.2 245 http://jcp.org/en/jsr/detail?id=245

Tableau 1.4 : Spécification Entreprise

Spécification Version JSR URL Élaguée


EJB 3.1 318 http://jcp.org/en/jsr/detail?id=318
JAF 1.1 925 http://jcp.org/en/jsr/detail?id=925
JavaMail 1.4 919 http://jcp.org/en/jsr/detail?id=919
JCA 1.6 322 http://jcp.org/en/jsr/detail?id=322
JMS 1.1 914 http://jcp.org/en/jsr/detail?id=914
JPA 2.0 317 http://jcp.org/en/jsr/detail?id=317
JTA 1.1 907 http://jcp.org/en/jsr/detail?id=907

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
18 Java EE 6 et GlassFish 3 

Tableau 1.5 : Gestion, sécurité et autres spécifications

Spécification Version JSR URL Élaguée


JACC 1.1 115 http://jcp.org/en/jsr/detail?id=115
Validation Bean 1.0 303 http://jcp.org/en/jsr/detail?id=303
Annotations 1.0 250 http://jcp.org/en/jsr/detail?id=250
communes
Déploiement 1.2 88 http://jcp.org/en/jsr/detail?id=88 X
d’applications
Java EE
Gestion Java EE 1.1 77 http://jcp.org/en/jsr/detail?id=77 X
Interface de 1.0 196 http://jcp.org/en/jsr/detail?id=196
fournisseur de service
d’authentification
pour les conteneurs
Support du débogage 1.0 45 http://jcp.org/en/jsr/detail?id=45
pour les autres
langages

Nouveautés de Java EE 6

Maintenant que vous connaissez l’architecture interne de Java EE, vous pourriez
vous demander ce qu’apporte Java EE 6. Le but principal de cette version est de
poursuivre la simplification de la programmation introduite par Java EE  5. Avec
Java EE 5, les EJB, les entités persistantes et les services web ont été remodelés afin
d’utiliser une approche plus orientée objet (via des classes Java implémentant des
interfaces Java) et pour se servir des annotations pour définir les métadonnées (les
descripteurs de déploiement XML sont donc devenus facultatifs). Java EE 6 pour-
suit dans cette voie et applique les mêmes paradigmes à la couche web. Aujourd’hui,
un bean géré par JSF est une classe Java annotée avec un descripteur XML.
Java EE 6 s’applique également à simplifier la plate-forme à l’aide de profils et en
supprimant certaines technologies obsolètes. Il ajoute des fonctionnalités supplé-
mentaires aux spécifications existantes (en standardisant, par exemple, les beans de
session singletons) et en ajoute de nouvelles (comme JAX-RS). Plus que jamais,
les applications Java EE 6 sont portables entre les conteneurs grâce aux noms JNDI
standard et à un conteneur EJB intégré.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  19

Plus léger

Le groupe d’experts pour Java EE 6 a relevé un défi intéressant : comment alléger


la plate-forme tout en lui ajoutant des spécifications supplémentaires ? Aujourd’hui,
un serveur d’applications doit implémenter vingt-huit spécifications pour être
conforme à Java EE  6. Un développeur doit donc connaître des milliers d’API,
certaines n’étant même plus pertinentes puisqu’elles ont été marquées comme éla-
guées. Pour rendre la plate-forme plus légère, le groupe d’experts a donc introduit
les profils, l’élagage et EJB Lite (un sous-ensemble des fonctionnalités complètes
d’EJB uniquement destiné aux interfaces locales, aux transactions et à la sécurité).
Nous étudierons EJB Lite plus en détail au Chapitre 6.

Élagage
La première version de Java EE est apparue en 1999 et, depuis, chaque nouvelle ver-
sion a ajouté son lot de nouvelles spécifications (comme on l’a vu à la Figure 1.1).
Cette inflation est devenue un problème en termes de taille, d’implémentation et
d’apprentissage. Certaines fonctionnalités n’étaient pas très bien supportées ou peu
déployées parce qu’elles étaient techniquement dépassées ou que d’autres solutions
avaient vu le jour entre-temps. Le groupe d’experts a donc décidé de proposer la
suppression de certaines fonctionnalités via l’élagage (pruning).
Java EE 6 a adopté ce mécanisme d’élagage (également appelé "marquage pour
suppression"), suivant en cela le groupe Java SE. Ce mécanisme consiste à proposer
une liste de fonctionnalités qui pourraient ne plus être reconduites dans Java EE 7.
Aucun de ces éléments n’est supprimé dans la version courante. Certaines fonction-
nalités seront remplacées par des spécifications plus récentes (les beans entités, par
exemple, sont remplacés par JPA) et d’autres quitteront simplement la spécifica-
tion Java EE 7 pour continuer d’évoluer comme des JSR indépendantes (les JSR 88
et 77, par exemple). Ceci dit, les fonctionnalités élaguées suivantes sont toujours
présentes dans Java EE 6 :
■■ EJB 2.x Entity Beans CMP (partie de la JSR 318). Ce modèle de composants
persistants complexe et lourd des beans entités d’EJB 2.x a été remplacé par JPA.
■■ JAX-RPC (JSR 101). Il s’agissait de la première tentative de modéliser les ser-
vices web SOAP comme des appels RPC. Cette spécification a désormais été
remplacée par JAX-WS, qui est bien plus simple à utiliser et plus robuste.
■■ JAXR (JSR 93). JAXR est l’API dédiée aux communications avec les registres
UDDI. Ce dernier étant peu utilisé, JAXR devrait quitter Java EE et continuer
d’évoluer comme une JSR distincte.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
20 Java EE 6 et GlassFish 3 

■■ Java EE Application Deployment (JSR 88). La JSR  88 est une spécification


que les développeurs d’outils peuvent utiliser pour le déploiement sur les serveurs
d’applications. Cette API n’ayant pas reçu beaucoup de soutien de la part des édi-
teurs, elle devrait quitter Java EE et continuer d’évoluer comme une JSR distincte.
■■ Java EE Management (JSR 77). Comme la JSR 88, la JSR 77 était une tenta-
tive de créer des outils de gestion des serveurs d’applications.

Profils

Les profils sont une innovation majeure de l’environnement Java EE  6. Leur but
principal consiste à réduire la taille de la plate-forme pour qu’elle convienne mieux
aux besoins du développeur. Quelles que soient la taille et la complexité de l’appli-
cation que vous développez aujourd’hui, vous la déploierez sur un serveur d’appli-
cations qui vous offre les API et les services de vingt-huit spécifications. L’une des
principales critiques adressées à Java EE est qu’il était trop lourd : les profils ont
donc été conçus pour régler ce problème. Comme le montre la Figure 1.4, les profils
sont des sous-ensembles ou des surensembles de la plate-forme et peuvent chevau-
cher cette dernière ou d’autres profils.

Figure 1.4
Profils de la plate-forme Java EE 6 complet

Java EE.
Profil X

Profil web

Profil Y

Java EE 6 définit un seul profil : le profil web. Son but consiste à permettre au déve-
loppeur de créer des applications web avec l’ensemble de technologies approprié.
Web Profile  1.0 est spécifié dans une JSR distincte  ; c’est le premier profil de la
plate-forme Java EE 6. D’autres seront créés dans le futur (on pourrait penser à un
profil minimal ou à un profil de portail). Le profil web, quant à lui, évoluera à son
rythme et nous pourrions disposer d’une version 1.1 ou 1.2 avant la sortie de Java
EE 7. Nous verrons également apparaître des serveurs d’applications compatibles
Web Profile 1.0 et non plus compatibles Java EE 6. Le Tableau 1.6 énumère les spéci-
fications contenues dans ce profil web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  21

Tableau 1.6 : Spécifications de Web Profile 1.0

Spécification Version JSR URL


JSF 2.0 314 http://jcp.org/en/jsr/detail?id=314
JSP 2.2 245 http://jcp.org/en/jsr/detail?id=245
JSTL 1.2 52 http://jcp.org/en/jsr/detail?id=52
Servlet 3.0 315 http://jcp.org/en/jsr/detail?id=315
Expression 1.2
Language
EJB Lite 3.1 318 http://jcp.org/en/jsr/detail?id=318
JPA 2.0 317 http://jcp.org/en/jsr/detail?id=317
JTA 1.1 907 http://jcp.org/en/jsr/detail?id=907
Annotations 1.0 250 http://jcp.org/en/jsr/detail?id=250
communes

Plus simple d’utilisation

Outre l’allègement de la plate-forme, un autre but de Java EE 6 était également de le


rendre plus simple d’utilisation. Le choix de cette version a été d’appliquer ce para-
digme à la couche web. Les composants de Java EE ont besoin de métadonnées pour
informer le conteneur de leur comportement – avant Java EE 5, la seule solution
était d’utiliser un fichier descripteur de déploiement XML. Avec l’apparition des
annotations dans les EJB, les entités et les services web, il est devenu plus simple
d’assembler et de déployer les composants puisqu’il y a moins de XML à écrire.
Java EE 5 a donc modifié l’architecture de la couche entreprise et les composants
sont passés à un modèle POJO et aux interfaces ; la couche web, en revanche, ne
bénéficiait pas encore de ces améliorations.
Avec Java EE 6, les servlets, les beans gérés par JSF, les convertisseurs JSF, les vali-
dateurs et les moteurs de rendus sont également des classes annotées pouvant éven-
tuellement être assorties de descripteurs de déploiement en XML. Le Listing  1.1
montre le code d’un bean géré par JSF  : vous constaterez que ce n’est, en fait,
qu’une classe Java avec une seule annotation. Si vous connaissez déjà JSF, vous
apprendrez avec plaisir que, dans la plupart des cas, le fichier faces-config.xml
est devenu facultatif (si vous ne connaissez pas JSF, vous le découvrirez aux Cha-
pitres 10, 11 et 12).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
22 Java EE 6 et GlassFish 3 

Listing 1.1 : Un bean géré par JSF


@ManagedBean
public class BookController {

@EJB
private BookEJB bookEJB;

private Book book = new Book();


private List<Book> bookList = new ArrayList<Book>();

public String doCreateBook() {


book = bookEJB.createBook(book);
bookList = bookEJB.findBooks();
return "listBooks.xhtml";
}

// Getters, setters
}

Les EJB sont également plus simples à développer en Java EE 6. Comme le montre
le Listing 1.2, une simple classe annotée sans interface suffit désormais pour accé-
der localement à un EJB. Les EJB peuvent également être déployés directement
dans un fichier war sans avoir été au préalable assemblés dans un fichier jar. Toutes
ces améliorations font des EJB les composants transactionnels les plus simples, qui
peuvent servir aussi bien à des applications web minimales qu’à des applications
d’entreprise complexes.

Listing 1.2 : EJB sans état


@Stateless
public class bookEJB {

@PersistenceContext(unitName = "chapter01PU")
private EntityManager em;

public Book findBookById(Long id) {


return em.find(Book.class, id);
}

public Book createBook(Book book) {


em.persist(book);
return book;
}
}

Plus riche

D’un côté, Java EE 6 s’est allégé en introduisant les profils  ; de l’autre, il s’est
enrichi en ajoutant de nouvelles spécifications et en améliorant celles qui existaient

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  23

déjà. Les services web REST ont fait leur chemin dans les applications modernes
et Java EE 6 suit donc les besoins des entreprises en ajoutant la nouvelle spécifica-
tion JAX-RS. Comme le montre le Listing 1.3, un service web REST est une classe
annotée qui répond à des actions HTTP. Vous en apprendrez plus sur JAX-RS au
Chapitre 15.

Listing 1.3 : Service web REST


@Path("books")
public class BookResource {
@PersistenceContext(unitName = "chapter01PU")
private EntityManager em;

@GET
@Produces({"application/xml", "application/json"})
public List<Book> getAllBooks() {
Query query = em.createNamedQuery("findAllBooks");
List<Book> books = query.getResultList();
return books;
}
}

La nouvelle version de l’API de persistance (JPA 2.0) a été améliorée par l’ajout de
collections de types de données simples (String, Integer, etc.), d’un verrouillage
pessimiste, d’une syntaxe JPQL plus riche, d’une toute nouvelle API de définition
de requêtes et par le support de la mise en cache. JPA est décrite aux Chapitres 2 à
5 de cet ouvrage.
Les EJB sont plus faciles à écrire (avec des interfaces éventuelles) et à assembler
(dans un fichier war) et disposent également de nouvelles fonctionnalités, comme
les appels asynchrones ou un service de temporisation plus élaboré pour planifier les
tâches. Un nouveau composant bean de session singleton fait également son appa-
rition. Comme le montre le Listing 1.4, une simple annotation suffit à transformer
une classe Java en singleton géré par un conteneur (une seule instance du composant
par application). Les Chapitres  6 à  9 vous en apprendront plus sur ces nouvelles
fonctionnalités.

Listing 1.4 : Bean de session singleton


@Singleton
public class CacheEJB {
private Map<Long, Object> cache = new HashMap<Long, Object>();

public void addToCache(Long id, Object object) {


if (!cache.containsKey(id))

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
24 Java EE 6 et GlassFish 3 

cache.put(id, object);
}

public Object getFromCache(Long id) {


if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

La couche présentation s’est également enrichie. JSF 2.0 ajoute en effet le support
d’Ajax et des Facelets (voir Chapitres 10 à 12).

Plus portable

Depuis sa création, le but de Java EE est de permettre le développement et le déploie-


ment des applications sur n’importe quel serveur d’applications, sans modifier son
code ou les fichiers de configuration. En réalité, ce n’est pas aussi simple que cela
puisse paraître : les spécifications ne couvrent pas tous les détails et les implémen-
tations finissent par offrir des solutions non portables. C’est ce qui s’est passé pour
les noms JNDI, par exemple : lorsque l’on déployait un EJB sur GlassFish, JBoss
ou WebLogic, le nom JNDI était différent parce qu’il ne faisait pas partie de la spé-
cification et il fallait donc modifier le code en fonction du serveur d’applications
utilisé. Ce problème précis est désormais corrigé car Java EE 6 spécifie une syntaxe
précise des noms JNDI qui est la même sur tous les serveurs d’applications (voir
Chapitre 7).
Une autre difficulté avec les EJB consiste à pouvoir les tester ou à les utiliser dans
un environnement Java SE. Certains serveurs d’application (comme JBoss) utilisent
pour cela leurs propres implémentations. EJB 3.1 fournit désormais une API stan-
dard pour l’exécution des EJB dans un environnement Java SE (voir Chapitre 7).

L’application CD-Bookstore

Tout au long de ce livre, nous présenterons des extraits de code qui manipulent des
entités, des EJB, des pages JSF, des écouteurs JMS et des services web SOAP ou
REST. Tous ces extraits proviennent de l’application CD-Bookstore, un site web de
commerce en ligne permettant de parcourir un catalogue de livres et de CD pour les
acheter. L’application interagit avec un système bancaire pour valider les cartes de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  25

crédit. Le diagramme des cas d’utilisation présenté à la Figure 1.5 décrit les acteurs
et les fonctionnalités de ce système.

Figure 1.5 CD BookStore
Diagramme des cas
Création
d’utilisation d'un compte
de l’application Gestion du catalogue
Utilisateur
CD-Bookstore. des articles
Parcours
du catalogue

Gestion Recherche
des clients d'un article

Connexion et
Employé déconnexion

Parcours
des commandes Modification
du compte Client

Création Achat
<<Extend>>
de commande d'articles

Validation
<<Include>>
de carte bancaire
Banque

Les acteurs qui interagissent avec le système décrit à la Figure 1.5 sont les suivants :
■■ Les employés de la société, qui doivent gérer à la fois le catalogue des articles et
les informations sur les clients. Ils peuvent également parcourir les commandes.
■■ Les utilisateurs, qui sont les visiteurs anonymes du site qui consultent le catalo-
gue des livres et des CD. Pour acheter un article, ils doivent créer un compte afin
de devenir clients.
■■ Les clients, qui peuvent parcourir le catalogue, modifier les informations de leur
compte et acheter des articles en ligne.
■■ La banque externe, à laquelle le système délègue la validation des cartes de
crédit.

INFO

Le code des exemples de ce livre est disponible sur le site web des éditions Pearson (http://
www.pearson.fr), sur la page consacrée à cet ouvrage.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
26 Java EE 6 et GlassFish 3 

Configuration de l’environnement de travail

Ce livre contient de nombreux extraits de code et la plupart des chapitres se termi-


nent par une section "Récapitulatif". Ces sections expliquent pas à pas comment
développer, compiler, déployer, exécuter et tester unitairement un composant. Pour
cela, vous aurez besoin des logiciels suivants :
■■ JDK 1.6 ;
■■ Maven 2 ;
■■ Junit 4 ;
■■ la base de données Derby 10.5 (alias JavaDB) ;
■■ le serveur d’application GlassFish v3.

JDK 1.6

Le JDK (Java Development Kit) est essentiel au développement et à l’exécution


des exemples de ce livre. Il comprend un certain nombre d’outils, notamment un
compilateur (javac), une machine virtuelle (java), un générateur de documentation
(javadoc), des outils de gestion (Visual VM), etc. Pour l’installer, rendez-vous sur
le site officiel de Sun (http://java.sun.com/javase/downloads), choisissez votre
plate-forme et votre langue, puis téléchargez la distribution appropriée.
Si vous travaillez sous Windows (ce livre ne traite pas des systèmes Linux et OS X),
double-cliquez sur le fichier jdk-6u18-windows-i586-p.exe. Le premier écran vous
demandera d’accepter la licence du logiciel et le second, présenté à la Figure 1.6,
énumérera les modules du JDK que vous pouvez installer (JDK, JRE, base de don-
nées Derby, sources).
Une fois l’installation terminée, il faut initialiser la variable JAVA_HOME avec le réper-
toire où vous avez choisi d’installer le JDK (C:\Program Files\Java\jdk1.6.0_18\
par défaut) puis ajouter le répertoire %JAVA_HOME%\bin à la variable PATH. Pour véri-
fier que Java est bien reconnu par votre système, tapez la commande java -version
(voir Figure 1.7).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  27

Figure 1.6
Configuration de
l’installation du JDK.

Figure 1.7
Affichage de la version
du JDK.

Maven 2

Afin de refléter ce que vous trouverez sur le terrain, nous avons décidé d’utiliser
Maven (http://maven.apache.org) pour construire les exemples de ce livre, bien
que sa description complète sorte du cadre de cet ouvrage (vous trouverez de très
nombreuses ressources consacrées à cet outil sur Internet ou dans les librairies).
Cependant, nous introduirons quelques éléments que vous devez connaître pour
comprendre et utiliser nos exemples.

Historique
La construction d’une application Java EE exige plusieurs opérations :
■■ génération du code et des ressources ;
■■ compilation des classes Java et des classes de test ;
■■ assemblage du code dans une archive (jar, ear, war, etc.) avec, éventuellement,
des bibliothèques jar externes.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
28 Java EE 6 et GlassFish 3 

Effectuer ces tâches manuellement prend du temps et risque de produire des erreurs.
Les équipes de développement ont donc recherché des moyens d’automatiser tout
ce processus.
En 2000, les développeurs Java commencèrent à utiliser Ant (http://ant.apache.
org), qui leur permettait d’écrire des scripts de construction de leurs applications.
Ant est lui-même écrit en Java et offre un grand nombre de commandes qui, à
l’instar de l’outil make d’Unix, sont portables entre les différentes plates-formes.
Les équipes de développement commencèrent donc à créer leurs propres scripts
en fonction de leurs besoins. Cependant, Ant atteignit ses limites lorsque les pro-
jets commencèrent à impliquer des systèmes hétérogènes complexes. Les sociétés
avaient du mal à industrialiser leur système de construction de leurs applications.
Il n’existait pas véritablement d’outil permettant de réutiliser simplement un script
de construction d’un projet à l’autre (le copier/coller était la seule méthode pour y
parvenir).
En 2002, la fondation Apache a mis à disposition Maven, qui non seulement résol-
vait tous ces problèmes mais allait également bien au-delà d’un simple outil de
construction. Maven offre aux projets une solution pour les construire, des biblio-
thèques partagées et une plate-forme évolutive au moyen d’extensions, permettant
ainsi d’assurer la qualité, de produire la documentation, de gérer les équipes de
travail, etc. Fondé sur le principe de "convention plutôt que configuration", Maven
introduit une description de projet standard et un certain nombre de conventions,
notamment une structure de répertoires standardisée (voir Figure  1.8). Avec son
architecture extensible reposant sur des extensions (appelées mojos), Maven offre
de nombreux services.

Figure 1.8
Structure de répertoires
standard de Maven.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  29

Descripteur de projet
Maven repose sur le fait qu’une grande majorité de projets Java et Java EE ont des
besoins similaires lors de la construction des applications. Un projet Maven doit
respecter des standards et définir des fonctionnalités spécifiques dans un descripteur
de projet ou POM (Project Object Model). Ce POM est un fichier XML (pom.xml)
situé à la racine du projet. Comme le montre le Listing 1.5, l’information minimale
permettant de définir l’identité d’un projet est le groupId, l’artifactId, la version
et le type de paquetage.

Listing 1.5 : pom.xml minimal


<?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/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
</project>

Un projet est souvent divisé en différents artéfacts qui sont alors regroupés sous le
même groupId (comme les paquetages en Java) et identifiés de façon unique par l’ar-
tifactId. Le marqueur packaging permet à Maven de produire l’artéfact dans un for-
mat standard (jar, war, ear, etc.). Enfin, version identifie un artéfact au cours de son
évolution (version 1.1, 1.2, 1.2.1, etc.). Maven impose cette numérotation des versions
pour qu’une équipe puisse gérer l’évolution du développement de son projet. Maven
introduit également le concept de versions SNAPSHOT (le numéro de version se termine
alors par la chaîne -SNAPSHOT) pour identifier un artéfact en cours de développement.
Le POM définit bien plus d’informations sur vos projets. Certaines sont purement
descriptives (nom, description, etc.), d’autres concernent l’exécution de l’applica-
tion, comme la liste des bibliothèques externes qu’elle utilise, etc. Enfin, le fichier
pom.xml précise l’environnement de construction du projet (outils de contrôle de
versions, serveur d’intégration, dépôts d’artéfacts) et tout autre processus spécifique
nécessaire à la construction du projet.

Gestion des artéfacts


Maven ne se contente pas de construire des artéfacts : il permet également de les
archiver et de les partager. Pour ce faire, il utilise un dépôt local sur le disque dur

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
30 Java EE 6 et GlassFish 3 

(%USER_HOME%/ .m2/repository par défaut) où il stocke tous les artéfacts manipulés


par les descripteurs du projet. Ce dépôt local (voir Figure 1.9) est rempli soit par
les artéfacts locaux du développeur (monProjet-1.1.jar, par exemple) soit par des
artéfacts externes (glassfish-3.0.jar, par exemple) que Maven télécharge à partir
de dépôts distants. Par défaut, Maven utilise le dépôt principal situé à l’URL http://
repo1.maven.org/maven2 pour télécharger les artéfacts manquants.

Figure 1.9
Exemple de dépôt local.

Comme le montre le Listing 1.6, un projet Maven déclare ses dépendances dans le


POM (groupId, artifactId, version, type). Si nécessaire, Maven les téléchargera
dans le dépôt local à partir de dépôts distants. En outre, grâce aux descripteurs POM
de ces artéfacts externes, Maven téléchargera également les artéfacts dont ils dépen-
dent, etc. L’équipe de développement n’a donc pas besoin de gérer manuellement les
dépendances des projets : toutes les bibliothèques nécessaires sont automatiquement
ajoutées par Maven.

Listing 1.6 : Dépendances dans le fichier pom.xml


...
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
<scope>provided</scope>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  31

</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...

Les dépendances peuvent avoir une visibilité limitée (désignée par scope) :
■■ test. La bibliothèque sert à compiler et à exécuter les classes de test mais n’est
pas assemblée dans l’artéfact produit.
■■ provided. La bibliothèque est fournie par l’environnement (fournisseur de per-
sistance, serveur d’application, etc.) et ne sert qu’à compiler le code.
■■ compile. La bibliothèque est nécessaire à la compilation et à l’exécution.
■■ runtime. La bibliothèque n’est requise que pour l’exécution mais est exclue
de la compilation (composants JSF ou bibliothèques de marqueurs JSTL, par
exemple).

Modularité des projets


Pour résoudre le problème de la modularité des projets, Maven fournit un méca-
nisme reposant sur des modules, chaque module étant lui-même un projet. Maven
peut ainsi construire un projet composé de plusieurs modules en calculant les dépen-
dances entre eux (voir Figure  1.10). Pour faciliter la réutilisation des paramètres
classiques, les descripteurs POM peuvent hériter des POM des projets parents.

Figure 1.10
Un projet et ses modules.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
32 Java EE 6 et GlassFish 3 

Extensions et cycle de vie


Maven utilise un cycle de vie en plusieurs phases (voir Figure 1.11) : nettoyage des
ressources, validation du projet, production des sources nécessaires, compilation
des classes Java, exécution des classes de test, assemblage du projet et installation
de celui-ci dans le dépôt local. Ce cycle de vie constitue une ossature sur laquelle
viennent s’ajouter les extensions Maven (alias mojos). Ces mojos dépendent du type
de projet (un mojo pour compiler, un autre pour tester, un autre pour construire,
etc.). Dans la description du projet, vous pouvez lier de nouvelles extensions à une
phase du cycle de vie, modifier la configuration d’une extension, etc. Lorsque vous
construisez un client d’un service web, par exemple, vous pouvez ajouter un mojo
qui produit les artéfacts du service web au cours de la phase de production des
sources.

Figure 1.11
Cycle de vie d’un projet.

Installation
Les exemples de ce livre ont été développés avec Maven 2.2.1. Après avoir installé
le JDK 1.6, assurez-vous que la variable JAVA_HOME pointe sur le répertoire de celui-
ci puis téléchargez Maven à partir de l’URL http://maven.apache.org/, dézippez le
fichier sur votre disque dur et ajoutez le répertoire apache-maven-2.2.1/bin à votre
variable PATH.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  33

Puis, dans une fenêtre de commande DOS, tapez mvn -version pour tester votre
installation. Comme le montre la Figure 1.12, Maven devrait afficher sa version et
celle du JDK.

Figure 1.12
Maven affiche
sa version.

Maven a besoin d’un accès Internet pour pouvoir télécharger les extensions et
les dépendances des projets à partir du dépôt principal. Si vous vous trouvez der-
rière un proxy, consultez la documentation pour ajuster votre configuration en
conséquence.

Utilisation
Voici quelques commandes que nous utiliserons pour les exemples de ce livre. Elles
invoquent toute une phase différente du cycle de vie (nettoyage, compilation, instal-
lation, etc.) et utilisent le fichier pom.xml pour ajouter des bibliothèques, personna­
liser la compilation ou ajouter des comportements via des extensions :
■■ mvn clean. Supprime tous les fichiers générés (classes compilées, code produit,
artéfacts, etc.).
■■ mvn compile. Compile les classes Java principales.
■■ mvn test-compile. Compile les classes de test.
■■ mvn test. Compile les classes de test et exécute les tests.
■■ mvn package. Compile et exécute les tests et crée l’archive du paquetage.
■■ mvn install. Construit et installe les artéfacts dans votre dépôt local.
■■ mvn clean install. Nettoie et installe (remarquez que vous pouvez indiquer
plusieurs commandes en les séparant par un espace).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
34 Java EE 6 et GlassFish 3 

INFO

Maven vous permet de compiler, d’exécuter et d’assembler les exemples de ce livre. Cepen-
dant, pour écrire le code, vous aurez besoin d’un environnement de développement inté-
gré (EDI). Personnellement, nous utilisons IntelliJ IDEA de JetBrains, dont vous apercevrez
quelques copies d’écran. Vous pouvez choisir n’importe quel IDE car cet ouvrage ne repose
que sur Maven, non sur des fonctionnalités spécifiques d’IntelliJ IDEA.

JUnit 4

JUnit est un framework open-source pour l’écriture et l’exécution de tests. Parmi ses
fonctionnalités, citons :
■■ les assertions pour tester des résultats attendus ;
■■ les "fixtures" pour partager des données de test communes ;
■■ les lanceurs pour exécuter les tests.
JUnit est une bibliothèque de tests unitaires qui est le standard de facto pour Java ;
elle est assemblée dans un unique fichier jar que vous pouvez télécharger à partir
de l’URL http://www.junit.org/ (ou utilisez la gestion des dépendances de Maven
pour le récupérer). La bibliothèque contient une API complète vous permettant
d’écrire vos tests unitaires, ainsi qu’un outil pour les exécuter. Ces tests unitaires
aident à rendre votre code plus robuste et plus fiable.

Historique
La première version de JUnit a été écrite par Erich Gamma et Kent Beck en 1998.
Elle s’inspirait du framework de test Sunit de Smalltalk, également écrit par Kent
Beck, et est rapidement devenue l’un des frameworks les plus connus du monde Java.
Apportant les avantages des tests unitaires à une grande variété de langages, JUnit a
inspiré une famille d’outils xUnit comme nUnit (.NET), pyUnit (Python), CppUnit
(C++), dUnit (Delphi), et bien d’autres encore. JUnit joue un rôle important dans le
développement piloté par les tests (DPT).

Fonctionnement
Depuis JUnit 4, l’écriture des tests unitaires s’est simplifiée grâce à l’usage des
annotations, des importations statiques et des autres fonctionnalités de Java. Par

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  35

rapport aux versions précédentes, JUnit 4 fournit un modèle de test plus simple, plus
riche et plus facile d’emploi. Il introduit également des initialisations plus souples,
des règles de nettoyage, des délais d’expiration et des cas de tests paramétrables.
Étudions quelques-unes de ses fonctionnalités au moyen d’un exemple simple. Le
Listing 1.7 représente un POJO Customer possédant quelques attributs dont une date
de naissance, des constructeurs, des getters et des setters.

Listing 1.7 : Une classe Customer


public class Customer {
private Long id;
private String firstName;
private String lastName
private String email;
private String phoneNumber;
private Date dateOfBirth;
private Date creationDate;

// Constructeurs, getters, setters


}

La classe CustomerHelper, présentée dans le Listing  1.8, fournit une méthode


­ alculateAge() permettant de calculer l’âge d’un client donné.
c

Listing 1.8 : La classe CustomerHelper


public class CustomerHelper {
private int ageCalcResult;
private Customer customer;

public void calculateAge() {


Date dateOfBirth = customer.getDateOfBirth();
Calendar birth = new GregorianCalendar();
birth.setTime(dateOfBirth);
Calendar now = new GregorianCalendar(2001, 1, 1);
ageCalcResult = now.get(Calendar.YEAR) -
birth.get(Calendar.YEAR);

// Pas encore implémentée


public Date getNextBirthDay() {
return null;
}
public void clear() {
ageCalcResult = 0;
customer = null;
}

// Getters, setters
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
36 Java EE 6 et GlassFish 3 

La méthode calculateAge() utilise l’attribut dateOfBirth pour renvoyer l’âge du


client. La méthode clear() réinitialise l’état de CustomerHelper et la méthode
getNextBirthDay() n’est pas encore implémentée. Cette classe auxiliaire a quelques
défauts : il semble notamment qu’il y ait un bogue dans le calcul de l’âge. Pour tester
la méthode calculateAge(), nous utiliserions la classe JUnit TestCustomerHelper
décrite dans le Listing 1.9.

Listing 1.9 : Classe de test pour CustomerHelper


import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;

public class CustomerHelperTest {


private static CustomerHelper customerHelper =
new CustomerHelper();

@Before
public void clearCustomerHelper() {
customerHelper.clear();
}

@Test
public void notNegative() {
Customer customer = new Customer();
customer.setDateNaissance(new GregorianCalendar(1975, 5,
27).getTime());

customerHelper.setCustomer(customer);
customerHelper.calculateAge();

int calculatedAge = customerHelper.getAgeCalcResult();


assert calculatedAge >= 0;
}

@Test
public void expectedValue() {
int expectedAge = 33;

Calendar birth = new GregorianCalendar();


birth.roll(Calendar.YEAR, expectedAge * (-1));
birth.roll(Calendar.DAY_OF_YEAR, -1);

Customer customer = new Customer();


customer.setDateOfBirth(birth.getTime());

customerHelper.setCustomer(customer);
customerHelper.calculateAge();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  37

assertTrue(customerHelper.getAgeCalcResult() == expectedAge);
}

@Test(expected = NullPointerException.class)
public void emptyCustomer() {
Customer customer = new Customer();

customerHelper.setCustomer(customer);
customerHelper.calculateAge();

assertEquals( customerHelper.getAgeCalcResult(), -1);


}

@Ignore("not ready yest")


@Test
public void nextBirthDay() {
// to do..
}
}

La classe de test du Listing 1.9 contient quatre méthodes de test. La méthode expec-


tedValue() échouera car il y a un bogue dans le calcul de l’âge effectué par la classe
CustomerHelper. La méthode nextBirthDay() est ignorée car elle n’est pas encore
implémentée. Les deux autres méthodes réussiront. emptyCustomer() s’attend à ce
que la méthode lance une exception NullPointerException.

Méthodes de test
Avec JUnit 4, les classes de test n’ont pas besoin d’hériter d’une classe quelconque.
Pour être exécutée comme un cas de test, une classe JUnit doit au moins posséder
une méthode annotée par @Test. Si vous tentez d’exécuter une classe qui ne com-
porte pas au moins une méthode @Test, vous obtiendrez une erreur (java.lang.
Exception: No runnable methods).

Une méthode de test doit utiliser l’annotation @Test, renvoyer void et ne prendre
aucun paramètre. Ces contraintes sont vérifiées lors de l’exécution et leur non-res-
pect provoque la levée d’une exception. L’annotation @Test peut prendre un para-
mètre facultatif expected pour indiquer que la méthode de test concernée doit lever
une exception. Si elle ne le fait pas ou si l’exception est différente de celle annoncée,
le test échoue. Dans notre exemple, une tentative de calculer l’âge d’un client vide
doit provoquer la levée d’une exception NullPointerException.
La méthode nextBirthDay() n’est pas implémentée dans le Listing 1.9 mais vous ne
souhaitez pas pour autant que ce test échoue : vous voulez simplement l’ignorer. Pour
ce faire, il suffit d’ajouter l’annotation @Ignore avant ou après l’annotation @Test.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
38 Java EE 6 et GlassFish 3 

Les lanceurs de tests signaleront le nombre de tests ignorés, ainsi que le nombre de
tests qui ont réussi ou échoué. Si vous voulez indiquer la raison pour laquelle un test
a été ignoré, vous pouvez éventuellement passer un paramètre String contenant le
message adéquat.

Méthodes d’assertions
Les cas de test doivent vérifier que les objets sont conformes à ce qui est attendu.
JUnit dispose pour cela d’une classe Assert contenant plusieurs méthodes. Pour
l’utiliser, vous devez soit utiliser la notation préfixe (Assert.assertEquals(), par
exemple) soit importer statiquement la classe Assert (c’est ce que nous avons fait
dans le Listing 1.9). Comme vous pouvez le constater avec la méthode notNega-
tive(), vous pouvez aussi vous servir du mot-clé assert de Java.

Fixtures
Les fixtures sont des méthodes permettant d’initialiser et de libérer n’importe quel
objet au cours des tests. JUnit utilise les annotations @Before et @After pour exécu-
ter du code respectivement avant et après chaque test. Les méthodes annotées par @
Before et @After peuvent porter n’importe quel nom (clearCustomerHelper() ici)
et une même classe de test peut en avoir plusieurs. JUnit utilise également les anno-
tations @BeforeClass et @AfterClass pour exécuter un code spécifique une seule
fois par classe. Ces méthodes doivent être uniques et statiques et sont très pratiques
pour allouer et libérer des ressources coûteuses.

Lancement de JUnit
Pour exécuter le lanceur de JUnit, vous devez ajouter le fichier jar de JUnit à votre
variable CLASSPATH (ou ajouter une dépendance Maven). Vous pouvez alors lancer
vos tests via la commande Java suivante :
java –ea org.junit.runner.JUnitCore
„ com.apress.javaee6.CustomerHelperTest

Notez que, lorsque l’on utilise le mot-clé assert, il faut préciser le paramètre -ea ;
sinon les assertions seront ignorées.
Cette commande produira le résultat suivant :
JUnit version 4.5
..E.I
Time: 0.016
There was 1 failure:

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  39

1) expectedValue(com.apress.javaee6.CustomerHelperTest) java.lang.
AssertionError: at
„ CustomerHelperTest.expectedValue (CustomerHelperTest.java:52)

FAILURES!!!
Tests run: 3, Failures: 1

La première information affichée est le numéro de version de JUnit (4.5, ici). Puis
JUnit indique le nombre de tests exécutés (trois, ici) et le nombre d’échecs (un seul
dans cet exemple). La lettre I indique qu’un test a été ignoré.

Intégration de JUnit
Actuellement, JUnit est très bien intégré à la plupart des EDI (IntelliJ IDEA,
Eclipse, NetBeans, etc.). Avec ces environnements, JUnit utilise généralement la
couleur verte pour indiquer les tests qui ont réussi et le rouge pour signaler les
échecs. La plupart des EDI fournissent également des outils pour faciliter la création
des classes de test.
JUnit est également intégré à Maven via l’extension Surefire utilisée au cours de la
phase de test. Cette extension exécute les classes de tests JUnit d’une application et
produit des rapports aux formats texte et XML. Pour lancer les tests JUnit via cette
extension, faites la commande suivante :
mvn test

Derby 10.5

Initialement nommé Cloudscape, le système de base de données Derby écrit en Java


a été offert par IBM à la fondation Apache et est devenu open-source. De son côté,
Sun Microsystems a produit sa propre distribution sous le nom de Java DB. Malgré
une empreinte mémoire réduite (2 Mo), Derby est un système de base de données
relationnelle entièrement fonctionnel qui supporte les transactions et peut aisément
s’intégrer dans n’importe quelle solution Java.
Derby a deux modes différents  : intégré ou serveur réseau. Le mode intégré cor-
respond au lancement de Derby par une simple application Java mono-utilisateur :
en ce cas, il s’exécute dans la même JVM que l’application. C’est ce mode que
nous utiliserons dans ce livre pendant les tests unitaires. Le mode serveur réseau
correspond au lancement de Derby sous forme de processus séparé, fournissant une
connectivité multi-utilisateurs. Nous utiliserons ce mode lorsque nous exécuterons
les applications.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
40 Java EE 6 et GlassFish 3 

Installation
L’installation de Derby est très simple ; en fait, vous constaterez qu’il est déjà ins-
tallé puisqu’il est fourni avec le JDK 1.6 – au cours de l’installation de ce dernier
(voir Figure 1.6), l’assistant propose d’installer par défaut Java DB. S’il n’est pas
installé sur votre machine, vous pouvez récupérer les binaires à partir de l’URL
http://db.apache.org.
Lorsque Derby est installé, configurez la variable DERBY_HOME pour qu’elle contienne
le répertoire où il a été placé sur votre système, puis ajoutez %DERBY_HOME%\bin à
votre variable PATH. Pour lancer Derby en mode serveur réseau, exécutez le script
%DERBY_HOME%\bin\startNetworkServer.bat : des informations s’afficheront alors
sur la console pour indiquer, par exemple, le numéro du port sur lequel il attend les
connexions (1527 par défaut).
Derby est fourni avec plusieurs programmes utilitaires, dont sysinfo. Ouvrez une
session DOS, tapez sysinfo et vous devriez voir apparaître des informations sur
votre environnement Java et Derby, analogues à celles de la Figure 1.13.
Figure 1.13
Résultat de sysinfo après
l’installation de Derby.

Derby fournit plusieurs outils (dans le sous-répertoire bin) permettant d’interagir


avec la base de données. Le plus simple est probablement ij, qui permet de saisir
des commandes SQL, et dblook, qui permet de visualiser une partie ou la totalité du
langage de définition des données (LDD) d’une base.
Assurez-vous d’avoir lancé le serveur réseau Derby et tapez la commande ij à
l’invite de commande. Puis saisissez les commandes suivantes pour créer une base
de données et une table, insérer des données dans cette table et l’interroger :
ij> connect ’jdbc:derby://localhost:1527/Chapter01DB;create=true’;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  41

Cette commande vous connecte à la base de données Chapter01DB. Comme celle-


ci n’existe pas encore, nous utilisons le paramètre create=true pour demander sa
création.
ij> create table customer (custID int primary key,
> firstname varchar(20), lastname varchar(20));

Cette commande crée une table customer avec une clé primaire et deux colonnes
varchar(20) pour stocker le prénom et le nom de chaque client. Vous pouvez ­afficher
la description de cette table à l’aide de la commande suivante :
ij> describe customer;
COLUMN_NAME |TYPE_NAME|DEC&|NUM&|COLUM&|COLUMN_DEF|CHAR_OCTE&|IS_NULL&
--------------------------------------------------------------------------
CUSTID |INTEGER |0 |10 |10 |NULL |NULL |NO
FIRSTNAME |VARCHAR |NULL|NULL|20 |NULL |40 |YES
LASTNAME |VARCHAR |NULL|NULL|20 |NULL |40 |YES

Maintenant que la table est créée, vous pouvez y ajouter des données à l’aide
­d’instructions insert :
ij> insert into customer values (1, ’Fred’, ’Chene’);
ij> insert into customer values (2, ’Sylvain’, ’Verin’);
ij> insert into customer values (3, ’Robin’, ’Riou’);

Vous pouvez ensuite utiliser toute la puissance de SQL pour obtenir, trier ou regrouper
des données :
ij> select count(*) from customer;
1
-----------
3

1 ligne sélectionnée
ij> select * from customer where custid=3;
CUSTID |FIRSTNAME |LASTNAME
---------------------------------------------------
3 |Robin |Riou

1 ligne sélectionnée
ij> exit;

Pour obtenir le LDD de la table créée, sortez d’ij et lancez dblook pour interroger
la base de données Chapter01DB :
C:\> dblook -d ’jdbc:derby://localhost:1527/Chapter01DB’
-- Horodatage : 2010-01-29 19:21:09.379
-- La base de données source est : Chapter01DB
-- L’URL de connexion est :
„ jdbc:derby://localhost:1527/Chapter01DB
-- appendLogs: false

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
42 Java EE 6 et GlassFish 3 

-- ----------------------------------------------
-- Instructions DDL pour tables
-- ----------------------------------------------

CREATE TABLE "APP"."CUSTOMER" ("CUSTID" INTEGER NOT NULL,


„ "FIRSTNAME" VARCHAR(20), "LASTNAME" VARCHAR(20));

-- ----------------------------------------------
-- Instructions DDL pour clés
-- ----------------------------------------------

-- primaire/unique
ALTER TABLE "APP"."CUSTOMER" ADD CONSTRAINT "SQL100129191119440" PRIMARY KEY
("CUSTID");

GlassFish v3
Bien qu’il s’agisse d’un serveur d’applications assez récent, GlassFish est déjà uti-
lisé par un grand nombre de développeurs et de sociétés. Non seulement il est l’im-
plémentation de référence de la technologie Java EE, mais c’est également lui que
vous obtenez lorsque vous téléchargez le SDK Java EE de Sun. Vous pouvez égale-
ment déployer des applications critiques sur GlassFish – en plus d’être un produit,
GlassFish est également une communauté réunie autour de l’Open Source sur le site
http://glassfish.org. Cette communauté est très réactive sur les listes de diffusion
et les forums.

Historique
Les origines de GlassFish remontent aux premiers jours de Tomcat, lorsque Sun
et le groupe JServ firent don de cette technologie à la fondation Apache. Depuis,
Sun a continué à utiliser Tomcat dans différents produits. En 2005, Sun lança le
projet GlassFish, qui avait pour but le développement d’un serveur d’applications
entièrement certifié Java EE. Sa première version, la 1.0, vit le jour en mai 2006.
Le conteneur web de GlassFish hérite beaucoup de Tomcat (en fait, une application
qui s’exécute sur Tomcat devrait également s’exécuter avec GlassFish sans avoir à
la modifier).
GlassFish v2 est apparu en septembre 2007 et a reçu depuis de nombreuses mises
à jour. Il s’agit de la version la plus déployée actuellement. GlassFish s’efforce de
ne pas modifier les habitudes des utilisateurs entre ses versions majeures et de ne
pas imposer de modification du code. En outre, il n’y a aucune différence de qualité
entre les versions "communautaire" et "commerciale" : les utilisateurs payants ont
accès à des correctifs et à des outils de gestion supplémentaires (GlassFish Enter-

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  43

prise Manager), mais la version open-source (http://glassfish. org) et la version


commerciale (http://www.sun.com/appserver) ont été testées de la même façon, ce
qui facilite le basculement vers une version commerciale à n’importe quel moment
dans le cycle du projet.
Dans ce livre, nous utiliserons GlassFish  v3. Comme il est d’usage avec l’Open
Source, des versions quotidiennes et des versions "préludes" sont disponibles. Les
buts principaux de cette nouvelle version majeure de GlassFish est la modularisation
des fonctionnalités essentielles avec l’introduction d’un noyau reposant sur OSGi et
un support complet de Java EE 6.

INFO

L’équipe de GlassFish a fait un effort considérable pour réaliser une documentation complète
et à jour en produisant de nombreux guides : Quick Start Guide, Installation Guide, Adminis-
tration Guide, Administration Reference, Application Deployment Guide, Developer’s Guide,
etc. Vous pouvez les lire à l’URL http:// wiki.glassfish.java.net/Wiki.jsp?page=GlassFishDocs.
Consultez également les FAQ, les How-To et le forum GlassFish pour obtenir encore plus
d’informations.

Architecture de GlassFish v3
En tant que programmeur d’application (et non en tant que développeur de Glass-
Fish), vous n’avez pas besoin de comprendre son architecture interne, mais son
architecture générale et ses choix techniques peuvent vous intéresser. À partir de la
version préliminaire de GlassFish v3, le serveur d’applications est construit sur un
noyau modulaire reposant sur OSGi. GlassFish s’exécute directement au-dessus de
l’implémentation de Felix d’Apache, mais devrait également fonctionner avec les
runtimes OSGi Equinox ou Knopflerfish. HK2 (Hundred-Kilobyte Kernel) abstrait
le module système OSGi pour fournir les composants qui peuvent être vus comme
des services. Ceux-ci peuvent être découverts et injectés en cours d’exécution. Pour
l’instant, OSGi n’est pas exposé aux développeurs Java EE.

INFO

OSGi est un standard pour la gestion et la découverte dynamique des composants. Les ap-
plications ou les composants peuvent être installés, lancés, arrêtés, mis à jour et désinstal-
lés à chaud, sans nécessiter un redémarrage. Les composants peuvent également détecter
dynamiquement l’ajout ou la suppression de services et s’adapter en conséquence  ; Felix
d’Apache, Equinox et Knopflerfish sont des implémentations d’OSGi.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
44 Java EE 6 et GlassFish 3 

Cette modularité et cette extensibilité permettent à GlassFish v3 de passer d’un


simple serveur web attendant des commandes d’administration à un runtime plus
puissant moyennant un simple déploiement d’artéfacts comme des fichiers war (un
conteneur web est chargé et lancé, puis l’application est déployée) ou des fichiers
jar EJB (qui chargeront et lanceront dynamiquement le conteneur EJB). En outre, le
serveur initial se lance en quelques secondes (moins de 5 secondes sur une machine
récente) et vous ne payez en temps de démarrage et en consommation mémoire
que ce que vous utilisez. Le lancement du conteneur web à la volée prend environ
3 secondes de plus et les déploiements s’effectuent souvent en moins de 1 seconde.
Tout ceci fait de GlassFish v3 un environnement très apprécié des développeurs.
Quel que soit le nombre de modules que charge dynamiquement GlassFish v3, la
console d’administration, l’interface en ligne de commande et le fichier de confi-
guration centralisé sont tous extensibles et chacun reste unique. Mentionnons éga-
lement le framework Grizzly, qui était au départ un serveur HTTP non bloquant
reposant sur les E/S et qui est désormais devenu l’un des éléments essentiels de
GlassFish, comme le montre la Figure 1.14.

Figure 1.14 Conteneur Conteneur


JSF JPA Metro JMS
web EJB
Architecture
de GlassFish v3. Console de gestion Centre de mises à jour CLI gestion

Service de Injection Grizzly Configuration Surveillance


nommage

Transaction Sécurité Cœur de Glassfish V Déploiement Clustering


(Module Subsystem)
HK2

OSGI

Java SE

Centre de mise à jour


Lorsque vous disposez d’un serveur d’application modulaire, vous pouvez commen-
cer à jouer avec les différents modules pour construire votre propre environnement,
exactement comme vous le feriez avec les EDI et les distributions Linux ou comme
vous le faites avec les extensions de Firefox. Le centre de mise à jour de GlassFish
est un ensemble d’outils graphiques et en ligne de commande qui vous permettent
de gérer cet environnement. La technologie derrière tout ceci s’appelle IPS (Image

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  45

Packaging System, également appelé pkg), le système de paquetage utilisé par Open-
Solaris. Outre l’ensemble de modules fourni par défaut avec GlassFish, l’utilisateur
peut se connecter à différents dépôts pour mettre à jour les fonctionnalités déjà ins-
tallées, en ajouter de nouvelles (support de Grails, conteneur de portlet, etc.), voire
ajouter des applications tierces. Dans un environnement d’entreprise, vous pouvez
configurer votre propre dépôt et utiliser pkg pour lancer l’installation d’un logiciel
reposant sur GlassFish.
En pratique, avec GlassFish v3, le centre de mise à jour est accessible via la console
d’administration, le client graphique qui se trouve dans le répertoire %GLASSFISH_
HOME%\updatetool\bin ou le programme pkg en ligne de commande. Tous trois
vous permettent d’énumérer, d’ajouter et de supprimer des composants à partir d’un
ensemble de dépôts. Dans le cas de pkg (qui se trouve dans le répertoire %GLASS-
FISH_HOME%\pkg\bin), les commandes les plus fréquentes sont pkg list, pkg ins-
tall, pkg uninstall et pkg image-update.

Sous-projets GlassFish
Le serveur d’applications GlassFish étant composé de tant de parties différentes, le
projet a été découpé en sous-projets. Cette décomposition permet de mieux com-
prendre non seulement les différentes parties mais également l’adoption des fonc-
tionnalités individuelles en dehors de l’environnement GlassFish, lorsque l’on est en
mode autonome ou dans un autre conteneur. La Figure 1.15 présente un résumé de
l’architecture des composantes fonctionnelles du serveur d’applications.
OpenMQ, par exemple, est une implémentation open-source de JMS de qualité pro-
fessionnelle. Bien qu’il soit souvent utilisé de façon autonome pour les architectures
orientées messages, OpenMQ peut également être intégré de différentes façons à
GlassFish (in-process, out-of-process, ou distant). Son administration peut s’effec-
tuer via la console d’administration de GlassFish ou par l’interface asadmin en ligne
de commande (voir la section "asadmin"). Le site web de sa communauté se trouve
à l’URL http://openmq.dev.java.net.
Metro est le cœur des services web. Cette pile complète est construite au-dessus
de JAX-WS et lui ajoute des fonctionnalités avancées, comme une sécurité de bout
en bout, un transport optimisé (MTOM, FastInfoset), une messagerie fiable et un
comportement transactionnel pour les services web SOAP. Cette qualité de service
(QoS) pour les services web repose sur des standards (OASIS, W3C), s’exprime
par des politiques et ne nécessite pas l’utilisation d’une nouvelle API en plus de
JAX-WS. Metro est également régulièrement testé avec les implémentations .NET

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
46 Java EE 6 et GlassFish 3 

de Microsoft pour assurer l’interopérabilité entre les deux technologies. Son site
communautaire se trouve à l’URL http://metro.dev.java.net.

Serveur
d'administration
Console Application
d'administration d'administration

Extensions du Instance d'un serveur d'applications


serveur web
Serveur Conteneur Connecteur
HTTP web Java EE EIS
Clients web
Écouteurs Services Java
HTTP web Message Fournisseur
Clients Service de messages
Java/C++/IIOP
ORB Conteneur Gestionnaire
EJB de persistance Serveur
Conteneur JDBC HADB
d'applications
client Écouteurs Gestion
IIOP du cycle Gestionnaire
de vie de transactions
Autres serveurs
d'applications Base de
compatibles Java EE Processus, gestion des threads, contrôle de l'exécution données

Figure 1.15
Composantes fonctionnelles de GlassFish.

Mojarra est le nom de l’implémentation de JSF dans GlassFish ; elle est disponible à
l’URL http:// mojarra.dev.java.net. Jersey est l’implémentation de référence et de
qualité professionnelle pour la nouvelle spécification JAX-RS. Cette spécification et
son implémentation sont des nouveaux venus dans Java EE 6 et GlassFish. En fait,
Jersey 1.0 est disponible via le centre de mise à jour de GlassFish v2 et v3 depuis
sa sortie en 2008.

Administration
GlassFish étant un serveur d’applications complet, il implémente évidemment l’in-
tégralité des spécifications Java EE 6, mais il dispose également de fonctionnalités
supplémentaires comme son administration, qui peut s’effectuer via une interface
web (la "console d’administration") ou au moyen d’asadmin, une interface en ligne
de commande puissante. Quasiment toute sa configuration est stockée dans un
fichier nommé domain.xml (situé dans le répertoire domains\domain1\config), ce
qui simplifie la recherche des erreurs. Ce fichier ne doit pas être modifié manuelle-
ment mais via l’un des deux outils d’administration, qui reposent tous les deux sur
l’instrumentation JMX fournie par GlassFish.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  47

Console d’administration
La console d’administration est une interface web (voir Figure 1.16) pour le serveur
d’applications. Cet outil est destiné à la fois aux administrateurs et aux développeurs
et fournit une représentation graphique des objets gérés par le serveur, une visua-
lisation améliorée des journaux, de l’état du système et de la surveillance des don-
nées. Au minimum, cette console permet de gérer la création et la modification des
configurations (réglage de la JVM, niveau des journaux, réglage du pool et du cache,
etc.), JDBC, JNDI, JavaMail, JMS et les ressources connecteur ainsi que les applica-
tions (déploiement). Dans le profil cluster de GlassFish, la console d’administration
est améliorée pour permettre à l’utilisateur de gérer les clusters, les instances, les
agents nœuds et les configurations de répartition de la charge. Une aide contextuelle
est toujours disponible via le bouton d’aide situé en haut à droite de la fenêtre. Dans
une installation par défaut, la console est accessible après le démarrage de GlassFish
par l’URL http://localhost:4848. À partir de GlassFish v3, il est possible de confi-
gurer un utilisateur anonyme afin d’éviter de devoir s’authentifier. Si cet utilisateur
n’existe pas, une installation typique utilise admin comme nom d’utilisateur et admi-
nadmin comme mot de passe par défaut.

Figure 1.16
Console d’administration web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
48 Java EE 6 et GlassFish 3 

Outil en ligne de commande asadmin


L’interface en ligne de commande asadmin est très puissante et c’est souvent elle
que l’on utilise en production car on peut écrire des scripts pour créer des instances
et des ressources, déployer des applications et surveiller les données d’un système
en cours d’exécution. Cette commande se trouve dans le répertoire bin de GlassFish
et peut gérer plusieurs domaines de serveurs d’application locaux ou distants. Elle
reconnaît plusieurs centaines de commandes mais vous n’en utiliserez probable-
ment qu’une petite partie. Pour en avoir la liste complète, faites asadmin help. Les
commandes utiles dans un profil développeur simple sont asadmin start-domain,
asadmin stop-domain, asadmin deploy, asadmin deploydir et asadmin undeploy.
Si vous faites une erreur de frappe, asadmin vous proposera la commande corres-
pondante la plus proche. Tapez asadmin resource, par exemple, et vous constate-
rez qu’asadmin vous propose les commandes de la Figure 1.17. Avec GlassFish v3,
asadmin dispose d’un historique des commandes et de la complétion de la saisie.

Figure 1.17
Ligne
de commande
asadmin.

Installation de GlassFish
GlassFish v3 peut être installé sous différents profils (chaque profil définit un ensemble
de fonctionnalités et de configurations). Le profil le plus classique en ce qui nous
concerne est le profil développeur. Si vous voulez utiliser les fonctionnalités de cluster
de GlassFish, en revanche, vous devrez soit l’installer sous le profil cluster, soit mettre
à jour votre installation existante en choisissant "Ajouter le support Cluster" à partir de
la console d’administration. Pour le moment, il n’existe que le profil développeur pour
GlassFish v3 (qui est nécessaire pour exécuter les applications Java EE 6).
GlassFish peut être téléchargé via différents mécanismes de distribution. Les choix
les plus évidents consistent à le récupérer à partir de l’URL http://glassfish.org, à

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6  49

l’installer avec le SDK Java EE ou à utiliser l’EDI NetBeans. Nous expliquerons ici
comment le télécharger et l’installer à partir du site communautaire.
Rendez-vous sur la page principale de téléchargement, https://glassfish.dev.java.
net/public/downloadsindex.html, et choisissez GlassFish Server v3. Sélectionnez
l’archive convenant à votre plate-forme et aux besoins de votre système d’exploita-
tion (la distribution Unix fonctionnera avec Linux, Solaris et Mac OS X). L’installation
du programme d’installation lancera l’installateur graphique qui :
■■ vous demande d’accepter les termes de la licence ;
■■ vous demande le répertoire où vous souhaitez installer GlassFish ;
■■ vous permet de configurer un nom d’utilisateur et un mot de passe pour l’admi-
nistrateur (ou crée par défaut un utilisateur anonyme) ;
■■ vous permet de configurer les ports HTTP et d’administration (en vérifiant qu’ils
ne sont pas déjà utilisés) ;
■■ installe et active l’outil de mise à jour (les clients pkg et updatetool).
Puis il décompresse une installation préconfigurée de GlassFish avec une configu-
ration par défaut : le port d’administration est 4848, le port HTTP est 8080 et aucun
utilisateur admin n’est configuré. L’outil de mise à jour n’est pas installé par défaut ;
il le sera à partir du réseau lors du premier démarrage. Lorsqu’il a été correctement
installé, GlassFish peut être lancé avec la ligne de commande asadmin suivante (voir
Figure 1.18).
asadmin start-domain domain1

Vous pouvez ensuite afficher la console d’administration (que nous avons montrée
à la Figure 1.16) en faisant pointer votre navigateur vers http://localhost:4848 ou
aller sur le serveur web par défaut via http://localhost:8080.

INFO

Si vous n’avez qu’un seul domaine, vous pouvez omettre le nom de domaine par défaut et
lancer GlassFish uniquement avec la commande asadmin start-domain. Si vous voulez voir
apparaître le journal à l’écran au lieu de consulter le fichier qui lui est consacré (domains/
domain1/logs/server.log), utilisez la commande asadmin start-domain --verbose.

GlassFish a bien d’autres fonctionnalités à offrir : je vous en montrerai quelques-


unes au cours de ce livre mais je vous laisserai explorer son support des langages
dynamiques (JRuby on Rails, Groovy et Grails, etc.), les services de diagnostic, les

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
50 Java EE 6 et GlassFish 3 

règles de gestion, les propriétés système, la surveillance des données, le flux d’appel
et les différentes configurations de sécurité.

Figure 1.18
Lancement
de GlassFish.

Résumé

Lorsqu’une société développe une application Java et doit ajouter des fonctionnali-
tés professionnelles comme la gestion des transactions, la sécurité, la concurrence
ou la messagerie, Java EE est attractif. Il est standard, les composants sont déployés
dans différents conteneurs qui offrent de nombreux services et fonctionnent avec
plusieurs protocoles. Java EE 6 suit les traces de sa version précédente en ajoutant la
simplicité d’utilisation de la couche web. Cette version est plus légère (grâce à l’éla-
gage, aux profils et à EJB Lite), plus simple d’utilisation (plus besoin d’interfaces
sur les EJB ou d’annotations sur la couche web), plus riche (elle ajoute de nouvelles
spécifications et fonctionnalités) et, enfin, plus portable (elle inclut un conteneur
EJB standardisé et autorise les noms JNDI).
La deuxième partie de ce chapitre a été consacrée à la mise en place de l’envi-
ronnement de développement. Ce livre contient de nombreux extraits de code et
des sections "Récapitulatif". Vous aurez besoin de plusieurs outils et frameworks
pour compiler, déployer, exécuter et tester ces codes : JDK 1.6, Maven 2, JUnit 4,
Derby 10.5 et GlassFish v3.
Ce chapitre vous a donné un bref aperçu de Java EE 6. Les suivants étudieront plus
en détail ses spécifications.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
2
Persistance en Java

Les applications sont composées d’une logique métier, d’interactions avec d’autres
systèmes, d’interfaces utilisateur et... de persistance. La plupart des données mani-
pulées par les applications doivent être stockées dans des bases de données pour
­pouvoir être ensuite récupérées et analysées. Les bases de données sont importantes :
elles stockent les données métier, servent de point central entre les applications et
traitent les données via des triggers ou des procédures stockées. Les données persis-
tantes sont omniprésentes – la plupart du temps, elles utilisent les bases de données
relationnelles comme moteur sous-jacent. Dans un système de gestion de base de
données relationnelle, les données sont organisées en tables formées de lignes et
de colonnes ; elles sont identifiées par des clés primaires (des colonnes spéciales
ne contenant que des valeurs uniques) et, parfois, par des index. Les relations entre
tables utilisent les clés étrangères et joignent les tables en respectant des contraintes
d’intégrité.
Tout ce vocabulaire est totalement étranger à un langage orienté objet comme Java.
En Java, nous manipulons des objets qui sont des instances de classes ; les objets
héritent les uns des autres, peuvent utiliser des collections d’autres objets et, parfois,
se désignent eux-mêmes de façon récursive. Nous disposons de classes concrètes, de
classes abstraites, d’interfaces, d’énumérations, d’annotations, de méthodes, d’at-
tributs, etc. Cependant, bien que les objets encapsulent soigneusement leur état et
leur comportement, cet état n’est accessible que lorsque la machine virtuelle (JVM)
s’exécute : lorsqu’elle s’arrête ou que le ramasse-miettes nettoie la mémoire, tout
disparaît. Ceci dit, certains objets n’ont pas besoin d’être persistants : par données
persistantes, nous désignons les données qui sont délibérément stockées de façon
permanente sur un support magnétique, une mémoire flash, etc. Un objet est persis-
tant s’il peut stocker son état afin de pouvoir le réutiliser plus tard.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
52 Java EE 6 et GlassFish 3 

Il existe différents moyens de faire persister l’état en Java. L’un d’eux consiste à uti-
liser le mécanisme de sérialisation qui consiste à convertir un objet en une suite de
bits : on peut ainsi sérialiser les objets sur disque, sur une connexion réseau (notam-
ment Internet), sous un format indépendant des systèmes d’exploitation. Java fournit
un mécanisme simple, transparent et standard de sérialisation des objets via l’implé-
mentation de l’interface java.io.Serializable. Cependant, bien qu’elle soit très
simple, cette technique est assez fruste : elle ne fournit ni langage d’interrogation ni
support des accès concurrents intensifs ou de la mise en cluster.
Un autre moyen de mémoriser l’état consiste à utiliser JDBC (Java Database Connec-
tivity), qui est l’API standard pour accéder aux bases de données relationnelles. On
peut ainsi se connecter à une base et exécuter des requêtes SQL (Structured Query
Language) pour récupérer un résultat. Cette API fait partie de la plate-forme Java
depuis la version 1.1 mais, bien qu’elle soit toujours très utilisée, elle a tendance
à être désormais éclipsée par les outils de correspondance entre modèle objet et
modèle relationnel (ORM, Object-Relational Mapping), plus puissants.
Le principe d’un ORM consiste à déléguer l’accès aux bases de données relation-
nelles à des outils ou à des frameworks externes qui produisent une vue orientée
objet des données relationnelles et vice versa. Ces outils établissent donc une corres-
pondance bidirectionnelle entre la base et les objets. Différents frameworks fournis-
sent ce service, notamment Hibernate, TopLink et Java Data Objects (JDO), mais il
est préférable d’utiliser JPA (Java Persistence API) car elle est intégrée à Java EE 6.

Résumé de la spécification JPA

JPA 1.0 a été créée avec Java EE 5 pour résoudre le problème de la persistance
des données en reliant les modèles objets et relationnels. Avec Java EE 6, JPA 2.0
conserve la simplicité et la robustesse de la version précédente tout en lui ajoutant
de nouvelles fonctionnalités. Grâce à cette API, vous pouvez accéder à des données
relationnelles et les manipuler à partir des EJB (Enterprise Java Beans), des compo-
sants web et des applications Java SE.
JPA est une couche d’abstraction au-dessus de JDBC, qui fournit une indépendance
vis-à-vis de SQL. Toutes les classes et annotations de cette API se trouvent dans le
paquetage javax.persistence. Ses composants principaux sont les suivants :
■■ ORM, qui est le mécanisme permettant de faire correspondre les objets à des
données stockées dans une base de données relationnelle.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  53

■■ Une API gestionnaire d’entités permettant d’effectuer des opérations sur la base
de données, notamment les opérations CRUD (Create, Read, Update, Delete).
Grâce à elle, il n’est plus nécessaire d’utiliser directement JDBC.
■■ JPQL (Java Persistence Query Language), qui permet de récupérer des données
à l’aide d’un langage de requêtes orienté objet.
■■ Des mécanismes de transaction et de verrouillage lorsque l’on accède de façon
concurrente aux données, fournis par JTA (Java Transaction API). Les transactions
locales à la ressource (non JTA) sont également reconnues par JPA.
■■ Des fonctions de rappel et des écouteurs permettant d’ajouter la logique métier
au cycle de vie d’un objet persistant.

Historique de la spécification

Les solutions ORM existent depuis longtemps, bien avant Java. Des produits comme
TopLink ont commencé à être utilisés avec Smalltalk en 1994, avant de basculer
vers Java. Les produits ORM commerciaux comme TopLink sont donc disponibles
depuis les premiers jours du langage Java. Cependant, bien qu’ils aient prouvé leur
utilité, ils n’ont jamais été standardisés pour cette plate-forme. Une approche com-
parable à ORM a bien été standardisée sous la forme de JDO, mais elle n’a jamais
réussi à pénétrer le marché de façon significative.
En 1998, EJB 1.0 vit le jour et fut ensuite intégré à J2EE 1.2. Il s’agissait d’un com-
posant distribué lourd, utilisé pour la logique métier transactionnelle. CMP (Entity
Container Managed Persistence) fut ensuite ajouté à EJB 1.0 et continua d’évoluer
jusqu’à EJB 2.1 (J2EE 1.4). La persistance ne pouvait prendre place qu’à l’intérieur
d’un conteneur, via un mécanisme d’instanciation complexe utilisant des interfaces
locales ou distantes. Les capacités ORM étaient également très limitées car l’héri-
tage était difficile à traduire en termes relationnels.
Parallèlement au monde J2EE, la solution open-source Hibernate apportait des
modifications surprenantes en terme de persistance car ce framework fournissait un
modèle orienté objet persistant et léger.
Après des années de plaintes à propos des composants Entity CMP 2.x et en réponse
au succès et à la simplicité des frameworks open-source comme Hibernate, le
modèle de persistance de Java EE fut entièrement revu dans Java EE 5 : JPA 1.0 était
né et proposait désormais une approche légère, largement inspirée des principes
de conception d’Hibernate. La spécification JPA 1.0 a donc été intégrée à EJB 3.0
(JSR 220).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
54 Java EE 6 et GlassFish 3 

Aujourd’hui, avec Java EE 6, la seconde version de JPA continue dans cette voie
de la simplicité tout en ajoutant de nouvelles fonctionnalités. Elle a évolué pour
posséder sa propre spécification, la JSR 317.

Nouveautés de JPA 2.0

Si JPA 1.0 était un modèle de persistance entièrement nouveau par rapport à son
prédécesseur Entity CMP 2.x, JPA 2.0 est la suite de JPA 1.0, dont elle conserve
l’approche orientée objet utilisant les annotations et, éventuellement, des fichiers
de correspondance en XML. Cette seconde version ajoute de nouvelles API, étend
JPQL et intègre de nouvelles fonctionnalités :
■■ Les collections de types simples (String, Integer, etc.) et d’objets intégrables
(embeddable) peuvent désormais être associées à des tables distinctes alors
qu’auparavant on ne pouvait associer que des collections d’entités.
■■ Les clés et les valeurs des associations peuvent désormais être de n’importe quel
type de base, des entités ou des objets intégrables.
■■ L’annotation @OrderColumn permet maintenant d’avoir un tri persistant.
■■ La suppression des orphelins permet de supprimer les objets fils d’une relation
lorsque l’objet parent est supprimé.
■■ Le verrouillage pessimiste a été ajouté au verrouillage optimiste, qui existait déjà.
■■ Une toute nouvelle API de définition de requêtes a été ajoutée afin de pouvoir
construire des requêtes selon une approche orientée objet.
■■ La syntaxe de JPQL a été enrichie (elle autorise désormais les expressions case,
par exemple).
■■ Les objets intégrables peuvent maintenant être embarqués dans d’autres objets
intégrables et avoir des relations avec les entités.
■■ La notation pointée a été étendue afin de pouvoir gérer les objets intégrables avec
des relations ainsi que les objets intégrables d’objets intégrables.
■■ Le support d’une nouvelle API de mise en cache a été ajouté.
Nous présenterons en détail toutes ces fonctionnalités aux Chapitres 3, 4 et 5.

Implémentation de référence

EclipseLink 1.1 est une implémentation open-source de JPA 2.0, mais ce framework


souple et puissant supporte également la persistance XML via JAXB (Java XML

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  55

Binding) et d’autres techniques comme SDO (Service Data Objects). Il offre un


ORM, un OXM (Object XML Mapping) et la persistance des objets sur EIS (Enter-
prise Information Systems) à l’aide de JCA (Java EE Connector Architecture).
Les origines d’EclipseLink remontent au produit TopLink d’Oracle, qui a été offert
à la fondation Eclipse en 2006. C’est l’implémentation de référence de JPA et
c’est le framework de persistance que nous utiliserons dans ce livre. Il est égale-
ment désigné sous les termes de fournisseur de persistance ou, simplement, de
fournisseur.

Comprendre les entités

Lorsque l’on évoque l’association d’objets à une base de données relationnelle, la


persistance des objets ou les requêtes adressées aux objets, il est préférable d’utili-
ser le terme d’entités plutôt que celui d’objets. Ces derniers sont des instances qui
existent en mémoire ; les entités sont des objets qui ont une durée de vie limitée
en mémoire et qui persistent dans une base de données. Les entités peuvent être
associées à une base de données, être concrètes ou abstraites, et elles disposent
de l’héritage, peuvent être mises en relation, etc. Une fois associées, ces entités
peuvent être gérées par JPA. Vous pouvez stocker une entité dans la base de don-
nées, la supprimer et l’interroger à l’aide d’un langage de requête (Java Persistence
Query Language, ou JPQL). Un ORM vous permet de manipuler des entités alors
qu’en coulisse c’est à la base de données qu’on accède. Comme nous le verrons,
une entité a un cycle de vie bien défini et, grâce aux méthodes de rappel et aux
écouteurs, JPA vous permet d’associer du code métier à certains événements de ce
cycle.

ORM = Object-Relational Mapping

Le principe d’un ORM consiste à déléguer à des outils ou à des frameworks externes
(JPA, dans notre cas) la création d’une correspondance entre les objets et les tables.
Le monde des classes, des objets et des attributs peut alors être associé aux bases
de données constituées de tables formées de lignes et de colonnes. Cette association
offre une vue orientée objet aux développeurs, qui peuvent alors utiliser de façon
transparente des entités à la place des tables. JPA utilise les métadonnées pour faire
correspondre les objets à une base de données.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
56 Java EE 6 et GlassFish 3 

Les métadonnées sont associées à chaque entité pour décrire son association  :
elles permettent au fournisseur de persistance de reconnaître une entité et d’inter-
préter son association. Ces métadonnées peuvent s’exprimer dans deux formats
différents :
■■ Annotations. Le code de l’entité est directement annoté avec toutes sortes d’an-
notations décrites dans le paquetage javax.persistence.
■■ Descripteurs XML. Ils peuvent être utilisés à la place (ou en plus) des anno-
tations. L’association est définie dans un fichier XML externe qui sera déployé
avec les entités. Cette technique peut être très utile lorsque la configuration de la
base de données varie en fonction de l’environnement, par exemple.
Pour faciliter les correspondances, JPA (comme de nombreuses autres fonctionna-
lités de Java EE 6) utilise le concept de "convention plutôt que configuration" (éga-
lement appelé "configuration par exception" ou "programmation par exception").
Le principe est que JPA utilise un certain nombre de règles de correspondance par
défaut (le nom de la table est le même que celui de l’entité, par exemple) : si ces
règles vous satisfont, vous n’avez pas besoin de métadonnées supplémentaires
(aucune annotation ni XML ne sont alors nécessaires) mais, dans le cas contraire,
vous pouvez adapter la correspondance à vos propres besoins à l’aide des métadon-
nées. En d’autres termes, fournir une configuration est une exception à la règle.
Voyons un exemple. Le Listing 2.1 présente une entité Livre avec quelques attributs.
Comme vous pouvez le constater, certains sont annotés (id, titre et description)
alors que d’autres ne le sont pas.

Listing 2.1 : Une entité Book simple


@Entity
public class Book {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  57

Pour être reconnue comme entité, la classe Book doit être annotée par @javax.
persistence.Entity (ou son équivalent XML). L’annotation @javax.
persistence.Id sert à indiquer la clé primaire, et la valeur de cet identifiant est
automatiquement générée par le fournisseur de persistance (@GeneratedValue).
L’annotation @Column est utilisée avec certains attributs pour adapter la correspon-
dance par défaut des colonnes (title ne peut plus contenir NULL et description a
une longueur de 2 000 caractères). Le fournisseur de persistance pourra ainsi faire
correspondre à l’entité Book une table BOOK (règle de correspondance par défaut),
produire une clé primaire et synchroniser les valeurs des attributs vers les colonnes
de la table. La Figure 2.1 montre cette association entre l’entité et la table.

Figure 2.1 <<entity>>
Book BOOK
L’entité Book est +ID bigint Nullable = false
-id : Long
TITLE varchar(255) Nullable = false
associée à la table -title : String
PRICE double Nullable = true
-price : Float Association
BOOK. DESCRIPTION varchar(2000) Nullable = true
-description : String
ISBN varchar(255) Nullable = true
-nbOfPage : Integer
NBOFPAGE integer Nullable = true
-illustrations : Boolean
ILLUSTRATIONS smallint Nullable = true

Comme nous le verrons au Chapitre 3, cette correspondance est riche et vous per-
met d’associer toutes sortes de choses. Le monde de la programmation orientée
objet abonde de classes et d’associations entre elles (et les collections de classes).
Les bases de données modélisent également les relations, mais différemment : en
utilisant des clés étrangères ou des jointures. JPA dispose donc d’un ensemble de
métadonnées permettant de gérer cette correspondance entre ces deux visions des
relations. L’héritage peut également être traduit : bien que ce soit un mécanisme fré-
quemment utilisé en programmation pour réutiliser le code, ce concept est inconnu
des bases de données relationnelles (elles doivent le simuler avec des clés étrangères
et des contraintes). Même si cette traduction de l’héritage implique quelques contor-
sions, JPA l’autorise et vous offre différentes stratégies pour y parvenir. Nous les
décrirons au Chapitre 3.

Interrogation des entités

JPA permet de faire correspondre les entités à des bases de données et de les inter-
roger en utilisant différents critères. La puissance de cette API vient du fait qu’elle
offre la possibilité d’interroger les entités et leurs relations de façon orientée objet
sans devoir utiliser les clés étrangères ou les colonnes de la base de données sous-
jacente. L’élément central de l’API, responsable de l’orchestration des entités, est le

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
58 Java EE 6 et GlassFish 3 

gestionnaire d’entités : son rôle consiste à gérer les entités, à lire et à écrire dans une
base de données et à autoriser les opérations CRUD simples sur les entités, ainsi que
des requêtes complexes avec JPQL. D’un point de vue technique, le gestionnaire
d’entités n’est qu’une interface dont l’implémentation est donnée par le fournisseur
de persistance, EclipseLink. L’extrait de code suivant montre comment créer un
gestionnaire d’entités et rendre une entité Livre persistante :
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter02PU");
EntityManager em = emf.createEntityManager();
em.persist(livre);

La Figure 2.2 montre comment l’interface EntityManager peut être utilisée par une
classe (Main, ici) pour manipuler des entités (Livre, ici). Grâce à des méthodes
comme persist() et find(), le gestionnaire d’entités masque les appels JDBC
adressés à la base de données, ainsi que les instructions SQL INSERT ou SELECT.

«Interface»
Main EntityManager
SQL / JDBC
+persist(entity : Object) : void Base de
+find(entityClass, : Class<T>, primaryKey : Object) : <T>
données

Book
-id : Long
-title : String
-price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean

Figure 2.2
Le gestionnaire d’entités interagit avec l’entité et la base de données sous-jacente.

Le gestionnaire d’entités permet également d’interroger les entités. Dans ce cas,


une requête JPA est semblable à une requête sur une base de données, sauf qu’elle
utilise JPQL au lieu de SQL. La syntaxe utilise la notation pointée habituelle. Pour
récupérer, par exemple, tous les livres intitulés H2G2, il suffirait d’écrire :
SELECT b FROM Book b WHERE b.title = ’H2G2’

Une instruction JPQL peut exécuter des requêtes dynamiques (créées à l’exécution),
des requêtes statiques (définies lors de la compilation), voire des instructions SQL
natives. Les requêtes statiques, également appelées requêtes nommées, sont définies
par des annotations ou des métadonnées XML. L’instruction JPQL précédente peut,
par exemple, être définie comme une requête nommée sur l’entité Livre.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  59

Le Listing 2.2 montre une entité Book définissant la requête nommée findBookBy-


Title à l’aide de l’annotation @NamedQuery.

Listing 2.2 : Une requête nommée findBookByTitle

@Entity
@NamedQuery(name = "findBookByTitle",
„ query = "SELECT b FROM Book b WHERE b.title =’H2G2’")
public class Book {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Comme nous le verrons au Chapitre 4, la méthode EntityManager.createNamed-


Query() permet d’exécuter la requête et renvoie une liste d’entités Book correspondant
aux critères de recherche.

Méthodes de rappel et écouteurs

Les entités sont simplement des POJO (Plain Old Java Objects) qui sont gérés ou
non par le gestionnaire d’entités. Lorsqu’elles sont gérées, elles ont une identité de
persistance et leur état est synchronisé avec la base de données. Lorsqu’elles ne le
sont pas (elles sont, par exemple, détachées du gestionnaire d’entités), elles peuvent
être utilisées comme n’importe quelle autre classe Java : ceci signifie que les entités
ont un cycle de vie, comme le montre la Figure 2.3. Lorsque vous créez une instance
de l’entité Book à l’aide de l’opérateur new, l’objet existe en mémoire et JPA ne le
connaît pas (il peut même finir par être supprimé par le ramasse-miettes) ; lorsqu’il
devient géré par le gestionnaire d’entités, son état est associé et synchronisé avec
la table BOOK. L’appel de la méthode EntityManager.remove() supprime les don-
nées de la base, mais l’objet Java continue d’exister en mémoire jusqu’à ce que le
ramasse-miettes le détruise.
Les opérations qui s’appliquent aux entités peuvent se classer en quatre catégories :
persistance, mise à jour, suppression et chargement, qui correspondent respective-
ment aux opérations d’insertion, de mise à jour, de suppression et de sélection dans

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
60 Java EE 6 et GlassFish 3 

la base de données. Chaque opération a un événement "Pre" et "Post" (sauf le char-


gement, qui n’a qu’un événement "Post") qui peuvent être interceptés par le gestion-
naire d’entités pour invoquer une méthode métier.

Figure 2.3
Cycle de vie d’une entité.
Existe en mémoire

Détaché Géré Supprimé

Base de
données

Comme nous le verrons au Chapitre 5, il existe donc des annotations @PrePersist,


@PostPersist, etc. Ces annotations peuvent être associées à des méthodes d’entités
(appelées fonctions de rappel) ou à des classes externes (appelées écouteurs). Vous
pouvez considérer les fonctions de rappel et les écouteurs comme des triggers d’une
base de données relationnelle.

Récapitulatif

Maintenant que vous connaissez un peu JPA, EclipseLink, les entités, le gestion-
naire d’entités et JPQL, rassemblons le tout pour écrire une petite application qui
stocke une entité dans une base de données. Nous allons donc écrire une simple
entité Book et une classe Main chargée de stocker un livre. Nous la compilerons avec
Maven 2 et l’exécuterons avec EclipseLink et une base de données cliente Derby.
Pour montrer la simplicité des tests unitaires sur une entité, nous verrons également
comment écrire une classe de test (BookTest) avec un cas de test JUnit 4 et à l’aide
du mode intégré de Derby, qui nous permettra de stocker les données en utilisant une
base de données en mémoire.
Pour respecter la structure de répertoires de Maven, les fichiers devront être placés
dans les répertoires suivants :
■■ src/main/java pour l’entité Book et la classe Main ;
■■ src/main/resources pour le fichier persistence.xml utilisé par la classe Main ;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  61

■■ src/test/java pour la classe BookTest, qui servira aux tests unitaires ;


■■ src/test/resources pour le fichier persistence.xml utilisé par les cas de test ;

■■ pom.xml,le Project Object Model (POM) de Maven, qui décrit le projet et ses
dépendances vis-à-vis d’autres modules et composants externes.

Écriture de l’entité Book

L’entité présentée dans le Listing 2.3 doit être développée sous le répertoire


Book
src/main/java. Elle a plusieurs attributs (un titre, un prix, etc.) de types différents
(String, Float, Integer et Boolean) et certaines annotations JPA :
■■ @Entity informe le fournisseur de persistance que cette classe est une entité et
qu’il devra la gérer.
■■ @Id définit l’attribut id comme étant la clé primaire.
■■ @GeneratedValue informe le fournisseur de persistance qu’il devra produire
automatiquement la clé primaire à l’aide des outils de la base de données
sous-jacente.
■■ @Column précise que le titre ne pourra pas être NULL lorsqu’il sera stocké dans
la base et modifie la longueur maximale par défaut de la colonne description.
■■ @NamedQuery définit une requête nommée qui utilise JPQL pour récupérer tous
les livres de la base.

Listing 2.3 : Une entité Book avec une requête nommée


package com.apress.javaee6.chapter02;
@Entity
@NamedQuery(name = "findAllBooks",
query = "SELECT b FROM Book b")
public class Book {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
62 Java EE 6 et GlassFish 3 

Pour des raisons de lisibilité, nous avons omis ici les constructeurs, les getters et les
setters de cette classe. Comme le montre ce code, hormis les quelques annotations,
Book est un simple POJO. Écrivons maintenant une classe Main qui stockera un livre
dans la base de données.

Écriture de la classe Main

La classe Main présentée dans le Listing 2.4 se trouve dans le même répertoire que
l’entité Livre. Elle commence par créer une instance de Book (avec le mot-clé new de
Java) puis initialise ses attributs. Vous remarquerez qu’il n’y a rien de spécial ici : ce
n’est que du code Java traditionnel. Puis elle utilise la classe Persistence pour obte-
nir une instance d’EntityManagerFactory afin de désigner une unité de persistance
appelée chapter02PU que nous décrirons plus tard dans la section "Unité de persis-
tance pour la classe Main". Cette fabrique permet à son tour de créer une instance
d’EntityManager (la variable em). Comme on l’a déjà mentionné, le gestionnaire
d’entités est l’élément central de JPA car il permet de créer une transaction, de stoc-
ker l’objet Book à l’aide de la méthode EntityManager.persist() puis de valider
la transaction. À la fin de la méthode main() on ferme les objets EntityManager et
EntityManagerFactory afin de libérer les ressources du fournisseur.

Listing 2.4 : Une classe Main pour stocker une entité Book


package com.apress.javaee6.chapter02;
public class Main {

public static void main(String[] args) {

// Crée une instance de Book


Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Comédie de science fiction");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

// Obtention d’un gestionnaire d’entités et d’une transaction


EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter02PU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

// Stocke le livre dans la base

tx.begin();
em.persist(book);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  63

tx.commit();

em.close();
emf.close();
}
}

Là encore, nous avons omis la gestion des exceptions pour des raisons de lisibilité.
Si une exception de persistance survenait, il faudrait annuler la transaction et enre-
gistrer un message dans le journal.

Unité de persistance pour la classe Main

Comme vous pouvez le constater avec la classe Main, l’objet EntityManagerFactory


a besoin d’une unité de persistance appelée chapter02PU qui doit être définie dans
le fichier persistence.xml situé dans le répertoire src/main/resources/META-INF
(voir Listing  2.5). Ce fichier, exigé par la spécification de JPA, est important car
c’est lui qui relie le fournisseur JPA (EclipseLink dans notre cas) à la base de don-
nées (Derby). Il contient toutes les informations nécessaires pour se connecter à la
base (cible, URL, pilote JDBC, nom et mot de passe de l’utilisateur) et informe le
fournisseur du mode de génération de la base (create-tables signifie que les tables
seront créées si elles n’existent pas). L’élément <provider> définit le fournisseur de
persistance – EclipseLink ici.

Listing 2.5 : Le fichier persistence.xml utilisé par la classe Main


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">

<persistence-unit name="chapter02PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>com.apress.javaee6.chapter02.Book</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/chapter02DB;
„ create=true"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
<property name="eclipselink.ddl-generation"
value="create-tables"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
64 Java EE 6 et GlassFish 3 

<property name="eclipselink.logging.level" value="INFO"/>


</properties>
</persistence-unit>
</persistence>

Cette unité de persistance énumère toutes les entités qui doivent être gérées par le
gestionnaire d’entités. Ici, le marqueur <class> désigne l’entité Book.

Compilation avec Maven

Vous disposez maintenant de tous les ingrédients pour lancer l’application : l’entité
Book que vous voulez stocker, la classe Main qui effectue le travail à l’aide d’un
gestionnaire d’entités et l’unité de persistance qui relie l’entité à la base de données
Derby. Pour compiler ce code, nous utiliserons Maven au lieu d’appeler directement
le compilateur javac. Vous devez donc d’abord créer un fichier pom.xml décrivant
le projet et ses dépendances (JPA, notamment). Vous devrez également informer
Maven que vous utilisez Java SE 6 en configurant l’extension maven-compiler-plu-
gin comme cela est décrit dans le Listing 2.6.

Listing 2.6 : Fichier pom.xml de Maven pour compiler, construire, exécuter


et tester l’application
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter02</artifactId>
<version>1.0</version>
<name>chapter02</name>

<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.5.3.0</version>
</dependency>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  65

<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.5.3.0</version> <scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Pour compiler le code, vous avez d’abord besoin de l’API de JPA, qui définit toutes
les annotations et les classes qui se trouvent dans le paquetage javax.persistence.
Vous obtiendrez ces classes dans une archive jar désignée par l’identifiant d’artéfact
javax.persistence et qui sera stockée dans le dépôt Maven. Le runtime Eclipse-
Link (c’est-à-dire le fournisseur de persistance) est défini dans l’identifiant d’ar-
téfact eclipselink. Vous avez également besoin des pilotes JDBC permettant de
se connecter à Derby. L’identifiant d’artéfact derbyclient désigne l’archive jar qui
contient le pilote JDBC pour se connecter à Derby lorsqu’il s’exécute en mode serveur
(il est alors lancé dans un processus séparé et écoute sur un port), tandis que l’iden-
tifiant d’artéfact derby contient les classes pour utiliser Derby comme une base de
données intégrée. Notez que ce dernier est réservé aux tests (<scope>test­</scope>)
et dépend de JUnit 4.
Pour compiler les classes, ouvrez une fenêtre de commande dans le répertoire racine
contenant le fichier pom.xml, puis entrez la commande Maven suivante :
mvn compile

Vous devriez voir apparaître le message BUILD SUCCESSFUL vous informant que la
compilation a réussi. Maven crée alors un sous-répertoire target contenant tous les
fichiers class ainsi que le fichier persistence.xml.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
66 Java EE 6 et GlassFish 3 

Exécution de la classe Main avec Derby

Avant d’exécuter la classe Main, vous devez lancer Derby. Pour ce faire, le moyen
le plus simple consiste à se rendre dans le répertoire %DERBY_HOME%\bin et à lancer
le script startNetworkServer.bat. Derby se lance et affiche les messages suivants
sur la console :
2010-01-31 14:56:54.816 GMT : Le gestionnaire de sécurité a été installé au
moyen de la stratégie de sécurité de serveur de base.
2010-01-31 14:56:55.562 GMT : Apache Derby Serveur réseau - 10.5.3.0 -
(802917) démarré et prêt à accepter les connexions sur le port 1527

Le processus Derby écoute sur le port 1527 et attend que le pilote JDBC lui envoie
une instruction SQL. Pour exécuter la classe Main, vous pouvez utiliser l’interpré-
teur java ou la commande Maven suivante :
mvn exec:java -Dexec.mainClass="com.apress.javaee6.chapter02.Main"

Lorsque vous exécutez la classe Main, plusieurs choses se passent. Tout d’abord,
Derby crée automatiquement la base de données chapter02tDB dès que l’entité Book
est initialisée car nous avions ajouté la propriété create=true à l’URL de JDBC
dans le fichier persistence.xml :
<property name="eclipselink.jdbc.url"
„ value="jdbc:derby://localhost:1527/chapter02DB;create=true"/>
Ce raccourci est très pratique lorsque l’on est en phase de développement car nous
n’avons pas besoin d’écrire un script SQL pour créer la base. Puis la propriété
eclipselink.ddl-generation demande à EclipseLink de créer automatiquement
la table LIVRE. Enfin, le livre est inséré dans cette table (avec un identifiant produit
automatiquement).
em.persist(book);
Utilisons maintenant les commandes Derby pour afficher la structure de la table :
tapez la commande ij dans une fenêtre de commandes (comme on l’a expliqué plus
haut, le répertoire %DERBY_HOME%\bin doit avoir été ajouté à votre variable PATH).
Cette commande lance l’interpréteur de Derby à partir duquel vous pouvez exé-
cuter des commandes pour vous connecter à la base, afficher les tables de la base
chapter02DB (show tables), vérifier la structure de la table BOOK (describe book)
et même consulter son contenu à l’aide d’instructions SQL comme SELECT * FROM
BOOK.

C:\> ij
version ij 10.5

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  67

ij> connect ’jdbc:derby://localhost:1527/chapter02DB’;


ij> show tables;
TABLE_SCHEM |TABLE_NAME |REMARKS
APP |BOOK |
APP |SEQUENCE |
ij> describe book;
COLUMN_NAME |TYPE_NAME|DEC&|NUM&|COLUM&|COLUMN_DEF|CHAR_OCTE&|IS_NULL&
---------------------------------------------------------------------------
ID |BIGINT |0 |10 |19 |NULL |NULL |NO
NBOFPAGE |INTEGER |0 |10 |10 |NULL |NULL |YES
PRICE |DOUBLE |NULL|2 |52 |NULL |NULL |YES
ILLUSTRATIONS |SMALLINT |0 |10 |5 |0 |NULL |YES
DESCRIPTION |VARCHAR |NULL|NULL|2000 |NULL |4000 |YES
ISBN |VARCHAR |NULL|NULL|255 |NULL |510 |YES
TITLE |VARCHAR |NULL|NULL|255 |NULL |510 |NO

Comme nous avons utilisé l’annotation @GeneratedValue pour produire automati-


quement un identifiant dans l’entité Book, EclipseLink a créé une table de séquence
pour stocker la numérotation (table SEQUENCE). En étudiant la structure de la table
BOOK, on constate que JPA a respecté certaines conventions par défaut pour nommer
la table et ses colonnes d’après les noms de l’entité et de ses attributs. L’annotation
@Column a redéfini certaines de ces conventions, comme la longueur de la colonne
description, qui a été fixée à 2000.

Écriture de la classe BookTest

On a reproché aux versions précédentes d’Entity CMP 2.x la complexité de mise en


place des tests unitaires pour les composants persistants. L’un des atouts principaux
de JPA est, justement, que l’on peut aisément tester les entités sans avoir besoin
d’un serveur d’application ou d’une base de données. Que peut-on tester, alors  ?
Les entités n’ont généralement pas besoin d’être testées isolément car la plupart des
méthodes sur les entités sont de simples getters ou setters ; elles contiennent peu de
méthodes métier. Vérifier qu’un setter affecte une valeur à un attribut et que le getter
correspondant permet de récupérer cette même valeur n’apporte pas grand-chose
(sauf si cela permet de détecter un effet de bord dans les getters ou les setters).
Qu’en est-il des tests des requêtes  ? Certains développeurs prétendront qu’il ne
s’agit pas de tests unitaires puisqu’il faut une vraie base de données pour les exé-
cuter. Effectuer des tests séparés avec des objets factices demanderait beaucoup de
travail. En outre, tester une entité à l’extérieur de tout conteneur (EJB ou conteneur
de servlet) aurait des répercussions sur le code car il faudrait modifier la gestion des
transactions. L’utilisation d’une base de données en mémoire et des transactions non
JPA semble donc un bon compromis. Les opérations CRUD et les requêtes JPQL

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
68 Java EE 6 et GlassFish 3 

peuvent en effet être testées avec une base de données très légère qui n’a pas besoin
de s’exécuter dans un processus distinct (il suffit d’ajouter un fichier jar au class-
path). C’est de cette façon que nous exécuterons notre classe BookTest, en utilisant
le mode intégré de Derby.
Maven utilise deux répertoires différents : l’un pour stocker le code de l’application,
un autre pour les classes de test. La classe BookTest, présentée dans le Listing 2.7,
est placée dans le répertoire src/test/java et teste que le gestionnaire d’entités
peut stocker un livre dans la base de données et le récupérer ensuite.

Listing 2.7 : Classe de test qui crée un livre et récupère tous les livres de la base
de données
public class BookTest {
private static EntityManagerFactory emf;
private static EntityManager em;
private static EntityTransaction tx;

@BeforeClass
public static void initEntityManager() throws Exception {
emf = Persistence.createEntityManagerFactory("chapter02PU");
em = emf.createEntityManager();
}

@AfterClass
public static void closeEntityManager() throws SQLException {
em.close();
emf.close();
}

@Before
public void initTransaction() {
tx = em.getTransaction();
}

@Test
public void createBook() throws Exception {

// Création d’une instance de Livre


Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Comédie de science fiction");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  69

// Stocke le livre dans la base de données


tx.begin();
em.persist(book);
tx.commit();
assertNotNull("ID ne doit pas être null", book.getId());

// Récupère tous les livres de la base de données


List<Book> books =
em.createNamedQuery("findAllBooks").getResultList();
assertNotNull(book);
}
}

Comme la classe Main, BookTest doit créer une instance EntityManager à l’aide
d’une fabrique EntityManagerFactory. Pour initialiser ces composants, nous nous
servons des fixtures de JUnit 4 : les annotations @BeforeClass et @AfterClass per-
mettent d’exécuter un code une seule fois, avant et après l’exécution de la classe
– c’est donc l’endroit idéal pour créer et fermer une instance EntityManager. L’an-
notation @Before, quant à elle, permet d’exécuter un certain code avant chaque test
– c’est là que nous créons une transaction.
Le cas de test est représenté par la méthode createBook() car elle est précédée de
l’annotation @Test de JUnit. Cette méthode stocke un livre (en appelant EntityMa-
nager.persist()) et vérifie avec assertNotNull que l’identifiant a bien été produit
automatiquement par EclipseLink. En ce cas, la requête nommée findAllBooks est
exécutée et l’on vérifie que la liste renvoyée n’est pas null.

Unité de persistance pour la classe BookTest

Maintenant que la classe de test est écrite, vous avez besoin d’un autre fichier per-
sistence.xml pour utiliser Derby intégré car le précédent définissait un pilote
JDBC et une URL de connexion Derby en mode serveur réseau. Le fichier src/
test/resources/META-INF/persistence.xml du Listing 2.8 utilise au contraire un
pilote JDBC pour le mode intégré.

Listing 2.8 : Fichier persistence.xml utilisé par la classe BookTest


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">

<persistence-unit name="chapter02PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
70 Java EE 6 et GlassFish 3 

</provider>
<class>com.apress.javaee6.chapter02.Book</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby:chap02DB;create=true"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
<property name="eclipselink.ddl-generation"
value="drop-and-create-tables"/>
<property name="eclipselink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
</persistence>

Il y a d’autres différences entre les deux fichiers persistence.xml. La valeur de


ddl-generation est ici drop-and-create-tables au lieu de create-tables car,
avant de tester, il faut supprimer et recréer les tables afin de repartir sur une struc-
ture de base de données propre. Notez également que le niveau des journaux est
FINE au lieu d’INFO car cela permet d’obtenir plus d’informations au cas où les tests
échoueraient.

Exécution de la classe BookTest avec Derby intégré

L’exécution du test est très simple puisqu’il suffit de se reposer sur Maven. Ouvrez
une fenêtre de commande dans le répertoire où se trouve le fichier pom.xml et tapez
la commande suivante :
mvn test

Le niveau de journalisation des traces étant FINE, vous devriez voir apparaître
de nombreuses informations indiquant que Derby crée une base et des tables en
mémoire. Puis la classe BookTest est exécutée et Maven devrait vous informer du
succès du test.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.415 sec
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]----------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]----------------------------------------------------------------
[INFO] Total time: 19 seconds
[INFO] Finished [INFO] Final Memory: 4M/14M
[INFO]----------------------------------------------------------------

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java  71

Résumé

Ce chapitre est un tour d’horizon rapide de JPA 2.0. Comme la plupart des autres
spécifications de Java EE 6, JPA utilise une architecture objet simple et abandonne
le modèle composant lourd de son ancêtre (EJB CMP  2.x). Ce chapitre a égale-
ment présenté les entités, qui sont des objets persistants utilisant des métadonnées
­exprimées via des annotations ou un fichier XML.
Dans la section "Récapitulatif", nous avons vu comment lancer une application JPA
avec EclipseLink et Derby. Les tests unitaires jouent un rôle important dans les
projets : avec JPA et des bases de données en mémoire comme Derby, la persistance
peut désormais être testée très simplement.
Dans les chapitres suivants, vous en apprendrez plus sur les composants principaux
de JPA. Le Chapitre 3 expliquera comment associer les entités, les relations et l’hé-
ritage à une base de données. Le Chapitre 4 sera consacré à l’API du gestionnaire
d’entités, à la syntaxe de JPQL et à l’utilisation des requêtes et des mécanismes de
verrouillage. Le Chapitre 5, le dernier de cette présentation de JPA, expliquera le
cycle de vie des entités et montrera comment ajouter une logique métier dans les
fonctions de rappel et les écouteurs.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
3
ORM : Object-Relational Mapping

Dans ce chapitre, nous passerons en revue les bases des ORM (Object-Relational
Mapping), qui consistent essentiellement à faire correspondre des entités à des tables
et des attributs à des colonnes. Nous nous intéresserons ensuite à des associations
plus complexes comme les relations, la composition et l’héritage. Un modèle objet
est composé d’objets interagissant ensemble ; or les objets et les bases de données
utilisent des moyens différents pour stocker les informations sur ces relations (via
des pointeurs ou des clés étrangères). Les bases de données relationnelles ne dis-
posent pas naturellement du concept d’héritage et cette association entre objets et
bases n’est par conséquent pas évidente. Nous irons donc dans les détails et présen-
terons des exemples qui montreront comment les attributs, les relations et l’héritage
peuvent être traduits d’un modèle objet vers une base de données.
Les chapitres précédents ont montré que les annotations étaient très utilisées dans
l’édition Entreprise depuis Java EE 5 (essentiellement pour les EJB, JPA et les ser-
vices web). JPA 2.0 poursuit dans cette voie et introduit de nouvelles annotations de
mapping (associations), ainsi que leurs équivalents XML. Même si nous utiliserons
surtout les annotations pour expliquer les différents concepts d’associations, nous
présenterons également les associations au moyen de XML.

Association d’une entité


Comme premier exemple, commençons par l’association la plus simple possible.
Dans le modèle de persistance de JPA, une entité est un objet Java classique (POJO) :
ceci signifie qu’une entité est déclarée, instanciée et utilisée comme n’importe quelle
autre classe Java. Une entité possède des attributs (son état) qui peuvent être mani-
pulés au moyen de getters et de setters. Chaque attribut est stocké dans une colonne
d’une table. Le Listing 3.1 présente une entité simple.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
74 Java EE 6 et GlassFish 3 

Listing 3.1 : Exemple d’entité Book


@Entity
public class Book {

@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

public Book() {
}

// Getters, setters
}

Cet exemple de code issu de l’application CD-BookStore représente une entité Book
dans laquelle on a omis les getters et les setters pour plus de clarté. Comme vous
pouvez le constater, à part les annotations, cette entité ressemble exactement à n’im-
porte quelle classe Java : elle a plusieurs attributs (id, title, price, etc.) de diffé-
rents types (Long, String, Float, Integer et Boolean), un constructeur par défaut et
des getters et setters pour chaque attribut. Les annotations vont permettre d’associer
très simplement cette entité à une table dans une base de données.
Tout d’abord, la classe est annotée avec @javax.persistence.Entity, ce qui permet
au fournisseur de persistance de la reconnaître comme une classe persistance et non
plus comme une simple classe POJO. Puis l’annotation @javax.persistence.Id
définit l’identifiant unique de l’objet. JPA étant destiné à associer des objets à des
tables relationnelles, les objets doivent posséder un identifiant qui sera associé à
une clé primaire. Les autres attributs (title, price, description, etc.) ne sont pas
annotés et seront donc stockés dans la table en appliquant une association standard.
Cet exemple de code ne contient que des attributs mais, comme nous le verrons au
Chapitre  5, une entité peut également avoir des méthodes métier. Notez que cette
entité Book est une classe Java qui n’implémente aucune interface et qui n’hérite d’au-
cune classe. En fait, pour être une entité, une classe doit respecter les règles suivantes :
■■ La classe de l’entité doit être annotée par @javax.persistence.Entity (ou dénotée
comme telle dans le descripteur XML).
■■ L’annotation @javax.persistence.Id doit être utilisée pour désigner une clé
p­ rimaire simple.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 75

■■ La classe de l’entité doit posséder un constructeur sans paramètre, public ou


protégé. Elle peut également avoir d’autres constructeurs.
■■ La classe de l’entité doit être une classe de premier niveau. Une énumération ou
une interface ne peut pas être considérée comme une entité.
■■ La classe de l’entité ne peut pas être finale et aucune méthode ou variable d’ins-
tance persistante ne peut être finale non plus.
■■ Si une instance d’entité doit être passée par valeur sous forme d’objet détaché
(via une interface distante, par exemple), la classe de l’entité doit implémenter
l’interface Serializable.
L’entité Book du Listing 3.1 respectant ces règles simples, le fournisseur de persis-
tance peut synchroniser les données entre les attributs de l’entité et les colonnes
de la table BOOK. Par conséquent, si l’attribut isbn est modifié par l’application, la
colonne ISBN le sera également (si l’entité est gérée, si le contexte de transaction est
actif, etc.).
Comme le montre la Figure 3.1, l’entité Book est stockée dans une table BOOK dont
chaque colonne porte le nom de l’attribut correspondant de la classe (l’attribut isbn
de type String est associé à une colonne ISBN de type VARCHAR). Ces règles d’asso-
ciations par défaut sont un aspect important du principe appelé "convention plutôt
que configuration" (ou "configuration par exception").

Figure 3.1 <<entity>>
Book
Synchronisation des -id : Long
données entre l’entité -title : String
et la table. -price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean
+Book ()

Couche de persistance
Couche base de données
Association

BOOK
+ID bigint Nullable = false
TITLE varchar(255) Nullable = false
PRICE double Nullable = true
DESCRIPTION varchar(2000) Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
ILLUSTRATIONS smallint Nullable = true

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
76 Java EE 6 et GlassFish 3 

Configuration par exception

Java EE 5 a introduit l’idée de configuration par exception. Ceci signifie que, sauf
mention contraire, le conteneur ou le fournisseur doivent appliquer les règles par
défaut. En d’autres termes, fournir une configuration est une exception à la règle.
Cette politique permet donc de configurer une application avec un minimum d’effort.
Revenons à l’exemple précédent (celui du Listing  3.1). Sans annotation, l’entité
Book serait traitée comme n’importe quel POJO et ne serait pas persistante – c’est
la règle : sans configuration spéciale, le comportement par défaut s’applique et il
consiste évidemment à considérer que la classe Book est une classe comme les autres.
Comme nous souhaitons modifier ce comportement, nous annotons la classe avec @
Entity. Il en va de même pour l’identifiant : nous avons besoin d’indiquer au four-
nisseur de persistance que cet attribut doit être associé à une clé primaire, et c’est la
raison pour laquelle nous l’annotons avec @Id. Ce type de décision caractérise bien
la politique de configuration par exception : les annotations ne sont pas nécessaires
dans le cas général  ; elles ne sont utilisées que pour outrepasser une convention.
Ceci signifie donc que tous les autres attributs de notre classe seront associés selon
les règles par défaut :
■■ Le nom de l’entité est associé à un nom de table relationnelle (l’entité Book sera
donc associée à une table BOOK). Si vous voulez l’associer à une autre table,
vous devrez utiliser l’annotation @Table, comme nous le verrons dans la section
"Associations élémentaires".
■■ Les noms des attributs sont associés à des noms de colonnes (l’attribut id, ou
la méthode getId(), est associé à une colonne ID). Si vous voulez changer ce
comportement, vous devrez utiliser l’annotation @Column.
■■ Ce sont les règles JDBC qui s’appliquent pour associer les types primitifs de
Java aux types de données de la base. Ainsi, un String sera associé à un VAR-
CHAR, un Long à un BIGINT, un Boolean à un SMALLINT, etc. La taille par défaut
d’une colonne associée à un String est de 255 caractères (VARCHAR(255)). Ces
règles par défaut peuvent varier en fonction du SGBDR : un String est associé
à un VARCHAR avec Derby, mais à un VARCHAR2 avec Oracle ; de la même façon,
un Integer est associé à un INTEGER avec Derby, mais à un NUMBER avec Oracle.
Les informations concernant la base de données sous-jacentes sont fournies par
le fichier persistence.xml, que nous étudierons dans la section "Contexte de
persistance" du Chapitre 4.
Selon toutes ces règles, l’entité Book sera donc associée à une table Derby ayant la
structure décrite dans le Listing 3.2.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 77

Listing 3.2 : Structure de la table BOOK


CREATE TABLE BOOK (
ID BIGINT NOT NULL,
TITLE VARCHAR(255),
PRICE DOUBLE(52, 0),
DESCRIPTION VARCHAR(255),
ISBN VARCHAR(255),
NBOFPAGE INTEGER,
ILLUSTRATIONS SMALLINT,
PRIMARY KEY (ID)
);

C’est donc un exemple d’association très simple. Les relations et l’héritage ont
également des règles d’association par défaut, que nous étudierons dans la section
"Association des relations".
La plupart des fournisseurs de persistance, dont EclipseLink, permettent de produire
automatiquement la base de données à partir des entités. Cette fonctionnalité est
tout spécialement pratique lorsque l’on est en phase de développement car, avec
uniquement les règles par défaut, on peut associer très simplement les données en
se contentant des annotations @Entity et @Id. Cependant, la plupart du temps, nous
devrons nous connecter à un SGBDR classique ou suivre des conventions de nom-
mage strictes : c’est la raison pour laquelle JPA définit un nombre important d’anno-
tations (ou leurs équivalents XML) – vous pourrez ainsi personnaliser chaque partie
de l’association (les noms des tables et des colonnes, les clés primaires, la taille des
colonnes, les colonnes NULL ou NOT NULL, etc.).

Associations élémentaires

D’importantes différences existent entre la gestion des données par Java et par un
SGBDR. En Java, nous utilisons des classes pour décrire à la fois les attributs qui
contiennent les données et les méthodes qui accèdent et manipulent ces données.
Lorsqu’une classe a été définie, nous pouvons créer autant d’instances que néces-
saire à l’aide du mot-clé new. Dans un SGBDR, en revanche, seules les données sont
stockées –  pas les comportements (exception faite des triggers et des procédures
stockées) –, et la structure du stockage est totalement différente de la structure des
objets puisqu’elle utilise une décomposition en lignes et en colonnes. L’association
d’objets Java à une base de données sous-jacente peut donc être simple et se conten-
ter des règles par défaut  ; parfois, cependant, ces règles peuvent ne pas convenir
aux besoins, auquel cas nous sommes obligés de les outrepasser. Les annotations

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
78 Java EE 6 et GlassFish 3 

des associations élémentaires permettent de remplacer les règles par défaut pour
la table, les clés primaires et les colonnes, et de modifier certaines conventions de
nommage ou de contenu des colonnes (valeurs non nulles, longueur, etc.).

Tables

La convention établit que les noms de l’entité et de la table sont identiques (une entité
Book est associée à une table BOOK, une entité AncientBook, à une table ANCIENTBOOK,
etc.). Toutefois, si vous le souhaitez, vous pouvez associer vos données à une table
différente, voire associer une même entité à plusieurs tables.

@Table
L’annotation @javax.persistence.Table permet de modifier les règles par défaut
pour les tables. Vous pouvez, par exemple, indiquer le nom de la table dans laquelle
vous voulez stocker vos données, le catalogue et le schéma de la base. Le Listing 3.3
montre comment associer la table T_BOOK à l’entité Book.

Listing 3.3 : Association de l’entité Book à la table T_BOOK


@Entity
@Table(name = "t_book")
public class Book {

@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

public Book() {
}

// Getters, setters
}

INFO

Dans l’annotation @Table, le nom de la table est en minuscules (t_book). Par défaut, la plu-
part des SGBDR lui feront correspondre un nom en majuscules (c’est notamment le cas de
Derby), sauf si vous les configurez pour qu’ils respectent la casse.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 79

@SecondaryTable
Jusqu’à maintenant, nous avons toujours supposé qu’une entité n’était associée qu’à
une seule table, également appelée table primaire. Si l’on a déjà un modèle de don-
nées, en revanche, on voudra peut-être disséminer les données sur plusieurs tables, ou
tables secondaires. Cette annotation permet de mettre en place cette configuration.
@SecondaryTable permet d’associer une table secondaire à une entité, alors que
@SecondaryTables (avec un "s") en associe plusieurs. Vous pouvez distribuer les
données d’une entité entre les colonnes de la table primaire et celles des tables
secondaires en définissant simplement les tables secondaires avec des annotations,
puis en précisant pour chaque attribut la table dans laquelle il devra être stocké (à
l’aide de l’annotation @Column, que nous décrirons dans la section "Attributs"). Le
Listing  3.4 montre comment répartir les attributs d’une entité Address entre une
table primaire et deux tables secondaires.

Listing 3.4 : Les attributs de l’entité Address sont répartis dans trois tables différentes
@Entity
@SecondaryTables({
@SecondaryTable(name = "city"),
@SecondaryTable(name = "country")
})
public class Address {

@Id
private Long id;
private String street1;
private String street2;
@Column(table = "city")
private String city;
@Column(table = "city")
private String state;
@Column(table = "city")
private String zipcode;
@Column(table = "country")
private String country;

// Constructeurs, getters, setters


}

Par défaut, les attributs de l’entité Address seraient associés à la table primaire (qui
s’appelle ADDRESS par défaut). L’annotation @SecondaryTables précise qu’il y a deux
tables secondaires : CITY et COUNTRY. Vous devez ensuite indiquer dans quelle table
secondaire stocker chaque attribut (à l’aide de l’annotation @Column(table="city")
ou @Column(table="country")). Le résultat, comme le montre la Figure 3.2, est la
création de trois tables se partageant les différents attributs mais ayant la même clé

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
80 Java EE 6 et GlassFish 3 

primaire (afin de pouvoir les joindre). N’oubliez pas que Derby met en majuscules
(CITY) les noms de tables en minuscules (villes).

Figure 3.2 COUNTRY
+#ID bigint Nullable = false
L’entité Address <<entity>> COUNTRY varchar(255) Nullable = true
est associée à trois Address
-id : Long ADDRESS
tables. +ID bigint Nullable = false
-street1 : String
STREET1 varchar(255) Nullable = true
-street2 : String
STREET2 varchar(255) Nullable = true
-city : String
-zipcode : String CITY
-country : String +#ID bigint Nullable = false
+Address() CITY varchar(255) Nullable = true
STATE varchar(255) Nullable = true
ZIPCODE varchar(255) Nullable = true

Comme vous l’avez sûrement compris, la même entité peut contenir plusieurs anno-
tations. Si vous voulez renommer la table primaire, vous pouvez donc ajouter une
annotation @Table. C’est ce que nous faisons dans le Listing 3.5.

Listing 3.5 : La table primaire est renommée en T_ADDRESS


@Entity
@Table(name = "t_address")
@SecondaryTables({
@SecondaryTable(name = "t_city"),
@SecondaryTable(name = "t_country")
})
public class Address {

// Attributs, constructeur, getters, setters


}

INFO

Vous devez être conscient de l’impact des tables secondaires sur les performances car, à
chaque fois que vous accéderez à une entité, le fournisseur de persistance devra accéder
à  plusieurs tables et les joindre. En revanche, les tables secondaires peuvent être intéres-
santes si vous avez des attributs de grande taille, comme des BLOB (Binary Large Objects),
car vous pourrez les isoler dans une table à part.

Clés primaires

Dans les bases de données relationnelles, une clé primaire identifie de façon unique
chaque ligne d’une table. Cette clé peut être une simple colonne ou un ensemble

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 81

de colonnes. Les clés primaires doivent évidemment être uniques (et la valeur NULL
n’est pas autorisée). Des exemples de clés primaires classiques sont un numéro de
client, un numéro de téléphone, un numéro de commande et un ISBN. JPA exige
que les entités aient un identifiant associé à une clé primaire qui suivra les mêmes
règles : identifier de façon unique une entité à l’aide d’un simple attribut ou d’un
ensemble d’attributs (clé composée). Une fois affectée, la valeur de la clé primaire
d’une entité ne peut plus être modifiée.

@Id et @GeneratedValue
Une clé primaire simple (non composée) doit correspondre à un seul attribut de la
classe de l’entité. L’annotation @Id que nous avons déjà rencontrée sert à indiquer
une clé simple. L’attribut qui servira de clé doit être de l’un des types suivants :
■■ Types primitifs de Java. byte, int, short, long, char.
■■ Classes enveloppes des types primitifs. Byte, Integer, Short, Long, Character.
■■ Tableau de types primitifs ou de classes enveloppes. int[], Integer[], etc.
■■ Chaîne, nombre ou dates. java.lang.String, java.math.BigInteger, java.
util.Date, java.sql.Date.

Lorsque l’on crée une entité, la valeur de cet identifiant peut être produite manuel­
lement par l’application, ou automatiquement par le fournisseur de persistance si
l’on précise l’annotation @GeneratedValue. Celle-ci peut avoir quatre valeurs :
■■ SEQUENCE et IDENTITY précisent, respectivement, une séquence SQL de la base de
données ou une colonne identité.
■■ TABLE demande au fournisseur de persistance de stocker le nom de la séquence
et sa valeur courante dans une table et d’incrémenter cette valeur à chaque fois
qu’une nouvelle instance de l’entité est stockée dans la base. Derby, par exemple,
crée une table SEQUENCE de deux colonnes : une pour le nom de la séquence (qui
est arbitraire) et l’autre pour la valeur (un entier incrémenté automatiquement
par Derby).
■■ AUTO demande que la génération d’une clé s’effectue automatiquement par la
base de données sous-jacente, qui est libre de choisir la technique la plus appro-
priée. C’est la valeur par défaut de l’annotation @GeneratedValue.
En l’absence de @GeneratedValue, l’application est responsable de la production des
identifiants à l’aide d’un algorithme qui devra renvoyer une valeur unique. Le code

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
82 Java EE 6 et GlassFish 3 

du Listing  3.6 montre comment obtenir automatiquement un identifiant. Genera-


tionType.AUTO étant la valeur par défaut de l’annotation, nous aurions pu omettre
l’élément strategy. Notez également que l’attribut id est annoté deux fois : avec
@Id et avec @GeneratedValue.

Listing 3.6 : L’entité Book avec un identifiant produit automatiquement


@Entity
public class Book {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters

Clés primaires composées


Lorsque l’on associe des entités, il est conseillé de dédier une seule colonne à la clé
primaire. Dans certains cas, toutefois, on est obligé de passer par une clé primaire
composée (pour, par exemple, créer une association avec une base de données exis-
tante ou lorsque les clés primaires doivent respecter une convention interne à l’entre-
prise – une date et un code pays, ou une étiquette temporelle, par exemple). Dans ce
cas, nous devons créer une classe de clé primaire pour représenter la clé primaire com-
posée. Pour ce faire, nous disposons de deux annotations pour cette classe, en fonction
de la façon dont on souhaite structurer l’entité : @EmbeddedId et @IdClass. Comme
nous le verrons, le résultat final est le même – on aboutira au même schéma de base de
données – mais cela modifiera légèrement la façon dont on interrogera l’entité.
L’application CD-BookStore, par exemple, doit fréquemment poster des articles sur
la page d’accueil pour signaler de nouveaux livres, titres musicaux ou artistes. Ces
articles ont un contenu, un titre et, comme ils sont écrits dans des langues diffé-
rentes, un code langue (EN pour l’anglais, FR pour le français, etc.). La clé primaire
des articles pourrait donc être composée du titre et du code langue car un article peut
être traduit en plusieurs langues tout en gardant son titre initial. La classe de clé pri-
maire NewsId sera donc composée de deux attributs de type String : title et lan-
guage. Pour pouvoir gérer les requêtes et les collections internes, les classes de clés

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 83

primaires doivent redéfinir les méthodes equals() et hashCode() ; en outre, leurs


attributs doivent être de l’un des types déjà mentionnés. Elles doivent également
être publiques et implémenter Serializable si elles doivent traverser des couches
de l’architecture (elles peuvent être gérées dans la couche de persistance et être
utilisées dans la couche présentation, par exemple). Enfin, elles doivent posséder un
constructeur par défaut (sans paramètre).

@EmbeddedId
Comme nous le verrons plus loin, JPA utilise différentes sortes d’objets intégrés
(embedded). Pour faire court, un objet intégré n’a pas d’identité (il n’a pas de clé
primaire) et ses attributs sont stockés dans des colonnes de la table associée à l’entité
qui le contient.
Le Listing 3.7 présente la classe NewsId comme une classe intégrable (embeddable).
Il s’agit simplement d’un objet intégré (annoté avec @Embeddable) composé de deux
attributs (title et language). Cette classe doit avoir un constructeur par défaut, des
getters, des setters et redéfinir equals() et hashCode(). Vous remarquerez que la
classe n’a pas d’identité par elle-même (aucune annotation @Id) : c’est ce qui carac-
térise un objet intégrable.

Listing 3.7 : La classe de clé primaire est annotée par @Embeddable


@Embeddable
public class NewsId {
private String title;
private String language;
// Constructeurs, getters, setters, equals et hashcode
}

L’entité News, présentée dans le Listing 3.8, doit maintenant intégrer la classe de clé
primaire NewsId avec l’annotation @EmbeddedId. Toutes les annotations @EmbeddedId
doivent désigner une classe intégrable annotée par @Embeddable.

Listing 3.8 : L’entité intègre la classe de clé primaire avec @EmbeddedId


@Entity
public class News {

@EmbeddedId
private NewsId id;
private String content;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
84 Java EE 6 et GlassFish 3 

Dans le prochain chapitre, nous verrons plus précisément comment retrouver les
entités à l’aide de leur clé primaire, mais le Listing 3.9 présente le principe général :
la clé primaire étant une classe avec un constructeur, vous devez d’abord l’instancier
avec les valeurs qui forment la clé, puis passer cet objet au gestionnaire d’entités
(l’attribut em).

Listing 3.9 : Code simplifié permettant de retrouver une entité à partir de sa clé primaire
composée
NewsId cle = new NewsId("Richard Wright est mort", "FR")
News news = em.find(News.class, cle);

@IdClass
L’autre méthode pour déclarer une clé primaire composée consiste à utiliser l’an-
notation @IdClass. Cette approche est différente de la précédente car, ici, chaque
attribut de la classe de la clé primaire doit également être déclaré dans la classe
entité et annoté avec @Id.
La clé primaire de l’exemple du Listing 3.10 est maintenant un objet classique qui
ne nécessite aucune annotation.

Listing 3.10 : La classe clé primaire n’est pas annotée


public class NewsId {
private String title;
private String language;

// Constructeurs, getters, setters, equals et hashcode


}

Comme le montre le Listing 3.11, l’entité News doit simplement définir la classe de


la clé primaire à l’aide de l’annotation @IdClass et annoter chaque attribut de la clé
avec @Id. Pour stocker l’entité News, vous devrez maintenant donner une valeur aux
attributs title et language.

Listing 3.11 : L’entité définit sa classe de clé primaire avec l’annotation @IdClass


@Entity
@IdClass(NewsId.class)
public class News {
@Id
private String title;
@Id
private String language;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 85

private String content;

// Constructeurs, getters, setters, equals et hashcode


}

Les deux approches, @EmbeddedId et @IdClass, donneront la même structure de


table : celle du Listing 3.12. Les attributs de l’entité et de la clé primaire se retrouve-
ront bien dans la même table et la clé primaire sera formée des attributs de la classe
clé primaire (title et language).

Listing 3.12 : Définition de la table NEWS avec une clé primaire composée


create table NEWS (
CONTENT VARCHAR(255),
TITLE VARCHAR(255) not null,
LANGUAGE VARCHAR(255) not null,
primary key (TITLE, LANGUAGE)
);

L’approche @IdClass est plus sujette aux erreurs car vous devez définir chaque attri-
but de la clé primaire à la fois dans la classe de la clé primaire et dans l’entité, en
vous assurant d’utiliser les mêmes noms et les mêmes types. L’avantage est que vous
n’avez pas besoin de modifier le code de la classe de la clé primaire. Vous pourriez,
par exemple, utiliser une classe existante que vous n’avez pas le droit de modifier.
La seule différence visible est la façon dont vous ferez référence à l’entité dans
JPQL. Dans le cas de @IdClass, vous utiliseriez un code comme celui-ci :
select n.title from News n

Alors qu’avec @EmbeddedId vous écririez :


select n.newsId.title from News n

Attributs

Une entité doit posséder une clé primaire (simple ou composée) pour être iden-
tifiable dans une base de données relationnelle. Elle dispose également de toutes
sortes d’attributs qui forment son état, qui doit également être associé à la table. Cet
état peut contenir quasiment tous les types Java que vous pourriez vouloir associer :
■■ types primitifs de Java (int, double, float, etc.) et leurs classes enveloppes
(Integer, Double, Float, etc.) ;
■■ tableaux d’octets ou de caractères (byte[], Byte[], char[], Character[]) ;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
86 Java EE 6 et GlassFish 3 

■■ chaînes, grands nombres et types temporels (java.lang.String, java.math.


BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar,
java.sql.Date, java.sql.Time, java.sql.Timestamp) ;

■■ types énumérés et types implémentant l’interface Serializable, définis par


l’utilisateur ;
■■ collection de types de base et de types intégrables.
Bien sûr, une entité peut également avoir des attributs entités, collections d’entités
ou d’instances de classes intégrables. Ceci implique d’introduire des relations entre
les entités (que nous étudierons dans la section "Association des relations").
Comme nous l’avons vu, en vertu de la configuration par exception, les attributs sont
associés selon des règles par défaut. Parfois, cependant, vous aurez besoin d’adap-
ter certaines parties de cette association : c’est là que les annotations JPA (ou leurs
équivalents XML) entrent une nouvelle fois en jeu.

@Basic
L’annotation @javax.persistence.Basic (voir Listing 3.13) est le type d’associa-
tion le plus simple avec une colonne d’une table car il redéfinit les options de base
de la persistance.

Listing 3.13 : Éléments de l’annotation @Basic


@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Basic {
FetchType fetch() default EAGER;
boolean optional() default true;
}

Cette annotation a deux paramètres  : optional et fetch. Le premier indique si la


valeur de l’attribut peut être null – il est ignoré pour les types primitifs. Le second
peut prendre deux valeurs, LAZY ou EAGER : il indique au fournisseur de persistance que
les données doivent être récupérées de façon "paresseuse" (uniquement lorsque l’ap-
plication en a besoin) ou "immédiate" (lorsque l’entité est chargée par le fournisseur).
Considérons, par exemple, l’entité Track du Listing 3.14. Un album CD est consti-
tué de plusieurs pistes ayant chacune un titre, une description et un fichier .WAV
d’une certaine durée. Ce dernier est un BLOB qui peut occuper plusieurs mégaoc-
tets. Lorsque nous accédons à l’entité Track, nous ne voulons pas charger immé-
diatement le fichier WAV  : nous annotons donc l’attribut avec @Basic(fetch =

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 87

FetchType.LAZY) pour que ces données ne soient lues dans la base que lorsqu’elles
seront vraiment nécessaires (lorsque nous accéderons à l’attribut wav via son getter,
par exemple).

Listing 3.14 : L’entité Track avec un chargement paresseux de l’attribut wav


@Entity
public class Track {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float duration;

@Basic(fetch = FetchType.LAZY)
@Lob
private byte[] wav;
@Basic(optional = true)
private String description;

// Constructeurs, getters, setters


}

Notez que l’attribut wav de type byte[] est également annoté par @Lob afin que sa
valeur soit stockée comme un LOB (Large Object) – les colonnes pouvant stocker
ces types de gros objets nécessitent des appels JDBC spéciaux pour être accessibles
à partir de Java. Pour en informer le fournisseur, il faut donc ajouter une annotation
@Lob à l’association de base.

@Column
L’annotation @javax.persistence.Column définit les propriétés d’une colonne.
Grâce à elle, nous pouvons modifier le nom de la colonne (qui, par défaut, est le
même que celui de l’attribut), sa taille et autoriser (ou non) la colonne à être NULL,
unique, modifiable ou utilisable dans une instruction INSERT de SQL. Le Listing 3.15
montre les différents éléments de son API, avec leurs valeurs par défaut.

Listing 3.15 : Éléments de l’annotation @Column


@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
88 Java EE 6 et GlassFish 3 

boolean updatable() default true;


String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0; // précision décimale
int scale() default 0; // échelle décimale
}

Pour redéfinir l’association par défaut de l’entité Book initiale, nous pouvons utiliser
de différentes façons l’annotation @Column (voir Listing 3.16). Ici, nous modifions
les noms des colonnes associées aux attributs title et nbOfPage, pour lesquelles
nous n’autorisons pas les valeurs NULL ; nous précisons également la longueur de la
colonne associée à description.

Listing 3.16 : Personnalisation de l’association de l’entité Book


@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "book_title", nullable = false,
„ updatable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
@Column(name = "nb_of_page", nullable = false)
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

L’entité Book du Listing 3.6 sera donc associée à la table définie dans le Listing 3.17.

Listing 3.17 : Définition de la table BOOK


create table BOOK (
ID BIGINT not null,
BOOK_TITLE VARCHAR(255) not null,
PRICE DOUBLE(52, 0),
DESCRIPTION VARCHAR(2000),
ISBN VARCHAR(255),
NB_OF_PAGE INTEGER not null,
ILLUSTRATIONS SMALLINT,
primary key (ID)
);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 89

La plupart des éléments de l’annotation @column influent sur l’association. Si l’on


fixe à 2 000 la longueur de l’attribut description, par exemple, la taille de la colonne
correspondante sera également de 2 000. Par défaut, updatable et insertable valent
true, ce qui signifie que l’on peut insérer ou modifier n’importe quel attribut dans la
base de données. En les mettant à false, on demande au fournisseur de persistance
de garantir qu’il n’insérera ni ne modifiera les données des colonnes associées à ces
attributs lorsque l’entité sera modifiée. Notez que ceci n’implique pas que l’entité
ne pourra pas être modifiée en mémoire – elle pourra l’être mais, en ce cas, elle ne
sera plus synchronisée avec la base car l’instruction SQL qui sera produite (INSERT
ou UPDATE) ne portera pas sur ces colonnes.

@Temporal
En Java, vous pouvez utiliser java.util.Date et java.util.Calendar pour stocker
des dates puis obtenir des représentations différentes, comme une date, une heure ou
des millisecondes. Pour utiliser une date avec un ORM, vous pouvez utiliser l’anno-
tation @javax.persistence.Temporal, qui a trois valeurs possibles : DATE, TIME ou
TIMESTAMP. Le Listing 3.18, par exemple, définit une entité Customer contenant une
date de naissance et un attribut technique qui stocke le moment exact où ce client a
été ajouté au système (à l’aide d’une valeur TIMESTAMP).

Listing 3.18 : Entité Customer avec deux attributs @Temporal


@Entity
public class Customer {

@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;

// Constructeurs, getters et setters


}

L’entité Customer du Listing 3.18 sera associée à la table décrite dans le Listing 3.19.


L’attribut dateOfBirth est associé à une colonne de type DATE et l’attribut creation-
Date, à une colonne de type TIMESTAMP.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
90 Java EE 6 et GlassFish 3 

Listing 3.19 : Définition de la table CUSTOMER


create table CUSTOMER (
ID BIGINT not null,
FIRSTNAME VARCHAR(255),
LASTNAME VARCHAR(255),
EMAIL VARCHAR(255),
PHONENUMBER VARCHAR(255),
DATEOFBIRTH DATE,
CREATIONDATE TIMESTAMP,
primary key (ID)
);

@Transient
Avec JPA, tous les attributs d’une classe annotée par @Entity sont automatiquement
associés à une table. Si vous ne souhaitez pas associer un attribut particulier, utilisez
l’annotation @javax. persistence.Transient. Ajoutons, par exemple, un attribut
age à l’entité Customer (voir Listing  3.20)  : l’âge pouvant être automatiquement
calculé à partir de la date de naissance, il n’est pas nécessaire de stocker cet attribut,
qui peut donc être déclaré comme transitoire.

Listing 3.20 : Entité Customer avec un âge transitoire


@Entity
public class Customer {

@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;

// Constructeurs, getters et setters


}

Cet attribut n’aura pas de colonne AGE associée.

@Enumerated
Java SE 5 a introduit les énumérations, qui sont si souvent utilisées qu’elles font
partie de la vie du développeur. Les valeurs d’une énumération sont des constantes

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 91

a­ uxquelles est implicitement associé un numéro déterminé par leur ordre d’appari-
tion dans l’énumération. Ce numéro ne peut pas être modifié en cours d’exécution
mais sert à stocker la valeur du type énuméré dans la base de données. Le Listing 3.21
montre une énumération de types de cartes de crédit.

Listing 3.21 : Énumération de types de cartes de crédit


public enum CreditCardType {
VISA,
MASTER_CARD,
AMERICAN_EXPRESS
}

Les numéros affectés lors de la compilation aux valeurs de ce type énuméré seront 0
pour VISA, 1 pour MASTER_CARD et 2 pour AMERICAN_EXPRESS. Par défaut, les fournis-
seurs de persistance associeront ce type énuméré à la base de données en supposant
que la colonne est de type Integer. Le Listing 3.22 montre une entité CreditCard
qui utilise l’énumération précédente avec une association par défaut.

Listing 3.22 : Association d’un type énuméré à des numéros


@Entity
@Table(name = "credit_card")
public class CreditCard {

@Id
private String number;
private String expiryDate;
private Integer controlNumber;
private CreditCardType creditCardType;

// Constructeurs, getters et setters


}

Les règles par défaut feront que l’énumération sera associée à une colonne de
type entier et tout ira bien. Imaginons maintenant que nous ajoutions une nou-
velle constante au début de l’énumération. L’affectation des numéros dépendant de
l’ordre d’apparition des constantes, les valeurs déjà stockées dans la base de don-
nées ne correspondront plus à l’énumération. Une meilleure solution consiste donc
à stocker le nom de la constante à la place de son numéro d’ordre. C’est ce que fait
le Listing 3.23 à l’aide de l’annotation @Enumerated avec la valeur STRING (sa valeur
par défaut est ORDINAL).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
92 Java EE 6 et GlassFish 3 

Listing 3.23 : Association d’un type énuméré avec une chaîne


@Entity
@Table(name = "credit_card")
public class CreditCard {

@Id
private String number;
private String expiryDate;
private Integer controlNumber;
@Enumerated(EnumType.STRING)
private CreditCardType creditCardType;

// Constructeurs, getters et setters


}

Désormais, la colonne CREDITCARDTYPE de la table sera de type VARCHAR et une carte


Visa sera stockée sous la forme "VISA".

Types d’accès

Pour l’instant, nous n’avons vu que des annotations de classes (@Entity ou @Table)
et d’attributs (@Basic, @Column, @Temporal, etc.), mais les annotations qui s’appli-
quent à un attribut (accès au champ) peuvent également être placées sur la méthode
getter correspondante (accès à la propriété). L’annotation @Id, par exemple, peut
être affectée à l’attribut id ou à la méthode getId(). Il s’agit surtout ici d’une ques-
tion de goût personnel et nous préférons utiliser les accès aux propriétés (getters
annotés) car nous trouvons le code plus lisible : nous pouvons lire rapidement les
attributs d’une entité sans être perturbés par les annotations (dans ce livre, toutefois,
nous avons décidé d’annoter les attributs afin d’éviter de devoir alourdir les listings
par les codes des getters). Dans certains cas comme l’héritage, cependant, ce n’est
plus simplement une affaire de goût car cela peut avoir un impact sur l’association.

INFO

Java définit un champ comme un attribut d’instance. Une propriété est un champ avec un
accesseur (getter et setter) respectant la convention des Java beans (le nom de la méthode
d’accès est de la forme getXXX, setXXX ou isXXX si elle renvoie un Boolean).

Lorsque l’on choisit entre un accès au champ (attribut) ou à la propriété (getter), on


choisit un type d’accès. Par défaut, c’est un type d’accès simple qui s’applique à
une entité : il peut s’agir d’un accès au champ ou à la propriété, mais pas les deux.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 93

La spécification indique que le comportement d’une application qui mélangerait les


emplacements des annotations sur les champs et les propriétés sans préciser explici-
tement le type d’accès est indéfini.
Lorsque l’on utilise un accès aux champs (voir Listing 3.24), le fournisseur de per-
sistance associe les attributs. Toutes les variables d’instance qui ne sont pas annotées
par @Transient sont persistantes.

Listing 3.24 : Entité Customer avec des champs annotés


@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
@Column(nom = "first_name", nullable = false, length = 50)
private String firstName;
@Column(nom = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(nom = "phone_number", length = 15)
private String phoneNumber;

// Constructeurs, getters et setters


}

Lorsque l’on utilise un accès aux propriétés, comme dans le Listing 3.25, le fournis-
seur de persistance accède à l’état persistant via les méthodes getter et l’association
repose sur ces getters plutôt que sur les attributs. Tous les getters non annotés par
@Transient sont persistants.

Listing 3.25 : Entité Client avec des propriétés annotées


@Entity
public class Customer {

private Long id;


private String firstName;
private String lastName;
private String email;
private String phoneNumber;

// Constructeur...

@Id @GeneratedValue
public Long getId() {
return id;
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
94 Java EE 6 et GlassFish 3 

@Column(nom = "first_name", nullable = false, length = 50)


public String getfirstName() {
return firstName;
}

public void setFirstName(String firstName) {


this.firstName = firstName;
}

@Column(nom = "last_name", nullable = false, length = 50)


public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {


this.lastName = lastName;
}

public String getEmail() {


return email;
}

public void setEmail(String email) {


this.email = email;
}

@Column(name = "phone_number", length = 555)


public String getPhoneNumber() {
return phoneNumber;
}

public void setPhoneNumber(String phoneNumber) {


this.phoneNumber = phoneNumber;
}
}

En termes d’associations, les deux entités des Listings 3.24 et 3.25 sont en tout point
identiques car les noms des attributs sont les mêmes que ceux des getters. Au lieu
d’utiliser le type d’accès par défaut, vous pouvez également le préciser explicite-
ment avec l’annotation @javax.persistence.Access.
Cette annotation a deux valeurs possibles, FIELD ou PROPERTY, et peut être utilisée sur
l’entité elle-même et/ou sur chaque attribut ou getter. Lorsque @Access(AccessType.
FIELD) est appliqué à l’entité, par exemple, seules les annotations d’associations
placées sur les attributs seront prises en compte par le fournisseur de persistance.
Il est également possible d’annoter avec @Access(AccessType.PROPERTY) des getters
individuels pour effectuer des accès par propriété.
Les types d’accès explicites peuvent être très pratiques (avec les objets intégrables
et l’héritage, par exemple), mais leur mélange provoque souvent des erreurs. Le Lis-
ting 3.26 montre ce qui pourrait se passer si vous mélangez ces deux types d’accès.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 95

Listing 3.26 : Entité Customer avec types d’accès explicites


@Entity
@Access(AccessType.FIELD)
public class Customer {

@Id @GeneratedValue
private Long id;
@Column(nom = "first_name", nullable = false, length = 50)
private String firstName;
@Column(nom = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(nom = "phone_number", length = 15)
private String phoneNumber;

// Constructeurs, getters et setters

@Access(AccessType.PROPERTY)
@Column(nom = "phone_number", length = 555)
public String getPhoneNumber() {
return phoneNumber;
}

public void setPhoneNumber(String phoneNumber) {


this. phoneNumber = phoneNumber;
}
}

Cet exemple définit explicitement le type d’accès FIELD au niveau de l’entité, ce qui
indique au gestionnaire de persistance qu’il ne doit traiter que les annotations sur les
attributs. phoneNumber est annoté par @Column, qui limite sa taille à 15 caractères. En
lisant ce code, on pourrait s’attendre à ce que le type de la colonne correspondante
dans la base de données soit VARCHAR(15), mais ce ne sera pas le cas. En effet, le
type d’accès a été explicitement modifié pour la méthode getter getPhoneNumber() :
la longueur d’un numéro de téléphone dans la base sera donc de 555 caractères. Ici,
l’AccessType.FIELD de l’entité a été écrasé par AccessType.PROPERTY et la colonne
sera donc de type VARCHAR(555).

Collections de types de base

Les collections sont très utilisées en Java. Dans cette section, nous étudierons les rela-
tions entre entités (qui peuvent être des collections d’entités) : essentiellement, ceci
signifie qu’une entité contient une collection d’autres entités ou d’objets intégrables.
En terme d’association, chaque entité est associée à sa propre table et l’on crée des
références entre les clés primaires et les clés étrangères. Comme vous le savez, une

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
96 Java EE 6 et GlassFish 3 

entité est une classe Java avec une identité et de nombreux autres attributs : mais
comment faire pour stocker une simple collection de types Java comme des String
et/ou des Integer  ? Depuis JPA  2.0, il n’est plus nécessaire de  créer une classe
distincte car on dispose des annotations @ElementCollection et @CollectionTable.
L’annotation @ElementCollection indique qu’un attribut de type java.util.Collec-
tion contient des types Java tandis que @CollectionTable permet de modifier les
détails de la table de la collection – son nom, par exemple. Si cette dernière est omise,
le nom de la table sera formé par la concaténation du nom de l’entité conteneur et de
celui de l’attribut collection, séparés par un blanc souligné ("_", ou underscore).
Utilisons une nouvelle fois l’entité Book et ajoutons-lui un attribut pour stocker des
tags. De nos jours, les tags et les nuages de tags sont partout et sont très pratiques
pour trier les données : dans notre exemple, nous voulons nous en servir pour décrire
un livre et le retrouver rapidement. Un tag n’étant qu’une simple chaîne, l’entité Book
contiendra donc une collection de chaînes pour stocker ces informations, comme le
montre le Listing 3.27.

Listing 3.27 : L’entité Book contient une collection de chaînes


@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbDePages;
private Boolean illustrations;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name="Tag")
@Column(name = "Value")
private ArrayList<String> tags;

// Constructeurs, getters et setters


}

L’annotation @ElementCollection informe le fournisseur de persistance que l’at-


tribut tags est une liste de chaînes qui devra être récupérée de façon paresseuse.
En l’absence de @CollectionTable, le nom de la table sera BOOK_TAGS au lieu du
nom précisé dans l’élément name de l’annotation (name = "Tag"). Vous remarquerez
que nous avons ajouté une annotation @Column supplémentaire pour renommer la
colonne en Value. Le résultat obtenu est représenté par la Figure 3.3.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 97

BOOK
+ID bigint Nullable = false
TITLE varchar(255) Nullable = false TAG
PRICE double Nullable = true #BOCK ID bigint Nullable = false
DESCRIPTION varchar(2000) Nullable = true VALUE varchar(255) Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
ILLUSTRATIONS smallint Nullable = true

Figure 3.3
Relation entre les tables BOOK et TAG.

INFO

Ces annotations n’existaient pas en JPA 1.0. Cependant, il était possible de stocker une liste
de types primitifs dans la base de données sous la forme d’un BLOB. En effet, java.util.Ar-
rayList implémente Serializable et JPA sait associer automatiquement des objets Seria-
lizable à des BLOB. En revanche, lorsque l’on utilisait une collection java.util.List, on
obtenait une exception car List n’implémente pas Serializable. L’annotation @Element-
Collection est donc un moyen plus élégant et plus pratique de stocker les listes de types
primitifs car leur stockage sous un format binaire opaque aux requêtes les rend inaccessibles.

Association des types de base

Comme les collections, les tables de hachage sont très utiles pour le stockage des
données. Avec JPA 1.0, on ne pouvait pas en faire grand-chose en terme d’ORM.
Désormais, les tables de hachage peuvent utiliser n’importe quelle combinaison de
types de base, d’objets intégrables et d’entités comme clés ou comme valeurs : ceci
apporte beaucoup de souplesse. Pour l’instant, intéressons-nous aux hachages qui
utilisent des types de base.
Lorsqu’un hachage emploie des types de base, vous pouvez vous servir des anno-
tations @ElementCollection et @CollectionTable exactement comme nous venons
de le voir pour les collections. En ce cas, les données du hachage sont stockées dans
une table collection.
Prenons l’exemple d’un CD contenant un certain nombre de pistes (voir Listing 3.28).
Une piste peut être considérée comme un titre et une position (la première piste de
l’album, la seconde, etc.). Vous pourriez alors utiliser un hachage de pistes utilisant
un entier pour représenter la position (une clé du hachage) et une chaîne pour repré-
senter le titre (la valeur de cette clé dans le hachage).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
98 Java EE 6 et GlassFish 3 

Listing 3.28 : Album CD avec un hachage de pistes


@Entity
public class CD {

@Id
@GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
@Lob
private byte[] cover;
@ElementCollection
@CollectionTable(name="track")
@MapKeyColumn (name = "position")
@Column(name = "title")
private HashMap<Integer, String> tracks;

// Constructeurs, getters et setters


}

Comme on l’a déjà indiqué, l’annotation @ElementCollection sert à indiquer que


les objets du hachage seront stockés dans une table collection. L’annotation @Col-
lectionTable, quant à elle, est utilisée ici pour modifier le nom par défaut de la
table collection en TRACK.
La différence avec les collections est que l’on introduit ici une nouvelle annota-
tion, @MapKeyColumn, pour préciser l’association correspondant à la colonne clé du
hachage. En son absence, le nom de cette colonne est formé par concaténation du
nom de l’attribut qui référence la relation et du suffixe _KEY. Le Listing 3.28 uti-
lise cette annotation pour la renommer en POSITION afin qu’elle porte un nom plus
lisible.
L’annotation @Column indique que la colonne contenant les valeurs du hachage sera
nommée TITLE. Le résultat obtenu est représenté par la Figure 3.4.

CD
+ID bigint Nullable = false
TITLE varchar(255) Nullable = true TRACK
PRICE double Nullable = true #CD ID bigint Nullable = false
DESCRIPTION varchar(255) Nullable = true POSITION integer Nullable = true
COVER blob(64000) Nullable = true TITLE varchar(255) Nullable = true

Figure 3.4
Relation entre les tables CD et TRACK.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 99

Associations avec XML

Maintenant que vous connaissez mieux les bases des associations avec les anno-
tations, étudions celles qui utilisent XML. Si vous avez déjà utilisé un framework
ORM comme Hibernate, vous savez déjà comment associer vos entités via un fichier
de descripteurs de déploiement XML. Depuis le début de ce chapitre, nous n’avons
pourtant pas utilisé une seule ligne de XML – uniquement des annotations. Nous
n’entrerons pas trop dans les détails des associations XML car nous avons décidé de
nous concentrer sur les annotations (parce qu’elles sont plus simples à utiliser dans
un livre et parce que la plupart des développeurs les préfèrent à XML). Considérez
simplement que toutes les annotations que nous avons présentées dans ce chapitre
ont un équivalent XML : cette section serait énorme si nous les présentions toutes.
Nous vous renvoyons donc au Chapitre 11 de la spécification JPA 2.0, qui présente
en détail tous les marqueurs XML.
Les descripteurs de déploiement XML sont une alternative aux annotations. Bien
que chaque annotation ait un marqueur XML équivalent et vice versa, il y a toute-
fois une différence car les marqueurs XML ont priorité sur les annotations : si vous
annotez un attribut ou une entité avec une certaine valeur et que vous déployiez en
même temps le descripteur XML correspondant avec une valeur différente, celle de
l’annotation sera ignorée.
Quand utiliser les annotations plutôt que XML et pourquoi ? C’est avant tout une
question de goût car les deux méthodes ont exactement le même effet. Lorsque les
métadonnées sont vraiment couplées au code (une clé primaire, par exemple), les
annotations sont judicieuses car, en ce cas, les métadonnées ne sont qu’un autre
aspect du programme. D’autres types de métadonnées comme la longueur des
colonnes ou autres détails concernant le schéma peuvent en revanche dépendre de
l’environnement de déploiement (le schéma de la base peut, par exemple, varier
entre les environnements de développement, de test et de production). En ce cas, il
est préférable de les exprimer à l’aide de descripteurs de déploiement externes (un
par environnement), afin de ne pas devoir modifier le code.
Revenons à notre entité Book. Imaginons que nous travaillons dans deux environ-
nements  : nous voulons associer l’entité à la table BOOK dans l’environnement de
développement et à la table BOOK_XML_MAPPING dans celui de test. La classe ne sera
annotée que par @Entity (voir Listing 3.29) et ne contiendra pas d’information sur la
table à laquelle elle est associée (elle ne contiendra donc pas d’annotation @Table).
L’annotation @Id définit une clé primaire produite automatiquement et @Column fixe
la taille de la description à 500 caractères.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
100 Java EE 6 et GlassFish 3 

Listing 3.29 : L’entité Book ne contient que quelques annotations


@Entity
public class Book {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
@Column(length = 500)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Vous pouvez modifier les associations de n’importe quelle donnée de l’entité en


utilisant un fichier book_mapping.xml (voir Listing 3.30) qui doit respecter un cer-
tain schéma XML. Le marqueur <table>, par exemple, permet de modifier le nom
de la table à laquelle sera associée l’entité (BOOK_XML_MAPPING au lieu du nom BOOK
par défaut). Dans le marqueur <attributes>, vous pouvez adapter les attributs en
précisant non seulement les noms ou les tailles de leurs colonnes, mais également
leurs relations avec d’autres entités. Dans notre exemple, nous modifions les noms
des colonnes title et nbOfPage.

Listing 3.30 : Association utilisant le fichier META-INF/book_mapping.xml


<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
version="1.0">

<entity class="com.apress.javaee6.chap03.Livre">
<table name="book_xml_mapping"/>
<attributes>
<basic name="title">
<column name="book_title" nullable="false"
updatable="false"/>
</basic>
<basic name="description">
<column length="2000"/>
</basic>
<basic name="nbOfPage">
<column name="nb_of_page" nullable="false"/>
</basic>
</attributes>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 101

</entity>

</entity-mappings>

Il ne faut jamais oublier que XML a priorité sur les annotations. Même si l’attribut
description est annoté par @Column(length = 500), la longueur de la colonne uti-
lisée sera celle définie dans le fichier book_mapping.xml, c’est-à-dire 2 000. Pensez
à toujours consulter le descripteur de déploiement XML en cas de doute.
La fusion des métadonnées XML et des métadonnées des annotations fera que l’en-
tité Book sera finalement associée à la table BOOK_XML_MAPPING, dont la structure est
définie dans le Listing 3.31.

Listing 3.31 : Structure de la table BOOK_XML_MAPPING


create table BOOK_XML_MAPPING (
ID BIGINT not null,
BOOK_TITLE VARCHAR(255) not null,
DESCRIPTION VARCHAR(2000),
NB_OF_PAGE INTEGER not null,
PRICE DOUBLE(52, 0),
ISBN VARCHAR(255),
ILLUSTRATIONS SMALLINT,
primary key (ID)
);

Il ne manque plus qu’une information pour que ceci fonctionne : vous devez réfé-
rencer le fichier book_mapping.xml dans le fichier persistence.xml à l’aide d’un
marqueur <mapping-file>. Le fichier persistence.xml définit le contexte de persis-
tance de l’entité et la base de données à laquelle elle sera associée : le fournisseur
de contenu a absolument besoin de ce fichier pour pouvoir retrouver les associations
XML externes. Déployez l’entité Book avec ces deux fichiers XML (placés dans le
répertoire META-INF), et c’est fini (voir Listing 3.32).

Listing 3.32 : Fichier persistence.xml faisant référence à un fichier d’association externe


<?xml version="1.0" encoding="UTF-8"?>
<persistence
xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="javaee6PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>com.apress.javaee6.chapter03.Book</class>
<mapping-file>META-INF/book_mapping.xml</mapping-file>
<properties>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
102 Java EE 6 et GlassFish 3 

<!--Persistence provider properties-->


</properties>
</persistence-unit> </persistence>

Objets intégrables

Dans la section "Clés primaires composées" plus haut dans ce chapitre, nous avons
rapidement vu comment une classe pouvait être intégrée pour servir de clé primaire
avec l’annotation @EmbeddedId. Les objets intégrables sont des objets qui n’ont pas
d’identité persistante par eux-mêmes. Une entité peut contenir des collections d’ob-
jets intégrables ainsi qu’un simple attribut d’une classe intégrable : dans les deux
cas, ils seront stockés comme faisant partie de l’entité et partageront son identité.
Ceci signifie que chaque attribut de l’objet intégré est associé à la table de l’entité.
Il s’agit donc d’une relation de propriété stricte (une composition) : quand l’entité
est supprimée, l’objet intégré disparaît également.
Cette composition entre deux classes passe par des annotations. La classe incluse
utilise @Embeddable et l’entité qui inclut utilise @Embedded. Prenons l’exemple d’un
client possédant un identifiant, un nom, une adresse e-mail et une adresse. Tous ces
attributs pourraient se trouver dans une entité Customer (voir Listing 3.34 un peu
plus loin) mais, pour des raisons de modélisation, ils sont répartis en deux classes :
Customer et Address. Cette dernière n’ayant pas d’identité propre mais étant simple-
ment une composante de l’état de Customer, c’est une bonne candidate au statut de
classe intégrable (voir Listing 3.33).

Listing 3.33 : La classe Address est une classe intégrable


@Embeddable
public class Address {

private String street1;


private String street2;
private String city;
private String state;
private String zipcode;
private String country;

// Constructors, getters, setters


}

Comme vous pouvez le constater à la lecture du Listing 3.33, la classe Address est


annotée comme étant non pas une entité mais une classe intégrable – l’annotation

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 103

@Embeddable indique qu’Address peut être intégrée dans une classe entité (ou dans
une autre classe intégrable). À l’autre extrémité de la composition, l’entité Customer
doit utiliser l’annotation @Embedded pour indiquer qu’Address est un attribut per-
sistant qui sera stocké comme composante interne et qu’il partage son identité (voir
Listing 3.34).

Listing 3.34 : L’entité Customer intègre un objet Address


@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Embedded
private Address address;

// Constructors, getters, setters


}

Chaque attribut d’Address est associé à la table de l’entité Customer. Il n’y aura donc
qu’une seule table qui aura la structure définie dans le Listing 3.35. Comme nous le ver-
rons plus loin dans la section "Clés primaires composées", les entités peuvent redéfinir
les attributs des objets qu’elles intègrent (avec l’annotation @AttributeOverrides).

Listing 3.35 : Structure de la table CUSTOMER avec tous les attributs d’Address


create table CUSTOMER (
ID BIGINT not null,
LASTNAME VARCHAR(255),
PHONENUMBER VARCHAR(255),
EMAIL VARCHAR(255),
FIRSTNAME VARCHAR(255),
STREET2 VARCHAR(255),
STREET1 VARCHAR(255),
ZIPCODE VARCHAR(255),
STATE VARCHAR(255),
COUNTRY VARCHAR(255),
CITY VARCHAR(255),
primary key (ID)
);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
104 Java EE 6 et GlassFish 3 

Types d’accès d’une classe intégrable

Le type d’accès d’une classe intégrable est déterminé par celui de la classe entité
dans laquelle elle est intégrée. Si cette entité utilise explicitement un type d’accès
par propriété, l’objet intégrable utilisera implicitement un accès par propriété aussi.
Vous pouvez préciser un type d’accès différent pour une classe intégrable au moyen
de l’annotation @Access.
Dans le Listing 3.36, l’entité Customer et la classe Address (voir Listing 3.37) utili-
sent des types d’accès différents.

Listing 3.36 : L’entité Customer avec un type d’accès par champ


@Entity
@Access(AccessType.FIELD)
public class Customer {

@Id @GeneratedValue
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(name = "phone_number", length = 15)
private String phoneNumber;
@Embedded
private Address address;

// Constructeurs, getters, setters


}

Listing 3.37 : La classe intégrable utilise un type d’accès par propriété


@Embeddable
@Access(AccessType.PROPERTY)
public class Address {

private String street1;


private String street2;
private String city;
private String state;
private String zipcode;
private String country;

// Constructeurs

@Column(nullable = false)
public String getStreet1() {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 105

return street1;
}
public void setStreet1(String street1) {
this.street1 = street1;
}

public String getStreet2() {


return street2;
}
public void setStreet2(String street2) {
this.street2 = street2;
}

@Column(nullable = false, length = 50)


public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}

@Column(length = 3)
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}

@Column(name = "zip_code", length = 10)


public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}

public String getCountry() {


return country;
}
public void setCountry(String country) {
this.country = country;
}
}

Il est fortement conseillé de configurer le type d’accès des classes intégrables afin
d’éviter les erreurs d’association qui pourraient se produire lorsqu’une telle classe
est intégrée dans plusieurs entités. Étendons notre modèle en ajoutant une entité
Order (voir Figure  3.5). La classe Address est maintenant intégrée à la fois par
­Customer (pour représenter l’adresse personnelle du client) et dans Order (pour
représenter l’adresse de livraison).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
106 Java EE 6 et GlassFish 3 

<<entity>> <<embeddable>> <<entity>>


<<field access>> Address <<property access>>
Customer Order
.
Figure 3.5
Address est intégrée par Customer et Order.

Chaque entité définit un type d’accès différent : Customer utilise un accès par champ
et Order, un accès par propriété. Le type d’accès d’un objet intégrable étant déter-
miné par celui de la classe entité dans laquelle il est déclaré, Address sera associée
de deux façons différentes, ce qui peut poser des problèmes. Pour les éviter, le type
d’accès d’Address doit être indiqué explicitement.

INFO

Les types d’accès explicites sont également très utiles avec l’héritage. Par défaut, les entités
filles héritent du type d’accès de leur entité parente. Dans une hiérarchie d’entités, il est
cependant possible d’accéder à chacune différemment des autres : l’ajout d’une annotation
@Access permet de redéfinir localement le type d’accès par défaut utilisé dans la hiérarchie.

Correspondance des relations

Le monde de la programmation orientée objet est rempli de classes et de relations


entre classes. Ces relations sont structurelles car elles lient des objets d’un certain
type à des objets d’autres types, permettant ainsi à un objet de demander à un autre
de réaliser une action. Il existe plusieurs types d’associations entre les classes.
Premièrement, une relation a une direction. Elle peut être unidirectionnelle (un objet
peut aller vers un autre) ou bidirectionnelle (un objet peut aller vers un autre et vice
versa). En Java, on utilise le point (.) pour naviguer entre les objets. Lorsque l’on
écrit, par exemple, customer.getAddress().getCountry(), on navigue d’un objet
Customer vers un objet Address puis un objet Country.

En UML (Unified Modeling Language), une relation unidirectionnelle entre deux


classes est représentée par une flèche indiquant la direction. À la Figure  3.6, par
exemple, Class1 (la source) peut naviguer vers Class2 (la cible), mais pas l’inverse.

Figure 3.6 Class1 Class2


Relation unidirectionnelle
entre deux classes.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 107

Comme le montre la Figure 3.7, une relation bidirectionnelle n’utilise pas de flèche :


Class1 peut naviguer vers Class2 et vice versa. En Java, ce type de relation est
représenté par une classe Class1 ayant un attribut instance de Class2 et par une
classe Class2 ayant un attribut instance de Class1.

Figure 3.7 Class1 Class2


Relation bidirectionnelle
entre deux classes.

Une relation a également une cardinalité. Chaque extrémité peut préciser le nombre
d’objets impliqués dans cette relation. Le diagramme UML de la Figure 3.8 indique,
par exemple, qu’une instance de Class1 est en relation avec zéro ou plusieurs
i­nstances de Class2.

Figure 3.8 Class1 Class2


1 0..*
Cardinalité des relations
entre classes.

En UML, une cardinalité est un intervalle de valeurs compris entre un minimum et


un maximum : 0..1 signifie qu’il y aura au minimum zéro objet et au maximum un
objet, 1 signifie qu’il n’y aura qu’une et une seule instance, 1..*, qu’il y aura une ou
plusieurs instances et 3..6, qu’il y aura entre trois et six objets. En Java, une relation
qui représente plusieurs objets utilise les collections de java.util.Collection,
java.util.Set, java.util.List ou java.util.Map.

Une relation a un propriétaire. Dans une relation unidirectionnelle, ce propriétaire


est implicite : à la Figure 3.6, il est évident que le propriétaire est Class1. Dans une
relation bidirectionnelle comme celle de la Figure 3.7, il faut en revanche l’indiquer
explicitement en désignant le côté propriétaire, qui spécifie l’association physique,
et le côté opposé (non propriétaire).
Dans les sections qui suivent, nous verrons comment associer des collections d’objets
avec les annotations JPA.

Relations dans les bases de données relationnelles

Dans le monde relationnel, les choses sont différentes puisque, à proprement parler,
une base de données relationnelle est un ensemble de relations (également appelées
tables) : tout est modélisé sous forme de table – pour modéliser une relation, vous ne
disposez ni de listes, ni d’ensembles, ni de tables de hachage : vous n’avez que des

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
108 Java EE 6 et GlassFish 3 

tables. Une relation entre deux classes Java sera représentée dans la base de données
par une référence à une table qui peut être modélisée de deux façons : avec une clé
étrangère (une colonne de jointure) ou avec une table de jointure.
À titre d’exemple, supposons qu’un client n’ait qu’une seule adresse, ce qui implique
une relation 1–1. En Java, la classe Customer aurait donc un attribut Address ; dans
le monde relationnel, vous pourriez avoir une table CUSTOMER pointant vers une table
ADDRESS via une clé étrangère, comme le montre la Figure 3.9.

Address
Customer Clé primaire Street City Country
Clé primaire Firstname Lastname Clé étrangère 11 Aligre Paris France
1 James Rorisson 11 12 Balham London UK
2 Dominic Johnson 12 13 Alfama Lisbon Portugal
3 Maca Macaron 13

Figure 3.9
Une relation entre deux tables utilisant une colonne de jointure.

La seconde méthode consiste à utiliser une table de jointure. La table CUSTOMER de la


Figure 3.10 ne stocke plus la clé étrangère vers ADDRESS mais utilise une table inter-
médiaire pour représenter la relation liant ces deux tables. Cette liaison est constituée
par les clés primaires des deux tables.

Address
Customer Clé primaire Street City Country
Clé primaire Firstname Lastname Clé étrangère 11 Aligre Paris France
1 James Rorisson 11 12 Balham London UK
2 Dominic Johnson 12 13 Alfama Lisbon Portugal
3 Maca Macaron 13

Table de jointure
Customer Pk Address Pk
1 11
2 12
3 13

Figure 3.10
Relation utilisant une table de jointure.

On n’utilise pas une table de jointure pour représenter une relation  1–1 car cela
pourrait avoir des conséquences sur les performances (il faudrait toujours accéder à
la troisième table pour obtenir l’adresse d’un client) ; elles sont généralement réser-
vées aux relations 1–N ou N–M. Comme nous le verrons dans la section suivante,

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 109

JPA utilise ces deux méthodes pour associer les relations entre les objets à une base
de données.

Relations entre entités

Revenons maintenant à JPA. La plupart des entités doivent pouvoir référencer ou


être en relation avec d’autres entités : c’est ce que produisent les diagrammes uti-
lisés pour modéliser les applications professionnelles. JPA permet d’associer ces
relations de sorte qu’une entité puisse être liée à une autre dans un modèle relation-
nel. Comme pour les annotations d’associations élémentaires, que nous avons déjà
étudiées, JPA utilise une configuration par exception pour ces relations : il utilise un
mécanisme par défaut pour stocker une relation mais, si cela ne vous convient pas,
vous disposez de plusieurs annotations pour adapter l’association à vos besoins.
La cardinalité d’une relation entre deux entités peut être 1–1, 1–N, N–1 ou N–M.
Les annotations des associations correspondantes sont donc nommées @OneToOne, @
OneToMany, @ManyToOne et @ManyToMany. Chacune d’elles peut être utilisée de façon
unidirectionnelle ou bidirectionnelle : le Tableau 3.1 énumère toutes les combi-
naisons possibles.

Tableau 3.1 : Combinaisons possibles entre cardinalités et directions

Cardinalité Direction
1–1 Unidirectionnelle
1–1 Bidirectionnelle
1–N Unidirectionnelle
N–1/1–N Bidirectionnelle
N–1 Unidirectionnelle
N–M Unidirectionnelle
N–M Bidirectionnelle

Vous pouvez constater que unidirectionnel et bidirectionnel sont des concepts répé-
titifs qui s’appliquent de la même façon à toutes les cardinalités. Vous verrez bientôt
la différence entre les relations unidirectionnelles et bidirectionnelles, puis com-
ment implémenter certaines de ces combinaisons, dont nous ne décrirons qu’un
sous-ensemble : les expliquer toutes serait répétitif. L’important est de comprendre
comment traduire la cardinalité et la direction en relations.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
110 Java EE 6 et GlassFish 3 

Unidirectionnelle et bidirectionnelle
Du point de vue de la modélisation objet, la direction entre les classes est naturelle.
Dans une relation unidirectionnelle, un objet A pointe uniquement vers un objet B
alors que, dans une relation bidirectionnelle, ils se font mutuellement référence.
Cependant, comme le montre l’exemple suivant d’un client et de son adresse, un peu
de travail est nécessaire lorsque l’on veut représenter une relation bidirectionnelle
dans une base de données.
Dans une relation unidirectionnelle, une entité Customer a un attribut de type Address
(voir Figure 3.11). Cette relation ne va que dans un seul sens : on dit que le client est
le propriétaire de la relation. Du point de vue de la base de données, ceci signifie que
la table CUSTOMER contiendra une clé étrangère (une colonne de jointure) pointant
vers la table ADDRESS. Par ailleurs, le propriétaire de la relation peut personnaliser la
traduction de cette relation : si vous devez modifier le nom de la clé étrangère, par
exemple, cette annotation aura lieu dans l’entité Customer (le propriétaire).

Figure 3.11 Address
Customer -id : Long
Relation unidirectionnelle
-id : Long -sreet1 : String
entre Customer -firstname : String -street2 : String
et Address. -lastname : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String

Comme on l’a mentionné précédemment, les relations peuvent également être bidi-
rectionnelles. Pour naviguer entre Address et Customer, nous devons ajouter un
attribut Customer à l’entité Address (voir Figure 3.12). Notez que les attributs repré-
sentant une relation n’apparaissent pas dans les diagrammes UML.

Figure 3.12 Address
Customer -id : Long
Relation bidirectionnelle -id : Long -street1 : String
entre Customer -firstName : String -street2 : String
et Address. -lastName : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String

En termes de Java et d’annotations, ceci revient à avoir deux associations de type 1–1


dans les deux directions opposées. Nous pouvons donc considérer une relation bidi-
rectionnelle comme une paire de relations unidirectionnelles allant dans les deux
sens (voir Figure 3.13).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 111

Figure 3.13 Address
Customer -id : Long
Relation bidirectionnelle -id : Long -street1 : String
représentée par -firstName : String -street2 : String
deux relations -lastName : String -city : String
unidirectionnelles. -email : String -state : String
-phoneNumber : String -zipcode : String
-country : String

Comment associer tout cela à une base de données ? Qui est le propriétaire de cette
relation bidirectionnelle ? À qui appartient l’information sur la colonne ou la table
de jointure  ? Si les relations unidirectionnelles ont un côté propriétaire, les bidi-
rectionnelles ont à la fois un côté propriétaire et un côté opposé, qui doivent être
indiqués explicitement par l’élément mappedBy des annotations @OneToOne, @OneTo-
Many et @ManyToMany. mappedBy identifie l’attribut propriétaire de la relation ; il est
obligatoire pour les relations bidirectionnelles.
Pour illustrer tout ceci, comparons du code Java à sa traduction dans la base de don-
nées. Comme vous pouvez le constater dans la partie gauche de la Figure 3.14, les
deux entités pointent l’une vers l’autre au moyen d’attributs : Customer possède un
attribut address annoté par @OneToOne et l’entité Address a un attribut customer éga-
lement annoté. Dans la partie droite de cette figure se trouvent les tables C­ USTOMER
et ADDRESS. CUSTOMER est la table propriétaire de la relation car elle contient la clé
étrangère vers ADDRESS.

CUSTOMER
@Entity +ID bigint Nullable false
public class C { LASTNAME varchar(255) Nu lable true
PHONENUMBER varchar(255) Nu lable true
@Id @GeneratedValue EMAIL varchar(255) Nu lable true
private Long i ; FIRSTNAME varchar(255) Nu lable true
private String firstName; #ADDRESS FK bigint Nu lable true
private String lastName;
private String email;
private String phoneNumber;
@On T O
@JoinColumn(name = " s k")
private Address address;
}
ADDRESS
@Entity +ID bigint Nullable false
public class Address { STREET2 varchar(255) Nullable true
STREET1 varchar(255) Nullable true
@Id @GeneratedValue ZIPCODE varchar(255) Nullable true
private Long id; STATE varchar(255) Nullable true
private String street1; COUNTRY varchar(255) Nullable true
private String street2; CITY varchar(255) Nullable true
private String city;
private String state;
private String zipcode;
private String country;
@ n ( p d = " d s")
private Customer customer;
}

Figure 3.14
Code de Customer et Address avec leur correspondance dans la base de données.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
112 Java EE 6 et GlassFish 3 

L’entité Address utilise l’élément mappedBy de son annotation @OneToOne. Ici, map-
pedBy indique que la colonne de jointure (address) est déclarée à l’autre extrémité
de la relation. De son côté, l’entité Customer définit la colonne de jointure avec
l’annotation @JoinColumn et renomme la clé étrangère en address_fk. Customer est
l’extrémité propriétaire de la relation et, en tant que telle, elle est la seule à définir
l’association de la colonne de jointure. Address est l’extrémité opposée et c’est donc
la table de l’entité propriétaire qui contient la clé étrangère (la table CUSTOMER a une
colonne ADDRESS_FK).
Il existe un élément mappedBy pour les annotations @OneToOne, @OneToMany et @Many-
ToMany, mais pas pour @ManyToOne.

INFO

Si vous connaissez Hibernate, vous pouvez considérer que l’élément mappedBy de JPA est
l’équivalent de l’attribut inverse de Hibernate, qui indique l’extrémité à ignorer dans une
relation.

@OnetoOne unidirectionnelle
Une relation 1–1 unidirectionnelle entre deux entités a une référence de cardina-
lité 1 qui ne peut être atteinte que dans une seule direction. Reprenons l’exemple
d’un client et de son adresse en supposant qu’il n’a qu’une seule adresse (cardina-
lité 1). Il faut pouvoir naviguer du client (la source) vers l’adresse (la cible) pour
savoir où habite le client. Dans le modèle de la Figure  3.15, nous n’avons pas
besoin de faire le trajet inverse (on n’a pas besoin de savoir quel client habite à une
adresse donnée).

Figure 3.15 Address
Customer -id : Long
Un client a une seule -id : Long -street1 : String
adresse. -firstName : String 1 -street2 : String
-lastName : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String

En Java, ceci signifie que la classe Customer aura un attribut Address (voir Listings 3.38
et 3.39).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 113

Listing 3.38 : Une entité Customer avec une seule adresse


@Entity
public class Customer {
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private Address address;

// Constructeurs, getters, setters


}

Listing 3.39 : L’entité Address


@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String street1;
private String street2;
private String city;
private String state;
private String zipcode;
private String country;

// Constructeurs, getters, setters


}

Comme vous pouvez le constater à la lecture des Listings 3.38 et 3.39, ces deux
entités utilisent un nombre minimal d’annotations – @Entity plus @Id et @Genera-
tedValue pour la clé primaire, c’est tout... Grâce à la configuration par exception,
le fournisseur de persistance les associera à deux tables et ajoutera une clé étran-
gère pour représenter la relation (allant du client à l’adresse). Cette relation  1–1
est déclenchée par le fait qu’Address est déclarée comme une entité et qu’elle est
incluse dans l’entité Customer sous la forme d’un attribut. Il n’y a donc pas besoin
d’annotation @OneToOne car le comportement par défaut suffit (voir Listings 3.40 et
3.41).

Listing 3.40 : La table CUSTOMER avec une clé étrangère vers ADDRESS


create table CUSTOMER (
ID BIGINT not null,
FIRSTNAME VARCHAR(255),
LASTNAME VARCHAR(255),
EMAIL VARCHAR(255),

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
114 Java EE 6 et GlassFish 3 

PHONENUMBER VARCHAR(255),
ADDRESS_ID BIGINT,
primary key (ID),
foreign key (ADDRESS_ID) references ADDRESS(ID)
);

Listing 3.41 : La table ADDRESS


create table ADDRESS (
ID BIGINT not null,
STREET1 VARCHAR(255),
STREET2 VARCHAR(255),
CITY VARCHAR(255),
STATE VARCHAR(255),
ZIPCODE VARCHAR(255),
COUNTRY VARCHAR(255),
primary key (ID)
);

Comme vous le savez, si un attribut n’est pas annoté, JPA lui applique les règles
d’association par défaut. La colonne de clé étrangère s’appellera donc ADDRESS_ID
(voir Listing 3.40), qui est la concaténation du nom de l’attribut (address, ici), d’un
blanc souligné et du nom de la clé primaire de la table destination (ici, la colonne
ID de la table ADDRESS). Notez également que, dans le langage de définition des don-
nées, la colonne ADDRESS_ID peut, par défaut, recevoir des valeurs NULL : par défaut,
une relation 1–1 est donc associée à zéro (NULL) ou une valeur.
Il existe deux annotations permettant d’adapter l’association d’une relation 1–1. La
première est @OneToOne (car la cardinalité de la relation est un)  : elle permet de
modifier certains attributs de la relation elle-même, comme la façon dont elle sera
parcourue. Son API est décrite dans le Listing 3.42.

Listing 3.42 : API de l’annotation @OneToOne


@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default EAGER;
boolean optional() default true;
String mappedBy() default "";
boolean orphanRemoval() default false;
}

L’autre annotation s’appelle @JoinColumn (son API ressemble beaucoup à celle de


@Column). Elle permet de personnaliser la colonne de jointure, c’est-à-dire la clé

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 115

étrangère, du côté du propriétaire de la relation. Le Listing 3.43 présente un exemple


d’utilisation de ces deux annotations.

Listing 3.43 : L’entité Customer avec une association de relation personnalisée


@Entity
public class Customer {
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@OneToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "add_fk", nullable = false)
private Address address;

// Constructeurs, getters, setters


}

Dans le Listing  3.43, on utilise @JoinColumn pour renommer la colonne de


clé étrangère en ADD_FK et rendre la relation obligatoire en refusant les valeurs
NULL(nullable=false). L’annotation @OneToOne, quant à elle, demande au fournisseur
de persistance de parcourir la relation de façon paresseuse.

@OnetoMany unidirectionnelle
Dans une relation 1–N, l’objet source référence un ensemble d’objets cibles. Une
commande, par exemple, est composée de plusieurs lignes de commande (voir
Figure 3.16). Inversement, une ligne de commande pourrait faire référence à la com-
mande dont elle fait partie à l’aide d’une annotation @ManyToOne. Dans la figure,
Order est l’extrémité "One" (la source) de la relation et OrderLine est son extrémité
"Many" (la cible).

Figure 3.16 OrderLine
Order -id : Long
Une commande compte -id : Long
*
-item : String
plusieurs lignes. -creationDate : Date -unitPrice : Double
-quantity : Integer

La cardinalité est multiple et la navigation ne se fait que dans le sens Order vers
OrderLine. En Java, cette multiplicité est décrite par les interfaces Collection, List
et Set du paquetage java.util. Le Listing 3.44 présente le code de l’entité Order
avec une relation 1–N vers OrderLine (voir Listing 3.45).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
116 Java EE 6 et GlassFish 3 

Listing 3.44 : L’entité Order contient des OrderLine


@Entity
public class Order {

@Id @GeneratedValue
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
private List<OrderLine> orderLines;

// Constructeurs, getters, setters


}

Listing 3.45 : L’entité OrderLine


@Entity
@Table(name = "order_line")
public class OrderLine {

@Id @GeneratedValue
private Long id;
private String item;
private Double unitPrice;
private Integer quantity;

// Constructeurs, getters, setters


}

Le code du Listing 3.44 n’utilise pas d’annotation particulière car il repose sur le


paradigme de configuration par exception. Le fait qu’une entité ait un attribut qui
soit une collection d’un type d’une autre entité déclenche une association OneTo-
Many par défaut. Les relations 1–N unidirectionnelles utilisent par défaut une table
de jointure pour représenter la relation ; cette table est une liste de couples de clés
étrangères : une clé fait référence à la table ORDER et est du même type que sa clé
primaire, l’autre désigne la table ORDER_LINE. Cette table de jointure s’appelle par
défaut ORDER_ORDER_ LINE et possède la structure décrite à la Figure 3.17.
Si vous n’aimez pas le nom de la table de jointure ou celui des clés étrangères, ou
si vous voulez associer la relation à une table existante, vous pouvez vous servir des
annotations de JPA pour redéfinir ces valeurs par défaut. Le nom d’une colonne de
jointure est formé par défaut par la concaténation du nom de l’entité, d’un blanc
souligné et du nom de la clé primaire désignée par la clé étrangère. Comme l’an-
notation @JoinColumn permet de modifier le nom des colonnes de clés étrangères,
@JoinTable fait de même pour la table de jointure. Vous pouvez également utili-
ser l’annotation @OneToMany (voir Listing 3.46), qui, comme @OneToOne, permet de
­personnaliser la relation elle-même (mode de parcours, etc.).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 117

ORDER LINE
ORDER
+ID bigint Nullable = false
+ID bigint Nullable = false
ITEM varchar(255) Nullable = true
CREATIONDATE timestamp Nullable = true
UNITPRICE double Nullable = true
QUANTITY integer Nullable = true

ORDER ORDER LINE


+#ORDER ID bigint Nullable = false
+#ORDERLINES ID bigint Nullable = false

Figure 3.17
Table de jointure entre ORDER et ORDER_LINE.

Listing 3.46 : API de l’annotation @JoinTable


@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface JoinTable {
String name() default "";
String catalog() default "";
String schema() default "";
JoinColumn[] joinColumns() default {};
JoinColumn[] inverseJoinColumns() default {};
UniqueConstraint[] uniqueConstraints() default {};
}

Dans l’API de l’annotation @JoinTable présentée dans le Listing 3.46, vous pouvez


remarquer deux attributs de type @JoinColumn  : joinColumns et inverseJoinCo-
lumns. Ils permettent de différencier l’extrémité propriétaire de la relation et son
extrémité opposée. L’extrémité propriétaire de la relation est décrite dans l’élément
joinColumns et, dans notre exemple, désigne la table ORDER. L’extrémité opposée,
la cible de la relation, est précisée par l’élément inverseJoinColumns et désigne la
table ORDER_LINE.
Dans l’entité Order (voir Listing 3.47), vous pouvez ajouter les annotations @OneTo-
Many et @JoinTable pour l’attribut orderLines afin de modifier le nom de la table de
jointure en JND_ORD_LINE (au lieu d’ORDER_ORDER_LINE) et pour renommer les deux
colonnes de clé étrangère.

Listing 3.47 : Entité Order avec une relation 1–N annotée


@Entity
public class Order {

@Id @GeneratedValue

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
118 Java EE 6 et GlassFish 3 

private Long id;


@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@OneToMany
@JoinTable(name = "jnd_ord_line",
joinColumns = @JoinColumn(name = "order_fk"),
inverseJoinColumns = @JoinColumn(name = "order_line_fk") )
private List<OrderLine> orderLines;
// Constructors, getters, setters

L’entité Order du Listing  3.47 sera associée à la table de jointure décrite dans le
Listing 3.48.

Listing 3.48 : Structure de la table de jointure


create table JND_ORD_LINE (
ORDER_FK BIGINT not null,
ORDER_LINE_FK BIGINT not null,
primary key (ORDER_FK, ORDER_LINE_FK),
foreign key (ORDER_LINE_FK) references ORDER_LINE(ID),
foreign key (ORDER_FK) references ORDER(ID)
);

La règle par défaut pour une relation 1–N unidirectionnelle consiste à utiliser une
table de jointure, mais il est très facile (et utile si vous utilisez une base de données
existante) de faire en sorte d’utiliser des clés étrangères (via une colonne de join-
ture). Pour cela, l’entité Order doit utiliser une annotation @JoinColumn à la place
de @JoinTable, comme le montre le Listing 3.49.

Listing 3.49 : Entité Order avec une colonne de jointure


@Entity
public class Order {
@Id @GeneratedValue
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "order_fk")
private List<OrderLine> orderLines;

// Constructeurs, getters, setters


}

Le code de l’entité OrderLine ne change pas, il est identique à celui du Listing 3.45.


Vous remarquerez qu’ici l’annotation @OneToMany redéfinit le mode de parcours par
défaut (en le fixant à EAGER au lieu de LAZY). En utilisant @JoinColumn, la relation
unidirectionnelle est ensuite traduite en utilisant une clé étrangère. Cette clé est

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 119

renommée en ORDER_FK et existe dans la table cible (ORDER_LINE). On obtient alors


la structure représentée à la Figure 3.18. Il n’y a plus de table de jointure et la liaison
entre les deux tables s’effectue grâce à la clé étrangère ORDER_FK.

ORDER ORDER LINE


+ID bigint Nullable = false +ID bigint Nullable = false
CREATIONDATE timestamp Nullable = true ITEM varchar(255) Nullable = true
QUANTITY integer Nullable = true
UNITPRICE double Nullable = true
#ORDER FK bigint Nullable = true

Figure 3.18
Colonne de jointure entre Order et OrderLine.

@ManytoMany bidirectionnelle
Une relation N–M bidirectionnelle intervient lorsqu’un objet source fait référence
à plusieurs cibles et qu’une cible fait référence à plusieurs sources. Un album CD,
par exemple, est créé par plusieurs artistes, et un même artiste peut apparaître sur
plusieurs albums. Côté Java, chaque entité contiendra donc une collection d’entités
cibles. En terme de base de données relationnelle, la seule façon de représenter une
relation N–M consiste à utiliser une table de jointure (une colonne de jointure ne peut
pas convenir) ; comme nous l’avons vu précédemment, il faut également définir expli-
citement le propriétaire d’une relation bidirectionnelle à l’aide de l’élément mappedBy.
Si l’on suppose que l’entité Artist est propriétaire de la relation, ceci implique
que CD est l’extrémité opposée (voir Listing 3.50) et qu’elle doit utiliser l’élément
mappedBy de son annotation @ManyToMany. Ici, mappedBy indique au fournisseur de
persistance qu’appearsOnCDs est le nom de l’attribut correspondant dans l’entité
propriétaire de la relation.

Listing 3.50 : Un CD est créé par plusieurs artistes


@Entity
public class CD {

@Id @GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
@ManyToMany(mappedBy = "appearsOnCDs")
private List<Artist> createdByArtists;

// Constructeurs, getters, setters

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
120 Java EE 6 et GlassFish 3 

Si Artist est propriétaire de la relation (voir Listing 3.51), c’est donc à cette entité
qu’il revient de personnaliser la table de jointure via les annotations @JoinTable et
@JoinColumn.

Listing 3.51 : Un artiste apparaît sur plusieurs CD


@Entity
public class Artist {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
@ManyToMany @JoinTable(name = "jnd_art_cd",
joinColumns = @JoinColumn(name = "artist_fk"),
inverseJoinColumns = @JoinColumn(name = "cd_fk"))
private List<CD> appearsOnCDs;

// Constructors, getters, setters

La table de jointure entre Artist et CD est renommée en JND_ART_CD et les noms de


ses colonnes sont également modifiés. L’élément joinColumns fait référence à l’ex-
trémité propriétaire (l’Artist), tandis qu’inverseJoinColumns désigne l’extrémité
inverse (le CD). La structure de la base obtenue est présentée à la Figure 3.19.

CD
ARTIST
+ID bigint Nullable = false
+ID bigint Nullable = false
TITLE varchar(255) Nullable = true
LASTNAME varchar(255) Nullable = true
PRICE double Nullable = true
FIRSTNAME varchar(255) Nullable = true
DESCRIPTION varchar(255) Nullable = true

JND ART CD
+#CD FK bigint Nullable = false
+#ARTIST FK bigint Nullable = false

Figure 3.19
Les tables Artist, CD et la table de jointure.

Dans une relation N–M et 1–1 bidirectionnelle, chaque extrémité peut, en fait, être
considérée comme la propriétaire de la relation. Quoi qu’il en soit, l’autre extré-
mité doit inclure l’élément mappedBy : dans le cas contraire, le fournisseur considé-
rera que les deux extrémités sont propriétaires et traitera cette relation comme deux
relations 1–N unidirectionnelles distinctes. Ici, cela produirait donc quatre tables :
ARTIST et CD et deux tables de jointures, ARTIST_CD et CD_ARTIST. On ne peut pas non
plus utiliser un élément mappedBy des deux côtés d’une relation.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 121

Chargement des relations

Toutes les annotations que nous avons vues (@OneToOne, @OneToMany, @ManyToOne
et @ManyToMany) définissent un attribut de chargement qui précise que les objets
associés doivent être chargés immédiatement (chargement "glouton") ou plus tard
(chargement "paresseux") et qui influe donc sur les performances. Selon l’applica-
tion, certaines relations sont utilisées plus souvent que d’autres : dans ces situations,
vous pouvez optimiser les performances en chargeant les données de la base lors
de la première lecture de l’entité (glouton) ou uniquement lorsqu’elle est utilisée
(paresseux). À titre d’exemple, prenons deux cas extrêmes.
Supposons que nous ayons quatre entités toutes reliées les unes aux autres avec des
cardinalités différentes (1–1, 1–N). Dans le premier cas (voir Figure 3.20), elles ont
toutes des relations "gloutonnes", ce qui signifie que, dès que l’on charge Class1
(par une recherche par ID ou par une requête), tous les objets qui en dépendent sont
automatiquement chargés en mémoire, ce qui peut avoir certaines répercussions sur
votre système.

Figure 3.20 Class1 Class2 Class3 Class4


1 1..* 1..*
Quatre entités avec des gloutonne gloutonne gloutonne
relations gloutonnes.

Dans le scénario opposé, toutes les relations utilisent un chargement paresseux (voir
Figure 3.21). Lorsque l’on charge Class1, rien d’autre n’est placé en mémoire (sauf
les attributs directs de Class1, bien sûr). Il faut explicitement accéder à Class2 (via
la méthode getter, par exemple) pour que le fournisseur de persistance charge les
données à partir de la base, etc. Pour manipuler le graphe complet des objets, il faut
donc appeler explicitement chaque entité :
class1.getClass2().getClass3().getClass4()

Figure 3.21 Class1 Class2 Class3 Class4


1 1..* 1..*
Quatre entités avec des paresseuse paresseuse paresseuse
relations paresseuses.

Mais ne pensez pas qu’EAGER est le Mal et LAZY, le Bien. EAGER placera toutes les
données en mémoire à l’aide d’un petit nombre d’accès à la base (le fournisseur de
persistance utilisera sûrement des jointures pour extraire ces données). Avec LAZY,
vous ne risquez plus de remplir la mémoire puisque vous contrôlez les objets qui
sont chargés, mais vous devrez faire plus d’accès à la base à chaque fois.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
122 Java EE 6 et GlassFish 3 

Le paramètre fetch est très important car, mal utilisé, il peut pénaliser les perfor-
mances. Chaque annotation a une valeur fetch par défaut que vous devez connaître
et changer si elle ne convient pas (voir Tableau 3.2).

Tableau 3.2 : Stratégie de chargements par défaut

Annotation Stratégie de chargement par défaut


@OneToOne EAGER
@ManyToOne EAGER
@OneToMany LAZY
@ManyToMany LAZY

Lorsque vous chargez une commande (Order) dans votre application, vous avez
toujours besoin d’accéder aux lignes de cette commande (OrderLine). Il peut donc
être avantageux de changer le mode de chargement par défaut de l’annotation @One-
ToMany en EAGER (voir Listing 3.52).

Listing 3.52 : Order est en relation "gloutonne" vers OrderLine


@Entity
public class Order {

@Id @GeneratedValue
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@OneToMany(fetch = FetchType.EAGER)
private List<OrderLine> orderLines;

// Constructeurs, getters, setters


}

Tri des relations

Avec les relations 1–N, les entités gèrent des collections d’objets. Du point de vue
de Java, ces collections ne sont généralement pas triées et les bases de données
relationnelles ne garantissent pas non plus d’ordre sur leurs tables. Si vous voulez
obtenir une liste triée, vous devez donc soit trier la collection dans votre programme,
soit utiliser une requête JPQL avec une clause Order By. Pour le tri des relations,
JPA dispose de mécanismes plus simples reposant sur les annotations.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 123

@OrderBy
L’annotation @OrderBy permet d’effectuer un tri dynamique  : les éléments de la
­collection seront triés lors de leur récupération à partir de la base de données.
L’exemple de l’application CD-BookStore permet à un utilisateur d’écrire des
articles à propos de musique et de livres : ces articles sont affichés sur le site web
et peuvent ensuite être commentés (voir Listing 3.53). Comme nous souhaitons que
ces commentaires apparaissent chronologiquement, nous devons les ordonner.

Listing 3.53 : Entité Comment avec une date de publication


@Entity
public class Comment {

@Id @GeneratedValue
private Long id;
private String nickname;
private String content;
private Integer note;
@Column(name = "posted_date")
@Temporal(TemporalType.TIMESTAMP)
private Date postedDate;

// Constructeurs, getters, setters


}

Les commentaires sont modélisés par l’entité Comment du Listing 3.53. Ils ont un
contenu, sont postés par un visiteur anonyme (identifié par un pseudo) et ont une
date de publication de type TIMESTAMP automatiquement créée par le système. Dans
l’entité News du Listing 3.54, nous trions la liste des commentaires par ordre décrois-
sant des dates de publication en combinant l’annotation @OrderBy avec @OneToMany.

Listing 3.54 : Les commentaires d’une entité News sont triés par ordre décroissant
des dates de publication
@Entity
public class News {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String content;
@OneToMany(fetch = FetchType.EAGER)
@OrderBy("postedDate desc")
private List<Comment> comments;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
124 Java EE 6 et GlassFish 3 

L’annotation @OrderBy prend en paramètre les noms des attributs sur lesquels
portera le tri (postedDate, ici) et la méthode (représentée par la chaîne ASC ou
DESC pour signifier, respectivement, un tri croissant ou décroissant). Vous pou-
vez utiliser plusieurs paires attribut/méthode en les séparant par des virgules  :
OrderBy("postedDate desc, note asc"), par exemple, demande de trier d’abord
sur les dates de publication (par ordre décroissant), puis sur le champ note (par
ordre croissant).
Cette annotation n’a aucun impact sur l’association dans la base de données – le
fournisseur de persistance est simplement informé qu’il doit utiliser une clause
order by lorsque la collection est récupérée.

@OrderColumn
JPA 1.0 supportait le tri dynamique avec l’annotation @OrderBy mais ne permettait
pas de maintenir un ordre persistant. JPA 2.0 règle ce problème à l’aide d’une nou-
velle annotation, @OrderColumn (voir Listing  3.55), qui informe le fournisseur de
persistance qu’il doit gérer la liste triée à l’aide d’une colonne séparée contenant un
index.

Listing 3.55 : L’API de @OrderColumn est semblable à celle de @Column


@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OrderColumn {
String name() default "";
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
boolean contiguous() default true;
int base() default 0;
String table() default "";
}

Reprenons l’exemple des articles et de leurs commentaires en les modifiant légère-


ment. Cette fois-ci, l’entité Comment du Listing 3.56 n’a plus d’attribut postedDate :
il n’y a donc plus moyen de trier chronologiquement les commentaires.

Listing 3.56 : Entité Comment sans date de publication


@Entity
public class Comment {

@Id @GeneratedValue

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 125

private Long id;


private String nickname;
private String content;
private Integer note;

// Constructeurs, getters, setters


}

L’entité News présentée dans le Listing 3.57 peut alors annoter la relation avec @Order-
Column afin que le fournisseur de persistance associe l’entité News à une table
contenant une colonne supplémentaire pour stocker l’ordre.

Listing 3.57 : L’ordre des commentaires est maintenant persistant


@Entity
public class News {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String content;
@OneToMany(fetch = FetchType.EAGER)
@OrderColumn("posted_index")
private List<Comment> comments;

// Constructeurs, getters, setters


}

Dans le Listing 3.57, @OrderColumn renomme la colonne supplémentaire en POS-


TED_INDEX. Si ce nom n’était pas redéfini, cette colonne porterait un nom formé de
la concaténation de l’entité référencée et de la chaîne _ORDER (COMMENT_ORDER, ici).
Le type de cette colonne doit être numérique.
Cette annotation a des conséquences sur les performances car le fournisseur de per-
sistance doit maintenant également gérer les modifications de l’index. Il doit main-
tenir le tri après chaque insertion, suppression ou réordonnancement. Si des données
sont insérées au milieu d’une liste triée, le fournisseur devra retrier tout l’index.
Les applications portables ne devraient pas supposer qu’une liste est toujours triée
dans la base sous prétexte que certains SGBDR optimisent automatiquement leurs
index pour que les données des tables apparaissent dans le bon ordre. Elles doivent
plutôt utiliser soit @OrderColumn, soit @OrderBy. Ces deux annotations ne peuvent
pas être utilisées en même temps.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
126 Java EE 6 et GlassFish 3 

Traduction de l’héritage

Depuis leur création, les langages orientés objet utilisent le paradigme de l’héri-
tage. C++ autorise l’héritage multiple alors que Java ne permet d’hériter que d’une
seule classe. En programmation orientée objet, les développeurs réutilisent souvent
le code en héritant des attributs et des comportements de classes existantes.
Nous venons de voir que les relations entre entités ont des équivalents directs dans
les bases de données. Ce n’est pas le cas avec l’héritage car ce concept est totale-
ment inconnu du modèle relationnel. Il impose donc plusieurs contorsions pour être
traduit dans un SGBDR.
Pour représenter un modèle hiérarchique dans un modèle relationnel plat, JPA pro-
pose trois stratégies possibles :
■■ Une seule table par hiérarchie de classes. L’ensemble des attributs de toute la
hiérarchie des entités est mis à plat et regroupé dans une seule table (il s’agit de
la stratégie par défaut).
■■ Jointures entre sous-classes. Dans cette approche, chaque entité de la hiérarchie,
concrète ou abstraite, est associée à sa propre table.
■■ Une table par classe concrète. Chaque entité concrète de la hiérarchie est asso-
ciée à une table.

INFO

Le support de la stratégie une table par classe concrète est encore facultatif avec JPA 2.0. Les
applications portables doivent donc l’éviter tant que ce support n’a pas été officiellement
déclaré comme obligatoire dans toutes les implémentations.

Tirant parti de la simplicité d’utilisation des annotations, JPA 2.0 fournit un support
déclaratif pour définir et traduire les hiérarchies d’héritage comprenant des enti-
tés concrètes, des entités abstraites, des classes traduites et des classes transitoires.
L’annotation @Inheritance s’applique à une entité racine pour imposer une straté-
gie d’héritage à cette classe et à ses classes filles. JPA traduit aussi la notion objet
de redéfinition qui permet aux attributs de la classe racine d’être redéfinis dans les
classes filles. Dans la section suivante, nous verrons également comment utiliser les
types d’accès avec l’héritage afin de mélanger les accès par champ et par propriété.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 127

Stratégies d’héritage

JPA propose trois stratégies pour traduire l’héritage. Lorsqu’il existe une hiérarchie
d’entités, sa racine est toujours une entité qui peut définir la stratégie d’héritage
à l’aide de l’annotation @Inheritance. Si elle ne le fait pas, c’est la stratégie par
défaut, consistant à créer une seule table par hiérarchie, qui s’applique. Pour expli-
quer chacune de ces stratégies, nous étudierons comment traduire les entités CD et
Book, qui héritent toutes les deux de l’entité Item (voir Figure 3.22).

Figure 3.22 <<entity>>
Item
Hiérarchie d’héritage -id : Long
entre CD, Book et Item. -title : String
-price : Float
-description : String

<<entity>> <<entity>>
Book CD
-isbn : String -musicCompany : String
-publisher : String -numberOfCDs : Integer
-nbOfPage : Integer -totalDuration : Float
-illustrations : Boolean -gender : String

Item est l’entité racine ; elle possède un identifiant qui servira de clé primaire et
dont héritent les entités CD et Book. Chacune de ces classes filles ajoute des attributs
supplémentaires comme l’ISBN pour Book ou la durée totale d’un album pour CD.

Stratégie utilisant une seule table


Il s’agit de la stratégie de traduction de l’héritage par défaut, dans laquelle toutes les
entités de la hiérarchie sont associées à la même table. Il n’est donc pas nécessaire
d’utiliser l’annotation @Inheritance sur l’entité racine, comme le montre le code
d’Item dans le Listing 3.58.

Listing 3.58 : L’entité Item définit une stratégie d’héritage avec une seule table
@Entity
public class Item {

@Id @GeneratedValue
protected Long id;
@Column(nullable = false)
protected String title;
@Column(nullable = false)

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
128 Java EE 6 et GlassFish 3 

protected Float price;


protected String description;

// Constructeurs, getters, setters


}

Item est la classe parente des entités Book (voir Listing 3.59) et CD (voir Listing 3.60).
Ces entités héritent des attributs d’Item ainsi que de la stratégie d’héritage par
défaut : elles n’ont donc pas besoin d’utiliser l’annotation @Inheritance.

Listing 3.59 : Book hérite d’Item


@Entity
public class Book extends Item {

private String isbn;


private String publisher;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Listing 3.60 : CD hérite d’Item


@Entity
public class CD extends Item {
private String musicCompany;
private Integer numberOfCDs;
private Float totalDuration;
private String gender;

// Constructeurs, getters, setters


}

Sans l’héritage, ces trois entités seraient traduites en trois tables distinctes. Avec la
stratégie de traduction de l’héritage par une seule table, elles finiront toutes dans la
même table portant par défaut le nom de la classe racine : ITEM. La structure de cette
table est décrite à la Figure 3.23.
Comme vous pouvez le constater, la table ITEM rassemble tous les attributs des enti-
tés Item, Book et CD. Cependant, elle contient une colonne supplémentaire qui n’est
liée à aucun des attributs des entités : la colonne discriminante, DTYPE.
La table ITEM sera remplie d’articles, de livres et de CD. Lorsqu’il accède aux don-
nées, le fournisseur de persistance doit savoir à quelle entité appartient chaque ligne
afin d’instancier la classe d’objet appropriée (Item, Book ou CD) : la colonne discri-
minante est donc là pour préciser explicitement le type de chaque colonne.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 129

Figure 3.23 ITEM
+ID bigint Nullable = false
Structure de la table ITEM. DTYPE varchar(31) Nullable = true
TITLE varchar(255) Nullable = false
PRICE double Nullable = false
DESCRIPTION varchar(255) Nullable = true
ILLUSTRATIONS smallint Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
PUBLISHER varchar(255) Nullable = true
MUSICCOMPANY varchar(255) Nullable = true
NUMBEROFCDS integer Nullable = true
TOTALDURATION double Nullable = true
GENDER varchar(255) Nullable = true

La Figure 3.24 montre un fragment de la table ITEM contenant quelques données.


Comme vous pouvez le constater, la stratégie avec une seule table a quelques défauts.
On voit, par exemple, que toutes les colonnes ne sont pas utiles à toutes les entités :
la première ligne stocke les données d’une entité Item (comme l’indique sa colonne
DTYPE) or les instances d’Item n’ont qu’un titre, un prix et une description (voir
Listing 3.58) ; elles n’ont pas de compagnie de disque, d’ISBN, etc. Ces colonnes
resteront donc toujours vides.

Figure 3.24 ID DTYPE TITLE PRICE DESCRIPTION MUSIC ISBN ...


COMPANY
Fragment de contenu 1 Item Pen 2,10 Beautiful black pen ...
de la table ITEM. 2 CD SoulTrane 23,50 Fantastic jazz album Prestige ...
3 CD ZootAllures 18 One of the best of Zappa Warner ...
4 Book The robots of dawn 22,30 Robots everywhere 0-554-456 ...
5 Book H2G2 17,50 Funny IT book ;0) 1-278-983 ...

La colonne discriminante s’appelle DTYPE par défaut, est de type String (traduit en
VARCHAR) et contient le nom de l’entité. Si ce comportement ne vous convient pas,
vous pouvez utiliser l’annotation @DiscriminatorColumn pour modifier le nom et le
type de cette colonne.
Dans le Listing  3.61, nous avons renommé la colonne discriminante en DISC (au
lieu de DTYPE) et modifié son type en Char au lieu de String ; chaque entité change
également sa valeur discriminante en I pour Item, B pour Book (voir Listing 3.62) et
C pour CD (voir Listing 3.63).

Listing 3.61 : Item redéfinit la colonne discriminante


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn (name = "disc",
discriminatorType = DiscriminatorType.CHAR) @
DiscriminatorValue("I")

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
130 Java EE 6 et GlassFish 3 

public class Item {

@Id @GeneratedValue
private Long id;
private String title;
private Float price;
private String description;

// Constructeurs, getters, setters


}

L’entité racine Item définit la colonne discriminante pour toute la hiérarchie à l’aide
de l’annotation @DiscriminatorColumn. Elle change ensuite sa propre valeur en I
avec l’annotation @DiscriminatorValue. Les entités filles doivent uniquement redé-
finir leur propre valeur discriminante.

Listing 3.62 : La valeur discriminante de Book est maintenant B


@Entity
@DiscriminatorValue("B")
public class Book extends Item {

private String isbn;


private String publisher;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters’


}

Listing 3.63 : La valeur discriminante de CD est maintenant C


@Entity
@DiscriminatorValue("C")
public class CD extends Item {

private String musicCompany;


private Integer numberOfCDs;
private Float totalDuration;
private String gender;

// Constructeurs, getters, setters


}

Le résultat est présenté à la Figure 3.25.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 131

Figure 3.25 ID DISC TITLE PRICE DESCRIPTION MUSIC ISBN ...


COMPANY
La table ITEM avec 1 I Pen 2,10 Beautiful black pen ...
un nom et des 2 C SoulTrane 23,50 Fantastic jazz album Prestige ...
valeurs différentes 3 C ZootAllures 18 One of the best of Zappa Warner ...
4 B The robots of dawn 22,30 Robots everywhere 0-554-456 ...
pour la colonne
5 B H2G2 17,50 Funny IT book ;0) 1-278-983 ...
discriminante.

Cette stratégie de table unique est la stratégie par défaut ; c’est la plus facile à com-
prendre et elle fonctionne bien lorsque la hiérarchie est relativement simple et stable.
En revanche, elle a quelques défauts : l’ajout de nouvelles entités dans la hiérarchie
ou d’attributs dans des entités existantes implique d’ajouter des colonnes à la table,
de migrer les données et de modifier les index. Cette stratégie exige également que
les colonnes des entités filles puissent recevoir la valeur NULL : si l’ISBN de l’entité
Book n’était pas nullable, par exemple, on ne pourrait pas insérer de CD car l’entité
CD n’a pas d’attribut ISBN.

Stratégie par jointure


Dans cette stratégie, chaque entité de la hiérarchie est associée à sa propre table.
L’entité racine est traduite dans une table définissant la clé primaire qui sera utilisée
par toutes les tables de la hiérarchie, ainsi qu’une colonne discriminante. Chaque
sous-classe est représentée par une table distincte contenant ses propres attributs
(non hérités de la classe racine) et une clé primaire qui fait référence à celle de la
table racine. Les classes filles n’ont en revanche pas de colonne discriminante.
Pour implémenter une stratégie par jointure, on utilise l’annotation @Inheritance
comme dans le Listing 3.64 (le code de CD et Book n’est pas modifié).

Listing 3.64 : L’entité Item avec une stratégie par jointure


@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {

@Id @GeneratedValue
protected Long id;
protected String title;
protected Float price;
protected String description;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
132 Java EE 6 et GlassFish 3 

Du point de vue du développeur, la stratégie par jointure est naturelle car chaque
entité, qu’elle soit abstraite ou concrète, sera traduite dans une table distincte.
La Figure 3.26 montre comment seront transposées les entités Item, Book et CD.

BOOK ITEM CD
+#ID bigint Nullable false +ID bigint Nullable false +#ID bigint Nullable false
ILLUSTRATIONS smallint Nullable true DTYPE varchar(31) Nullable true MUSICCOMPANY varchar(255) Nullable true
ISBN varchar(255) Nullable true TITLE varchar(255) Nullable true NUMBEROFCDS integer Nullable true
NBOFPAGE integer Nullable true PRICE double Nullable true TOTALDURATION double Nullable true
PUBLISHER varchar(255) Nullable true DESCRIPTION varchar(255) Nullable true GENDER varchar(255) Nullable true

Figure 3.26
Traduction de l’héritage avec une stratégie par jointure.

Vous pouvez là aussi utiliser les annotations @DiscriminatorColumn et @Discrimi-


natorValue dans l’entité racine pour personnaliser la colonne discriminante et ses
valeurs (la colonne DTYPE de la table ITEM).
La stratégie par jointure est intuitive et proche de ce que vous connaissez du méca-
nisme d’héritage. Cependant, elle a un impact sur les performances des requêtes. En
effet, son nom vient du fait que, pour recréer une instance d’une sous-classe, il faut
joindre sa table à celle de la classe racine. Plus la hiérarchie est profonde, plus il
faudra donc de jointures pour recréer l’entité feuille.

Stratégie une table par classe


Dans cette stratégie (une table par classe concrète), chaque entité est traduite dans sa
propre table, comme avec la stratégie par jointure. La différence est qu’ici tous les
attributs de l’entité racine seront également traduits en colonnes de la table associée
à l’entité fille. Du point de vue de la base de données, cette stratégie utilise donc un
modèle dénormalisé. Ici, il n’y a pas de table partagée, pas de colonne partagée ni
de colonne discriminante. La seule exigence est que toutes les tables de la hiérarchie
doivent partager la même clé primaire.
Adapter notre exemple à cette stratégie consiste simplement à préciser TABLE_PER_
CLASS dans l’annotation @Inheritance de l’entité racine Item (voir Listing 3.65).

Listing 3.65 : L’entité Item avec une stratégie une table par classe
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Item {

@Id @GeneratedValue
protected Long id;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 133

protected String title;


protected Float price;
protected String description;

// Constructeurs, getters, setters


}

La Figure 3.27 montre les tables ITEM, BOOK et CD obtenues. Vous remarquerez que
BOOK et CD dupliquent les colonnes ID, TITLE, PRICE et DESCRIPTION de la table ITEM
et que les tables ne sont pas liées.

BOOK ITEM CD
+ID bigint Nullable false +ID bigint Nullable false +ID bigint Nullable false
TITLE varchar(255) Nullable true TITLE varchar(255) Nullable true MUSICCOMPANY varchar(255) Nullable true
PRICE double Nullable true PRICE double Nullable true NUMBEROFCDS integer Nullable true
ILLUSTRATIONS smallint Nullable true DESCRIPTION varchar(255) Nullable true TITLE varchar(255) Nullable true
DESCRIPTION varchar(255) Nullable true TOTALDURATION double Nullable true
ISBN varchar(255) Nullable true PRICE double Nullable true
NBOFPAGE integer Nullable true DESCRIPTION varchar(255) Nullable true
PUBLISHER varchar(255) Nullable true GENDER varchar(255) Nullable true

Figure 3.27
Les tables BOOK et CD dupliquent les colonnes d’ITEM.

Chaque table peut être redéfinie en annotant chaque entité avec @Table.
Cette stratégie est performante lorsque l’on interroge des instances d’une seule
entité car l’on se retrouve alors dans un scénario comparable à l’utilisation de la
stratégie à une seule table – la requête ne porte que sur une table. L’inconvénient est
que les requêtes polymorphiques à travers une hiérarchie de classes sont plus coû-
teuses que les deux autres stratégies : pour, par exemple, trouver tous les articles,
dont les livres et les CD, il faut interroger toutes les tables des sous-classes avec
une opération en utilisant une UNION, ce qui est coûteux lorsqu’il y a beaucoup de
données.

Redéfinition des attributs

Avec la stratégie une table par classe, les colonnes de la classe racine sont dupli-
quées dans les classes filles en portant le même nom. Un problème se pose donc
si l’on utilise une base existante où ces colonnes ont des noms différents. Pour le
résoudre, JPA utilise l’annotation @AttributeOverride pour redéfinir l’association
de la colonne et @AttributeOverrides pour en redéfinir plusieurs.
Pour renommer les colonnes ID, TITLE et DESCRIPTION dans les tables BOOK et CD, par
exemple, le code de l’entité Item ne change pas, mais Book (voir Listing 3.66) et CD
(voir Listing 3.67) doivent utiliser l’annotation @AttributeOverride.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
134 Java EE 6 et GlassFish 3 

Listing 3.66 : Book redéfinit certaines colonnes d’Item


@Entity
@AttributeOverrides({
@AttributeOverride(name = "id",
column = @Column(name = "book_id")),
@AttributeOverride(name = "title",
column = @Column(name = "book_title")),
@AttributeOverride(name = "description",
column = @Column(name = "book_description"))
})
public class Book extends Item {

private String isbn;


private String publisher;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Listing 3.67 : CD redéfinit certaines colonnes d’Item


@Entity
@AttributeOverrides({
@AttributeOverride(name = "id",
column = @Column(name = "cd_id")),
@AttributeOverride(name = "title",
column = @Column(name = "cd_title")),
@AttributeOverride(name = "description",
column = @Column(name = "cd_description"))
})
public class CD extends Item {

private String musicCompany;


private Integer numberOfCDs;
private Float totalDuration;
private String gender;

// Constructeurs, getters, setters


}

Ici, il faut redéfinir plusieurs attributs et donc utiliser @AttributeOverrides, qui


prend en paramètre un tableau d’annotations @AttributeOverride. Chacune d’elles
désigne un attribut de l’entité Item et redéfinit l’association de la colonne à l’aide
d’une annotation @Column. Ainsi, name = "title" désigne l’attribut title d’Item
et @Column(name = "cd_title") informe le fournisseur de persistance que cet attri-

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 135

but doit être traduit par une colonne CD_TITLE. Le résultat obtenu est présenté à la
Figure 3.28.

BOOK ITEM CD
+BOOK_ID bigint Nullable false +ID bigint Nullable false +CD_ID bigint Nullable false
BOOK TITLE varchar(255) Nullable true TITLE varchar(255) Nullable true CD TITLE varchar(255) Nullable true
BOOK DESCRIPTION
varchar(255) Nullable true PRICE double Nullable true CD DESCRIPTION varchar(255) Nullable true
PRICE double Nullable true DESCRIPTION varchar(255) Nullable true PRICE double Nullable true
ILLUSTRATIONS small nt Nullable true MUSICCOMPANY varchar(255) Nullable true
ISBN varchar(255) Nullable true NUMBEROFCDS integer Nullable true
NBOFPAGE nteger Nullable true TOTALDURATION double Nullable true
PUBLISHER varchar(255) Nullable true GENDER varchar(255) Nullable true

Figure 3.28
Les tables BOOK et CD ont redéfini des colonnes d’ITEM.

INFO

Dans la section "Classes intégrables" de ce chapitre, nous avons vu qu’un objet intégrable
pouvait être partagé par plusieurs entités (Address était intégré dans Customer et Order).
Les objets intégrables étant des composantes à part entière de l’entité qui les intègre, leurs
colonnes seront également dupliquées dans les tables de chaque entité. Vous pouvez alors
utiliser l’annotation @AttributeOverrides si vous avez besoin de redéfinir les colonnes des
objets intégrables.

Type de classes dans une hiérarchie d’héritage

L’exemple utilisé pour expliquer les stratégies de traduction de l’héritage n’uti-


lise que des entités, mais les entités n’héritent pas que d’entités. Une hiérarchie de
classes peut contenir un mélange d’entités, de classes qui ne sont pas des entités
(classes transitoires), d’entités abstraites et de superclasses déjà traduites. Hériter de
ces différents types de classes a un impact sur la traduction de la hiérarchie.

Entités abstraites
Dans les exemples précédents, l’entité Item était concrète. Elle était annotée par @
Entity et ne comprenait pas de mot-clé abstract ; mais une classe abstraite peut
également être désignée comme une entité. Elle ne diffère d’une entité concrète
que parce qu’elle ne peut pas être directement instanciée avec le mot-clé new, mais
elle fournit une structure de données que partageront toutes ses entités filles (Book
et CD) et elle respecte les stratégies de traduction d’héritage. Du point de vue du
fournisseur de persistance, la seule différence se situe du côté de Java, pas dans la
correspondance en table.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
136 Java EE 6 et GlassFish 3 

Non-entités
Les non-entités sont également appelées classes transitoires, ce qui signifie qu’elles
sont des POJO. Une entité peut hériter d’une non-entité ou peut être étendue par
une non-entité. La modélisation objet et l’héritage permettent de partager les états
et les comportements ; dans une hiérarchie de classes, les non-entités peuvent donc
servir à fournir une structure de données commune à leurs entités filles. L’état d’une
superclasse non entité n’est pas persistant car il n’est pas géré par le fournisseur de
persistance (n’oubliez pas que la condition pour qu’une classe le soit est la présence
de l’annotation @Entity).
Comme le montre le Listing 3.68, Item est désormais une non-entité.

Listing 3.68 : Item est un simple POJO sans annotation @Entity

public class Item {

protected String title;


protected Float price;
protected String description;

// Constructeurs, getters, setters


}

L’entité Book du Listing 3.69 hérite d’Item ; le code Java peut donc accéder aux
attributs title, price et description ainsi qu’à toutes les méthodes d’Item. Que
cette dernière soit concrète ou abstraite n’aura aucune influence sur la traduction
finale.

Listing 3.69 : L’entité Book hérite d’un POJO

@Entity
public class Book extends Item {

@Id @GeneratedValue
private Long id;
private String isbn;
private String publisher;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 137

Book est une entité qui hérite d’Item, mais seuls les attributs de Book seront stockés
dans une table. Aucun attribut d’Item n’apparaît dans la structure de la table du
Listing 3.70. Pour qu’un Book soit persistant, vous devez créer une instance de Book,
initialiser les attributs que vous souhaitez (title, price, isbn, publisher, etc.),
mais seuls ceux de Book (id, isbn, etc.) seront stockés.

Listing 3.70 : La table BOOK ne contient aucun des attributs d’Item

create table BOOK (


ID BIGINT not null,
ILLUSTRATIONS SMALLINT,
ISBN VARCHAR(255),
NBOFPAGE INTEGER,
PUBLISHER VARCHAR(255),
primary key (ID)
);

Superclasse "mapped"

JPA définit un type de classe spéciale, appelée superclasse "mapped", qui partage
son état, son comportement ainsi que les informations de traduction des entités qui
en héritent. Cependant, les superclasses "mapped" ne sont pas des entités, elles ne
sont pas gérées par le fournisseur de persistance, n’ont aucune table qui leur soit
associée et ne peuvent pas être interrogées ni faire partie d’une relation ; en revanche,
elles peuvent fournir des propriétés de persistance aux entités qui en­ ­héritent. Les
superclasses "mapped" ressemblent aux classes intégrables, sauf qu’elles peuvent
être utilisées avec l’héritage. Elles sont annotées par @MappedSuperclass.
Dans le Listing 3.71, la classe racine Item est annotée par @MappedSuperclass, pas
par @Entity. Elle définit une stratégie de traduction de l’héritage (JOINED) et annote
certains de ces attributs avec @Column. Cependant, les superclasses "mapped" n’étant
pas associées à des tables, l’annotation @Table n’est pas autorisée.

Listing 3.71 : Item est une superclasse "mapped"

@MappedSuperclass
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {

@Id @GeneratedValue protected Long id;


@Column(length = 50, nullable = false)
protected String title;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
138 Java EE 6 et GlassFish 3 

protected Float price;


@Column(length = 2000)
protected String description;

// Constructeurs, getters, setters


}

Comme vous pouvez le constater, les attributs title et description sont annotés
par @Column. Le Listing 3.72 montre l’entité Book qui hérite d’Item.

Listing 3.72 : Book hérite d’une superclasse "mapped"


@Entity
public class Book extends Item {

private String isbn;


private String publisher;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Cette hiérarchie sera traduite en une seule table. Item n’est pas une entité et n’a donc
aucune table associée. Les attributs d’Item et de Book seront traduits en colonnes de
la table BOOK – les superclasses "mapped" partageant également leurs informations
de traduction, les annotations @Column d’Item seront donc héritées. Le Listing 3.73
montre que les colonnes TITLE et DESCRIPTION de la table BOOK ont bien été modifiées
selon les annotations d’Item.

Listing 3.73 : Structure de la table BOOK


create table BOOK (
ID BIGINT not null,
TITLE VARCHAR(50) not null,
PRICE DOUBLE(52, 0),
ILLUSTRATIONS SMALLINT,
DESCRIPTION VARCHAR(2000),
ISBN VARCHAR(255),
NBOFPAGE INTEGER,
PUBLISHER VARCHAR(255),
primary key (ID)
);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 139

Résumé

Grâce à la configuration par exception, il n’y a pas besoin de faire grand-chose pour
traduire des entités en tables : il faut simplement informer le fournisseur de persis-
tance qu’une classe est une entité (avec @Entity) et qu’un attribut est un identifiant
(avec @Id), et JPA s’occupe du reste. Ce chapitre aurait été bien plus court s’il s’était
contenté du comportement par défaut, mais JPA fournit également un grand nombre
d’annotations pour adapter le moindre détail de l’ORM.
Les annotations élémentaires permettent d’adapter la traduction des attributs (@Basic,
@Temporal, etc.) ou des classes. Vous pouvez ainsi modifier le nom de la table ou le
type de la clé primaire, voire empêcher le stockage avec l’annotation @Transient.
À partir de JPA 2.0, il devient possible de stocker dans la base de données des col-
lections de types de base ou d’objets intégrables. Selon votre modèle, vous pouvez
traduire des relations (@OneToOne, @ManyToMany, etc.) de directions et de cardinalités
différentes. Il en va de même pour l’héritage (@Inheritance, @MappedSuperclass,
etc.), où vous pouvez choisir entre plusieurs stratégies pour traduire une hiérarchie
d’entités et de non-entités.
Ce chapitre s’est intéressé à la partie statique de JPA, à la façon d’associer des entités
à des tables. Le chapitre suivant présentera les aspects dynamiques : l’interrogation
de ces entités.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
4
Gestion des objets persistants

L’API de persistance de Java, JPA, a deux aspects. Le premier est la possibilité d’as-
socier des objets à une base de données relationnelle. La configuration par exception
permet aux fournisseurs de persistance de faire l’essentiel du travail sans devoir
ajouter beaucoup de code, mais la richesse de JPA tient également à la possibilité
d’adapter ces associations à l’aide d’annotations ou de descriptions XML. Que ce
soit une modification simple (changer le nom d’une colonne, par exemple) ou une
adaptation plus complexe (pour traduire l’héritage), JPA offre un large spectre de
possibilités. Vous pouvez donc associer quasiment n’importe quel modèle objet à
une base de données existante.
Le second aspect concerne l’interrogation de ces objets une fois qu’ils ont été
associés à une base. Élément central de JPA, le gestionnaire d’entités permet de
manipuler de façon standard les instances des entités. Il fournit une API pour créer,
rechercher, supprimer et synchroniser les objets avec la base de données et permet
d’exécuter différentes sortes de requêtes JPQL sur les entités, comme des requêtes
dynamiques, statiques ou natives. Le gestionnaire d’entités autorise également la
mise en place de mécanismes de verrouillage sur les données.
Le monde des bases de données relationnelles repose sur SQL (Structured Query
Language). Ce langage de programmation a été conçu pour faciliter la gestion des
données relationnelles (récupération, insertion, mise à jour et suppression), et sa
syntaxe est orientée vers la manipulation de tables. Vous pouvez ainsi sélection-
ner des colonnes de tables constituées de lignes, joindre des tables, combiner les
résultats de deux requêtes SQL à l’aide d’une union, etc. Ici, il n’y a pas d’objets
mais uniquement des lignes, des colonnes et des tables. Dans le monde Java, où
l’on manipule des objets, un langage conçu pour les tables (SQL) doit être un peu
déformé pour convenir à un langage à objets (Java). C’est là que JPQL (Java Persis-
tence Query Language) entre en jeu.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
142 Java EE 6 et GlassFish 3 

JPQL est le langage qu’utilise JPA pour interroger les entités stockées dans une base
de données relationnelle. Sa syntaxe ressemble à celle de SQL mais opère sur des
objets entités au lieu d’agir directement sur les tables. JPQL ne voit pas la structure
de la base de données sous-jacente et ne manipule ni les tables ni les colonnes –
uniquement des objets et des attributs. Il utilise pour cela la notation pointée que
connaissent bien tous les développeurs Java.
Dans ce chapitre, nous verrons comment gérer les objets persistants. Nous appren-
drons comment réaliser les opérations CRUD (Create, Read, Update et Delete) avec
le gestionnaire d’entités et créerons des requêtes complexes en JPQL. La fin du
­chapitre expliquera comment JPA gère la concurrence d’accès aux données.

Interrogation d’une entité

Comme premier exemple, étudions une requête simple  : trouver un livre par son
identifiant. Le Listing 4.1 présente une entité Book utilisant l’annotation @Id pour
informer le fournisseur de persistance que l’attribut id doit être associé à une clé
primaire.

Listing 4.1 : Entité Book simple


@Entity
public class Book {

@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

L’entité Book contient les informations pour l’association. Ici, elle utilise la plupart
des valeurs par défaut : les données seront donc stockées dans une table portant le
même nom que l’entité (BOOK) et chaque attribut sera associé à une colonne homo-
nyme. Nous pouvons maintenant utiliser une classe Main distincte (voir Listing 4.2)
qui utilise l’interface javax.persistence.EntityManager pour stocker une instance
de Book dans la table.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 143

Listing 4.2 : Classe Main pour stocker et récupérer une entité Book


public class Main {

public static void main(String[] args) {

// 1-Création d’une instance de l’entité Book.


Book book = new Book();
book.setId(1234L);
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

// 2- Création d’un gestionnaire d’entités et d’une


// transaction.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter04PU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

// 3-Stockage du livre dans la base de données.


tx.begin();
em.persist(book);
tx.commit();

// 4-Récupération du livre par son identifiant.


book = em.find(Book.class, 1234L);

System.out.println(book);

em.close();
emf.close();
}
}

La classe Main du Listing 4.2 utilise quatre étapes pour stocker un livre dans la base
de données puis le récupérer.
1. Création d’une instance de l’entité Book. Les entités sont des POJO gérés par
le fournisseur de persistance. Du point de vue de Java, une instance de classe
doit être créée avec le mot-clé new, comme n’importe quel POJO. Il faut bien
insister sur le fait qu’à ce stade le fournisseur de persistance ne connaît pas
encore l’objet Book.
2. Création d’un gestionnaire d’entités et d’une transaction. C’est la partie
importante du code car on a besoin d’un gestionnaire d’entités pour les manipu-
ler. On crée donc d’abord une fabrique de gestionnaires d’entités pour l’unité de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
144 Java EE 6 et GlassFish 3 

persistance chapter04PU. Cette fabrique sert ensuite à fournir un gestionnaire


(la variable em) qui permettra de créer une transaction (la variable tx), puis à
stocker et à récupérer un objet Book.
3. Stockage du livre dans la base de données. Le code lance une transaction
(tx.begin()) et utilise la méthode EntityManager.persist() pour insérer une
instance de Book. Lorsque la transaction est validée (tx.commit()), les données
sont écrites dans la base de données.
4. Récupération d’un livre par son identifiant. Là encore, on utilise le gestion-
naire d’entités afin de retrouver un livre à partir de son identifiant à l’aide de la
méthode EntityManager.find().
Vous remarquerez que ce code ne contient aucune requête SQL ou JPQL ni d’appels
JDBC. La Figure  4.1 montre l’interaction entre ces composants. La classe Main
interagit avec la base de données sous-jacente via l’interface EntityManager, qui
fournit un ensemble de méthodes standard permettant de réaliser des opérations sur
l’entité Book. En coulisse, cet EntityManager utilise le fournisseur de persistance
pour interagir avec la base de données. Lorsque l’on appelle l’une des méthodes de
l’EntityManager, le fournisseur de persistance produit et exécute une instruction
SQL via le pilote JDBC correspondant.

<<Interface>>
Main EntityManager
SQL / JDBC
+persist(entity : Object) : void Base de
+find(entityClass, : Class<T>, primaryKey : Object) : <T>
données

Book
-id : Long
-title : String
-price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean

Figure 4.1
Le gestionnaire d’entités interagit avec l’entité et la base de données sous-jacente.

Quel pilote JDBC utiliser ? Comment se connecter à la base ? Quel est le nom de la
base ? Toutes ces informations sont absentes du code précédent. Lorsque la classe
Main crée une fabrique EntityManagerFactory, elle lui passe le nom d’une unité de
persistance en paramètre – chapter04PU ici. Cette unité de persistance indique au
gestionnaire d’entités le type de la base à utiliser et les paramètres de connexion :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 145

toutes ces informations sont précisées dans le fichier persistence.xml (voir Lis-
ting 4.3) qui doit être déployé avec les classes.

Listing 4.3 : Le fichier persistence.xml définit l’unité de persistance


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="chapter04PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>com.apress.javaee6.chapter04.Book</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value= "org.apache.derby.jdbc.ClientDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/chapter04DB"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
</properties>
</persistence-unit>
</persistence>

L’unité de persistance chapter04PU définit une connexion JDBC pour la base de


données Derby nommée chapter04DB. Elle se connecte à cette base sous le compte
utilisateur APP avec le mot de passe APP. Le marqueur <class> demande au fournisseur
de persistance de gérer la classe Book.
Pour que ce code fonctionne, le SGBDR Derby doit s’exécuter sur le port 1527 et
les classes Book et Main doivent avoir été compilées et déployées avec ce fichier
META-INF/persistence.xml. Si vous avez activé la trace d’exécution, vous verrez
apparaître quelques instructions SQL mais, grâce à l’API d’EntityManager, votre
code manipule des objets de façon orientée objet, sans instruction SQL ni appel
JDBC.

Le gestionnaire d’entités

Le gestionnaire d’entités est une composante essentielle de JPA. JPA gère l’état et le
cycle de vie des entités et les interroge dans un contexte de persistance. C’est égale-
ment lui qui est responsable de la création et de la suppression des instances d’enti-
tés persistantes et qui les retrouve à partir de leur clé primaire. Il peut les ­verrouiller

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
146 Java EE 6 et GlassFish 3 

pour les protéger des accès concurrents en utilisant un verrouillage optimiste ou


pessimiste et se servir de requêtes JPQL pour rechercher celles qui répondent à
certains critères.
Lorsqu’un gestionnaire d’entités obtient une référence à une entité, celle-ci est dite
gérée. Avant cela, elle n’était considérée que comme un POJO classique (elle était
détachée). L’avantage de JPA est que les entités peuvent être utilisées comme des
objets normaux par différentes couches de l’application et devenir gérées par le
gestionnaire d’entités lorsqu’il faut charger ou insérer des données dans la base.
Lorsqu’une entité est gérée, il devient possible d’effectuer des opérations de per-
sistance : le gestionnaire d’entités synchronisera automatiquement l’état de l’entité
avec la base de données. Lorsqu’une entité est détachée (non gérée), elle redevient
un simple POJO et peut être utilisée par les autres couches (une couche de présen-
tation JSF, par exemple) sans que son état ne soit synchronisé avec la base.
Le véritable travail de persistance commence avec le gestionnaire d’entités. L’in-
terface javax.persistence.EntityManager est implémentée par un fournisseur de
persistance qui produira et exécutera des instructions SQL. Le Listing 4.4 présente
son API de manipulation des entités.

Listing 4.4 : EntityManager API


public interface EntityManager {

public EntityTransaction getTransaction();


public EntityManagerFactory getEntityManagerFactory();
public void close();
public boolean isOpen();

public void persist(Object entity);


public <T> T merge(T entity);
public void remove(Object entity);
public <T> T find(Class<T> entityClass, Object primaryKey);
public <T> T find(Class<T> entityClass, Object primaryKey,
LockModeType lockMode);
public <T> T find(Class<T> entityClass, Object primaryKey,
LockModeType lockMode,
Map<String, Object> properties);
public <T> T getReference(Class<T> entityClass,
Object primaryKey);

public void flush();


public void setFlushMode(FlushModeType flushMode);
public FlushModeType getFlushMode();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 147

public void lock(Object entity, LockModeType lockMode);


public void lock(Object entity, LockModeType lockMode,
Map<String, Object> properties);

public void refresh(Object entity);


public void refresh(Object entity, LockModeType lockMode);
public void refresh(Object entity, LockModeType lockMode,
Map<String, Object> properties);

public void clear();


public void detach(Object entity);
public boolean contains(Object entity);

public Map<String, Object> getProperties();


public Set<String> getSupportedProperties();

public Query
createQuery(String qlString);
public Query
createQuery(QueryDefinition qdef);
public Query
createNamedQuery(String name);
public Query
createNativeQuery(String sqlString);
public Query
createNativeQuery(String sqlString,
Class resultClass);
public Query createNativeQuery(String sqlString,
String resultSetMapping);

public void joinTransaction();

public <T> T unwrap(Class<T> cls);


public Object getDelegate();

public QueryBuilder getQueryBuilder();


}

Dans la section suivante, nous verrons comment obtenir une instance d’EntityManager.
Ne soyez pas effrayé par l’API du Listing 4.4 : ce chapitre expliquera la plupart de
ces méthodes.

Obtenir un gestionnaire d’entités

Le gestionnaire d’entités est l’interface centrale pour interagir avec les entités, mais
l’application doit d’abord en obtenir un. Selon que l’on soit dans un environne-
ment géré par un conteneur (comme nous le verrons au Chapitre 6 avec les EJB) ou
géré par une application, le code peut être très différent. Dans le premier cas, par
exemple, c’est le conteneur qui gère les transactions, ce qui signifie que l’on n’a pas
besoin d’appeler explicitement les opérations commit() ou rollback(), contrairement
à un environnement géré par l’application.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
148 Java EE 6 et GlassFish 3 

Le terme "géré par l’application" signifie que c’est l’application qui est respon-
sable de l’obtention explicite d’une instance d’EntityManager et de la gestion de
son cycle de vie (elle doit fermer le gestionnaire d’entités lorsqu’elle n’en a plus
besoin, par exemple). Dans le Listing 4.2, nous avons vu comment une classe qui
s’exécutait dans l’environnement Java SE obtenait une instance du gestionnaire :
elle utilise la classe Persistence pour lancer une fabrique EntityManagerFactory
associée à une unité de persistance (chapter04PU), et cette fabrique sert ensuite à
créer un gestionnaire d’entités. L’utilisation d’une fabrique pour créer un gestion-
naire d’entités est assez simple, mais ce qui différencie un environnement géré par
l’application d’un environnement géré par un conteneur est la façon dont on obtient
cette fabrique.
Dans un environnement géré par un conteneur, l’application s’exécute dans une ser-
vlet ou dans un conteneur d’EJB. Avec Java EE, la méthode la plus classique pour
obtenir un gestionnaire d’entités consiste alors soit à utiliser l’annotation @Persis-
tenceContext pour en injecter un, soit à utiliser JNDI. Un composant qui s’exécute
dans un conteneur (servlet, EJB, service web, etc.) n’a en revanche pas besoin de
créer ou de fermer le gestionnaire d’entités puisque son cycle de vie est géré par le
conteneur. Le Listing 4.5 montre le code d’une session sans état dans laquelle on
injecte une référence à l’unité de persistance chapter04PU.

Listing 4.5 : Injection d’une référence à un gestionnaire d’entités dans un EJB sans état
@Stateless
public class BookBean {

@PersistenceContext(unitName = "chapter04PU")
private EntityManager em;

public void createBook() {


// Création d’une instance de Book.
Book book = new Book();
book.setId(1234L);
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

// Stockage de l’instance dans la base de données.


em.persist(book);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 149

// Récupération d’une instance par son identifiant.


book = em.find(Book.class, 1234L);

System.out.println(book);
}
}

Le code du Listing 4.5 est bien plus simple que celui du Listing 4.2 : il n’y a pas
besoin des objets Persistence ou EntityManagerFactory car le gestionnaire d’enti-
tés est injecté par le conteneur. En outre, les beans sans état gérant les transactions,
il n’est pas non plus nécessaire d’appeler explicitement commit() ou rollback().
Nous reviendrons sur ce style de gestionnaire d’entités au Chapitre 6.

Contexte de persistance

Avant d’explorer en détail l’API de l’EntityManager, vous devez avoir compris un


concept essentiel : le contexte de persistance, qui est l’ensemble des instances d’en-
tités gérées à un instant donné. Dans un contexte de persistance, il ne peut exis-
ter qu’une seule instance d’entité avec le même identifiant de persistance – si, par
exemple, une instance de Book ayant l’identifiant 1234 existe dans le contexte de
persistance, aucun autre livre portant cet identifiant ne peut exister dans le même
contexte. Seules les entités contenues dans le contexte de persistance sont gérées
par le gestionnaire d’entités – leurs modifications seront reflétées dans la base de
données.
Le gestionnaire d’entités modifie ou consulte le contexte de persistance à chaque
appel d’une méthode de l’interface javax.persistence.EntityManager. Lorsque
la méthode persist() est appelée, par exemple, l’entité passée en paramètre sera
ajoutée au contexte de persistance si elle ne s’y trouve pas déjà. De même, lorsque
l’on recherche une entité à partir de son identifiant, le gestionnaire d’entités vérifie
d’abord si elle existe déjà dans le contexte de persistance. Ce contexte peut donc
être considéré comme un cache de premier niveau  : c’est un espace réduit où le
gestionnaire stocke les entités avant d’écrire son contenu dans la base de données.
Les objets ne vivent dans le contexte de persistance que le temps de la transaction.
La configuration d’un gestionnaire d’entités est liée à la fabrique qui l’a créé. Que
l’on se trouve dans un environnement géré par l’application ou par un conteneur,
la fabrique a besoin d’une unité de persistance pour créer le gestionnaire. Celle-ci,
définie dans le fichier META-INF/persistence.xml (voir Listing  4.6), précise les
informations nécessaires pour la connexion à la base de données et donne la liste

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
150 Java EE 6 et GlassFish 3 

des entités qui pourront être gérées dans un contexte persistant. Elle porte un nom
(chapter04PU) et a un ensemble d’attributs.

Listing 4.6 : Unité de persistance avec un ensemble d’entités gérables


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="chapter04PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>com.apress.javaee6.chapter04.Book</class>
<class>com.apress.javaee6.chapter04.Customer</class>
<class>com.apress.javaee6.chapter04.Address</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/chapter04DB"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
</properties>
</persistence-unit>
</persistence>

L’unité de persistance est le pont qui relie le contexte de persistance et la base de


données. D’un côté, les marqueurs <class> donnent la liste des entités pouvant être
gérées dans le contexte de persistance, de l’autre, le fichier donne toutes les infor-
mations permettant de se connecter physiquement à la base. Ici, nous sommes dans
un environnement géré par l’application (transaction-type="RESOURCE_ LOCAL").
Dans un environnement géré par un conteneur, le fichier persistence.xml définirait
une source de données à la place des informations de connexion et le type de tran-
saction serait JTA (transaction-type="JTA").

Manipulation des entités

Le gestionnaire d’entités sert également à créer des requêtes JPQL complexes pour
récupérer une ou plusieurs entités. Lorsqu’elle manipule des entités uniques, l’in-
terface EntityManager peut être considérée comme un DAO (Data Access Object)
générique permettant d’effectuer les opérations CRUD sur n’importe quelle entité
(voir Tableau 4.1).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 151

Tableau 4.1 : Méthodes de l’interface EntityManager pour manipuler les entités

Méthode Description
void persist(Object entity) Crée une instance gérée et persistante.
<T> T find(Class<T> entityClass, Recherche une entité de la classe et de la clé
Object primaryKey) indiquées.
<T> T getReference(Class<T> Obtient une instance dont l’état peut être
entityClass, Object primaryKey) récupéré de façon paresseuse.
void remove(Object entity) Supprime l’instance d’entité du contexte de
persistance et de la base de données.
<T> T merge(T entity) Fusionne l’état de l’entité indiquée dans le
contexte de persistance courant.
void refresh(Object entity) Rafraîchit l’état de l’instance à partir de la
base de données en écrasant les éventuelles
modifications apportées à l’entité.
void flush() Synchronise le contexte de persistance avec la
base de données.
void clear() Vide le contexte de persistance. Toutes les
entités gérées deviennent détachées.
void clear(Object entity) Supprime l’entité indiquée du contexte de
persistance.
boolean contains(Object entity) Teste si l’instance est une entité gérée
appartenant au contexte de persistance courant

Pour mieux comprendre ces méthodes, nous utiliserons un exemple simple d’une
relation 1–1 unidirectionnelle entre un client et son adresse. Les deux entités Cus-
tomer (voir Listing 4.7) et Address (voir Listing 4.8) ont des identifiants produits
automatiquement (grâce à l’annotation @GeneratedValue), et Customer récupère
l’Address de façon paresseuse (c’est-à-dire uniquement lorsqu’il en a besoin).

Listing 4.7 : L’entité Customer avec une relation 1–1 unidirectionnelle avec Address
@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
152 Java EE 6 et GlassFish 3 

private String email;


@OneToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "address_fk")
private Address address;

// Constructeurs, getters, setters


}

Listing 4.8 : L’entité Address


@Entity
public class Address {

@Id @GeneratedValue
private Long id;
private String street1;
private String city;
private String zipcode;
private String country;

// Constructeurs, getters, setters


}

Ces deux entités seront traduites dans la base de données avec la structure présentée
à la Figure 4.2. Notez que la colonne ADDRESS_FK est la colonne de type clé étrangère
permettant d’accéder à ADDRESS.

CUSTOMER ADDRESS
+ID bigint Nullable = false +ID bigint Nullable = false
LASTNAME varchar(255) Nullable = false STREET1 varchar(255) Nullable = true
EMAIL varchar(255) Nullable = true ZIPCODE varchar(255) Nullable = true
FIRSTNAME varchar(255) Nullable = true COUNTRY varchar(255) Nullable = true
#ADDRESS FK bigint Nullable = true CITY varchar(255) Nullable = true

Figure 4.2
Les tables CUSTOMER et ADDRESS.

Pour plus de visibilité, les fragments de code utilisés dans la section suivante supposent
que l’attribut em est de type EntityManager et que tx est de type EntityTransaction.

Rendre une entité persistante


Rendre une entité persistante signifie que l’on insère les données dans la base si elles
ne s’y trouvent pas déjà (sinon une exception sera lancée). Pour ce faire, il faut créer
une instance de l’entité avec l’opérateur new, initialiser ses attributs, lier une entité

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 153

à une autre lorsqu’il y a des relations et, enfin, appeler la méthode EntityManager.
persist() comme le montre le cas de test JUnit du Listing 4.9.

Listing 4.9 : Rendre persistant un Customer avec une Address


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");
Address address = new Address("Ritherdon Rd", "London",
"8QE", "UK");
customer.setAddress(address);

tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();

assertNotNull(customer.getId());
assertNotNull(address.getId());

Dans le Listing  4.9, le client et l’adresse ne sont que deux objets qui résident
dans la mémoire de la JVM. Tous les deux ne deviennent des entités gérées que
lorsque le gestionnaire d’entités (la variable em) les prend en compte en les rendant
persistantes (em.persist(customer)). Dès cet instant, les deux objets deviennent
candidats à une insertion dans la base de données. Lorsque la transaction est vali-
dée (tx.commit()), les données sont écrites dans la base : une ligne d’adresse est
ajoutée à la table ADDRESS et une ligne client, à la table CUSTOMER. L’entité Customer
étant la propriétaire de la relation, sa table contient une clé étrangère vers ADDRESS.
Les deux expressions assertNotNull testent que les deux entités ont bien reçu un
identifiant (fourni automatiquement par le fournisseur de persistance grâce aux
annotations).
Notez l’ordre d’appel des méthodes persist() : on rend d’abord le client persis-
tant, puis son adresse. Si l’on avait fait l’inverse, le résultat aurait été le même. Plus
haut, nous avons écrit que l’on pouvait considérer le gestionnaire d’entités comme
un cache de premier niveau : tant que la transaction n’est pas validée, les données
restent en mémoire et il n’y a aucun accès à la base. Le gestionnaire d’entités met
les données en cache et, lorsqu’il est prêt, les écrit dans l’ordre qu’attend la base de
données (afin de respecter les contraintes d’intégrité). À cause de la clé étrangère
que contient la table CUSTOMER, l’instruction insert dans ADRESS doit s’effectuer en
premier, suivie de celle de CUSTOMER.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
154 Java EE 6 et GlassFish 3 

INFO

La plupart des entités de ce chapitre n’implémentent pas l’interface Serializable car elles
n’en ont tout simplement pas besoin pour être rendues persistantes. Elles sont passées par
référence d’une méthode à l’autre et ce n’est que lorsqu’il faut les rendre persistantes que
l’on appelle la méthode EntityManager.persist(). Si, toutefois, vous devez passer des
entités par valeur (appel distant, conteneur EJB externe, etc.), celles-ci doivent implémen-
ter l’interface java.io.Serializable pour indiquer au compilateur qu’il faut que tous les
champs de l’entité soient sérialisables afin qu’une instance puisse être sérialisée dans un flux
d’octets et passée par RMI (Remote Method Invocation).

Recherche par identifiant


Nous pouvons utiliser deux méthodes pour trouver une entité par son identifiant. La
première est EntityManager.find(), qui prend deux paramètres : la classe de l’entité
et l’identifiant (voir Listing 4.10). Cet appel renvoie l’entité si elle a été trouvée,
null sinon.

Listing 4.10 : Recherche d’un client par son identifiant


Customer customer = em.find(Customer.class, 1234L);
if (customer!= null) {
// Traiter l’objet
}

La seconde méthode est getReference() (voir Listing 4.11). Elle ressemble beau-


coup à find() car elle prend les mêmes paramètres, mais elle permet de récupé-
rer une référence d’entité à partir de sa clé primaire, pas à partir de ses données.
Cette méthode est prévue pour les situations où l’on a besoin d’une instance d’entité
gérée, mais d’aucune autre donnée que la clé primaire de l’entité recherchée. Lors
d’un appel à getReference(), les données de l’état sont récupérées de façon pares-
seuse, ce qui signifie que, si l’on n’accède pas à l’état avant que l’entité soit détachée,
les données peuvent être manquantes. Cette méthode lève l’exception EntityNot-
FoundException si elle ne trouve pas l’entité.

Listing 4.11 : Recherche d’un client par référence


try {
Customer customer = em.getReference(Customer.class, 1234L);
// Traiter l’objet
} catch(EntityNotFoundException ex) {
// Entité non trouvée
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 155

Suppression d’une entité


La méthode EntityManager.remove() supprime une entité qui est alors également
ôtée de la base, détachée du gestionnaire d’entités et qui ne peut plus être synchro-
nisée avec la base. En termes d’objets Java, l’entité reste accessible tant qu’elle ne
sort pas de la portée et que le ramasse-miettes ne l’a pas supprimée. Le code du
Listing 4.12 montre comment supprimer une entité après l’avoir créée.

Listing 4.12 : Création et suppression d’entités Customer et Address


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");
Address address = new Address("Ritherdon Rd", "London",
"8QE", "UK");
customer.setAddress(address);

tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();

tx.begin();
em.remove(customer);
tx.commit();

// Les données sont supprimées de la base


// mais l’objet reste accessible
assertNotNull(customer);

Le code du Listing 4.12 crée une instance de Customer et d’Address, lie l’adresse


au client (customer.setAddress(address)) et les rend persistantes. Dans la base de
données, la ligne du client est liée à son adresse via une clé étrangère. Puis le code
ne supprime que l’entité Customer : selon la configuration de la suppression en cas-
cade, l’instance d’Address peut être laissée intacte alors qu’aucune autre entité ne la
référence plus – la ligne d’adresse est alors orpheline.

Suppression des orphelins


Pour des raisons de cohérence des données, il faut éviter de produire des orphelins
car ils correspondent à des lignes de la base de données qui ne sont plus référencées
par aucune autre table et qui ne sont donc plus accessibles. Avec JPA vous pouvez
demander au fournisseur de persistance de supprimer automatiquement les orphe-
lins ou de répercuter en cascade une opération de suppression. Si une entité cible
(Address) appartient uniquement à une source (Customer) et que cette source soit
supprimée par l’application, le fournisseur doit également supprimer la cible.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
156 Java EE 6 et GlassFish 3 

Les relations 1–1 ou 1–N disposent d’une option demandant la suppression des
orphelins. Dans notre exemple, il suffit d’ajouter l’élément orphanRemoval=true à
l’annotation @OneToOne, comme dans le Listing 4.13.
Listing 4.13 : L’entité Customer gère la suppression des Address orphelines
@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
@OneToOne (fetch = FetchType.LAZY, orphanRemoval = true)
private Address address;

// Constructeurs, getters, setters


}

Désormais, le code du Listing 4.12 supprimera automatiquement l’entité Address


lorsque le client sera supprimé. L’opération de suppression intervient au moment de
l’écriture dans la base de données (lorsque la transaction est validée).

Synchronisation avec la base de données


Jusqu’à maintenant, la synchronisation avec la base s’est effectuée lorsque la tran-
saction est validée. Le gestionnaire d’entités est un cache de premier niveau qui
attend cette validation pour écrire les données dans la base, mais que se passe-t-il
lorsqu’il faut insérer un client et une adresse ?
tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();

Toutes les modifications en attente exigent une instruction SQL et les deux insert
ne seront produits et rendus permanents que lorsque la transaction sera validée par
commit(). Pour la plupart des applications, cette synchronisation automatique suf-
fit  : on ne sait pas exactement quand le fournisseur écrira vraiment les données
dans la base, nous pouvons être sûrs que l’écriture aura lieu lorsque la transaction
sera validée. Bien que la base de données soit synchronisée avec les entités dans
le contexte de persistance, nous pouvons explicitement écrire des données dans
la base (avec flush()) ou, inversement, rafraîchir des données à partir de la base
(avec refresh()). Si des données sont écrites dans la base à un instant précis et que
l’application appelle plus tard la méthode rollback() pour annuler la transaction,
les données écrites seront supprimées de la base.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 157

Écriture de données
La méthode EntityManager.flush force le fournisseur de persistance à écrire les don-
nées dans la base ; elle permet donc de déclencher manuellement le même processus
que celui utilisé en interne par le gestionnaire d’entités lorsqu’il écrit le contexte de
persistance dans la base de données.
tx.begin();
em.persist(customer);
em.flush();
em.persist(address);
tx.commit();

Il se passe deux choses intéressantes dans le code précédent. La première est que
em.flush() n’attendra pas que la transaction soit validée pour écrire le contexte de
persistance dans la base de données : une instruction insert sera produite et exécu-
tée à l’instant de l’appel à flush(). La seconde est que ce code ne fonctionnera pas
à cause des contraintes d’intégrité. Sans écriture explicite, le gestionnaire d’entités
met en cache toutes les modifications, les ordonne et les exécute de façon cohérente
du point de vue de la base. Avec une écriture explicite, l’instruction insert sur
la table CUSTOMER s’exécutera mais la contrainte d’intégrité sur la clé étrangère (la
colonne ADDRESS_FK de CUSTOMER) sera violée et la transaction sera donc annulée.
Les données écrites seront alors supprimées de la base. Vous devez donc faire atten-
tion lorsque vous utilisez des écritures explicites et ne les utiliser que lorsqu’elles
sont nécessaires.

Rafraîchissement d’une entité


La méthode refresh() effectue une synchronisation dans la direction opposée de
flush(), c’est-à-dire qu’elle écrase l’état courant d’une entité gérée avec les données
qui se trouvent dans la base. Son utilisation typique consiste à annuler des modi-
fications qui ont été faites sur l’entité en mémoire. L’extrait de classe de test du
Listing 4.14 recherche un client par son identifiant, modifie son prénom et annule ce
changement en appelant la méthode refresh().

Listing 4.14 : Rafraîchissement de l’entité Customer à partir de la base de données


Customer customer = em.find(Customer.class, 1234L);
assertEquals(customer.getFirstName(), "Antony");
customer.setFirstName("William");

em.refresh(customer);
assertEquals(customer.getFirstName(), "Antony");

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
158 Java EE 6 et GlassFish 3 

Contenu du contexte de persistance


Le contexte de persistance contient les entités gérées. Grâce à l’interface Entity-
Manager, vous pouvez tester si une entité est gérée et supprimer toutes les entités du
contexte de persistance.

Contains
La méthode EntityManager.contains() renvoie un Boolean indiquant si une
instance d’entité particulière est actuellement gérée par le gestionnaire d’enti-
tés dans le contexte de persistance courant. Le cas de test du Listing 4.15 rend un
Customer persistant – on peut immédiatement vérifier que l’entité est gérée (em.
contains(customer) renvoie true). Puis on appelle la méthode remove() pour sup-
primer cette entité de la base de données et du contexte de persistance ; l’appel à
em.contains(customer) renvoie alors false.

Listing 4.15 : Cas de test pour vérifier que l’entité Customer se trouve dans le contexte
de persistance
Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");

tx.begin();
em.persist(customer);
tx.commit();

assertTrue(em.contains(customer));

tx.begin();
em.remove(customer);
tx.commit();

assertFalse(em.contains(customer));

Clear et Detach
La méthode clear() porte bien son nom car elle vide le contexte de persis-
tance : toutes les entités qui étaient gérées deviennent donc détachées. La méthode
detach(Object entity) supprime l’entité indiquée du contexte de persistance –
après cette éviction, les modifications apportées à cette entité ne seront plus syn-
chronisées avec la base de données. Le Listing 4.16 crée une entité, vérifie qu’elle
est gérée, puis la supprime du contexte de persistance et vérifie qu’elle est bien
détachée.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 159

Listing 4.16 : Test si l’entité Customer se trouve dans le contexte de persistance


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");

tx.begin();
em.persist(customer);
tx.commit();

assertTrue(em.contains(customer));

em.detach(customer);

assertFalse(em.contains(customer));

La méthode clear() peut agir sur tout le contexte de persistance (clear ()) ou
­uniquement sur une entité (clear(Object entity)).

Fusion d’une entité


Une entité détachée n’est plus associée à un contexte de persistance. Si vous voulez
la gérer, vous devez la fusionner. Prenons l’exemple d’une entité devant s’afficher
dans une page JSF. L’entité est d’abord chargée à partir de la base de données dans la
couche de persistance (elle est gérée), elle est renvoyée par un appel d’un EJB local
(elle est détachée car le contexte de transaction s’est terminé), la couche de présenta-
tion l’affiche (elle est toujours détachée), puis elle revient pour être mise à jour dans
la base de données. Cependant, à ce moment-là, l’entité est détachée et doit donc
être attachée à nouveau – fusionnée – afin de synchroniser son état avec la base.
Le Listing  4.17 simule cette situation en vidant le contexte de persistance avec
clear() afin de détacher l’entité.

Listing 4.17 : Nettoyage du contexte de persistance


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");

tx.begin();
em.persist(customer);
tx.commit();

em.clear();

// Modifie une valeur d’une entité détachée. customer.setFirstName("William");

tx.begin();
em.merge(customer);
tx.commit();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
160 Java EE 6 et GlassFish 3 

Le Listing 4.17 crée un client et le rend persistant. L’appel em.clear() force le déta-


chement de l’entité client mais les entités détachées continuent de vivre en dehors
du contexte de persistance dans lequel elles étaient ; par contre, la synchronisation
de leur état avec celui de la base de données n’est plus garantie. Le setter customer.
setFirstName("William") est donc exécuté sur une entité détachée et les données
ne sont pas modifiées dans la base. Pour répercuter cette modification, il faut réatta-
cher l’entité (c’est-à-dire la fusionner) avec un appel à em.merge(customer).

Modification d’une entité


Bien que la modification d’une entité soit simple, elle peut en même temps être
difficile à comprendre. Comme nous venons de le voir, vous pouvez utiliser Entity-
Manager.merge() pour attacher une entité et synchroniser son état avec la base de
données. Lorsqu’une entité est gérée, les modifications qui lui sont apportées seront
automatiquement reflétées mais, si elle ne l’est pas, vous devez appeler explicitement
merge().

Le Listing  4.18 rend persistant un client prénommé Antony. Lorsque la méthode


em.persist() est appelée, l’entité devient gérée et toutes les modifications qui
lui seront désormais appliquées seront donc répercutées dans la base de données.
L’appel de la méthode setFirstName() modifie l’état de l’entité. Le gestionnaire
d’entités met en cache toutes les actions exécutées à partir de tx.begin() et ne les
répercute dans la base que lorsque la transaction est validée avec tx.commit().

Listing 4.18 : Modification du prénom d’un client


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");

tx.begin();
em.persist(customer);

customer.setFirstName("William");

tx.commit();

Répercussion d’événements
Par défaut, chaque opération du gestionnaire d’entités ne s’applique qu’à l’entité
passée en paramètre à l’opération. Parfois, cependant, on souhaite propager son
action à ses relations – c’est ce que l’on appelle répercuter un événement. Jusqu’à
présent, nos exemples reposaient sur ce comportement par défaut : le Listing 4.19,
par exemple, crée un client en instanciant une entité Customer et une entité Address,

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 161

en les liant (avec customer. setAddress(address)), puis en les rendant toutes les
deux persistantes.

Listing 4.19 : Rendre Customer et son Address persistantes


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");
Address address = new Address("Ritherdon Rd", "London",
"8QE", "UK");
customer.setAddress(address);

tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();

Comme il existe une relation entre Customer et Address, on peut répercu-


ter l’action persist() du client vers son adresse. Ceci signifie qu’un appel à
em.persist(customer) répercutera l’événement persist à l’entité Address si elle
autorise la propagation de ce type d’événement. Le code peut donc être allégé en
ôtant l’instruction em.persist(address), comme le montre le Listing 4.20.

Listing 4.20 : Propagation d’un événement persist à Address


Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");
Address address = new Address("Ritherdon Rd", "London",
"8QE", "UK");
customer.setAddress(address);

tx.begin();
em.persist(customer);
tx.commit();

Sans cette répercussion, le client serait persistant, mais pas son adresse. Pour que
cette répercussion ait lieu, l’association de la relation doit donc être modifiée. Les
annotations @OneToOne, @OneToMany, @ManyToOne et @ManyToMany disposent d’un
attribut cascade pouvant recevoir un tableau d’événements à propager. Nous devons
donc modifier l’association de l’entité Customer (voir Listing 4.21) en ajoutant un
attribut cascade à l’annotation @OneToOne. Ici, on ne se contente pas de propager
persist, on fait de même pour l’événement remove, afin que la suppression d’un
client entraîne celle de son adresse.

Listing 4.21 : L’entité Customer propage les événements persist et remove


@Entity
public class Customer {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
162 Java EE 6 et GlassFish 3 

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
@OneToOne (fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "address_fk")
private Address address;

// Constructeurs, getters, setters


}

Le Tableau 4.2 énumère les événements que vous pouvez propager vers une cible de
relation. Vous pouvez même tous les propager en utilisant le type CascadeType.ALL.

Tableau 4.2 : Événements pouvant être propagés

Type Description
PERSIST Propage les opérations persist à la cible de la relation.
REMOVE Propage les opérations remove à la cible de la relation.
MERGE Propage les opérations merge à la cible de la relation.
REFRESH Propage les opérations refresh à la cible de la relation.
CLEAR Propage les opérations clear à la cible de la relation.
ALL Propage toutes les opérations précédentes.

L’API de cache

La plupart des spécifications (pas seulement JAVA EE) s’intéressent beaucoup aux
fonctionnalités et considèrent le reste, comme les performances, l’adaptabilité ou la
mise en cluster, comme des détails d’implémentation. Les implémentations doivent
respecter strictement la spécification mais peuvent également ajouter des fonction-
nalités spécifiques. Un parfait exemple pour JPA serait la gestion d’un cache.
Jusqu’à JPA 2.0, la mise en cache n’était pas mentionnée dans la spécification.
Comme on l’a déjà évoqué, le gestionnaire d’entités est un cache de premier niveau
utilisé pour traiter les données afin qu’elles conviennent à la base de données et
pour mettre en cache les entités en cours d’utilisation. Ce cache permet de réduire
le nombre de requêtes SQL de chaque transaction – si un objet est modifié plusieurs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 163

fois au cours de la même transaction, le gestionnaire d’entités ne produira qu’une


seule instruction UPDATE à la fin de cette transaction –, mais un cache de premier
niveau n’est pas un cache de performance.
Toutes les implémentations de JPA utilisent un cache de performance (appelé cache
de second niveau) pour optimiser les accès à la base de données, les requêtes, les
jointures, etc. Les caches de second niveau réduisent le trafic avec la base de données
car ils conservent les objets en mémoire et les rendent disponibles à toute l’applica-
tion. Chaque implémentation utilise sa propre technique de cache – en développant
ses propres mécanismes ou en utilisant des solutions open-source. Le cache peut
être distribué sur un cluster ou non – en fait, tout est possible puisque la spécification
ne dit rien sur le sujet.
JPA 2.0 reconnaît la nécessité d’un cache de second niveau et a donc ajouté des
opérations de gestion du cache dans une API standard. Celle-ci, présentée dans le
Listing  4.22, est minimaliste – le but de JPA n’est pas de standardiser un cache
pleinement fonctionnel – mais elle permet d’interroger et de supprimer des entités
d’un cache de second niveau de façon standard. Comme EntityManager, javax.
persistence.Cache est une interface implémentée par le système de cache du
­fournisseur de persistance.

Listing 4.22 : API de cache


public interface Cache {

// Teste si le cache contient les données de l’entité indiquée.


public boolean contains(Class cls, Object primaryKey);

// Supprime du cache les données de l’entité indiquée.


public void evict(Class cls, Object primaryKey);

// Ôte du cache les données des entités de la classe indiquée.


public void evict(Class cls);

// Vide le cache.
public void evictAll();
}

JPQL

Nous venons de voir comment manipuler séparément les entités avec l’API d’En-
tityManager. Vous savez maintenant comment récupérer une entité à partir de son

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
164 Java EE 6 et GlassFish 3 

identifiant, la supprimer, modifier ses attributs, etc. Mais rechercher une entité par
son identifiant est assez limité (ne serait-ce que parce qu’il vous faut connaître cet
identifiant...) : en pratique, vous aurez plutôt besoin de récupérer une entité en fonc-
tion de critères autres que son identifiant (par son nom ou son ISBN, par exemple)
ou de récupérer un ensemble d’entités satisfaisant certaines conditions (tous les clients
qui habitent en France, par exemple). Cette possibilité est inhérente aux bases de don-
nées relationnelles et JPA dispose d’un langage permettant ce type d’interactions :
JPQL.
JPQL (Java Persistence Query Language) sert à définir des recherches d’entités
persistantes indépendamment de la base de données sous-jacente. C’est un langage
de requête qui s’inspire de SQL (Structured Query Language), le langage standard
pour interroger les bases de données relationnelles. La différence principale est que
le résultat d’une requête SQL est un ensemble de lignes et de colonnes (une table)
alors que celui d’une requête JPQL est une entité ou une collection d’entités. Sa
syntaxe est orientée objet et est donc plus familière aux développeurs ne connaissant
que ce type de programmation. Ils peuvent ainsi manipuler un modèle objet en uti-
lisant la notation pointée classique (maClasse.monAttribut, par exemple) et oublier
la structure des tables.
En coulisse, JPQL utilise un mécanisme de traduction pour transformer une requête
JPQL en langage compréhensible par une base de données SQL. La requête s’exé-
cute sur la base de données sous-jacente avec SQL et des appels JDBC, puis les
instances d’entités sont initialisées et sont renvoyées à l’application – tout ceci de
façon simple et à l’aide d’une syntaxe riche.
La requête JPQL la plus simple qui soit sélectionne toutes les instances d’une seule
entité :
SELECT b
FROM Book b

Si vous connaissez SQL, cette instruction devrait vous sembler familière. Au lieu
de sélectionner le résultat à partir d’une table, JPQL sélectionne des entités, Book
ici. La clause FROM permet également de donner un alias à cette entité : ici, b est
un alias de Book. La clause SELECT indique que le type de la requête est l’entité b
(Book). L’exécution de cette instruction produira donc une liste de zéros ou plusieurs
instances de Book.
Pour restreindre le résultat, on utilise la clause WHERE afin d’introduire un critère de
recherche :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 165

SELECT b
FROM Book b
WHERE b.title = "H2G2"

L’alias sert à naviguer dans les attributs de l’entité via l’opérateur point. L’entité
Book ayant un attribut persistant nommé title de type String, b.title désigne
donc l’attribut title de l’entité Book. L’exécution de cette instruction produira une
liste de zéros ou plusieurs instances de Book ayant pour titre H2G2.
La requête la plus simple est formée de deux parties obligatoires : les clauses SELECT
et FROM. La première définit le format du résultat de la requête tandis que la seconde
indique l’entité ou les entités à partir desquelles le résultat sera obtenu. Une requête
peut également contenir des clauses WHERE, ORDER BY, GROUP BY et HAVING pour
restreindre ou trier le résultat. La syntaxe complète de SELECT est définie dans le
Listing 4.23.
Il existe également les instructions DELETE et UPDATE, qui permettent respectivement
de supprimer et de modifier plusieurs instances d’une classe d’entité.

Select

La clause SELECT porte sur une expression qui peut être une entité, un attribut d’en-
tité, une expression constructeur, une fonction agrégat ou toute séquence de ce qui
précède. Ces expressions sont les briques de base des requêtes et servent à atteindre
les attributs des entités ou à traverser les relations (ou une collection d’entités) via
la notation pointée classique. Le Listing  4.23 définit la syntaxe d’une instruction
SELECT.

Listing 4.23 : Syntaxe de l’instruction SELECT


SELECT <expression select>
FROM <clause from>
[WHERE <expression conditionnelle>]
[ORDER BY <clause order by>]
[GROUP BY <clause group by>]
[HAVING <clause having>]

Une instruction SELECT simple renvoie une entité. Si une entité Customer a un alias
c, par exemple, SELECT c renverra une entité ou une liste d’entités.
SELECT c
FROM Customer c

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
166 Java EE 6 et GlassFish 3 

Une clause SELECT peut également renvoyer des attributs. Si l’entité Customer a
un attribut firstName, SELECT c.firstName renverra un String ou une collection de
String contenant les prénoms.

SELECT c.firstName
FROM Customer c

Pour obtenir le prénom et le nom d’un client, il suffit de créer une liste contenant les
deux attributs correspondants :
SELECT c.firstName, c.lastName
FROM Customer c

Si l’entité Customer est en relation 1–1 avec Address, c.address désigne l’adresse
du client et le résultat de la requête suivante renverra donc non pas une liste de
clients mais une liste d’adresses :
SELECT c.address
FROM Customer c

Les expressions de navigation peuvent être reliées les unes aux autres pour traverser
des graphes d’entités complexes. Avec cette technique, nous pouvons construire des
expressions comme c.address.country.code afin de désigner le code du pays de
l’adresse d’un client.
SELECT c.address.country.code
FROM Customer c

L’expression SELECT peut contenir un constructeur afin de renvoyer une instance


de classe Java initialisée avec le résultat de la requête. Cette classe n’a pas besoin
d’être une entité, mais le constructeur doit être pleinement qualifié et correspondre
aux attributs.
SELECT NEW com.apress.javaee6.CustomerDTO(c.firstName,
c.lastName, c.address.street1)
FROM Customer c

Le résultat de cette requête sera une liste d’objets CustomerDTO instanciés avec
l’opérateur new et initialisés avec le prénom, le nom et la rue des clients.
L’exécution des requêtes précédentes renverra soit une valeur unique, soit une col-
lection de zéros ou plusieurs entités (ou attributs) pouvant contenir des doublons.
Pour supprimer ces derniers, il faut utiliser l’opérateur DISTINCT :
SELECT DISTINCT c
FROM Customer c

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 167

SELECT DISTINCT c.firstName


FROM Customer c

Le résultat d’une requête peut être le résultat d’une fonction agrégat appliquée à une
expression. La clause SELECT peut utiliser les fonctions agrégats AVG, COUNT, MAX, MIN
et SUM. En outre, leurs résultats peuvent être regroupés par une clause GROUP BY et
filtrés par une clause HAVING.
SELECT COUNT(c)
FROM Customer c

Les clauses SELECT, WHERE et HAVING peuvent également utiliser des expressions sca-
laires portant sur des nombres (ABS, SQRT, MOD, SIZE, INDEX), des chaînes (CONCAT,
SUBSTRING, TRIM, LOWER, UPPER, LENGTH) et des dates (CURRENT_DATE, CURRENT_ TIME,
CURRENT_TIMESTAMP).

From

La clause FROM d’une requête définit les entités en déclarant des variables d’identifi-
cation ou alias qui pourront être utilisés dans les autres clauses (SELECT, WHERE, etc.).
Sa syntaxe est simplement formée du nom de l’entité et de son alias. Dans l’exemple
suivant, l’entité est Customer et l’alias est c :
SELECT c
FROM Customer c

Where

La clause WHERE d’une requête est formée d’une expression conditionnelle permet-
tant de restreindre le résultat d’une instruction SELECT, UPDATE ou DELETE. Il peut
s’agir d’une expression simple ou d’un ensemble d’expressions conditionnelles
­permettant de filtrer très précisément la requête.
La façon la plus simple de restreindre le résultat d’une requête consiste à utiliser un
attribut d’une entité. L’instruction suivante, par exemple, sélectionne tous les clients
prénommés Vincent :
SELECT c
FROM Customer c
WHERE c.firstName = ’Vincent’

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
168 Java EE 6 et GlassFish 3 

Vous pouvez restreindre encore plus les résultats en utilisant les opérateurs logiques
AND et OR. L’exemple suivant utilise AND pour sélectionner tous les clients prénommés
Vincent qui habitent en France :
SELECT c
FROM Customer c
WHERE c.firstName = ’Vincent’ AND c.address.country = ’France’

La clause WHERE utilise également les opérateurs de comparaison =, >, >=, <, <=, <>,
[NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY et [NOT]
MEMBER [OF]. Voici quelques exemples d’utilisation :

SELECT c
FROM Customer c
WHERE c.age > 18

SELECT c
FROM Customer c
WHERE c.age NOT BETWEEN 40 AND 50

SELECT c
FROM Customer c
WHERE c.address.country IN (’USA’, ’Portugal’)

L’expression LIKE est formée d’une chaîne pouvant contenir des caractères "jokers" :
le blanc souligné (_) capture un caractère quelconque et le caractère pourcent (%)
capture un nombre quelconque (éventuellement nul) de caractères :
SELECT c
FROM Customer c
WHERE c.email LIKE ’%mail.com’

Liaison de paramètres
Jusqu’à maintenant, les clauses WHERE dont nous nous sommes servis n’utilisaient
que des valeurs fixes. Dans une application, cependant, les requêtes dépendent sou-
vent de paramètres et JPQL fournit donc deux moyens pour lier ces paramètres : par
position ou par nom.
Les paramètres positionnels sont indiqués par un point d’interrogation suivi d’un
entier (?1, par exemple). Lorsque la requête sera utilisée, il faudra fournir les valeurs
qui viendront remplacer ces paramètres.
SELECT c
FROM Customer c
WHERE c.firstName = ?1 AND c.address.country = ?2

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 169

Les paramètres nommés sont représentés par un identifiant de type String préfixé
par le caractère deux-points (:). Lorsque la requête sera utilisée, il faudra fournir des
valeurs à ces paramètres nommés.
SELECT c
FROM Customer c
WHERE c.firstName = :fname AND c.address.country = :country

Nous verrons dans la section "Requêtes", plus loin dans ce chapitre, comment lier
ces paramètres dans une application.

Sous-requêtes
Une sous-requête est une requête SELECT intégrée dans l’expression conditionnelle
d’une clause WHERE ou HAVING. Le résultat de cette sous-requête est évalué et inter-
prété dans l’expression conditionnelle de la requête principale. Pour, par exemple,
obtenir les clients les plus jeunes de la base de données, on exécute d’abord une
sous-requête avec MIN(age) et l’on évalue son résultat dans la requête principale :
SELECT c
FROM Customer c
WHERE c.age = (SELECT MIN(c.age) FROM Customer c)

Order By

La clause ORDER BY permet de trier les entités ou les valeurs renvoyées par une
requête SELECT. Le tri s’applique à l’attribut précisé dans cette clause. Si cet attribut
est suivi d’ASC ou d’aucun mot-clé, le tri sera ascendant ; s’il est suivi de DESC, le tri
sera descendant.
SELECT c
FROM Customer c
WHERE c.age > 18
ORDER BY c.age DESC

Le tri peut utiliser plusieurs expressions.


SELECT c
FROM Customer c
WHERE c.age > 18
ORDER BY c.age DESC, c.address.country ASC

Group By et Having

La clause GROUP BY permet de regrouper des valeurs du résultat en fonction d’un


ensemble de propriétés. Les entités sont alors divisées en groupes selon les valeurs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
170 Java EE 6 et GlassFish 3 

de l’expression de la clause GROUP BY. Pour, par exemple, regrouper les clients par
pays et les compter, on utilisera la requête suivante :
SELECT c.address.country, count(c)
FROM Customer c
GROUP BY c.address.country

GROUP BY définit les expressions de regroupement (c.address.country) qui servi-


ront à agréger et à compter les résultats. Notez que les expressions qui apparaissent
dans la clause GROUP BY doivent également apparaître dans la clause SELECT.
La clause HAVING définit un filtre qui s’appliquera après le regroupement des résul-
tats, un peu comme une seconde clause WHERE qui filtrerait le résultat de GROUP BY.
En ajoutant une clause HAVING à la requête précédente, on peut n’obtenir que les
pays ayant plus de 100 clients.
SELECT c.address.country, count(c)
FROM Customer c
GROUP BY c.address.country
HAVING count(c) > 100

GROUP BY et HAVING ne peuvent apparaître que dans une clause SELECT.

Suppressions multiples

Nous savons supprimer une entité à l’aide de la méthode EntityManager.remove()


et interroger une base de données pour obtenir une liste d’entités correspondant à
certains critères. Pour supprimer un ensemble, nous pourrions donc exécuter une
requête et parcourir son résultat pour supprimer séparément chaque entité. Bien que
ce soit un algorithme tout à fait valide, ses performances seraient désastreuses car
il implique trop d’accès à la base. Il existe une meilleure solution : les suppressions
multiples.
JPQL sait effectuer des suppressions multiples sur les différentes instances d’une
classe d’entité précise, ce qui permet de supprimer un grand nombre d’entités en une
seule opération. L’instruction DELETE ressemble à l’instruction SELECT car elle peut
utiliser une clause WHERE et prendre des paramètres. Elle renvoie le nombre d’entités
concernées par l’opération. Sa syntaxe est décrite dans le Listing 4.24.

Listing 4.24 : Syntaxe de l’instruction DELETE


DELETE FROM <nom entité> [[AS] <variable identification>]
[WHERE <expression conditionnelle>]

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 171

L’instruction suivante, par exemple, supprime tous les clients âgés de moins de
18 ans :
DELETE FROM Customer c
WHERE c.age < 18

Mises à jour multiples

L’instruction UPDATE permet de modifier toutes les entités répondant aux critères de
sa clause WHERE. Sa syntaxe est décrite dans le Listing 4.25.

Listing 4.25 : Syntaxe de l’instruction UPDATE


UPDATE <nom entité> [[AS] <variable identification>]
SET <mise à jour> {, <mise à jour>}*
[WHERE <expression conditionnelle>]

L’instruction suivante, par exemple, modifie le prénom de tous nos jeunes clients en
"trop jeune" :
UPDATE Customer c
SET c.firstName = ’TROP JEUNE’
WHERE c.age < 18

Requêtes

Nous connaissons maintenant la syntaxe de JPQL et savons comment écrire ses


instructions à l’aide de différentes clauses (SELECT, FROM, WHERE, etc.) : le problème
consiste maintenant à les intégrer dans une application. Pour ce faire, JPA 2.0 per-
met d’intégrer quatre sortes de requêtes dans le code, chacune correspondant à un
besoin différent :
■■ Les requêtes dynamiques. Ce sont les requêtes les plus faciles car il s’agit sim-
plement de chaînes de requêtes JPQL indiquées dynamiquement au moment de
l’exécution.
■■ Les requêtes nommées. Ce sont des requêtes statiques et non modifiables.
■■ Les requêtes natives. Elles permettent d’exécuter une instruction SQL native à
la place d’une instruction JPQL.
■■ API des critères. Ce nouveau concept a été introduit par JPA 2.0.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
172 Java EE 6 et GlassFish 3 

Le choix entre ces quatre types est centralisé au niveau de l’interface EntityMana-
ger, qui dispose de plusieurs méthodes fabriques (voir Tableau 4.3) renvoyant toutes
une interface Query.

Tableau 4.3 : Méthodes d’EntityManager pour créer des requêtes

Méthode Description
Query createQuery(String jpqlString) Crée une instance de Query permettant
d’exécuter une instruction JPQL pour des
requêtes dynamiques.
Query createQuery(QueryDefinition qdef) Crée une instance de Query permettant
d’exécuter une requête par critère.
Query createNamedQuery(String name) Crée une instance de Query permettant
d’exécuter une requête nommée (en JPQL ou
en SQL natif).
Query createNativeQuery(String Crée une instance de Query permettant
sqlString) d’exécuter une instruction SQL native.
Query createNativeQuery(String Crée une instance de Query permettant
sqlString, Class resultClass) d’exécuter une instruction SQL native en lui
passant la classe du résultat attendu.

Une API complète permet de contrôler l’implémentation de Query obtenue par l’une
de ces méthodes. L’API Query, présentée dans le Listing 4.26, est utilisable avec les
requêtes statiques (requêtes nommées) et les requêtes dynamiques en JPQL, ainsi
qu’avec les requêtes natives en SQL. Cette API permet également de lier des para-
mètres aux requêtes et de contrôler la pagination.

Listing 4.26 : API Query


public interface Query {
// Exécute une requête et renvoie un résultat.
public List getResultList();
public Object getSingleResult();
public int executeUpdate();

// Initialise les paramètres de la requête.


public Query setParameter(String name, Object value);
public Query setParameter(String name, Date value,
TemporalType temporalType);
public Query setParameter(String name, Calendar value,
TemporalType temporalType);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 173

public Query setParameter(int position, Object value);


public Query setParameter(int position, Date value,
TemporalType temporalType);
public Query setParameter(int position, Calendar value,
TemporalType temporalType);
public Map<String, Object> getNamedParameters();
public List getPositionalParameters();

// Restreint le nombre de résultats renvoyés par une requête.


public Query setMaxResults(int maxResult);
public int getMaxResults();
public Query setFirstResult(int startPosition);
public int getFirstResult();

// Fixe et obtient les "hints" d’une requête.


public Query setHint(String hintName, Object value);
public Map<String, Object> getHints();
public Set<String> getSupportedHints();

// Fixe le mode flush pour l’exécution de la requête.


public Query setFlushMode(FlushModeType flushMode);
public FlushModeType getFlushMode();

// Fixe le mode de verrouillage utilisé par la requête.


public Query setLockMode(LockModeType lockMode);
public LockModeType getLockMode();

// Permet d’accéder à l’API spécifique du fournisseur.


public <T> T unwrap(Class<T> cls);
}

Les méthodes les plus utilisées de cette API sont celles qui exécutent la requête.
Ainsi, pour effectuer une requête SELECT, vous devez choisir entre deux méthodes
en fonction du résultat que vous voulez obtenir :
■■ La méthode getResultList() exécute la requête et renvoie une liste de résultats
(entités, attributs, expressions, etc.).
■■ La méthode getSingleResult() exécute la requête et renvoie un résultat unique.
Pour exécuter une mise à jour ou une suppression, utilisez la méthode execu-
teUpdate(), qui exécute la requête et renvoie le nombre d’entités concernées par
son exécution.
Comme nous l’avons vu plus haut dans la section "JPQL", une requête peut prendre
des paramètres nommés (:monParam, par exemple) ou positionnels (?1, par exemple).
L’API Query définit plusieurs méthodes setParameter() pour initialiser ces para-
mètres avant l’exécution d’une requête.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
174 Java EE 6 et GlassFish 3 

Une requête peut renvoyer un grand nombre de résultats. Selon l’application,


ceux-ci peuvent être traités tous ensemble ou par morceaux (une application web,
par exemple, peut vouloir n’afficher que dix lignes à la fois). Pour contrôler cette
pagination, l’interface Query définit les méthodes setFirstResult() et setMaxRe-
sults(), qui permettent respectivement d’indiquer le premier résultat que l’on sou-
haite obtenir (en partant de zéro) et le nombre maximal de résultats par rapport à ce
point précis.
Le mode flush indique au fournisseur de persistance comment gérer les modifi-
cations et les requêtes en attente. Deux modes sont possibles : AUTO et COMMIT. Le
premier (qui est également celui par défaut) précise que c’est au fournisseur de s’as-
surer que les modifications en attente soient visibles par le traitement de la requête.
COMMIT est utilisé lorsque l’on souhaite que l’effet des modifications apportées aux
entités n’écrase pas les données modifiées dans le contexte de persistance.
Les requêtes peuvent être verrouillées par un appel à la méthode
LockMode(LockModeType).
set­­

Les sections qui suivent décrivent les trois types de requêtes en utilisant quelques-
unes des méthodes que nous venons de décrire.

Requêtes dynamiques

Les requêtes dynamiques sont définies à la volée par l’application lorsqu’elle en a


besoin. Elles sont créées par un appel à la méthode EntityManager.createQuery(),
qui prend en paramètre une chaîne représentant une requête JPQL.
Dans le code qui suit, la requête JPQL sélectionne tous les clients de la base. Le
résultat étant une liste, on utilise la méthode getResultList() pour renvoyer une
liste d’entités Customer (List<Customer>). Si vous savez que la requête ne renverra
qu’une seule entité, utilisez plutôt la méthode getSingleResult() car cela vous
évitera de devoir ensuite extraire cette entité d’une liste.
Query query = em.createQuery("SELECT c FROM Customer c"); List<Customer>
customers = query.getResultList();

La chaîne contenant la requête peut également être élaborée dynamiquement par


l’application – en cours d’exécution – à l’aide de l’opérateur de concaténation et en
fonction de certains critères.
String jpqlQuery = "SELECT c FROM Customer c";
if (someCriteria)

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 175

jpqlQuery += " WHERE c.firstName = ’Vincent’";


query = em.createQuery(jpqlQuery);
List<Customer> customers = query.getResultList();

La requête précédente récupère les clients prénommés Vincent, mais vous voudrez
peut-être pouvoir choisir ce prénom et le passer en paramètre : vous pouvez le faire
en utilisant des noms ou des positions. Dans l’exemple suivant, on utilise un para-
mètre nommé :fname (notez le préfixe deux-points) dans la requête et on le lie à une
valeur avec la méthode setParameter() :
jpqlQuery = "SELECT c FROM Customer c";
if (someCriteria)
jpqlQuery += " where c.firstName = :fname";
query = em.createQuery(jpqlQuery);
query.setParameter("fname", "Vincent");
List<Customer> customers = query.getResultList();

Notez que le nom de paramètre fname ne contient pas le symbole deux-points utilisé
dans la requête. Le code équivalent avec un paramètre positionnel serait le suivant :
jpqlQuery = "SELECT c FROM Customer c";
if (someCriteria)
jpqlQuery += " where c.firstName = ?1";
query = em.createQuery(jpqlQuery);
query.setParameter(1, "Vincent");
List<Customer> customers = query.getResultList();

Si vous voulez paginer la liste des clients par groupes de dix, utilisez la méthode
setMaxResults() de la façon suivante :

Query query = em.createQuery("SELECT c FROM Customer c");


query.setMaxResults(10);
List<Customer> customers = query.getResultList();

Le problème des requêtes dynamiques est le coût de la traduction de la chaîne JPQL


en instruction SQL au moment de l’exécution. La requête étant créée à l’exécution,
elle ne peut pas être prévue à la compilation  : à chaque appel, le fournisseur de
persistance doit donc analyser la chaîne JPQL, obtenir les métadonnées de l’ORM
et produire la requête SQL correspondante. Ce surcoût de traitement des requêtes
dynamiques peut donc être un problème : lorsque cela est possible, utilisez plutôt
des requêtes statiques (requêtes nommées).

Requêtes nommées

Les requêtes nommées sont différentes des requêtes dynamiques parce qu’elles sont
statiques et non modifiables. Bien que cette nature statique n’offre pas la souplesse

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
176 Java EE 6 et GlassFish 3 

des requêtes dynamiques, l’exécution des requêtes nommées peut être plus efficace
car le fournisseur de persistance peut traduire la chaîne JPQL en SQL au démarrage
de l’application au lieu d’être obligé de le faire à chaque fois que la requête est
exécutée.
Les requêtes nommées sont exprimées dans les métadonnées via une annotation @
NamedQuery ou son équivalent XML. Cette annotation prend deux éléments : le nom
de la requête et son contenu. Dans le Listing 4.27, nous modifions l’entité Customer
pour définir trois requêtes statiques à l’aide d’annotations.

Listing 4.27 : L’entité Customer avec des requêtes nommées


@Entity
@NamedQueries({
@NamedQuery(name = "findAll", query="select c from Customer c"),
@NamedQuery(name = "findVincent",
query="select c from Customer c
„ where c.firstName = ’Vincent’"),
@NamedQuery(name = "findWithParam",
query="select c from Customer c where c.firstName = :fname")
)}
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String email;
@OneToOne @JoinColumn(name = "address_fk")
private Address address;

// Constructeurs, getters, setters


}

L’entité Customer définissant plusieurs requêtes nommées, nous utilisons l’anno-


tation @NamedQueries, qui prend en paramètre un tableau de @NamedQuery. La pre-
mière requête, nommée findAll, renvoie toutes les entités Customer de la base, sans
aucune restriction (pas de clause WHERE). La requête findWithParam prend quant à
elle un paramètre fname pour choisir les clients en fonction de leur prénom. Si l’en-
tité Customer n’avait défini qu’une seule requête, nous aurions simplement utilisé
une annotation @NamedQuery, comme dans l’exemple suivant :
@Entity
@NamedQuery(name = "findAll", query="select c from Customer c")
public class Customer {
...
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 177

L’exécution de ces requêtes ressemble à celle des requêtes dynamiques : on appelle


la méthode EntityManager.createNamedQuery() en lui passant le nom de la requête
tel qu’il est défini dans les annotations. Cette méthode renvoie un objet Query qui
peut servir à initialiser les paramètres, le nombre maximal de résultats, le mode de
récupération, etc. Pour, par exemple, exécuter la requête findAll, on écrirait le code
suivant :
Query query = em.createNamedQuery("findAll");
List<Customer> customers = query.getResultList();

Le fragment de code qui suit appelle la requête findWithParam en lui passant le para-
mètre fname et en limitant le nombre de résultats à 3 :
Query query = em.createNamedQuery("findWithParam");
query.setParameter("fname", "Vincent");
query.setMaxResults(3);
List<Customer> customers = query.getResultList();

La plupart des méthodes de l’API Query renvoyant un objet Query, vous pouvez
utiliser un raccourci élégant qui consiste à appeler les méthodes les unes après les
autres (setParameter().setMaxResults(), etc.).
Query query = em.createNamedQuery("findWithParam").
„ setParameter("fname", "Vincent").setMaxResults(3);
List<Customer> customers = query.getResultList();

Les requêtes nommées permettent d’organiser les définitions de requêtes et amélio-


rent les performances de l’application. Cette organisation vient du fait qu’elles sont
définies de façon statique sur les entités et généralement placées sur la classe entité
qui correspond directement au résultat de la requête (ici, findAll renvoie des clients
et doit donc être définie sur l’entité Customer).
Cependant, la portée du nom de la requête est celle de l’unité de persistance et ce
nom doit être unique dans cette portée, ce qui signifie qu’il ne peut exister qu’une
seule requête findAll : ceci implique donc de nommer différemment cette requête
si l’on devait, par exemple, en écrire une autre pour rechercher toutes les adresses.
Une pratique courante consiste à préfixer le nom de la requête par celui de l’entité :
on aurait ainsi une méthode Customer.findAll pour Customer et Address.findAll
pour Address.
Un autre problème est que le nom de la requête, qui est une chaîne, est modifiable
et que vous risquez donc d’obtenir une exception indiquant que la requête n’existe
pas si vous faites une erreur de frappe ou que vous refactorisiez le code. Pour limiter

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
178 Java EE 6 et GlassFish 3 

ce risque, vous pouvez remplacer ce nom par une constante. Le Listing 4.28 montre
comment refactoriser l’entité Customer.

Listing 4.28 : L’entité Customer définit une requête nommée à l’aide d’une constante
@Entity
@NamedQuery(name = Customer.FIND_ALL,
query="select c from Customer c")
public class Customer {

public static final String FIND_ALL = "Customer.findAll";

// Attributs, constructeurs, getters, setters


}

La constante FIND_ALL identifie la requête findAll sans ambiguïté en préfixant son


nom du nom de l’entité. C’est cette même constante qui est ensuite utilisée dans
l’annotation @NamedQuery et que vous pouvez utiliser pour exécuter la requête :
Query query = em.createNamedQuery(Customer.FIND_ALL);
List<Customer> customers = query.getResultList();

Requêtes natives

JPQL dispose d’une syntaxe riche permettant de gérer les entités sous n’importe
quelle forme et de façon portable entre les différentes bases de données, mais JPA
autorise également l’utilisation des fonctionnalités spécifiques d’un SGBDR via
des requêtes natives. Celles-ci prennent en paramètre une instruction SQL (SELECT,
UPDATE ou DELETE) et renvoient une instance de Query pour exécuter cette instruc-
tion. En revanche, les requêtes natives peuvent ne pas être portables d’une base de
données à l’autre.
Si le code n’est pas portable, pourquoi alors ne pas utiliser des appels JDBC ? La
raison principale d’utiliser des requêtes JPA natives plutôt que des appels JDBC
est que le résultat de la requête sera automatiquement converti en entités. Pour,
par exemple, récupérer toutes les entités Customer de la base en utilisant SQL,
vous devez appeler la méthode EntityManager.createNativeQuery(), qui prend
en paramètre la requête SQL et la classe d’entité dans laquelle le résultat sera
traduit :
Query query = em.createNativeQuery("SELECT * FROM t_customer",
Customer.class);
List<Customer> customers = query.getResultList();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 179

Comme vous pouvez le constater, la requête SQL est une chaîne qui peut être
créée dynamiquement en cours d’exécution (exactement comme les requêtes JPQL
dynamiques). Là aussi la requête pourrait être complexe et, ne la connaissant pas
à l’avance, le fournisseur de persistance sera obligé de l’interpréter à chaque fois,
ce qui aura des répercussions sur les performances de l’application. Toutefois,
comme les requêtes nommées, les requêtes natives peuvent utiliser le mécanisme
des annotations pour définir des requêtes SQL statiques. Ici, cette annotation s’ap-
pelle @NamedNativeQuery et peut être placée sur n’importe quelle entité (voir Lis-
ting 4.29) – comme avec JPQL, le nom de la requête doit être unique dans l’unité
de persistance.

Listing 4.29 : L’entité Customer définit une requête native nommée


@Entity
@NamedNativeQuery(name = "findAll",
query="select * from t_customer")
@Table(name = "t_customer")
public class Customer {

// Attributs, constructeurs, getters, setters


}

Concurrence

JPA peut servir à modifier des données persistantes et JPQL permet de récupérer des
données répondant à certains critères. L’application qui les utilise peut s’exécuter
dans un cluster de plusieurs nœuds, avoir plusieurs threads et une seule base de don-
nées : il est donc assez fréquent d’accéder aux entités de façon concurrente. Dans
cette situation, l’application doit contrôler la synchronisation des données au moyen
d’un mécanisme de verrouillage. Que votre programme soit simple ou complexe,
il y a de grandes chances pour que vous soyez obligé d’utiliser des verrous à un
endroit ou à un autre de votre code.
Pour illustrer le problème de l’accès concurrent à une base de données, prenons
l’exemple d’une application comprenant les deux méthodes de la Figure 4.3. L’une
des méthodes recherche un livre par son identifiant et augmente son prix de 2 €.
L’autre fait la même chose, mais augmente le prix de 5 €. Si les deux méthodes sont
exécutées en même temps dans des transactions distinctes et qu’elles manipulent le
même livre, vous ne pouvez donc pas prévoir le prix final. Dans notre exemple, son

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
180 Java EE 6 et GlassFish 3 

prix initial était de 10 € : selon la transaction qui se termine en dernier, son prix final
sera de 12 € ou de 15 €.

tx1.begin() tx2.begin()

// Le prix du livre est de 10€ // Le prix du livre est de 10€


Book book = em.find(Book.class, 12); Book book = em.find(Book.class, 12);

book.raisePriceByT Euros(); book.raisePriceByFi Euros();

tx1.comit(); tx2.comit();
// Le prix est maintenant de 12€ // Le prix est maintenant de 15€

temps

Figure 4.3
Les transactions tx1 et tx2 modifient le prix d’un livre de façon concurrente.

Ce problème de concurrence, où le "gagnant" est celui qui valide la transaction


en dernier, n’est pas spécifique à JPA. Cela fait bien longtemps que les SGBD ont
dû résoudre ce problème et ont trouvé différentes solutions pour isoler les transac-
tions les unes des autres. Un mécanisme classique consiste à verrouiller la ligne sur
laquelle porte l’instruction SQL.
JPA 2.0 dispose de deux types de verrouillages (JPA 1.0 ne proposait que le
­verrouillage optimiste) :
■■ Le verrouillage optimiste. Il repose sur la supposition que la plupart des tran-
sactions n’entreront pas en conflit les unes avec les autres, ce qui permet une
concurrence aussi permissive que possible.
■■ Le verrouillage pessimiste. Il fait la supposition inverse, ce qui impose d’obtenir
un verrou sur la ressource avant de la manipuler.
Prenons un exemple de la vie quotidienne pour illustrer ces concepts : la traversée
d’une avenue. Dans une zone à faible trafic, vous pourriez traverser l’avenue sans
regarder si des voitures arrivent (traversée optimiste) alors que, dans une zone à fort
trafic, il ne faut certainement pas le faire (traversée pessimiste).
JPA utilise différents mécanismes de verrouillage en fonction des niveaux de l’API.
Les verrous optimistes et pessimistes peuvent être obtenus via les méthodes Entity-
Manager.find() et EntityManager.refresh() (en plus de la méthode lock()), ainsi
que par les requêtes JPQL : ceci signifie donc que le verrouillage peut s’effectuer
au niveau du gestionnaire d’entités et au niveau Query avec les méthodes énumérées
dans les Tableaux 4.4 et 4.5.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 181

Tableau 4.4 : Méthodes d’EntityManager pour verrouiller les entités

Méthode Description
<T> T find(Class<T> entityClass, Recherche une entité de la classe avec la clé
Object primaryKey, LockModeType indiquée et la verrouille selon le type du verrou.
lockMode)

void lock(Object entity, Verrouille une instance d’entité contenue dans


LockModeType lockMode) le contexte de persistance avec le type de verrou
indiqué.
void refresh(Object entity, Rafraîchit l’état de l’instance à partir de la base de
LockModeType lockMode) données en écrasant les éventuelles modifications
apportées à l’entité et verrouille celle-ci selon le
type de verrou indiqué.

Tableau 4.5 : Méthodes de Query pour verrouiller les requêtes JPQL

Méthode Description
Query setLockMode(LockModeType Fixe le type de verrou utilisé pour l’exécution de
lockMode) la requête.

Toutes ces méthodes attendent un paramètre LockModeType pouvant prendre les


valeurs suivantes :
■■ OPTIMISTIC. Verrouillage optimiste.
■■ OPTIMISTIC_FORCE_INCREMENT. Verrouillage optimiste et incrémentation de la
colonne version de l’entité (voir la section "Gestion de version").
■■ PESSIMISTIC_READ. Verrouillage pessimiste sans avoir besoin de relire les données
à la fin de la transaction pour obtenir un verrou.
■■ PESSIMISTIC_WRITE. Verrouillage pessimiste et sérialisation entre les transactions
pour mettre à jour l’entité.
■■ PESSIMISTIC_FORCE_INCREMENT. Verrouillage pessimiste et incrémentation de la
colonne version de l’entité (voir la section "Gestion de version").
■■ NONE. Aucun mécanisme de verrouillage n’est utilisé.
Vous pouvez utiliser ces paramètres à différents endroits en fonction de vos besoins.
Vous pouvez lire puis verrouiller :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
182 Java EE 6 et GlassFish 3 

Book book = em.find(Book.class, 12);


// Verrouille pour augmenter le prix
em.lock(book, LockModeType.PESSIMISTIC);
book.raisePriceByTwoEuros();

Ou vous pouvez lire et verrouiller :


Book book = em.find(Book.class, 12, LockModeType.PESSIMISTIC);
// Le livre est déjà verrouillé : on augmente son prix
book.raisePriceByTwoEuros();

La concurrence et le verrouillage sont les motivations essentielles de la gestion des


versions.

Gestion de version
Java utilise le système des versions : Java SE 5.0, Java SE 6.0, EJB 3.1, JAX-RS 1.0,
etc. Lorsqu’une nouvelle version de JAX-RS apparaît, par exemple, son numéro de
version est augmenté et vous mettez à jour votre environnement avec JAX-RS 1.1.
JPA utilise exactement le même mécanisme lorsque l’on a besoin de versions d’en-
tités. La première fois que vous rendez une entité persistante, elle prend le numéro
de version 1. Si, plus tard, vous modifiez un attribut et que vous répercutiez cette
modification dans la base de données, le numéro de version de l’entité passe à 2, etc.
La version de l’entité évolue à chaque fois qu’elle est modifiée.
Pour que ceci fonctionne, l’entité doit posséder un attribut annoté par @Version, lui
permettant de stocker son numéro de version. Cet attribut est ensuite traduit par une
colonne dans la base de données. Les types autorisés pour les numéros de version
sont int, Integer, short, Short, long, Long ou Timestamp. Le Listing 4.30 montre
comment ajouter un numéro de version à l’entité Book.

Listing 4.30 : L’entité Book avec une annotation @Version


@Entity
public class Book {

@Id @GeneratedValue
private Long id;
@Version
private Integer version;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
// Constructeurs, getters, setters
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 183

L’entité peut lire la valeur de sa version mais ne peut pas la modifier : seul le fournis-
seur de persistance peut initialiser ou modifier cette valeur lorsque l’objet est écrit
ou modifié dans la base de données. Dans le Listing 4.31, par exemple, on rend une
nouvelle entité Book persistante. Lorsque la transaction se termine, le fournisseur
de persistance fixe son numéro de version à 1. Puis on modifie le prix du livre et,
après l’écriture des données dans la base, le numéro de version est incrémenté et
vaut donc 2.

Listing 4.31 : Modification du prix d’un livre


Book book = new Book("H2G2", 21f,
"Best IT book", "123-456", 321, false);

tx.begin();
em.persist(book);
tx.commit();
assertEquals(1, book.getVersion());

tx.begin();
book.raisePriceByTwoEuros();
tx.commit();
assertEquals(2, book.getVersion());

L’attribut de version n’est pas obligatoire, mais il est conseillé lorsque l’entité est
susceptible d’être modifiée en même temps par plusieurs processus ou plusieurs
threads. La gestion de version est au cœur du verrouillage optimiste car elle offre
une protection pour les modifications concurrentes épisodiques des entités. En fait,
une entité est automatiquement gérée par verrouillage optimiste lorsqu’elle utilise
l’annotation @Version.

Verrouillage optimiste

Comme son nom l’indique, le verrouillage optimiste part du principe que les tran-
sactions sur la base de données n’entreront pas en conflit les unes avec les autres.
En d’autres termes, on estime qu’il y a de fortes chances pour que la transaction qui
modifie une entité soit la seule à modifier cette entité à cet instant. La décision de
verrouiller l’entité est donc prise à la fin de la transaction, afin de garantir que les
modifications apportées à l’entité seront cohérentes avec l’état courant de la base
de données. Les transactions qui violeraient cette contrainte provoqueraient la levée
d’une exception OptimisticLockException et seraient annulées.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
184 Java EE 6 et GlassFish 3 

Comment lever une OptimisticLockException ? Soit en verrouillant explicitement


l’entité (avec les méthodes lock() ou find()), soit en laissant le fournisseur de per-
sistance contrôler l’attribut annoté par @Version. L’utilisation de cette annotation
permet au gestionnaire d’entités d’effectuer un verrouillage optimiste simplement
en comparant la valeur de l’attribut de version dans l’instance de l’entité avec la
valeur de la colonne correspondante dans la base. Sans cette annotation, le gestion-
naire d’entités ne peut pas réaliser de verrouillage optimiste.
À la Figure 4.4, les transactions tx1 et tx2 obtiennent toutes les deux une instance
de la même entité de Book. À ce moment précis, la version de l’entité est  1. La
première transaction augmente le prix du livre de 2 € et valide cette modification :
lorsque les données sont écrites dans la base, le fournisseur de persistance incré-
mente le numéro de version, qui passe donc à 2. Si la seconde transaction augmente
le prix de 5 € et valide également cette modification, le gestionnaire d’entités de tx2
réalisera que le numéro de version dans la base est différent de celui de l’entité, ce
qui signifie que la version a été modifiée par une autre transaction : une exception
OptimisticLockException sera alors lancée.

tx1.begin(); tx2.begin();

// Le prix du livre est de 10€ // Le prix du livre est de 10€


Book book = em.find(Book.class, 12); Book book = em.find(Book.class, 12);
b t sio ) == 1 k t s == 1

book.raisePriceByTwoEuros();
book.raisePriceByFiveEuros();
tx1.comit();
// Le prix est maintenant de 12€
b sio ( == 2 tx2.comit();
d t êt 1 ll ut 2
p k p
temps

Figure 4.4
OptimisticLockException est lancée par la transaction tx2.

Le comportement par défaut de l’annotation @Version consiste à lancer l’exception


OptimisticLockException lorsque les données sont écrites dans la base (lorsque la
transaction est validée ou par un appel explicite à la méthode em.flush()), mais vous
pouvez également contrôler l’endroit de placement du verrou optimiste en choisis-
sant une stratégie "lire puis verrouiller" ou "lire et verrouiller". Le code de lire et
verrouiller, par exemple, serait de la forme :
Book book = em.find(Book.class, 12);
// Verrouillage pour augmenter le prix
em.lock(book, LockModeType.OPTIMISTIC); book.raisePriceByTwoDollars();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 185

Avec le verrouillage optimiste, la valeur du paramètre LockModeType peut être OPTI-


MISTIC ou OPTIMISTIC_FORCE_INCREMENT (ou, respectivement, READ ou WRITE, mais
ces valeurs sont dépréciées). La seule différence entre les deux est qu’OPTIMISTIC_
FORCE_INCREMENT forcera une mise à jour (incrémentation) de la colonne contenant
la version de l’entité.
Il est fortement conseillé d’utiliser le verrouillage optimiste pour toutes les entités
auxquelles on est susceptible d’accéder de façon concurrente. Ne pas utiliser de
verrou peut provoquer un état incohérent de l’entité, la perte de modifications et
d’autres problèmes. Ce type de verrouillage donne de meilleures performances car il
décharge la base de ce travail ; c’est une alternative au verrouillage pessimiste, qui,
lui, exige un verrouillage de bas niveau de la base de données.

Verrouillage pessimiste

Le verrouillage pessimiste part du principe opposé à celui du verrouillage optimiste


puisqu’il consiste à verrouiller systématiquement l’entité avant de la manipuler. Ce
mécanisme est donc très restrictif et dégrade les performances de façon significative
puisqu’il implique que la base pose un verrou avec SELECT ... FOR UPDATE SQL
lorsqu’elle lit les données.
Généralement, les bases de données offrent un service de verrouillage pessimiste
permettant au gestionnaire d’entités de verrouiller une ligne de la table pour empê-
cher un autre thread de modifier cette même ligne. C’est donc un mécanisme effi-
cace pour garantir que deux clients ne modifieront pas la même ligne en même
temps, mais il exige des vérifications de bas niveau qui pénalisent les performances.
Les transactions qui violent cette contrainte provoquent la levée d’une exception
PessimisticLockException et sont annulées.

Le verrouillage optimiste convient bien lorsqu’il y a peu de contention entre les tran-
sactions mais, quand cette contention augmente, le verrouillage pessimiste peut se
révéler préférable car le verrou sur la base est obtenu immédiatement, alors que les
transactions optimistes échouent souvent plus tard. En temps de crise, par exemple,
les marchés boursiers reçoivent d’énormes ordres de ventes. Si 100 millions de per-
sonnes veulent vendre leurs actions en même temps, le système doit utiliser un ver-
rouillage pessimiste pour assurer la cohérence des données. Notez qu’actuellement
le marché est plutôt pessimiste qu’optimiste, mais cela n’a rien à voir avec JPA.
Le verrouillage pessimiste peut s’appliquer aux entités qui ne sont pas annotées par
@Version.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
186 Java EE 6 et GlassFish 3 

Résumé

Dans ce chapitre, nous avons vu comment interroger les entités. Le gestionnaire


d’entités est la pièce maîtresse de la persistance des entités : il peut créer, modifier,
rechercher par identifiant, supprimer et synchroniser les entités avec la base de don-
nées en utilisant le contexte de persistance, qui se comporte comme un cache de
premier niveau. JPA fournit également JPQL, un langage de requête très puissant
et indépendant des SGBDR. Grâce à lui, vous pouvez récupérer les entités à l’aide
d’une syntaxe claire disposant de clauses WHERE, ORDER BY ou GROUP BY. Lorsque
vous accédez aux entités de façon concurrente, vous savez comment utiliser les
numéros de version et quand utiliser le verrouillage optimiste ou le verrouillage
pessimiste.
Dans le prochain chapitre, nous en apprendrons plus sur le cycle de vie des entités et
verrons comment y greffer du code à l’aide de méthodes de rappel ou d’écouteurs.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
5
Méthodes de rappel et écouteurs

Au chapitre précédent, nous avons vu comment interroger les entités liées à une
base de données. Nous savons maintenant comment rendre une entité persistante,
la supprimer, la modifier et la retrouver à partir de son identifiant. Grâce à JPQL,
nous pouvons récupérer une ou plusieurs entités en fonction de certains critères de
recherche avec des requêtes dynamiques, statiques et natives. Toutes ces opérations
sont réalisées par le gestionnaire d’entités – la composante essentielle qui manipule
les entités et gère leur cycle de vie.
Nous avons décrit ce cycle de vie en écrivant que les entités sont soit gérées par
le gestionnaire d’entités (ce qui signifie qu’elles ont une identité de persistance et
qu’elles sont synchronisées avec la base de données), soit détachées de la base de
données et utilisées comme des POJO classiques. Mais le cycle de vie d’une entité
est un peu plus riche. Surtout, JPA permet d’y greffer du code métier lorsque cer-
tains événements concernent l’entité : ce code est ensuite automatiquement appelé
par le fournisseur de persistance à l’aide de méthodes de rappel.
Vous pouvez considérer les méthodes de rappel et les écouteurs comme les triggers
d’une base de données relationnelle. Un trigger exécute du code métier pour chaque
ligne d’une table alors que les méthodes de rappel et les écouteurs sont appelés sur
chaque instance d’une entité en réponse à un événement ou, plus précisément, avant
et après la survenue d’un événement. Pour définir ces méthodes "Pre" et "Post",
nous pouvons utiliser des annotations ou des descripteurs XML.

Cycle de vie d’une entité


Maintenant que nous connaissons la plupart des mystères des entités, intéressons-
nous à leur cycle de vie. Lorsqu’une entité est créée ou rendue persistante par le
gestionnaire d’entités, celle-ci est dite gérée. Auparavant, elle n’était considérée par

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
188 Java EE 6 et GlassFish 3 

la JVM que comme un simple POJO (elle était alors détachée) et pouvait être uti-
lisée par l’application comme un objet normal. Dès qu’une entité devient gérée, le
gestionnaire synchronise automatiquement la valeur de ses attributs avec la base de
données sous-jacente.
Pour mieux comprendre tout ceci, examinez la Figure 5.1, qui représente les états
que peut prendre une entité Customer, ainsi que les transitions entre ces états.
Figure 5.1
Customer cust = new Customer() Supprimée par le ramasse-miettes
Cycle de vie
d’une entité. Existe en mémoire
Customer cust = em.find()
Requête JPQL
em.persist(cust)
Supprimée de
la base de données,
mais toujours en mémoire
em.clear()
em.merge(cust)
Sérialisée vers une autre couche em.remove(cust)
Détachée Gérée Supprimée
em.merge(cust)

em.refresh(cust)
Modifiée avec les accesseurs

Base de
données

On crée une instance de l’entité Customer à l’aide de l’opérateur new. Dès lors, cet
objet existe en mémoire bien que JPA ne le connaisse pas. Si l’on n’en fait rien, il
devient hors de portée et finit par être supprimé par le ramasse-miettes, ce qui marque
la fin de son cycle de vie. Nous pouvons aussi le rendre persistant à l’aide de la
méthode EntityManager.persist(), auquel cas l’entité devient gérée et son état est
synchronisé avec la base de données. Pendant qu’elle est dans cet état, nous pouvons
modifier ses attributs en utilisant ses méthodes setters (customer. SetFirstName(),
par exemple) ou rafraîchir son contenu par un appel à EntityManager.refresh().
Toutes ces modifications garderont l’entité synchronisée avec la base. Si l’on appelle
la méthode EntityManager.contains(customer), celle-ci renverra true car customer
appartient au contexte de persistance (il est géré).
Un autre moyen de gérer une entité consiste à la charger à partir de la base de don-
nées à l’aide de la méthode EntityManager.find() ou d’une requête JPQL récupé-
rant une liste d’entités qui seront alors toutes automatiquement gérées.
Dans l’état géré, un appel à la méthode EntityManager.remove() supprime l’entité de
la base de données et elle n’est plus gérée. Cependant, l’objet Java continue d’exister
en mémoire, et il reste utilisable tant que le ramasse-miettes ne le supprime pas.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 189

Examinons maintenant l’état détaché. Nous avons vu au chapitre précédent qu’un


appel explicite à EntityManager.clear() supprimait l’entité du contexte de per-
sistance – elle devient alors détachée. Il y a un autre moyen, plus subtil, de déta-
cher une entité : en la sérialisant. Bien que dans de nombreux exemples de ce livre
les entités n’héritent d’aucune classe, elles doivent implémenter l’interface java.
io.Serializable pour passer par un réseau afin d’être invoquées à distance ou pour
traverser des couches afin d’être affichées dans une couche présentation – cette res-
triction est due non pas à JPA mais à Java. Une entité qui est sérialisée, qui passe par
le réseau et est désérialisée est considérée comme un objet détaché : pour la réatta-
cher, il faut appeler la méthode EntityManager.merge().
Les méthodes de rappel et les écouteurs permettent d’ajouter une logique métier qui
s’exécutera lorsque certains événements du cycle de vie d’une entité surviennent,
voire à chaque fois qu’un événement intervient dans le cycle de vie d’une entité.

Méthodes de rappel

Le cycle de vie d’une entité se décompose en quatre parties : persistance, modifi-


cation, suppression et chargement, qui correspondent aux opérations équivalentes
sur la base de données. Chacune de ces parties est associée à un événement "Pré" et
"Post" qui peut être intercepté par le gestionnaire d’entités pour appeler une méthode
métier qui doit avoir été marquée par l’une des annotations du Tableau 5.1.

Tableau 5.1 : Annotations des méthodes de rappel du cycle de vie

Annotation Description
@PrePersist La méthode sera appelée avant l’exécution d’EntityManager.persist().
@PostPersist La méthode sera appelée après que l’entité sera devenue persistante. Si
l’entité produit sa clé primaire (avec @GeneratedValue), sa valeur est
accessible dans la méthode.
@PreUpdate La méthode sera appelée avant une opération de modification de l’entité
dans la base de données (appel des setters de l’entité ou de la méthode
EntityManager.merge()).

@PostUpdate La méthode sera appelée après une opération de modification de l’entité


dans la base de données.
@PreRemove La méthode sera appelée avant l’exécution d’EntityManager.remove().
@PostRemove La méthode sera appelée après la suppression de l’entité.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
190 Java EE 6 et GlassFish 3 

Annotation Description
@PostLoad La méthode sera appelée après le chargement de l’entité (par une requête
JPQL, par un appel à EntityManager.find()) ou avant qu’elle soit
rafraîchie à partir de la base de données. Il n’existe pas d’annotation
@PreLoad car cela n’aurait aucun sens d’agir sur une entité qui n’a pas
encore été construite.

La Figure 5.2 a ajouté ces annotations au diagramme d’états de la Figure 5.1.


Figure 5.2
Cycle de vie Supprimée par le ramasse-miettes
d’une entité avec
findById ou JPQL Existe en mémoire
les annotations
des méthodes de @PostLoad

rappel. @PrePersist @PostRemove


@PostPersist

@PreRemove
Détachée Gérée Supprimée

@PostLoad après fusion


@PreUpdate et @PostUpdate
si l'entité a été modifiée @PreUpdate and @PostUpdate lorsque les accesseurs
sont appelés
@PostLoad après le rafraîchissement

Avant d’insérer une entité dans la base de données, le gestionnaire d’entités appelle
la méthode annotée par @PrePersist. Si l’insertion ne provoque pas d’exception,
l’entité est rendue persistante, son identifiant est créé, puis la méthode annotée par
@PostPersist est appelée. Il en va de même pour les mises à jour (@PreUpdate, @
PostUpdate) et les suppressions (@PreRemove, @PostRemove). Lorsqu’une entité est
chargée à partir de la base de données (via un appel à EntityManager.find() ou
une requête JPQL), la méthode annotée par @PostLoad est appelée. Lorsque l’entité
détachée a besoin d’être fusionnée, le gestionnaire d’entités doit d’abord vérifier si
la version en mémoire est différente de celle de la base (@PostLoad) et modifier les
données (@PreUpdate, @PostUpdate) si c’est le cas.
Outre les attributs, les constructeurs, les getters et les setters, les entités peuvent
contenir du code métier pour valider leur état ou calculer certains de leurs attributs.
Comme le montre le Listing 5.1, ce code peut être placé dans des méthodes Java clas-
siques invoquées par d’autres classes ou dans des méthodes de rappel (callbacks).
Dans ce dernier cas, c’est le gestionnaire d’entités qui les appellera automatiquement
en fonction de l’événement qui a été déclenché.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 191

Listing 5.1 : Entité Customer avec méthodes de rappel


@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;

@PrePersist
@PreUpdate
private void validate() {
if (dateOfBirth.getTime() > new Date().getTime())
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number");
}

@PostLoad
@PostPersist
@PostUpdate
public void calculateAge() {
if (dateOfBirth == null) {
age = null;
return;
}
Calendar birth = new GregorianCalendar();
birth.setTime(dateOfBirth);
Calendar now = new GregorianCalendar();
now.setTime(new Date());
int adjust = 0;
if (now.get(DAY_OF_YEAR) - birth.get(DAY_OF_YEAR) < 0) {
adjust = -1;
}
age = now.get(YEAR) - birth.get(YEAR) + adjust;
}

// Constructeurs, getters, setters


}

Dans le Listing 5.1, l’entité Customer définit une méthode pour valider les données
(elle vérifie les valeurs des attributs dateOfBirth et phoneNumber). Cette méthode
étant annotée par @PrePersist et @PreUpdate, elle sera appelée avant l’insertion

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
192 Java EE 6 et GlassFish 3 

ou la modification des données dans la base. Si ces données ne sont pas valides, la
méthode lèvera une exception à l’exécution et l’insertion ou la modification sera
annulée : ceci garantit que la base contiendra toujours des données valides.
La méthode calculateAge() calcule l’âge du client. L’attribut age est transitoire et
n’est donc pas écrit dans la base de données : lorsque l’entité est chargée, rendue
persistante ou modifiée, cette méthode calcule l’âge à partir de la date de naissance
et initialise l’attribut.
Les méthodes de rappel doivent respecter les règles suivantes :
■■ Elles peuvent avoir un accès public, privé, protégé ou paquetage, mais elles ne
peuvent pas être statiques ni finales. Dans le Listing 5.1, la méthode validate()
est privée.
■■ Elles peuvent être marquées par plusieurs annotations du cycle de vie (la méthode
validate() est annotée par @PrePersist et @PreUpdate). Cependant, une anno-
tation de cycle de vie particulière ne peut apparaître qu’une seule fois dans une
classe d’entité (il ne peut pas y avoir deux annotations @PrePersist dans la
même entité, par exemple).
■■ Elles peuvent lancer des exceptions non contrôlées mais pas d’exceptions contrô-
lées. Le lancement d’une exception annule la transaction s’il y en a une en cours.
■■ Elles peuvent invoquer JNDI, JDBC, JMS et les EJB, mais aucune opération
d’EntityManager ou de Query.
■■ Avec l’héritage, si une méthode est définie dans la superclasse, elle sera appelée
avant la méthode de la classe fille. Si, par exemple, la classe Customer du Lis-
ting 5.1 héritait d’une classe Person fournissant une méthode @PrePersist, cette
dernière serait appelée avant celle de Customer.
■■ Si une relation utilise la répercussion des événements, la méthode de rappel asso-
ciée sera également appelée en cascade. Si un Customer contient une collection
d’adresses et que la suppression d’un Customer soit répercutée sur Address, la
suppression d’un client invoquera la méthode @PreRemove d’Address et celle de
Customer.

Écouteurs (listeners)

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 193

Les méthodes de rappel d’une entité fonctionnent bien lorsque la logique métier
n’est liée qu’à cette entité. Les écouteurs permettent d’extraire cette logique dans
une classe séparée qui pourra être partagée par plusieurs entités. En réalité, un
écouteur d’entité est simplement un POJO qui définit une ou plusieurs méthodes
de rappel du cycle de vie. Pour enregistrer un écouteur, il suffit que l’entité utilise
l’annotation @EntityListeners.
Par rapport à l’exemple précédent, nous allons extraire les méthodes calculateAge()
et validate() pour les placer respectivement dans deux classes écouteurs, AgeCal-
culationListener (voir Listing 5.2) et DataValidationListener (voir Listing 5.3).

Listing 5.2 : Écouteur pour calculer l’âge d’un client


public class AgeCalculationListener {

@PostLoad
@PostPersist
@PostUpdate
public void calculateAge(Customer customer) {
if (customer.getDateOfBirth() == null) {
customer.setAge(null);
return;
}

Calendar birth = new GregorianCalendar();


birth.setTime(customer.getDateOfBirth());
Calendar now = new GregorianCalendar();
now.setTime(new Date());
int adjust = 0;
if (now.get(DAY_OF_YEAR) - birth.get(DAY_OF_YEAR) < 0) {
adjust = -1;
}
customer.setAge(now.get(YEAR) - birth.get(YEAR) + adjust);
}
}

Listing 5.3 : Écouteur pour valider les attributs d’un client


public class DataValidationListener {

@PrePersist
@PreUpdate
private void validate(Customer customer) {
if (dateOfBirth.getTime() > new Date().getTime())
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number");
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
194 Java EE 6 et GlassFish 3 

Une classe écouteur ne doit obéir qu’à des règles simples. La première est qu’elle
doit avoir un constructeur public sans paramètre. La seconde est que les signa-
tures des méthodes de rappel sont légèrement différentes de celles du Listing 5.1.
Lorsqu’elle est appelée sur un écouteur, une méthode de rappel doit en effet avoir
accès à l’état de l’entité (le prénom et le nom du client, par exemple) : elle doit donc
avoir un paramètre d’un type compatible avec celui de l’entité. Nous avons vu que,
lorsqu’elle est définie dans l’entité, une méthode de rappel a la signature suivante,
sans paramètre :
void <MÉTHODE>();

Les méthodes de rappel définies dans un écouteur peuvent en revanche avoir deux
types de signatures. Si une méthode doit servir à plusieurs entités, elle doit prendre
un paramètre de type Object :
void <MÉTHODE>(Object uneEntité)

Si elle n’est destinée qu’à une seule entité ou à ses sous-classes, le paramètre peut
être celui de l’entité :
void <MÉTHODE>(Customer customerOuSousClasses)

Pour indiquer que ces deux écouteurs seront prévenus des événements du cycle de
vie de l’entité Customer, celle-ci doit le préciser à l’aide de l’annotation @EntityLis-
teners (voir Listing 5.4). Cette annotation prend en paramètre une classe écouteur
ou un tableau d’écouteurs. Lorsqu’il y a plusieurs écouteurs et qu’un événement du
cycle de vie survient, le fournisseur de persistance parcourt chacun de ces écouteurs
dans l’ordre où ils ont été indiqués et invoquera la méthode de rappel en lui passant
une référence à l’entité concernée par l’événement. Puis il appellera les méthodes de
rappel de l’entité elle-même (s’il y en a).

Listing 5.4 : L’entité Customer définit deux écouteurs


@EntityListeners({DataValidationListener.class,
AgeCalculationListener.class})
@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 195

private Date dateOfBirth;


@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;

// Constructeurs, getters, setters


}

Ce code produit exactement le même résultat que l’exemple précédent (voir Lis-
ting  5.1). L’entité Customer utilise la méthode DataValidationListener.vali-
date() pour valider ses données avant toute insertion ou mise à jour et la méthode
AgeCalculationListener.calculateAge() pour calculer son âge.

Les règles que doivent respecter les méthodes d’un écouteur sont les mêmes que
celles suivies par les méthodes de rappel, mis à part quelques détails :
■■ Elles ne peuvent lancer que des exceptions non contrôlées. Ceci implique que les
autres écouteurs et méthodes de rappel ne seront pas appelés et que l’éventuelle
transaction sera annulée.
■■ Dans une hiérarchie de classes, si plusieurs entités définissent des écouteurs,
ceux de la superclasse seront appelés avant ceux des sous-classes. Si une entité
ne veut pas hériter des écouteurs de sa superclasse, elle peut explicitement les
exclure à l’aide d’une annotation @ExcludeSuperclassListeners (ou son équi-
valent XML).
L’entité Customer du Listing 5.4 définissait deux écouteurs, mais il est également
possible qu’un écouteur soit défini par plusieurs entités, ce qui peut se révéler utile
lorsque l’écouteur fournit une logique générale dont les entités pourront profiter. Le
Listing 5.5, par exemple, crée un écouteur de débogage affichant le nom des événe-
ments déclenchés.

Listing 5.5 : Écouteur de débogage utilisable par n’importe quelle entité


public class DebugListener {

@PrePersist
void prePersist(Object object) {
System.out.println("prePersist");
}
@PostPersist
void postPersist(Object object) {
System.out.println("postPersist");
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
196 Java EE 6 et GlassFish 3 

@PreUpdate
void preUpdate(Object object) {
System.out.println("preUpdate");
}
@PostUpdate
void postUpdate(Object object) {
System.out.println("postUpdate");
}

@PreRemove
void preRemove(Object object) {
System.out.println("preRemove");
}
@PostRemove
void postRemove(Object object) {
System.out.println("postRemove");
}

@PostLoad
void postLoad(Object object) {
System.out.println("postLoad");
}
}

Notez que chaque méthode prend un Object en paramètre, ce qui signifie que n’im-
porte quel type d’entité peut utiliser cet écouteur en ajoutant la classe de DebugLis-
tener à son annotation @EntityListeners. Cependant, pour que toutes les entités
d’une application utilisent cet écouteur, il faudrait ajouter manuellement cette anno-
tation à chacune d’elles : pour éviter cela, JPA permet de définir des écouteurs par
défaut qui couvrent toutes les entités d’une unité de persistance. Comme il n’existe
pas d’annotation s’appliquant à la portée entière d’une unité de persistance, ces
écouteurs par défaut ne peuvent être déclarés que dans un fichier d’association
XML.
Au Chapitre 3, nous avons vu comment utiliser les fichiers XML à la place des anno-
tations. Il suffit de suivre ici les mêmes étapes pour définir DebugListener comme
écouteur par défaut. Pour cela, vous devez créer et déployer avec l’application le
fichier XML présenté dans le Listing 5.6.

Listing 5.6 : Écouteur de débogage défini comme écouteur par défaut


<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
version="2.0">

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 197

<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener
class="com.apress.javaee6.DebugListener"/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>

Dans ce fichier, le marqueur <persistence-unit-metadata> sert à définir toutes


les métadonnées qui n’ont pas d’équivalent avec les annotations. Le marqueur
<persistence-unit-defaults> définit toutes les valeurs par défaut de l’unité de
persistance et <entity-listener> définit l’écouteur par défaut. Ce fichier doit être
nommé persistence.xml et être déployé avec l’application. DebugListener sera
alors automatiquement appelé par toutes les entités.
Si l’on définit une liste d’écouteurs par défaut, chacun d’eux sera appelé dans l’ordre
où il apparaît dans le fichier XML. Les écouteurs par défaut sont toujours invoqués
avant ceux définis par l’annotation @EntityListeners. Pour qu’ils ne s’appliquent
pas à une entité particulière, celle-ci doit le préciser avec l’annotation @Exclude­
DefaultListeners, comme dans le Listing 5.7.

Listing 5.7 : L’entité Customer exclut les écouteurs par défaut


@ExcludeDefaultListeners
@Entity
public class Customer {

@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;

// Constructeurs, getters, setters

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
198 Java EE 6 et GlassFish 3 

Résumé

Ce chapitre a décrit le cycle de vie d’une entité et expliqué comment le gestionnaire


d’entités capture les événements pour appeler les méthodes de rappel. Celles-ci
­peuvent être définies sur une seule entité et marquées par plusieurs annotations
(@PrePersist, @PostPersist, etc.). Les méthodes de rappel peuvent également être
extraites dans des classes écouteurs pour être utilisées par plusieurs entités, voire
toutes (en utilisant des écouteurs par défaut). Avec les méthodes de rappel, nous
avons vu que les entités ne sont pas de simples objets anémiques qui ne contien-
draient que des attributs, des getters et des setters : elles peuvent contenir une logique
métier appelée par d’autres objets de l’application ou invoquée automatiquement
par le gestionnaire d’entités au gré du cycle de vie de l’entité. Les autres composants
de Java EE 6, comme les EJB, utilisent également ce type d’interception.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
6
Enterprise Java Beans

Le chapitre précédent a montré comment implémenter des objets persistants avec


JPA et comment les interroger avec JPQL. La couche de persistance utilise des objets
qui encapsulent et associent leurs attributs à une base de données relationnelle grâce
à des annotations. Le principe consiste à garder les entités aussi transparentes que
possible et à ne pas les mélanger avec la logique métier. Les entités peuvent bien sûr
posséder des méthodes pour valider leurs attributs, mais elles ne sont pas conçues
pour représenter des tâches complexes, qui nécessitent souvent une interaction avec
d’autres composants (autres objets persistants, services externes, etc.).
La couche de persistance pas plus que l’interface utilisateur ne sont faites pour trai-
ter du code métier, surtout quand il y a plusieurs interfaces (web, Swing, terminaux
mobiles, etc.). Pour séparer la couche de persistance de la couche présentation, pour
implémenter la logique métier, pour ajouter la gestion des transactions et la sécurité,
les applications ont besoin d’une couche métier  : avec Java  EE, cette couche est
implémentée par les EJB (Enterprise Java Beans).
La décomposition en couches est importante pour la plupart des applications. En
suivant une approche descendante, les chapitres précédents sur JPA ont modélisé les
classes de domaine en définissant généralement des noms (Artist, CD, Book, Cus-
tomer, etc.). Au-dessus de cette couche, la couche métier modélise les actions (ou
verbes) de l’application (créer un livre, acheter un livre, afficher une commande,
livrer un livre…). Souvent, cette couche interagit avec des services web externes
(SOAP ou REST), envoie des messages asynchrones à d’autres systèmes (à l’aide de
JMS) ou poste des e-mails ; elle orchestre différents composants allant des bases de
données aux systèmes externes, sert de plaque tournante aux transactions et à la sécu-
rité et constitue un point d’entrée pour toutes sortes de clients comme les interfaces
web (servlets ou beans gérés par JSF), le traitement par lot ou les systèmes externes.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
200 Java EE 6 et GlassFish 3 

Ce chapitre est une introduction aux EJB et les trois chapitres suivants vous don-
neront toutes les informations nécessaires pour construire la couche métier d’une
application d’entreprise. Nous y expliquerons les différents types d’EJB et leurs
cycles de vie ; nous décrirons également la notion de programmation orientée aspect
(POA), ainsi que la gestion des transactions et de la sécurité.

Introduction aux EJB

Les EJB sont des composants côté serveur qui encapsulent la logique métier et la
prennent en charge ; ils s’occupent aussi de la sécurité. Les EJB savent également
traiter les messages, l’ordonnancement, l’accès distant, les services web (SOAP et
REST), l’injection de dépendances, le cycle de vie des composants, la programma-
tion orientée aspect avec intercepteurs, etc. En outre, ils s’intègrent parfaitement
avec les autres technologies de Java SE et Java EE – JDBC, JavaMail, JPA, JTA
(Java Transaction API), JMS (Java Messaging Service), JAAS (Java Authentica-
tion and Authorization Service), JNDI (Java Naming and Directory Interface) et
RMI (Remote Method Invocation). C’est la raison pour laquelle on les utilise pour
construire les couches métier (voir Figure 6.1) au-dessus de la couche de persistance
et comme point d’entrée pour les technologies de la couche présentation, comme
JSF (JavaServer Faces).

Figure 6.1
<<layer>>
Architecture en couches. Présentation

<<layer>>
Logique métier

<<layer>>
Persistance

<<layer>>
Base de données

Les EJB utilisent un modèle de programmation très puissant qui allie simplicité
d’utilisation et robustesse – il réduit la complexité tout en ajoutant la réutilisabilité et

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 201

l’adaptabilité aux applications essentielles pour l’entreprise. C’est sûrement actuel-


lement le modèle de développement Java côté serveur le plus simple et, pourtant,
tout ceci est facilement obtenu en annotant un objet Java ordinaire (un POJO) qui
sera déployé dans un conteneur. Un conteneur EJB est un environnement d’exécu-
tion qui fournit des services comme la gestion des transactions, le contrôle de la
concurrence, la gestion des pools et la sécurité, mais les serveurs d’applications lui
ont ajouté d’autres fonctionnalités, comme la mise en cluster, la répartition de la
charge et la reprise en cas de panne. Les développeurs EJB peuvent désormais se
concentrer sur l’implémentation de la logique métier et laisser au conteneur le soin
de s’occuper des détails techniques.
Avec la version 3.1, les EJB peuvent, plus que jamais, être écrits une bonne fois pour
toutes et être déployés sur n’importe quel conteneur respectant la spécification. Les
API standard, les noms JNDI portables, les composants légers et la configuration par
exception facilitent ce déploiement sur les implémentations open-source ou com-
merciales. La technologie sous-jacente ayant été créée il y a dix ans, les applications
EJB bénéficient d’une base de code stable et de haute qualité, utilisée depuis long-
temps par de nombreux environnements.

Types d’EJB

Les applications d’entreprise pouvant être complexes, la plate-forme Java EE définit


plusieurs types d’EJB. Les Chapitres 6 à 9 ne s’intéresseront qu’aux beans de ses-
sion et au service timer : les premiers encapsulent la logique métier de haut niveau
et forment donc la partie la plus importante de la technologie des EJB. Un bean de
session peut avoir les caractéristiques suivantes :
■■ Sans état. Le bean de session ne contient aucun état conversationnel entre les
méthodes et n’importe quel client peut utiliser n’importe quel instance.
■■ Avec état. Le bean de session contient l’état conversationnel qui doit être mémo-
risé entre les méthodes pour un utilisateur donné.
■■ Singleton. Un bean de session unique est partagé par les clients et autorise les
accès concurrents.
Le service timer est la réponse standard de Java EE au problème de l’ordonnancement
des tâches. Les applications d’entreprise qui dépendent de notifications temporelles
l’utilisent pour modéliser les processus métier de type workflow.
Les MDB (Message-Driven Beans) reçoivent des messages asynchrones à l’aide de
JMS. Bien qu’ils ne fassent pas partie de la spécification EJB, nous les traiterons

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
202 Java EE 6 et GlassFish 3 

au Chapitre  13 car ce modèle de composants sert essentiellement à intégrer des


systèmes avec MOM (Message-Oriented Middleware). Les MDB délèguent généra-
lement la logique métier aux beans de session.
Les EJB peuvent également être utilisés comme points terminaux d’un service web.
Les Chapitres 14 et 15 présenteront les services SOAP et REST, qui peuvent être
soit de simples POJO déployés dans un conteneur web, soit des beans de session
déployés dans un conteneur EJB.

INFO

Pour des raisons de compatibilité, la spécification EJB 3.1 mentionne encore les beans enti-
tés. Ce modèle de composants persistants a été élagué et est susceptible d’être supprimé de
Java EE 7. JPA étant la technologie qui a été retenue pour associer et interroger les bases de
données, nous ne présenterons pas les beans entités dans ce livre.

Anatomie d’un EJB

Les beans de session encapsulent la logique métier, sont transactionnels et repo-


sent sur un conteneur qui gère un pool, la programmation multithreads, la sécurité,
etc. Pour créer un composant aussi puissant, il suffit pourtant d’une seule classe
Java et d’une seule annotation. Au chapitre suivant, nous verrons toutefois que les
beans de session peuvent être plus complexes : ils peuvent utiliser différents types
d’interfaces, d’annotations, de configuration XML et d’appels d’interception. Le
Listing 6.1 montre la simplicité avec laquelle un conteneur peut savoir qu’une classe
est un bean de session et qu’elle fournit tous les services d’entreprise.

Listing 6.1 : Un EJB sans état simple


@Stateless
public class BookEJB {

@PersistenceContext(unitName = "chapter06PU")
private EntityManager em;
public Book findBookById(Long id) {
return em.find(Book.class, id);
}
public Book createBook(Book book) {
em.persist(book);
return book;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 203

Les versions précédentes de J2EE exigeaient des développeurs qu’ils créent plu-
sieurs artéfacts pour obtenir un bean de session : une interface locale ou distante (ou
les deux), une interface "home" locale ou distante (ou les deux) et un descripteur de
déploiement. Java EE 5 et EJB 3.0 ont considérablement simplifié ce modèle pour
ne plus exiger qu’une seule classe et une ou plusieurs interfaces métier. EJB  3.1
va encore plus loin puisqu’il permet à un POJO annoté d’être un bean de session.
Comme le montre le code du Listing 6.1, la classe n’implémente aucune interface et
n’utilise pas non plus de configuration XML : l’annotation @Stateless suffit à trans-
former une classe Java en composant transactionnel et sécurisé. Puis, en utilisant le
gestionnaire d’entités que nous avons présenté aux chapitres précédents, BookEJB
crée et récupère des livres de la base de données de façon simple mais efficace. Nous
verrons au chapitre suivant qu’il est également très simple de déclarer un bean à état
ou un bean singleton.
Cette simplicité s’applique aussi au code client. L’appel d’une méthode de BookEJB
ne nécessite qu’une seule annotation, @EJB, pour obtenir une référence à l’aide d’une
injection. Cette injection de dépendances permet à un conteneur (client, web ou
EJB) d’injecter automatiquement une référence vers un EJB. Dans le Listing 6.2,
par exemple, la classe Main obtient une référence à BookEJBRemote en annotant par @
EJB l’attribut statique et privé bookEJB. Si l’EJB est déployé dans un conteneur, Main
doit accéder à cet EJB à distance : il suffit d’ajouter une interface distante à l’EJB
pour qu’on puisse y accéder à distance.

Listing 6.2 : Classe client invoquant l’EJB sans état


public class Main {

@EJB
private static BookEJBRemote bookEJB;

public static void main(String[] args) {

Book book = new Book();


book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Scifi book created by Douglas Adams");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
bookEJB.createBook(book);
}

Le Listing 6.2 montre l’une des différences qui existent entre une classe Java pure
et un bean de session. Ici, la classe Main n’utilise pas le mot-clé new pour créer une
instance de BookEJB : elle doit d’abord obtenir une référence à l’EJB – par injection

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
204 Java EE 6 et GlassFish 3 

ou par une recherche JNDI – avant d’appeler l’une de ses méthodes. On utilise ce
mécanisme parce que l’EJB s’exécute dans un environnement géré – il doit être
déployé dans un conteneur (intégré ou non).
Comme la plupart des composants de Java EE 6, les EJB ont besoin des métadon-
nées (exprimées sous forme d’annotation ou de XML) pour informer le conteneur
des actions requises (démarcation des transactions) ou des services à injecter. Les
EJB peuvent être déployés avec le descripteur de déploiement facultatif ejb-jar.
xml, qui, s’il est présent, a priorité sur les annotations. Grâce à la configuration par
exception, une simple annotation suffit généralement à transformer un POJO en EJB
puisque le conteneur applique le comportement par défaut.

Conteneur d’EJB

Comme on l’a mentionné précédemment, un EJB est un composant côté serveur


qui doit s’exécuter dans un conteneur. Cet environnement d’exécution fournit les
fonctionnalités essentielles, communes à de nombreuses applications d’entreprise :
■■ Communication distante. Sans écrire de code complexe, un client EJB (un
autre EJB, une interface utilisateur, un processus non interactif, etc.) peut appeler
des méthodes à distance via des protocoles standard.
■■ Injection de dépendances. Le conteneur peut injecter plusieurs ressources dans
un EJB (destinations et fabriques JMS, sources de données, autres EJB, variables
d’environnement, etc.).
■■ Gestion de l’état. Le conteneur gère l’état des beans à état de façon transparente.
Vous pouvez ainsi gérer l’état d’un client particulier, comme si vous développiez
une application classique.
■■ Pooling. Le conteneur crée pour les beans sans état et les MDB un pool d’ins-
tances qui peut être partagé par plusieurs clients. Une fois qu’il a été invoqué, un
EJB n’est pas détruit mais retourne dans le pool pour être réutilisé.
■■ Cycle de vie. Le conteneur prend en charge le cycle de vie de chaque composant.
■■ Messages. Le conteneur permet aux MDB d’écouter les destinations et de
consommer les messages sans qu’il soit nécessaire de trop se plonger dans les
détails de JMS.
■■ Gestion des transactions. Avec la gestion déclarative des transactions, un
EJB peut utiliser des annotations pour informer le conteneur de la politique de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 205

transaction qu’il doit utiliser. C’est le conteneur qui prend en charge la validation
ou l’annulation des transactions.
■■ Sécurité. Les EJB peuvent préciser un contrôle d’accès au niveau de la classe ou
des méthodes afin d’imposer une authentification de l’utilisateur et l’utilisation
de rôles.
■■ Gestion de la concurrence. À part les singletons, tous les autres types d’EJB
sont thread-safe par nature. Vous pouvez donc développer des applications paral-
lèles sans vous soucier des problèmes liés aux threads.
■■ Intercepteurs transversaux. Les problèmes transversaux peuvent être placés
dans des intercepteurs qui seront automatiquement appelés par le conteneur.
■■ Appels de méthodes asynchrones. Avec EJB 3.1, il est désormais possible
d’avoir des appels asynchrones sans utiliser de messages.
Lorsque l’EJB est déployé, le conteneur s’occupe de toutes ces fonctionnalités, ce
qui permet au développeur de se concentrer sur la logique métier tout en bénéficiant
de ces services sans devoir ajouter le moindre code système.
Les EJB sont des objets gérés. Lorsqu’un client appelle un EJB (comme dans le Lis-
ting 6.2), il travaille non pas directement avec une instance de cet EJB mais avec un
proxy de cette instance. À chaque fois qu’un client invoque une méthode de l’EJB,
cet appel est en réalité pris en charge par le proxy. Tout ceci est, bien entendu, trans-
parent pour le client : de sa création à sa destruction, un EJB vit dans un conteneur.
Dans une application Java EE, le conteneur EJB interagira généralement avec
d’autres conteneurs : le conteneur de servlets (responsable de la gestion de l’exécu-
tion des servlets et des pages JSF), le conteneur client d’application (pour la gestion
des applications autonomes), le gestionnaire de messages (pour l’envoi, la mise en
attente et la réception des messages), le fournisseur de persistance, etc. Ces conte-
neurs s’exécutent tous dans un serveur d’applications (GlassFish, JBoss, Weblogic,
etc.) dont l’implémentation est spécifique mais qui fournit le plus souvent des fonc-
tionnalités de clustering, de montée en charge, de répartition de la charge, de reprise
en cas de panne, d’administration, de cache, etc.

Conteneur intégré

Dès le moment où ils sont créés, les EJB doivent s’exécuter dans un conteneur qui
s’exécute lui-même dans une JVM séparée. Pensez à GlassFish, JBoss, Weblogic, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
206 Java EE 6 et GlassFish 3 

et vous vous rappellerez que le serveur d’applications doit d’abord être lancé avant
que vous puissiez déployer et utiliser vos EJB. Pour un environnement en produc-
tion, où le serveur tourne en permanence, c’est tout à fait souhaitable ; par contre,
pour un environnement de développement où l’on a souvent besoin de déployer
pour déboguer, par exemple, cela prend trop de temps. Un autre problème avec les
serveurs qui s’exécutent dans un processus différent est que cela limite les pos-
sibilités de tests unitaires car ils ne peuvent s’exécuter simplement sans déployer
l’EJB sur un serveur. Pour résoudre ces problèmes, certaines implémentations de
serveurs d’applications étaient fournies avec des conteneurs intégrés, mais ceux-ci
leur étaient spécifiques. Désormais, EJB 3.1 contient la spécification d’un conteneur
intégré, ce qui assure la portabilité entre les différents serveurs.
Le principe d’un conteneur intégré est de pouvoir exécuter des applications EJB
dans un environnement Java SE afin de permettre aux clients de s’exécuter dans la
même JVM. Ceci permet notamment de faciliter les tests et l’utilisation des EJB
dans les applications classiques. L’API du conteneur intégré (définie dans javax.
ejb.embeddable) fournit le même environnement géré que le conteneur d’exécution
de Java EE et inclut les mêmes services : injection, accès à l’environnement d’un
composant, gestion des transactions, etc.
L’extrait de code suivant montre comment créer une instance d’un conteneur inté-
gré, obtenir un contexte JNDI, rechercher un EJB et appeler l’une de ses méthodes :

EJBContainer ec = EJBContainer.createEJBContainer();
Context ctx = ec.getContext();
BookEJB bookEJB = (BookEJB) ctx.lookup("java:global/BookEJB");
bookEJB.createBook(book);

Dans le prochain chapitre, nous verrons comment utiliser l’API de "bootstrap" pour
lancer le conteneur et exécuter les EJB.

Injection de dépendances et JNDI

Les EJB utilisent l’injection de dépendances pour accéder à différents types de res-
sources (autres EJB, destinations JMS, ressources d’environnement, etc.). Dans ce
modèle, le conteneur pousse les données dans le bean. Comme le montre le Lis-
ting 6.2, un client s’injecte une dépendance à un EJB à l’aide de l’annotation @EJB :
@EJB
private static BookEJB bookEJB;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 207

L’injection a lieu lors du déploiement. Si les données risquent de ne pas être utili-
sées, le bean peut éviter le coût de l’injection en effectuant à la place une recherche
JNDI. En ce cas, le code ne prend les données que s’il en a besoin au lieu d’accepter
des données qui lui sont transmises et dont il n’aura peut-être pas besoin.
JNDI est une API permettant d’accéder à différents types de services d’annuaires,
elle permet au client de lier et de rechercher des objets par nom. JNDI est définie
dans Java SE et est indépendante de l’implémentation sous-jacente, ce qui signifie
que les objets peuvent être recherchés dans un annuaire LDAP (Lightweight Direc-
tory Access Protocol) ou dans un DNS (Domain Name System) à l’aide d’une API
standard.
L’alternative au code précédent consiste donc à utiliser un contexte JNDI et à y
rechercher un EJB déployé portant le nom java:global/chapter06/BookEJB :
Context ctx = new InitialContext();
BookEJB bookEJB =
(BookEJB)ctx.lookup("java:global/chapter06/BookEJB");

JNDI existe depuis longtemps mais, bien que son API fût standardisée et portable
entre les serveurs d’applications, ce n’était pas le cas des noms JNDI, qui restaient
spécifiques aux plates-formes. Lorsqu’un EJB était déployé dans GlassFish ou
JBoss, son nom dans le service d’annuaire était différent et donc non portable : un
client devait rechercher un EJB avec un certain nom sous GlassFish et un autre sous
JBoss... EJB 3.1 a standardisé les noms JNDI afin qu’ils soient désormais portables.
Dans l’exemple précédent, le nom java:global/chapter06/BookEJB respecte cette
nouvelle convention de nommage :
java:global[/<nom-app>]/<nom-module>/<nom-bean>
„ [!<nom-interface-pleinement-qualifié>]

Le chapitre suivant montrera comment utiliser ce nom pour rechercher des EJB.

Méthodes de rappel et intercepteurs

Le cycle de vie de tous les types d’EJB (sans et avec état, singleton et MDB) est géré
par le conteneur. Un EJB peut ainsi avoir des méthodes annotées (@PostConstruct, @
PreDestroy, etc.) ressemblant aux méthodes de rappel utilisées par les entités et qui
seront automatiquement appelées par le conteneur au cours des différentes étapes
de son cycle de vie. Ces méthodes peuvent initialiser l’état du bean, rechercher des
ressources avec JNDI ou libérer les connexions aux bases de données.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
208 Java EE 6 et GlassFish 3 

Pour les problèmes transversaux, les développeurs peuvent utiliser des intercepteurs
qui reposent sur le modèle de la programmation par aspect, dans lequel l’appel d’une
méthode est automatiquement enrichi de fonctionnalités supplémentaires.
Les cycles de vie des EJB, les méthodes de rappel et les intercepteurs seront étudiés
au Chapitre 8 (le Chapitre 5 a présenté le cycle de vie des entités).

Assemblage
Comme la plupart des composants Java EE (servlets, pages JSF, services web, etc.),
les EJB doivent être assemblés avant d’être déployés dans un conteneur d’exécution.
Dans la même archive, on trouve généralement la classe bean métier, ses interfaces,
intercepteurs, les éventuelles superclasses ou superinterfaces, les exceptions, les
classes utilitaires et, éventuellement, un descripteur de déploiement (ejb-jar.xml).
Lorsque tous ces artéfacts sont assemblés dans un fichier jar, on peut les déployer
directement dans un conteneur. Une autre possibilité consiste à intégrer le fichier jar
dans un fichier ear (entreprise archive) et à déployer ce dernier.
Un fichier ear sert à assembler un ou plusieurs modules (des EJB ou des applications
web) en une archive unique afin que leur déploiement sur un serveur d’applications
soit simultané et cohérent. Comme le montre la Figure 6.2, pour déployer une appli-
cation web on peut assembler les EJB et les entités dans des fichiers jar séparés, les
servlets dans un fichier war et tout regrouper dans un fichier ear. Il suffit ensuite de
déployer ce fichier sur le serveur d’applications pour pouvoir manipuler les entités à
partir de la servlet en utilisant les EJB.

Figure 6.2 <<artifact>>
BookApplication.ear
Assemblage des EJB.
<<artifact>>
BookEJB.jar <<artifact>>
session/BookEJB.Class BookApplication.war
META-INF/ejb-jar.xml
session/BookEJB.class
<<artifact>> entity/Book.class
BookEntity.jar servlet.BookServlet
entity/Book.Class META INF/ejb jar.xml
META-INF/persitence.xml META INF/persistence.xml
WEB INF/web.xml
<<artifact>>
BookServlet.war
servlet/BookServlet.Class
WEB-INF/web.xml

Depuis EJB 3.1, les EJB peuvent également être assemblés directement dans un
module web (un fichier war). À droite de la Figure  6.2, la servlet, l’EJB et l’en-
tité sont tous assemblés dans le même fichier war, avec tous les descripteurs de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 209

déploiement. Vous remarquerez que le descripteur de déploiement est stocké dans


META-INF/ejb-jar.xml dans le module EJB et dans WEB-INF/web-jar.xml dans le
module web.

Tour d’horizon de la spécification EJB

La spécification EJB 1.0 remonte à 1998 et EJB 3.1 est apparue en 2009 avec Java
EE 6. Au cours de ces dix années, la spécification a beaucoup évolué tout en conser-
vant ses bases solides. Des composants lourds aux POJO annotés en passant par les
beans entités et par JPA, les EJB se sont réinventés afin de mieux correspondre aux
besoins des développeurs et des architectures modernes.
Plus que jamais, la spécification EJB 3.1 permet d’éviter la dépendance vis-à-vis des
éditeurs en fournissant des fonctionnalités qui, auparavant, n’étaient pas standard
(les noms JNDI ou les conteneurs intégrés, par exemple). Elle est donc bien plus
portable que par le passé.

Historique

Peu après la création du langage Java, l’industrie a ressenti le besoin de disposer


d’une technologie permettant de satisfaire les besoins des applications à grande
échelle et qui intégrerait RMI et JTA. L’idée d’un framework de composants métier
distribué et transactionnel fit donc son chemin et, en réponse, IBM commença à
développer ce qui allait ensuite devenir EJB.
EJB 1.0 reconnaissait les beans de session avec et sans état et disposait d’un support
optionnel des beans entités. Le modèle de programmation utilisait des interfaces
"home" et distantes en plus du bean session lui-même ; les EJB étaient accessibles
via une interface qui offrait un accès distant avec des paramètres passés par valeur.
EJB 1.1 ajouta le support des beans entités et introduisit les descripteurs de déploie-
ment XML pour stocker les métadonnées (qui étaient ensuite sérialisées en binaire
dans un fichier). Cette version gérait mieux l’assemblage et le déploiement des
applications grâce à l’introduction des rôles.
En 2001, EJB 2.0 fut la première version à être standardisée par le JCP (sous le nom
de JSR  19). Elle résolvait le problème du surcoût du passage des paramètres par
valeur en introduisant les interfaces locales. Les clients s’exécutant dans le conte-
neur accédaient aux EJB par leur interface locale (en utilisant des paramètres passés

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
210 Java EE 6 et GlassFish 3 

par référence) et ceux qui s’exécutaient dans un autre conteneur utilisaient l’inter-
face distante. Cette version a également introduit les MDB, et les beans entités ont
reçu le support des relations et d’un langage de requêtes (EJB QL).
Deux ans plus tard, EJB 2.1 (JSR 153) a ajouté le support des services web, per-
mettant ainsi aux beans de session d’être invoqués par SOAP/HTTP. Un service
timer fut également créé pour pouvoir appeler les EJB à des instants précis ou à des
intervalles donnés.
Trois ans se sont écoulés entre EJB 2.1 et EJB 3.0, ce qui a permis au groupe d’ex-
perts de remodéliser entièrement la conception. En 2006, la spécification EJB 3.0
(JSR 220) amorça une rupture avec les versions précédentes en s’attachant à la sim-
plicité d’utilisation grâce à des EJB ressemblant plus à des POJO. Les beans entités
furent remplacés par une toute nouvelle spécification (JPA) et les beans de session
n’eurent plus besoin d’interfaces "home" ou spécifiques. L’injection des dépendances,
les intercepteurs et les méthodes de rappel du cycle de vie firent leur apparition.
En 2009, la spécification EJB 3.1 (JSR 318) fut intégrée à Java EE 6 ; elle poursuit
dans la voie de la version précédente en simplifiant encore le modèle de program-
mation et en lui ajoutant de nouvelles fonctionnalités.

Nouveautés d’EJB 3.1

La spécification EJB 3.1 (JSR 318) a apporté plusieurs modifications  : JPA ne


fait désormais plus partie de la spécification EJB et évolue dans une JSR distincte
(JSR 317). La spécification est maintenant organisée en deux documents différents :
■■ "EJB Core Contracts and Requirements" est le document principal qui spécifie
les EJB.
■■ "Interceptor Requirements" est le document qui spécifie les intercepteurs.
Il faut garder à l’esprit que la spécification doit supporter le modèle de compo-
sant EJB 2.x, ce qui signifie que ses 600 pages doivent tenir compte des interfaces
"home", des beans entités, d’EJB QL, etc. Pour simplifier l’adoption future de la
spécification, le groupe d’experts Java EE 6 a rassemblé une liste de fonctionnalités
éventuellement amenées à disparaître : aucune n’a été supprimée d’EJB 3.1, mais la
prochaine version en retiendra et en supprimera certaines :
■■ beans entités 2.x ;
■■ vue cliente d’un bean entité 2.x ;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 211

■■ EJB QL (langage de requête pour la persistance gérée par un conteneur) ;


■■ services web JAX-RPC ;
■■ vue cliente d’un service web JAX-RPC.
La spécification EJB 3.1 ajoute les fonctionnalités et les simplifications suivantes :
■■ Vue sans interface. On peut accéder aux beans de session avec une vue locale
sans passer par une interface métier locale.
■■ Déploiement war. Il est désormais possible d’assembler et de déployer direc-
tement les composants EJB dans un fichier war.
■■ Conteneur intégré. Une nouvelle API embeddable permet d’exécuter les com-
posants EJB dans un environnement Java SE (pour les tests unitaires, les trai-
tements non interactifs, etc.).
■■ Singleton. Ce nouveau type de composant facilite l’accès à l’état partagé.
■■ Service timer plus élaboré. Cette fonctionnalité permet de créer automatiquement
des expressions temporelles.
■■ Asynchronisme. Les appels asynchrones sont désormais possibles sans MDB.
■■ EJB Lite. Définit un sous-ensemble de fonctionnalités utilisables dans les profils
Java EE (le profil web, par exemple).
■■ Noms JNDI portables. La syntaxe de recherche des composants EJB est
désormais standard.

EJB Lite

Les Enterprise Java Beans sont le modèle de composant prédominant de Java EE 6
car c’est la méthode la plus simple pour effectuer des traitements métiers transac-
tionnels et sécurisés. Cependant, EJB 3.1 continue de définir les beans entités, les
interfaces "home", EJB QL, etc., ce qui signifie qu’un nouvel éditeur qui implémen-
terait la spécification EJB 3.1 devrait également implémenter les beans entités. Les
développeurs débutant avec les EJB seraient donc submergés par de nombreuses
technologies dont ils n’ont finalement pas besoin.
Pour toutes ces raisons, la spécification définit EJB Lite, un sous-ensemble minimal de
l’API EJB. Ce sous-ensemble comprend un choix réduit mais efficace des fonctionna-
lités des EJB adaptées à l’écriture d’une logique métier portable, transactionnelle et

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
212 Java EE 6 et GlassFish 3 

sécurisée. Toute application EJB Lite peut être déployée sur n’importe quel produit
JAVA EE implémentant EJB 3.1. Le Tableau 6.1 énumère ses composantes.

Tableau 6.1 : Comparaison entre EJB Lite et EJB complète

Fonctionnalité EJB Lite EJB 3.1 complète


Beans de session beans (avec et sans état, Oui Oui
singleton)
MDB Non Oui
Beans entités 1.x/2.x Non Oui (élagable)
Vue sans interface Oui Oui
Interface locale Oui Oui
Interface distante Non Oui
Interfaces 2.x Non Oui (élagable)
Services web JAX-WS Non Oui
Services web JAX-RS Non Oui
Services web JAX-RPC Non Oui (élagable)
Timer service Non Oui
Asynchronous calls Non Oui
Interceptors Oui Oui
Interopérabilité RMI/IIOP Non Oui
Support des transactions Oui Oui
Sécurité Oui Oui
API Embeddable Oui Oui

Implémentation de référence
GlassFish est un projet de serveur d’applications open-source conduit par Sun
Microsystems pour la plate-forme Java EE. Lancé en 2005, il est devenu l’implé-
mentation de référence de Java EE 5 en 2006. Aujourd’hui, GlassFish v3 est l’im-
plémentation de référence d’EJB 3.1. Ce produit est construit de façon modulaire
(il repose sur le runtime OSGi Felix d’Apache), ce qui lui permet de démarrer très
rapidement, et il utilise différents conteneurs d’’applications (Java EE 6, bien sûr,
mais également Ruby, PHP, etc.).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 213

Dans ce livre, nous utiliserons GlassFish comme serveur d’applications pour


déployer et exécuter les EJB, les pages JSF, les services web SOAP et REST et les
MDB JMS.

Récapitulatif

Dans la section "Récapitulatif" du Chapitre 2, nous avons vu le développement com-


plet d’une entité Book (présentée dans le Listing 2.3) qui était associée à une base de
données Derby. Puis le Listing 2.4 a présenté une classe Main utilisant le gestionnaire
d’entités pour rendre un livre persistant et récupérer tous les livres de la base (en
utilisant des démarcations explicites de transactions tx.begin() et tx.commit()).
Nous allons ici reprendre ce cas d’utilisation, mais en remplaçant la classe Main du
Chapitre 2 par un bean de session sans état (BookEJB).
Par nature, les EJB sont transactionnels : BookEJB prendra donc en charge les opé-
rations CRUD (Create, Read, Update, Delete) de l’entité Book. BookEJB et Book
seront ensuite assemblés et déployés dans GlassFish. L’EJB a besoin d’une inter-
face distante car une application cliente externe (la classe Main) appellera à dis-
tance les méthodes de l’EJB (voir Figure 6.3) en se servant d’un conteneur client
d’application.

Figure 6.3 Main <<Interface>>


+main(args : String []) : void BookEJBRemote
Récapitulatif.
+findBooks() : List<Book>
+findBookByld(id : Long) : Book
+createBook(book : Book) : Book
+deleteBook(book : Book) : void
+updateBook(book : Book) : Book

<<stateless ejb>>
BookEJB <<entity>>
em : EntityManager Book

+findBooks() : List<Book> id : Long


+findBookByld(id : Long) : Book title : String jdbc/chapter06DS
chapter06DB
+createBook(book : Book) : Book price : Float
+deleteBook(book : Book) : void description : String
+updateBook(book : Book) : Book isbn : String
nbOfPage : Integer
illustrations : Boolean

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
214 Java EE 6 et GlassFish 3 

Pour utiliser les transactions, le bean de session sans état doit accéder à la base en
passant par une source de données (jdbc/chapter06DS) qui devra être créée dans
GlassFish et être liée à la base chapter06DB.
La structure de répertoire du projet respecte les conventions de Maven ; les classes
et les fichiers seront donc placés dans les répertoires suivants :
■■ src/main/java, pour l’entité Book, BookEJB, l’interface BookEJBRemote et la
classe Main ;
■■ src/main/resources :, pour le fichier persistence.xml, qui contient l’unité de
persistance pour le SGBDR Derby ;
■■ src/test/java, pour la classe des tests unitaires BookTest ;
■■ src/test/resources, pour le fichier persistence.xml, utilisé pour la base de
données intégrée Derby servant aux cas de tests ;
■■ pom.xml, le fichier Maven décrivant le projet et ses dépendances vis-à-vis d’autres
modules et composants externes.

L’entité Book

Le Listing  6.3 décrivant la même entité Book que celle du Chapitre  2 (voir Lis-
ting 2.3), nous n’y reviendrons pas. Notez toutefois que son fichier doit se trouver
dans le répertoire src/main/java.

Listing 6.3 : Entité Book avec une requête nommée


@Entity
@NamedQuery(name = "findAllBooks", query = "SELECT b FROM Book b") public class
Book {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 215

Le bean de session sans état BookEJB

BookEJB est un bean de session sans état qui sert de façade et gère les opérations
CRUD de l’entité Book. Le Listing 6.4 montre que cette classe doit être annotée par @
javax.ejb.Stateless et qu’elle implémente l’interface BookEJBRemote décrite dans
le Listing 6.5. Par injection de dépendances, l’EJB obtient une référence à un ges-
tionnaire d’entités qui est ensuite employé pour chacune des méthodes suivantes :
■■ findBooks utilise la requête nommée findAllBooks définie dans l’entité Book pour
récupérer toutes les instances de Book dans la base de données.
■■ findBookById invoque EntityManager.find() pour retrouver un livre dans la base
à partir de son identifiant.
■■ createBook rend persistante l’instance de Book qui lui est passée en paramètre.
■■ updateBook utilise la méthode merge() pour attacher au gestionnaire d’entités
l’objet Book détaché qui lui est passé en paramètre. Cet objet est alors synchro-
nisé avec la base de données.
■■ deleteBook est une méthode qui réattache l’objet qui lui est passé en paramètre
au gestionnaire d’entités, puis le supprime.

Listing 6.4 : Bean de session sans état servant de façade aux opérations CRUD
@Stateless
public class BookEJB implements BookEJBRemote {

@PersistenceContext(unitName = "chapter06PU")
private EntityManager em;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

public Book findBookById(Long id) {


return em.find(Book.class, id);
}

public Book createBook(Book book) {


em.persist(book);
return book;
}

public void deleteBook(Book book) {


em.remove(em.merge(book));

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
216 Java EE 6 et GlassFish 3 

public Book updateBook(Book book) {


return em.merge(book);
}
}

Les principales différences entre la classe Main du Chapitre 2 (voir Listing 2.4) et


celle du Listing 6.4 est qu’une instance d’EntityManager est directement injectée
dans le bean de session : on n’utilise plus une EntityManagerFactory pour créer
le gestionnaire. Le conteneur EJB gérant le cycle de vie de l’EntityManager, il en
injecte une instance puis la ferme lorsque l’EJB est supprimé. En outre, les appels
JPA ne sont plus encadrés par tx.begin() et tx.commit() car les méthodes des
beans de session sont implicitement transactionnelles. Ce comportement par défaut
sera décrit au Chapitre 9.
Le BookEJB doit implémenter une interface distante puisqu’il est invoqué à dis-
tance par la classe Main. Comme le montre le Listing 6.5, la seule différence entre
une interface Java normale et une interface distante est la présence de l’annotation
@Remote.

Listing 6.5 : Interface distante


@Remote
public interface BookEJBRemote {

public List<Book> findBooks();


public Book findBookById(Long id);
public Book createBook(Book book);
public void deleteBook(Book book);
public Book updateBook(Book book);
}

Unité de persistance pour le BookEJB

Au Chapitre 2, les transactions étaient gérées par l’application (transaction-type


="RESOURCE_LOCAL") et l’unité de persistance (voir Listing 2.5) devait donc définir
le pilote et l’URL JDBC, ainsi que l’utilisateur et son mot de passe afin d’établir
une connexion à la base de données Derby. Dans un environnement géré par un
conteneur comme celui des EJB, les transactions sont en revanche gérées par le
conteneur, non par l’application ; c’est la raison pour laquelle le type de transaction
de l’unité de persistance doit valoir JTA (voir Listing 6.6).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 217

Listing 6.6 : Unité de persistance utilisant la source de données chapter06DS


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="chapter06PU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<jta-data-source>jdbc/chapter06DS</jta-data-source>
<class>com.apress.javaee6.chapter06.Book</class>
<properties>
<property name="eclipselink.ddl-generation"
value="drop-and-create-tables"/>
<property name="eclipselink.logging.level" value="INFO"/>
</properties>
</persistence-unit>
</persistence>

Dans le Listing 6.4, on injecte dans le BookEJB une référence à un EntityManager


associé à l’unité de persistance chapter06PU. Celle-ci (définie dans le Listing 6.6)
doit définir la source de données à laquelle se connecter (jdbc/chapter06DS) sans
préciser d’autres informations d’accès (URL, pilote JDBC, etc.) car elles sont
contenues dans la source de données qui sera créée plus tard dans GlassFish.

La classe Main

La classe Main (voir Listing 6.7) déclare une instance de l’interface BookEJBRemote


et la décore avec l’annotation @EJB pour qu’une référence puisse être injectée – n’ou-
bliez pas que cette classe Main est exécutée dans le conteneur client d’application
et que l’injection est donc possible. La méthode main() commence par créer une
nouvelle instance de Book, initialise ses attributs et utilise la méthode createBook()
de l’EJB pour la rendre persistante. Puis elle modifie le titre, met à jour le livre dans
la base et le supprime. Ce code n’ayant pas de contexte de persistance, l’entité Book
est un objet détaché manipulé comme une classe Java normale, sans intervention
de JPA. C’est l’EJB qui détient le contexte de persistance et utilise le gestionnaire
d’entités pour accéder à la base de données.

Listing 6.7 : Classe Main utilisant le BookEJB


public class Main {

@EJB
private static BookEJBRemote bookEJB;

public static void main(String[] args) {


Book book = new Book();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
218 Java EE 6 et GlassFish 3 

book.setTitle("The Hitchhiker’s Guide to the Galaxy");


book.setPrice(12.5F);
book.setDescription("Scifi book created by Douglas Adams");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354); book.setIllustrations(false);
bookEJB.createBook(book);
book.setTitle("H2G2");
bookEJB.updateBook(book);
bookEJB.deleteBook(book);
}

Compilation et assemblage avec Maven

Nous pouvons maintenant utiliser Maven pour compiler l’entité Book, le BookEJB,
l’interface BookEJBRemote et la classe Main, puis assembler le résultat dans un fichier
jar avec l’unité de persistance. Maven utilise un fichier pom.xml (voir Listing 6.8)
pour décrire le projet et les dépendances externes. Ici, on a besoin de l’API JPA
(javax.persistence) et de l’API EJB (javax.ejb). Les classes seront compilées
et assemblées (<packaging>jar</packaging>) dans un fichier jar nommé chap-
ter06- 1.0.jar. Comme le montre le Listing 6.8, l’élément maven-compiler-plu-
gin indique à Maven que l’on utilise Java SE 6.

Listing 6.8 : Fichier pom.xml utilisé par Maven pour construire l’application


<?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/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId> com.apress.javaee6</groupId>
<artifactId>chapter06</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>chapter06</name>

<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
</dependency>
<dependency>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 219

<groupId>org.glassfish.embedded</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>
com.apress.javaee6.chapter06.Main
</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Grâce à l’extension maven-jar-plugin, le fichier jar produit contiendra un fichier


META-INF\MANIFEST.MF permettant d’ajouter des métadonnées à la structure même
du jar. Pour que le jar soit exécutable, on ajoute la classe Main à l’élément Main-Class.
Vous remarquerez que ce code contient la dépendance glassfish-embedded-all, qui
est utilisée par la classe de test (<scope>test</scope>) pour invoquer le conteneur
intégré et lancer l’EJB.
Pour compiler et assembler les classes, tapez la commande suivante dans une fenêtre
de commandes :
mvn package

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
220 Java EE 6 et GlassFish 3 

Le message BUILD SUCCESSFUL devrait s’afficher pour vous informer du succès de


l’opération. Si vous vérifiez le contenu du répertoire target, vous constaterez que
Maven a créé le fichier chapter06-1.0.jar.

Déploiement sur GlassFish

Maintenant que le bean de session BookEJB a été assemblé dans une archive jar, nous
pouvons le déployer sur le serveur d’applications GlassFish (après nous être assu-
rés que GlassFish et Derby s’exécutent). La source de données jdbc/chapter06DS
nécessaire à l’unité de persistance doit être créée à l’aide de la console d’administra-
tion de GlassFish ou à partir de la ligne de commandes, l’utilisation de cette dernière
étant la plus rapide et la plus simple à reproduire.
Avant de créer une source de données, nous avons besoin d’un pool de connexions.
GlassFish définit un ensemble de pools prêts à l’emploi, mais nous pouvons créer le
nôtre à l’aide de la commande suivante :
asadmin create-jdbc-connection-pool
„ --datasourceclassname=org.apache.derby.jdbc.ClientDataSource
„ --restype=javax.sql.DataSource
„ --property portNumber=1527:password=APP:user=APP:
„ serverName=localhost:databaseName=chapter06DB:
„ connectionAttributes=;create\=true Chapter06Pool

Cette commande crée le pool Chapter06Pool en utilisant une source de données


Derby et un ensemble de propriétés définissant la connexion à la base : son nom
(chapter06DB), le serveur (localhost), le port (1527), un utilisateur (APP) et un mot
de passe (APP). Si l’on teste maintenant cette source de données, Derby créera auto-
matiquement la base (car l’on a précisé connectionAttributes=;create\ =true).
La commande suivante permet de tester la source de données :
asadmin ping-connection-pool Chapter06Pool

Après l’exécution de cette commande, le répertoire chapter06DB devrait apparaître


sur le disque dur à l’endroit où Derby stocke les données. La base et le pool de
connexions étant créés, nous devons maintenant déclarer la source de données jdbc/
chapter06DS et la lier à ce pool :

asadmin create-jdbc-resource --connectionpoolid Chapter06Pool


„ jdbc/chapter06DS
La commande suivante énumère toutes les sources de données hébergées par
GlassFish :
asadmin list-jdbc-resources

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 221

L’utilitaire asadmin permet de déployer l’application sur GlassFish. Après exécu-


tion, la commande suivante affichera un message nous informant du résultat du
déploiement :
asadmin deploy --force=true target\chapter06-1.0.jar

Maintenant que l’EJB est déployé sur GlassFish avec l’entité et l’unité de persis-
tance, que Derby s’exécute et que la source de données a été créée, il est temps de
lancer la classe Main.

Exécution de la classe Main avec Derby

La classe Main (voir Listing 6.7) est une application autonome qui s’exécute à l’ex-
térieur du conteneur GlassFish, mais elle utilise l’annotation @EJB, qui a besoin d’un
conteneur pour injecter une référence à l’interface BookEJBRemote. La classe Main
doit s’exécuter dans un conteneur client d’application (ACC) ; nous aurions pu uti-
liser une recherche JNDI au lieu d’un ACC, mais ce dernier peut comprendre un
fichier jar pour lui donner accès aux ressources du serveur d’applications. Pour exé-
cuter l’ACC, il suffit d’utiliser le programme appclient fourni avec GlassFish en lui
passant le fichier jar en paramètre :
appclient -client chapter06-1.0.jar

N’oubliez pas que le fichier chapter06-1.0.jar est exécutable puisque nous avons
ajouté un élément Main-Class au fichier MANIFEST.MF. Avec la commande précé-
dente, l’ACC exécute la classe Main et injecte une référence à l’interface BookEJB­
Remote, qui, à son tour, crée, modifie et supprime l’entité Book.

La classe BookEJBTest

Pour les équipes de développement modernes, la classe Main ne suffit pas – il faut
appliquer des tests unitaires aux classes. Avec la version précédente des EJB, tester
unitairement BookEJB n’était pas chose facile car il fallait utiliser des fonctionnalités
spécifiques de certains serveurs d’applications ou bricoler le code. Désormais, grâce
au nouveau conteneur intégré, un EJB devient une classe testable comme une autre
car elle peut s’exécuter dans un environnement Java SE. La seule exigence consiste
à ajouter un fichier jar spécifique au classpath, comme on l’a fait dans le fichier pom.
xml du Listing 6.8 avec la dépendance glassfish-embedded-all.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
222 Java EE 6 et GlassFish 3 

Au Chapitre 2, nous avons déjà présenté tous les artéfacts requis par les tests uni-
taires avec une base de données intégrée. Pour tester unitairement l’EJB, nous
avons besoin de la base Derby intégrée, d’une unité de persistance différente et du
conteneur d’EJB intégré. Il suffit ensuite d’écrire une classe de test JUnit (voir Lis-
ting  6.9) pour initialiser l’EJBContainer (EJBContainer.createEJBContainer()),
lancer quelques tests (createBook()) et fermer le conteneur (ec.close()).

Listing 6.9 : Classe JUnit pour tester l’EJB avec le conteneur intégré


public class BookEJBTest {

private static EJBContainer ec;


private static Context ctx;

@BeforeClass
public static void initContainer() throws Exception {
ec = EJBContainer.createEJBContainer();
ctx = ec.getContext();
}

@AfterClass
public static void closeContainer() throws Exception {
ec.close();
}

@Test
public void createBook() throws Exception {
// Création d’une instance de Book
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction comedy book");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

// Recherche de l’EJB
BookEJBRemote bookEJB = (BookEJBRemote)
„ ctx.lookup("java:global/chapter06/BookEJBRemote");

// Rend le livre persistant dans la base


book = bookEJB.createBook(book);
assertNotNull("ID should not be null", book.getId());

// Récupère tous les livres de la base


List<Book> books = bookEJB.findBooks();
assertNotNull(books);
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 223

La méthode de test createBook() crée une instance de Book, recherche l’interface


distante en utilisant JNDI pour rendre le livre persistant et récupère la liste de tous
les livres stockés dans la base. Assurez-vous que la classe BookEJBTest soit dans le
répertoire src/test/java de Maven, puis faites la commande suivante :
mvn test

BookEJBTest s’exécute et Maven devrait vous informer que le test s’est bien passé :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.691 sec
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ---------------------------------------------------------------- [INFO]
BUILD SUCCESSFUL [INFO] ------------------------------------------------------
---------- [INFO] Total time: 26 seconds
[INFO] Finished [INFO] Final Memory: 4M/14M [INFO] ---------------------------
-------------------------------------

Résumé

Ce chapitre a présenté EJB 3.1. À partir des versions 2.x, la spécification EJB a
évolué pour passer d’un modèle lourd – dans lequel il fallait assembler les inter-
faces home et distante/locale avec une grande quantité de fichiers XML –  à une
simple classe Java sans interface et avec une seule annotation. La fonctionnalité
sous-jacente est pourtant toujours la même : fournir une logique métier transaction-
nelle et sécurisée.
EJB 3.1 permet de simplifier encore plus le modèle de programmation (vue sans
interface, déploiement war), l’enrichit (conteneur intégré, singletons, service timer,
appels asynchrones) et améliore sa portabilité entre les serveurs d’applications
(noms JNDI standardisés). La simplification la plus importante est probablement
la création d’EJB Lite, un sous-ensemble de l’API EJB qui offre une version d’EJB
plus simple mais néanmoins efficace pouvant être utilisée dans le profil web de
Java EE. Le conteneur EJB intégré, de son côté, facilite la mise en place et la porta-
bilité des tests unitaires. EJB 3.1 est donc le digne successeur d’EJB 3.0.
Le Chapitre 7 s’intéressera aux beans de session avec et sans état, aux singletons et
au service timer. Le Chapitre 8 présentera les méthodes de rappel et les intercepteurs.
Le Chapitre 9 sera consacré aux transactions et à la sécurité.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
7
Beans de session et service timer

Les Chapitres 2 à 5 se sont intéressés aux objets persistants qui utilisent les entités
JPA. Ces entités encapsulent les données, l’association avec le modèle relationnel et,
parfois, la logique de validation. Nous allons maintenant présenter le développement
d’une couche métier qui gère ces objets persistants avec les beans de session qui
prennent en charge les tâches complexes nécessitant des interactions avec d’autres
composants (entités, services web, messages, etc.). Cette séparation logique entre
entités et beans de session respecte le paradigme de "séparation des problèmes"
selon lequel une application est divisée en plusieurs composants dont les opérations
se recouvrent le moins possible.
Dans ce chapitre, nous présenterons les trois types de beans de session : sans état,
avec état et singleton. Les premiers sont les plus adaptables des trois car ils ne
mémorisent aucune information et effectuent toute la logique métier dans un seul
appel de méthode. Les seconds gèrent un état conversationnel avec un seul client.
Les beans de session singletons (une seule instance par application) ont été ajoutés
dans la spécification EJB 3.1.
La dernière section du chapitre sera consacrée à l’utilisation du service timer pour
planifier les tâches.
Les beans pilotés par messages (MDB), qui font également partie de la spécification
EJB, seront présentés au Chapitre 13 avec JMS (Java Message Service). Comme
nous le verrons aux Chapitres 14 et 15, un bean de session sans état peut être trans-
formé en service web SOAP ou REST. Ou, plus exactement, ces services web peu-
vent profiter de certaines fonctionnalités d’EJB comme les transactions, la sécurité,
les intercepteurs, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
226 Java EE 6 et GlassFish 3 

Beans de session

Les beans de session sont parfaits pour implémenter la logique métier, les processus
et le workflow mais, avant de les utiliser, vous devez choisir le type qui convient :
■■ Sans état. Ne mémorisent aucun état conversationnel pour l’application. Ils
servent à gérer les tâches qui peuvent s’effectuer à l’aide d’un seul appel de
méthode.
■■ Avec état. Mémorisent l’état et sont associés à un client précis. Ils servent à gérer
les tâches qui demandent plusieurs étapes.
■■ Singletons. Implémentent le patron de conception Singleton. Le conteneur
s’assurera qu’il n’en existe qu’une seule instance pour toute l’application.
Bien que ces trois types de beans de session aient des fonctionnalités spécifiques,
ils en ont aussi beaucoup en commun et, surtout, ils utilisent tous le même modèle
de programmation. Comme nous le verrons plus tard, un bean de session peut avoir
une interface locale ou distante, ou aucune interface. Les beans de session sont des
composants gérés par un conteneur et doivent donc être assemblés dans une archive
(un fichier jar, war ou ear) et déployés dans le conteneur. Ce dernier est responsable
de la gestion de leur cycle de vie (qui sera étudié au chapitre suivant), des transac-
tions, des intercepteurs et de bien d’autres choses encore. La Figure 7.1 montre un
schéma très synthétique des beans de session et du service timer dans un conteneur
EJB.

Figure 7.1
<<executionEnvironment>>
Beans de session et service Conteneur EJB

timer dans un conteneur EJB.


<<component>>
Bean de session

<<component>>
Service timer

Beans sans état

Les beans sans état sont les beans de session les plus connus dans les applications
Java EE. Ils sont simples, puissants, efficaces et répondent aux besoins fréquents des
tâches métiers. "Sans état" signifie simplement qu’une tâche doit se réaliser par un
seul appel de méthode.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 227

À titre d’exemple, revenons aux racines de la programmation orientée objet, où un


objet encapsule son état et son comportement. Pour rendre un livre persistant dans
une base de données en utilisant un seul objet, vous devez réaliser les opérations
suivantes : créer une instance book de Book, initialiser ses attributs et appeler une
méthode afin qu’il se stocke lui-même dans la base de données (book.persistTo­
Database()). Dans le code suivant, vous pouvez constater que l’objet book est appelé
plusieurs fois entre la première et la dernière ligne et qu’il mémorise son état :
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);
book.persistToDatabase();

Les beans sans état sont la solution idéale lorsque l’on doit implémenter une tâche
qui peut se réaliser en un seul appel de méthode. Si l’on reprend le code précédent et
que l’on y ajoute un composant sans état, il faut donc créer un objet Book, initialiser
ses attributs, puis utiliser un composant sans état pour invoquer une méthode qui
stockera le livre en un seul appel. L’état est donc géré par Book, non par le composant
sans état.
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);
statelessComponent.persistToDatabase(book);

Les beans de session sans état sont également les beans les plus efficaces car ils peu-
vent être placés dans un pool pour y être partagés par plusieurs clients – le conteneur
conserve en mémoire un certain nombre d’instances (un pool) de chaque EJB sans
état et les partage entre les clients. Ces beans ne mémorisant pas l’état des clients,
toutes leurs instances sont donc équivalentes. Lorsqu’un client appelle une méthode
d’un bean sans état, le conteneur choisit une instance du pool et l’affecte au client ;
lorsque ce dernier en a fini, l’instance retourne dans le pool pour y être réutilisée.
Comme le montre la Figure  7.2, il suffit donc d’un petit nombre de beans pour
gérer plusieurs clients (le conteneur ne garantit pas qu’il fournira toujours la même
­instance du bean pour un client donné).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
228 Java EE 6 et GlassFish 3 

Figure 7.2
<<executionEnvironment>>
Conteneur EJB
Clients accédant à des
beans sans état placés <<component>> <<executionEnvironment>>
Client 1 Pool
dans un pool.
<<component>> <<component>>
Client 2 Instance 1
...
<<component>>
<<component>>
Instance 2
Client n

Le Listing 7.1 montre qu’un EJB sans état ressemble à une simple classe Java avec
uniquement une annotation @Stateless. Il peut utiliser n’importe quel service du
conteneur dans lequel il se trouve, notamment l’injection de dépendances. L’anno-
tation @PersistenceContext sert à injecter une référence de gestionnaire d’entités.
Le contexte de persistance des beans de session sans état étant transactionnel, toutes
les méthodes appelées sur cet EJB (createBook(), createCD(), etc.) le seront égale-
ment (nous y reviendrons plus en détail au Chapitre 9). Vous remarquerez que toutes
les méthodes reçoivent les paramètres nécessaires au traitement de la logique métier
en un seul appel : createBook(), par exemple, prend un objet Book en paramètre et
le rend persistant sans avoir besoin d’aucune autre information.

Listing 7.1 : Bean de session sans état ItemEJB


@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

public List<CD> findCDs() {


Query query = em.createNamedQuery("findAllCDs");
return query.getResultList();
}

public Book createBook(Book book) {


em.persist(book);
return book;
}

public CD createCD(CD cd) {


em.persist(cd);
return cd;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 229

Les beans de session sans état offrent souvent plusieurs méthodes métiers étroite-
ment liées. Le bean ItemEJB du Listing 7.1, par exemple, définit des méthodes qui
concernent les articles vendus par l’application CD-BookStore : vous y trouverez
donc les opérations de création, de modification ou de recherche de livres et de CD,
ainsi que d’autres traitements métiers apparentés.
Grâce à l’annotation @javax.ejb.Stateless, le POJO ItemEJB devient un bean de
session sans état – elle transforme donc une simple classe Java en composant pour
conteneur. Le Listing 7.2 contient la spécification de cette annotation.

Listing 7.2 : API de l’annotation @Stateless


@Target({TYPE}) @Retention(RUNTIME)
public @interface Stateless {
String name() default "";
String mappedName() default "";
String description() default "";
}

Le paramètre name précise le nom du bean, qui est, par défaut, celui de la classe
(ItemEJB dans l’exemple du Listing 7.1). Ce paramètre peut être utilisé pour recher-
cher un EJB particulier avec JNDI, par exemple. description est une chaîne permet-
tant de décrire l’EJB et mappedName est le nom JNDI global affecté par le conteneur
– ce dernier est spécifique à l’éditeur et n’est donc pas portable. mappedName n’a
aucun rapport avec le nom JNDI global et portable que nous avons évoqué au cha-
pitre précédent et que nous décrirons en détail dans la section "Accès JNDI global",
plus loin dans ce chapitre.
Les beans de session sans état peuvent supporter un grand nombre de clients en
minimisant les ressources nécessaires : c’est la raison pour laquelle les applications
qui les utilisent sont plus adaptables. Les beans de session avec état, au contraire, ne
sont liés qu’à un et un seul client.

Beans avec état

Les beans sans état fournissent des méthodes métiers aux clients mais n’entretien-
nent pas d’état conversationnel avec eux. Les beans de session avec état, par contre,
préservent cet état  : ils permettent donc d’implémenter les tâches qui nécessitent
plusieurs étapes, chacune tenant compte de l’état de l’étape précédente. Prenons
comme exemple le panier virtuel d’un site de commerce en ligne  : un client se
connecte (sa session débute), choisit un premier livre et l’ajoute à son panier, puis

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
230 Java EE 6 et GlassFish 3 

choisit un second livre et l’ajoute également. Puis le client valide la commande, la


paye et se déconnecte (la session se termine). Ici, le panier virtuel conserve l’état –
les livres choisis – pendant tout le temps de la session.
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);
statefullComponent.addBookToShoppingCart(book);
book.setTitle("The Robots of Dawn");
book.setPrice(18.25F);
book.setDescription("Isaac Asimov’s Robot Series");
book.setIsbn("0-553-29949-2");
book.setNbOfPage(276);
book.setIllustrations(false);
statefullComponent.addBookToShoppingCart(book);
statefullComponent.checkOutShoppingCart();

Le code précédent montre bien comment fonctionne un bean de session avec état.
Il crée deux livres et les ajoute au panier virtuel d’un composant avec état. À la fin,
la méthode checkOutShoppingCart() se fie a l’état mémorisé pour commander les
deux livres.
Quand un client invoque un bean avec état sur le serveur, le conteneur EJB doit four-
nir la même instance à chaque appel de méthode – ce bean ne peut pas être réutilisé
par un autre client. La Figure 7.3 montre la relation 1–1 qui s’établit entre l’instance
et le client ; du point de vue du développeur, aucun code supplémentaire n’est néces-
saire car cette relation est gérée automatiquement par le conteneur.

Figure 7.3
<<executionEnvironment>>
Clients accédant Conteneur EJB

à des beans avec état. <<component>> <<component>>


Client 1 Instance 1

<<component>> <<component>>
Client 2 Instance 2

Cette relation 1–1 a évidemment un prix : si l’on a 1 million de clients, ceci signifie
que l’on aura 1 million de beans en mémoire. Pour réduire cette occupation, les beans
doivent donc être supprimés temporairement de la mémoire entre deux requêtes
– cette technique est appelée passivation et activation. La passivation consiste à
supprimer une instance de la mémoire et à la sauvegarder dans un emplacement

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 231

persistant (un fichier sur disque, une base de données, etc.) : elle permet de libérer la
mémoire et les ressources. L’activation est le processus inverse : elle restaure l’état
et l’applique à une instance. Ces deux opérations sont réalisées par le conteneur : le
développeur n’a pas à s’en occuper – comme nous le verrons au prochain chapitre, il
doit simplement se charger de la libération des ressources (connexion à une base de
données ou à une fabrique JMS, etc.) avant que la passivation n’ait lieu.
Le Listing 7.3 applique l’exemple du panier virtuel à un bean avec état. Un client
se connecte au site web, parcourt le catalogue des articles et ajoute deux livres au
panier (à l’aide de la méthode addItem()). L’attribut cartItems stocke le contenu
du panier. Supposons que le client décide alors d’aller chercher un café : pendant
ce temps, le conteneur peut passiver l’instance pour libérer la mémoire, ce qui
entraîne la sauvegarde du contenu du panier dans une zone de stockage permanente.
Quelques minutes plus tard, le client revient et veut connaître le montant total de
son panier (avec la méthode getTotal()) avant de passer commande. Le conteneur
active donc l’EJB pour restaurer les données dans le panier et le client peut alors
commander (méthode checkout()) ses livres. Lorsqu’il se déconnecte, sa session se
termine et le conteneur libère la mémoire en supprimant définitivement l’instance
du bean en mémoire.

Listing 7.3 : Bean de session avec état


@Stateful
@StatefulTimeout(20000)
public class ShoppingCartEJB {

private List<Item> cartItems = new ArrayList<Item>();

public void addItem(Item item) {


if (!cartItems.contains(item))
cartItems.add(item);
}

public void removeItem(Item item) {


if (cartItems.contains(item))
cartItems.remove(item);
}

public Float getTotal() {


if (cartItems == null || cartItems.isEmpty())
return 0f;

Float total = 0f;


for (Item cartItem : cartItems) {
total += (cartItem.getPrice());
}
return total;
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
232 Java EE 6 et GlassFish 3 

@Remove
public void checkout() {
// Code métier
cartItems.clear();
}

@Remove
public void empty() {
cartItems.clear();
}
}

Ce que nous venons de voir pour le panier virtuel représente l’utilisation classique
des beans avec état, dans laquelle le conteneur gère automatiquement l’état conver-
sationnel. Ici, la seule annotation nécessaire est @javax.ejb.Stateful, qui utilise
les mêmes paramètres que @Stateless (voir Listing 7.2).
Nous avons également utilisé les annotations facultatives @javax.ejb.State-
fulTimeout et @javax.ejb.Remove. @Remove décore les méthodes checkout() et
empty() : leur appel provoquera désormais la suppression définitive de l’instance de
la mémoire. @StatefulTimeout met en place un délai d’expiration en millisecondes
– si le bean ne reçoit aucune demande du client au bout de ce délai, il sera supprimé
par le conteneur. Il est également possible de se passer de ces annotations en se fiant
au fait que le conteneur supprime automatiquement une instance lorsqu’une session
client se termine ou expire, mais s’assurer que le bean est détruit au moment adéquat
permet de réduire l’occupation mémoire, ce qui peut se révéler essentiel pour les
applications à haute concurrence.

Singletons

Un bean singleton est simplement un bean de session qui n’est instancié qu’une
seule fois par application. C’est donc une implémentation du fameux patron de
conception du Gang of Four, décrit dans l’ouvrage Design Patterns: Elements of
Reusable Object-Oriented Software, d’Erich Gamma, Richard Helm, Ralph John-
son et John M. Vlissides (Addison-Wesley, 1995). Il garantit qu’une seule instance
d’une classe existera dans l’application et fournit un point d’accès global vers cette
classe. Les objets singletons sont nécessaires dans toutes les situations où l’on n’a
besoin que d’un seul exemplaire d’un objet – pour décrire une souris, un gestion-
naire de fenêtres, un spooler d’impression, un système de fichiers, etc.
Un autre cas d’utilisation des singletons est la création d’un cache unique pour
toute l’application afin d’y stocker des objets. Dans un environnement géré par

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 233

l’application, vous devez modifier le code de la classe du cache afin de la transfor-


mer en singleton (voir Listing 7.4). Pour cela, il faut d’abord rendre son constructeur
privé pour empêcher la création d’une nouvelle instance. La méthode publique et
statique getInstance() se chargera alors de renvoyer la seule instance possible de
la classe. Pour ajouter un objet au cache en utilisant le singleton, une classe cliente
devra alors réaliser l’appel suivant :
CacheSingleton.getInstance().addToCache(myObject);
Le mot-clé synchronized permet d’empêcher toute interférence lors de l’accès à
cette méthode par plusieurs threads.

Listing 7.4 : Classe Java respectant le patron de conception Singleton


public class CacheSingleton {

private static CacheSingleton instance = new CacheSingleton();


private Map<Long, Object> cache = new HashMap<Long, Object>();

private CacheSingleton() {
}

public static synchronized CacheSingleton getInstance() {


return instance;
}

public void addToCache(Long id, Object object) {


if (!cache.containsKey(id))
cache.put(id, object);
}

public void removeFromCache(Long id) {


if (cache.containsKey(id))
cache.remove(id);
}

public Object getFromCache(Long id) {


if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

EJB 3.1 introduit les beans de session singletons qui respectent le patron de concep-
tion Singleton : une fois instancié, le conteneur garantit qu’il n’y aura qu’une seule
instance du singleton pour toute la durée de l’application. Comme le montre la
Figure  7.4, une instance est partagée par plusieurs clients. Les beans singletons
mémorisent leur état entre les appels des clients.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
234 Java EE 6 et GlassFish 3 

Figure 7.4
<<executionEnvironment>>
Clients accédant Conteneur EJB

à un bean singleton. <<component>>


Client 1 <<component>>
Instance unique
<<component>>
Client 2

INFO

Les singletons ne sont pas compatibles avec les clusters. Un cluster est un groupe de conte-
neurs fonctionnant de concert (ils partagent les mêmes ressources, les mêmes EJB, etc.).
Lorsqu’il y a plusieurs conteneurs répartis en cluster sur des machines différentes, chaque
conteneur aura donc sa propre instance du singleton.

Il n’y a pas grand-chose à faire pour transformer le code du Listing 7.4 en bean de


session singleton (voir Listing 7.5). En fait, il suffit d’annoter la classe avec
­ Singleton et de ne pas s’occuper du constructeur privé ou de la méthode statique
@
getInstance() : le conteneur s’assurera qu’une seule instance est créée. L’annota-
tion @javax.ejb.Singleton a la même API que celle de l’annotation @Stateless
décrite dans le Listing 7.2.

Listing 7.5 : Bean de session singleton


@Singleton
public class CacheEJB {

private Map<Long, Object> cache = new HashMap<Long, Object>();

public void addToCache(Long id, Object object) {


if (!cache.containsKey(id))
cache.put(id, object);
}

public void removeFromCache(Long id) {


if (cache.containsKey(id))
cache.remove(id);
}

public Object getFromCache(Long id) {


if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 235

Comme vous pouvez le constater, les beans de session sans état, avec état et sin-
gletons sont très simples à écrire puisqu’il suffit d’une seule annotation. Les sin-
gletons, toutefois, ont plus de possibilités : ils peuvent être initialisés au lancement
de l’application, chaînés ensemble, et il est possible de personnaliser leurs accès
concurrents.

Initialisation
Lorsqu’une classe client veut appeler une méthode d’un bean singleton, le conteneur
s’assure de créer l’instance ou d’utiliser celle qui existe déjà. Parfois, cependant,
l’initialisation d’un singleton peut être assez longue : CacheEJB peut, par exemple,
devoir accéder à une base de données pour charger un millier d’objets. En ce cas, le
premier appel au bean prendra du temps et le premier client devra attendre la fin de
son initialisation.
Pour éviter ce temps de latence, vous pouvez demander au conteneur d’initialiser un
bean singleton dès le démarrage de l’application en ajoutant l’annotation @Startup
à la déclaration du bean :
@Singleton
@Startup
public class CacheEJB {
// ...
}

Chaînage de singletons
Dans certains cas, l’ordre explicite des initialisations peut avoir une importance
lorsque l’on a plusieurs beans singletons. Supposons que le bean CacheEJB ait
besoin de stocker des données provenant d’un autre bean singleton (un CountryCo-
deEJB renvoyant tous les codes ISO des pays, par exemple) : ce dernier doit donc
être initialisé avant le CacheEJB. L’annotation @javax.ejb.DependsOn est justement
prévue pour exprimer les dépendances entre les singletons :
@Singleton
public class CountryCodeEJB {
...
}

@DependsOn("CountryCodeEJB")
@Singleton
public class CacheEJB {
...
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
236 Java EE 6 et GlassFish 3 

@DependsOn prend en paramètre une ou plusieurs chaînes désignant chacune le nom


d’un bean singleton dont dépend le singleton annoté. Le code suivant, par exemple,
montre que CacheDB dépend de l’initialisation de CountryCodeEJB et ZipCodeEJB –
@DependsOn("CountryCodeEJB", "ZipCodeEJB") demande au conteneur de garantir
que les singletons CountryCodeEJB et ZipCodeEJB seront initialisés avant CacheEJB.
@Singleton
public class CountryCodeEJB {
...
}

@Singleton
public class ZipCodeEJB {
...
}

@DependsOn("CountryCodeEJB", "ZipCodeEJB")
@Startup
@Singleton
public class CacheEJB {
...
}

Comme vous pouvez le constater dans le code précédent, il vous est même possible
de combiner ces dépendances avec une initialisation lors du démarrage de l’appli-
cation : CacheEJB étant initialisé dès le lancement (car il est annoté par @Startup),
CountryCodeEJB et ZipCodeEJB le seront également, mais avant lui.

Concurrence
Un singleton n’ayant qu’une seule instance partagée par plusieurs clients, les accès
concurrents peuvent être contrôlés de trois façons différentes par l’annotation
@ConcurrencyManagement :

■■ Concurrence gérée par le conteneur (Container-Managed Concurrency ou


CMC). Le conteneur contrôle les accès concurrents en utilisant les métadonnées
(annotation ou l’équivalent en XML).
■■ Concurrence gérée par le bean (Bean-Managed Concurrency ou BMC). Le
conteneur autorise tous les accès concurrents et délègue la responsabilité de la
synchronisation de ces accès au bean lui-même.
■■ Concurrence interdite. Si un client appelle une méthode métier qui est en cours
d’utilisation par un autre client, l’exception ConcurrentAccessException est levée.
En l’absence d’indication explicite, la gestion par défaut est CMC. Un bean singleton
peut utiliser CMC ou BMC, mais pas les deux.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 237

Concurrence gérée par le conteneur


Avec CMC, la valeur par défaut, le conteneur est responsable du contrôle des accès
concurrents à l’instance du bean singleton. Vous pouvez alors vous servir de l’anno-
tation @Lock pour préciser le type de verrouillage :
■■ @Lock(LockType.WRITE). Une méthode annotée par un verrou WRITE (exclusif)
n’autorisera aucun autre appel concurrent tant qu’elle est en cours d’exécution.
Si un client  C1 appelle une méthode avec un verrou exclusif, le client  C2 ne
pourra pas l’appeler tant que l’appel de C1 ne s’est pas terminé.
■■ @Lock(LockType.READ). Une méthode annotée par un verrou READ (partagé)
autorisera un nombre quelconque d’appels concurrents. Deux clients C1 et C2
pourront appeler simultanément une méthode avec un verrou partagé.
L’annotation @Lock peut être associée à la classe, aux méthodes ou aux deux. Dans
le premier cas, cela revient à l’associer à toutes les méthodes. En l’absence d’in-
dication, le type de verrouillage par défaut est WRITE. Dans le code du Listing 7.6,
le bean CacheEJB utilise un verrou READ, ce qui implique que toutes ses méthodes
auront un verrou partagé, sauf getFromCache(), qui l’a redéfini à WRITE.

Listing 7.6 : Bean de session Singleton avec CMC


@Singleton
@Lock(LockType.READ)
public class CacheEJB {

private Map<Long, Object> cache = new HashMap<Long, Object>();

public void addToCache(Long id, Object object) {


if (!cache.containsKey(id))
cache.put(id, object);
}

public void removeFromCache(Long id) {


if (cache.containsKey(id))
cache.remove(id);
}

@AccessTimeout(2000)
@Lock(LockType.WRITE)
public Object getFromCache(Long id) {
if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
238 Java EE 6 et GlassFish 3 

Vous remarquerez que la méthode getFromCache() utilise également une annota-


tion @AccessTimeout. Celle-ci permet de limiter le temps pendant lequel un accès
concurrent sera bloqué : si le verrou n’a pas pu être obtenu dans ce délai, la requête
sera rejetée. Ici, si un appel à getFromCache() est bloqué pendant plus de 2 secondes,
l’appelant recevra l’exception ConcurrentAccessTimeoutException.

Concurrence gérée par le bean


Avec BMC, le conteneur autorise tous les accès à l’instance du bean singleton. C’est
donc le développeur qui doit protéger l’état contre les erreurs de synchronisation
dues aux accès concurrents. Pour ce faire, il peut utiliser les primitives de synchro-
nisation de Java, comme synchronized et volatile. Dans le Listing 7.7, le bean
CacheEJB utilise BMC (@ConcurrencyManagement(BEAN)) et protège les accès à la
méthode addToCache() à l’aide du mot-clé synchronized.

Listing 7.7 : Bean de session singleton avec BMC


@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class CacheEJB {

private Map<Long, Object> cache = new HashMap<Long, Object>();

public synchronized void addToCache(Long id, Object object) {


if (!cache.containsKey(id))
cache.put(id, object);
}

public void removeFromCache(Long id) {


if (cache.containsKey(id))
cache.remove(id);
}

public synchronized Object getFromCache(Long id) {


if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

Concurrence interdite
Les accès concurrents peuvent également être interdits sur une méthode ou sur
­l’ensemble du bean : en ce cas, un client appelant une méthode en cours d’utilisation

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 239

par un autre client recevra l’exception ConcurrentAccessException. Ceci peut avoir


des conséquences sur les performances puisque les clients devront gérer ­l’exception,
réessayeront d’accéder au bean, etc. Dans le Listing 7.8, le bean CacheEJB interdit
la concurrence sur la méthode addToCache() ; les deux autres méthodes utilisent le
verrouillage par défaut défini au niveau de la classe : CMC avec @LockREAD).

Listing 7.8 : Bean de session singleton interdisant la concurrence


@Singleton
@Lock(LockType.READ)
public class CacheEJB {

private Map<Long, Object> cache = new HashMap<Long, Object>();

@ConcurrencyManagement(ConcurrencyManagementType.CONCURRENCY_NOT_ALLOWED)
public void addToCache(Long id, Object object) {
if (!cache.containsKey(id))
cache.put(id, object);
}

public void removeFromCache(Long id) {


if (cache.containsKey(id))
cache.remove(id);
}

@AccessTimeout(2000)
@Lock(LockType.WRITE)
public Object getFromCache(Long id) {
if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

Modèle des beans de session

Pour l’instant, les exemples de beans de session que nous avons présentés utilisaient
le modèle de programmation le plus simple : un POJO annoté sans interface. En
fonction de vos besoins, les beans peuvent vous offrir un modèle bien plus riche
vous permettant de réaliser des appels distants, l’injection de dépendances ou des
appels asynchrones.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
240 Java EE 6 et GlassFish 3 

Interfaces et classe bean


Les beans de session que nous avons étudiés n’étaient composés que d’une seule
classe. En réalité, ils peuvent inclure les éléments suivants :
■■ Interfaces métiers. Ces interfaces contiennent les déclarations des méthodes
métiers visibles par les clients et implémentées par la classe bean. Un bean de
session peut avoir des interfaces locales, distantes, ou aucune interface (une vue
sans interface avec uniquement un accès local).
■■ Une classe bean. Cette classe contient les implémentations des méthodes métiers
et peut implémenter aucune ou plusieurs interfaces métiers. En fonction du type
de bean, elle doit être annotée par @Stateless, @Stateful ou @Singleton.
Comme le montre la Figure 7.5, une application cliente peut accéder à un bean de
session par l’une de ses interfaces (locale ou distante) ou directement en invoquant
la classe elle-même.

Figure 7.5 Local
<<component>> <<component>>
Les beans de session Client Bean de session
Distant
peuvent avoir différents
types d’interfaces.
pas d'interface

Vues distantes, locales et sans interface


Selon d’où un client invoque un bean de session, la classe de ce dernier devra implé-
menter des interfaces locales ou distantes, voire aucune interface. Si, dans votre
architecture, les clients se trouvent à l’extérieur de l’instance JVM du conteneur
d’EJB, ils devront utiliser une interface distante. Comme le montre la Figure 7.6,
ceci s’applique également aux clients qui s’exécutent dans une JVM séparée (un
client riche, par exemple), dans un conteneur client d’application (ACC) ou dans un
conteneur web ou EJB externe. Dans ces situations, les clients devront invoquer les
méthodes des beans de session via RMI (Remote Method Invocation). Les appels
locaux, en revanche, ne peuvent être utilisés que lorsque le bean et le client s’exé-
cutent dans la même JVM – un EJB invoquant un autre EJB ou un composant web
(servlet, JSF) tournant dans un conteneur web de la même JVM, par exemple.
Une application peut également utiliser des appels distants et locaux sur le même
bean de session.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 241

<<executionEnvironment>>
<<executionEnvironment>> Conteneur client d'applications
Serveur d'applications
<<component>>
Application cliente
<<executionEnvironment>> <<executionEnvironment>> *
Conteneur web Conteneur EJB
<<component>>
<<component>> <<component>> Application cliente
*
Servlet Bean de session

Appel local
Appel <<executionEnvironment>>
local * Serveur d'applications
<<component>> <<component>>
JSF Bean de session
<<component>>
Composant quelconque

* : Appel distant

Figure 7.6
Beans de session appelés par plusieurs clients.

Un bean de session peut implémenter plusieurs interfaces ou aucune. Une interface


métier est une interface classique de Java qui n’hérite d’aucune interface EJB spé-
cifique. Comme toute interface Java, les interfaces métiers énumèrent les méthodes
qui seront disponibles pour l’application cliente. Elles peuvent utiliser les annotations
suivantes :
■■ @Remote indique une interface métier distante. Les paramètres des méthodes sont
passés par valeur et doivent être sérialisables pour être pris en compte par le
protocole RMI.
■■ @Local indique une interface métier locale. Les paramètres des méthodes sont
passés par référence du client au bean.
Une interface donnée ne peut pas utiliser plus d’une de ces annotations. Les beans
de session que nous avons vu jusqu’à présent n’avaient pas d’interface – la vue
sans interface est une variante de la vue locale qui expose localement toutes les
méthodes métiers publiques de la classe bean sans nécessiter l’emploi d’une inter-
face métier.
Le Listing  7.9 présente une interface locale (ItemLocal) et une interface distante
(ItemRemote) implémentées par le bean de session sans état ItemEJB. Dans cet
exemple, les clients pourront appeler localement ou à distance la méthode findCDs()
puisqu’elle est définie dans ces deux interfaces. La méthode createCd(), par contre,
ne pourra être appelée que par RMI.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
242 Java EE 6 et GlassFish 3 

Listing 7.9 : Bean de session sans état implémentant une interface distante et locale
@Local
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}

@Remote
public interface ItemRemote {
List<Book> findBooks();
List<CD> findCDs();
Book createBook(Book book);
CD createCD(CD cd);
}

@Stateless
public class ItemEJB implements ItemLocal, ItemRemote {
...
}

Dans le code du Listing 7.9, vous pourriez également préciser la nature de l’inter-


face dans la classe du bean. En ce cas, il faudrait inclure le nom de l’interface dans
les annotations @Local et @Remote comme le montre le Listing 7.10. Cette approche
est tout particulièrement adaptée lorsque l’on dispose d’interfaces existantes et que
l’on souhaite les utiliser avec le bean de session.

Listing 7.10 : Une classe bean définissant une interface locale et une interface distante
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}

public interface ItemRemote {


List<Book> findBooks();
List<CD> findCDs();
Book createBook(Book book);
CD createCD(CD cd);
}

@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
public class ItemEJB implements ItemLocal, ItemRemote {
...
}

Interfaces de services web


Outre les appels distants par RMI, les beans sans état peuvent également être appe-
lés à distance comme services web SOAP ou REST. Ceux-ci faisant l’objet des

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 243

Chapitres  14 et 15, nous ne les citons ici que parce qu’ils font partie des diffé-
rentes façons d’appeler un bean de session sans état, simplement en implémentant
des interfaces annotées différentes. Le Listing 7.11 présente un bean sans état avec
une interface locale, un service web SOAP (@WebService) et un service web REST
(@Path).

Listing 7.11 : Bean de session sans état implémentant une interface de services web
@Local
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}

@WebService
public interface ItemWeb {
List<Book> findBooks();
List<CD> findCDs();
Book createBook(Book book);
CD createCD(CD cd);
}

@Path(/items)
public interface ItemRest {
List<Book> findBooks();
}

@Stateless public class ItemEJB implements ItemLocal, ItemWeb, ItemRest {


...
}

Classes bean
Un bean de session sans état est une classe Java classique qui implémente une
logique métier. Pour qu’elle devienne une classe bean de session, elle doit satisfaire
les obligations suivantes :
■■ Elle doit être annotée par @Stateless, @Stateful, @Singleton ou leurs équivalents
XML dans un descripteur de déploiement.
■■ Elle doit implémenter les méthodes de ses éventuelles interfaces.
■■ Elle doit être publique et ni finale ni abstraite.
■■ Elle doit fournir un constructeur public sans paramètre qui servira à créer les
instances.
■■ Elle ne doit pas définir de méthode finalize().

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
244 Java EE 6 et GlassFish 3 

■■ Les noms des méthodes métiers ne doivent pas commencer par ejb et ne peuvent
être ni finals ni statiques.
■■ Le paramètre et la valeur de retour d’une méthode distante doivent être d’un type
reconnu par RMI.

Vue cliente
Maintenant que nous avons vu des exemples de beans de session et de leurs diffé-
rentes interfaces, nous pouvons étudier la façon dont le client les appelle. Le client
d’un bean de session peut être n’importe quel type de composant : un POJO, une
interface graphique (Swing), une servlet, un bean géré par JSF, un service web (SOAP
ou REST) ou un autre EJB (déployé dans le même conteneur ou dans un autre).
Pour appeler une méthode d’un bean de session, un client n’instancie pas directe-
ment le bean avec l’opérateur new. Pourtant, il a besoin d’une référence à ce bean
(ou à l’une de ses interfaces) : il peut en obtenir une via l’injection de dépendances
(avec l’annotation @EJB) ou par une recherche JNDI. Sauf mention contraire, un
client invoque un bean de façon synchrone mais, comme nous le verrons plus loin,
EJB 3.1 autorise maintenant les appels de méthodes asynchrones.

@EJB
Java EE utilise plusieurs annotations pour injecter des références de ressources
(@Resource), de gestionnaires d’entités (@PersistenceContext), de services web (@Web-
ServiceRef), etc. L’annotation @javax.ejb.EJB, en revanche, est spécialement conçue
pour injecter des références de beans de session dans du code client. L’injection de
dépendances n’est possible que dans des environnements gérés, comme les conte-
neurs EJB, les conteneurs web et les conteneurs clients d’application.
Reprenons nos premiers exemples dans lesquels les beans de session n’avaient pas
d’interface. Pour qu’un client invoque une vue de bean sans interface, il doit obtenir
une référence à la classe elle-même. Dans le code suivant, par exemple, le client
obtient une référence à la classe ItemEJB en utilisant l’annotation @EJB :
@Stateless
public class ItemEJB {
...
}
// Code client
@EJB ItemEJB itemEJB;

Si le bean de session implémente plusieurs interfaces, par contre, le client devra indi-
quer celle qu’il veut référencer. Dans le code qui suit, le bean ItemEJB implémente

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 245

deux interfaces et le client peut invoquer cet EJB via son interface locale ou distante,
mais il ne peut plus l’invoquer directement.
@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
// Code client
@EJB ItemEJB itemEJB; // Impossible
@EJB ItemLocal itemEJBLocal;
@EJB ItemRemote itemEJBRemote;

Si le bean expose au moins une interface, il doit préciser qu’il propose également
une vue sans interface en utilisant l’annotation @LocalBean. Comme vous pouvez
le constater dans le code suivant, le client peut maintenent appeler le bean via son
interface locale, distante, ou directement via sa classe.
@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
@LocalBean
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
// Code client
@EJB ItemEJB itemEJB;
@EJB ItemLocal itemEJBLocal;
@EJB ItemRemote itemEJBRemote;

Si l’injection n’est pas possible (lorsque le composant n’est pas géré par le conte-
neur), vous pouvez utiliser JNDI pour rechercher les beans de session à partir de leur
nom JNDI portable.

Accès JNDI global


Les beans de session peuvent également être recherchés par JNDI, qui est surtout
utilisée pour les accès distants lorsqu’un client non géré par un conteneur ne peut
pas utiliser l’injection de dépendances. Mais JNDI peut également être utilisée par
des clients locaux, même si l’injection de dépendances produit un code plus clair.
Pour rechercher des beans de session, une application cliente doit faire communi-
quer l’API JNDI avec un service d’annuaire.
Un bean de session déployé dans un conteneur est automatiquement lié à un nom
JNDI. Avant Java EE 6, ce nom n’était pas standardisé, ce qui impliquait qu’un bean
déployé dans des conteneurs différents (GlassFish, JBoss, WebLogic, etc.) portait

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
246 Java EE 6 et GlassFish 3 

des noms différents. La spécification Java EE 6 a corrigé ce problème en définissant


des noms JNDI portables ayant la syntaxe suivante :
java:global[/<nom-app>]/<nom-module>/<nom-bean>
„ [!<nom-interface-pleinement-qualifié>]

Les différentes parties d’un nom JNDI ont les significations suivantes :
■■ <nom-app> est facultative car cette partie ne s’applique que si le bean est assem-
blé dans un fichier ear. En ce cas, <nom-app> est, par défaut, le nom du fichier ear
(sans l’extension .ear).
■■ <nom-module> est le nom du module dans lequel a été assemblé le bean de ses-
sion. Ce module peut être un module EJB dans un fichier jar autonome ou un
module web dans un fichier war. Par défaut, <nom-module> est le nom du fichier
archive, sans son extension.
■■ <nom-bean> est le nom du bean de session.
■■ <nom-interface-pleinement-qualifié> est le nom pleinement qualifié de chaque
interface métier qui a été définie. Dans le cas des vues sans interface, ce nom est
le nom pleinement qualifié de la classe du bean.
Pour illustrer cette convention, prenons l’exemple du bean ItemEJB. ItemEJB est
le <nom-bean> et est assemblé dans l’archive cdbookstore.jar (le <nom-module>).
L’EJB a une interface distante et une vue sans interface (signalée par l’annota-
tion @LocalBean). Lorsqu’il sera déployé, le conteneur créera donc les noms JNDI
suivants :
package com.apress.javaee6;
@Stateless
@LocalBean
@Remote (ItemRemote)
public class ItemEJB implements ItemRemote {
...
}
// noms JNDI
java:global/cdbookstore/ItemEJB!com.apress.javaee6.ItemEJB
java:global/cdbookstore/ItemEJB!com.apress.javaee6.ItemRemote

Outre cette convention, si le bean n’expose qu’une seule interface cliente (ou n’a
qu’une vue sans interface), le conteneur enregistre une entrée JNDI pour cette vue
avec la syntaxe suivante :
java:global[/<nom-app>]/<nom-module>/<nom-bean>

Le code qui suit représente le bean ItemEJB avec seulement une vue sans interface.
Le nom JNDI est alors uniquement composé du nom du module (cdbookstore) et
de celui du bean.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 247

package com.apress.javaee6;
@Stateless
public class ItemEJB {
...
}
// Nom JNDI
} java:global/cdbookstore/ItemEJB

Contexte de session
Les beans de session sont des composants métiers résidant dans un conteneur.
Généralement, ils n’accèdent pas au conteneur et n’utilisent pas directement ses
services (transactions, sécurité, injection de dépendances, etc.), qui sont prévus pour
être gérés de façon transparente par le conteneur pour le compte du bean. Parfois,
cependant, le bean a besoin d’utiliser explicitement les services du conteneur (pour
annuler explicitement une transaction, par exemple) : en ce cas, il doit passer par
l’interface javax.ejb.SessionContext, qui donne accès au contexte d’exécution
qui lui a été fourni. SessionContext hérite de l’interface javax.ejb.EJBContext  ;
une partie des méthodes de son API est décrite dans le Tableau 7.1.

Tableau 7.1 : Une partie des méthodes de l’interface SessionContext

Méthode Description
getCallerPrincipal Renvoie le java.security.Principal associé à l’appel.
getRollbackOnly Teste si la transaction courante a été marquée pour annulation.
getTimerService Renvoie l’interface javax.ejb.TimerService. Cette méthode
ne peut être utilisée que par les beans sans état et singletons. Les
beans avec état ne peuvent pas utiliser les services timer.
getUserTransaction Renvoie l’interface javax.transaction.UserTransaction
permettant de délimiter les transactions. Cette méthode ne peut être
utilisée que par les beans de session avec des transactions gérées
par les beans (BMT).
isCallerInRole Teste si l’appelant a fourni un rôle de sécurité précis.
setRollbackOnly Autorise le bean à marquer la transaction pour annulation. Cette
méthode ne peut être utilisée que par les beans avec BMT.
wasCancelCalled Teste si un client a appelé la méthode cancel() sur l’objet Future
client correspondant à la méthode métier asynchrone en cours
d’exécution.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
248 Java EE 6 et GlassFish 3 

Un bean de session peut avoir accès à son contexte d’environnement en injectant une
référence SessionContext à l’aide d’une annotation @Resource.
@Stateless
public class ItemEJB {
@Resource
private SessionContext context;
...
public Book createBook(Book book) {
...
if (cantFindAuthor())
context.setRollbackOnly();
}
}

Descripteur de déploiement
Les composants Java EE 6 utilisent une configuration par exception, ce qui signifie
que le conteneur, le fournisseur de persistance ou le serveur de message appli-
queront un ensemble de services par défaut à ces composants. Si l’on souhaite
­disposer d’un comportement particulier, il faut explicitement utiliser une anno-
tation ou son équivalent XML : c’est ce que nous avons déjà fait avec les entités
JPA pour personnaliser les associations. Ce principe s’applique également aux
beans de session : une seule annotation (@Stateless, @Stateful, etc.) suffit pour
que le conteneur applique certains services (transaction, cycle de vie, sécurité,
intercepteurs, concurrence, asynchronisme, etc.) mais, si vous voulez les modi-
fier, d’autres annotations (ou leurs équivalents XML) sont à votre disposition. Les
annotations et les descripteurs de déploiement XML permettent en effet d’attacher
des informations supplémentaires à une classe, une interface, une méthode ou une
variable.
Un descripteur de déploiement XML est une alternative aux annotations, ce qui
signifie que toute annotation a un marqueur XML équivalent. Lorsque les deux
mécanismes sont utilisés, la configuration décrite dans le descripteur de déploie-
ment a priorité sur les annotations. Nous ne rentrerons pas ici dans les détails de la
structure d’un descripteur de déploiement XML (stocké dans un fichier nommé ejb-
jar.xml) car il est facultatif et peut être très verbeux. Le Listing 7.12 montre à quoi
pourrait ressembler le fichier ejb-jar.xml d’ItemEJB (voir Listing 7.9). Il définit
la classe bean, l’interface locale et distante, son type (Stateless) et indique qu’il
utilise des transactions gérées par le conteneur (CMT). L’élément <env-entry> défi-
nit les entrées de l’environnement du bean de session. Nous y reviendrons dans la
section "Contexte de nommage de l’environnement".

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 249

Listing 7.12 : Le fichier ejb-jar.xml


<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>ItemEJB</ejb-name>
<ejb-class>com.apress.javaee6.ItemEJB</ejb-class>
<local>com.apress.javaee6.ItemLocal</local>
<remote>com.apress.javaee6.ItemLocal</remote>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<env-entry>
<env-entry-name>aBookTitle</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Beginning Java EE 6</env-entry-value>
</env-entry>
</session>
</enterprise-beans>
</ejb-jar>

Si le bean de session est déployé dans un fichier jar, le descripteur de déploiement


doit être stocké dans le fichier META-INF/ejb-jar.xml. S’il est déployé dans un
fichier war, il doit être stocké dans le fichier WEB-INF/web.xml.

Injection de dépendances
Nous avons déjà évoqué l’injection de dépendances, et vous la rencontrerez encore
plusieurs fois dans les prochains chapitres. Il s’agit d’un mécanisme simple mais
puissant utilisé par Java EE 6 pour injecter des références de ressources dans des
attributs  : au lieu que l’application recherche les ressources dans JNDI, celles-ci
sont injectées par le conteneur.
Les conteneurs peuvent injecter différents types de ressources dans les beans de
session à l’aide de plusieurs annotations (ou descripteurs de déploiement) :
■■ @EJB injecte dans la variable annotée une référence de la vue locale, distante ou
sans interface de l’EJB.
■■ @PersistenceContext et @PersistenceUnit expriment, respectivement, une
dépendance sur un EntityManager et sur une EntityManagerFactory.
■■ @WebServiceRef injecte une référence à un service web.
■■ @Resource injecte plusieurs ressources, comme les sources de données JDBC,
les contextes de session, les transactions utilisateur, les fabriques de connexion
JMS et les destinations, les entrées d’environnement, le service timer, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
250 Java EE 6 et GlassFish 3 

L’extrait de code du Listing  7.13 montre un extrait de bean de session sans état
utilisant plusieurs annotations pour injecter différentes ressources dans les attributs.
Vous remarquerez que ces annotations peuvent porter sur les variables d’instance
ainsi que sur les méthodes setter.

Listing 7.13 : Un bean sans état utilisant l’injection


@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;

@EJB
private CustomerEJB customerEJB;

@WebServiceRef
private ArtistWebService artistWebService;

private SessionContext context;

@Resource
public void setCtx(SessionContext ctx) {
this.ctx = ctx;
}
...
}

Contexte de nommage de l’environnement


Les paramètres des applications d’entreprise peuvent varier d’un déploiement à l’autre
(en fonction du pays, de la version de l’application, etc.). Dans l’application CD-
BookStore, par exemple, ItemConverterEJB (voir Listing 7.14) convertit le prix d’un
article dans la monnaie du pays dans lequel l’application a été déployée (en appliquant
un taux de change par rapport au dollar). Si ce bean sans état est déployé en Europe,
le prix de l’article doit être multiplié par 0,8 et le nom de la monnaie doit être l’euro.

Listing 7.14 : Bean de session sans état convertissant des prix en euros


@Stateless
public class ItemConverterEJB {

public Item convertPrice(Item item) {


item.setPrice(item.getPrice() * 0.80);
item.setCurrency("Euros");
return item;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 251

Comme vous l’aurez compris, coder en dur ces paramètres implique de modifier le
code, de le recompiler et de redéployer le composant pour chaque pays ayant une
monnaie différente. Une autre possibilité consisterait à utiliser une base de données
à chaque appel de la méthode convertPrice(), mais cela gaspillerait des ressources.
En réalité, on veut simplement stocker ces paramètres à un endroit où ils pourront
être modifiés lors du déploiement : le descripteur de déploiement est donc un empla-
cement de choix.
Avec EJB 3.1, le descripteur de déploiement (ejb-jar.xml) est facultatif, mais son
utilisation est justifiée lorsque l’on a des paramètres liés à l’environnement. Ces
entrées peuvent en effet être placées dans le fichier et être accessibles via l’injec-
tion de dépendances (ou par JNDI). Elles peuvent être de type String, Character,
Byte, Short, Integer, Long, Boolean, Double et Float. Le Listing 7.15, par exemple,
montre que le fichier ejb-jar.xml d’ItemConverterEJB définit deux entrées : cur-
rencyEntry, de type String et de valeur Euros, et changeRateEntry, de type Float
et de valeur 0.80.

Listing 7.15 : Entrées d’environnement d’ItemConverterEJB dans ejb-jar.xml


<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>ItemConverterEJB</ejb-name>
<ejb-class>com.apress.javaee6.ItemConverterEJB</ejb-class>
<env-entry>
<env-entry-name>currencyEntry</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Euros</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>changeRateEntry</env-entry-name>
<env-entry-type>java.lang.Float</env-entry-type>
<env-entry-value>0.80</env-entry-value>
</env-entry>
</session>
</enterprise-beans>
</ejb-jar>

Maintenant que les paramètres de l’application ont été externalisés dans le descrip-
teur de déploiement, ItemConverterEJB peut utiliser l’injection de dépendances pour
obtenir leurs valeurs. Dans le Listing 7.16, @Resource(name = "currencyEntry")
injecte la valeur de l’entrée currencyEntry dans l’attribut currency ; si les types
de l’entrée et de l’attribut ne sont pas compatibles, le conteneur lève une exception.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
252 Java EE 6 et GlassFish 3 

Listing 7.16 : Un ItemConverterEJB utilisant des entrées d’environnement


@Stateless
public class ItemConverterEJB {

@Resource(name = "currencyEntry")
private String currency;
@Resource(name = "changeRateEntry")
private Float changeRate;

public Item convertPrice(Item item) {


item.setPrice(item.getPrice() * changeRate);
item.setCurrency(currency);
return item;
}
}

Appels asynchrones

Par défaut, les appels des beans de session via des vues distantes, locales et sans
interface sont synchrones : un client qui appelle une méthode reste bloqué pendant
la durée de cet appel. Un traitement asynchrone est donc souvent nécessaire lorsque
l’application doit exécuter une opération qui dure longtemps. L’impression d’une
commande, par exemple, peut prendre beaucoup de temps si des dizaines de docu-
ments sont déjà dans la file d’attente de l’imprimante, mais un client qui appelle une
méthode d’impression d’un document souhaite simplement déclencher un processus
qui imprimera ce document, puis continuer son traitement.
Avant EJB 3.1, les traitements asynchrones pouvaient être pris en charge par JMS
et les MDB (voir Chapitre 13). Il fallait créer des objets administrés (fabriques et
destinations JMS), utiliser l’API JMS de bas niveau afin d’envoyer un message à
un destinataire, puis développer un MDB pour consommer et traiter le message.
Cela fonctionnait mais se révélait assez lourd dans la plupart des cas car il fallait
mettre en œuvre un système MOM pour simplement appeler une méthode de façon
asynchrone.
Depuis EJB 3.1, on peut appeler de façon asynchrone une méthode de bean de ses-
sion en l’annotant simplement avec @javax.ejb.Asynchronous. Le Listing  7.17
montre le bean OrderEJB, qui dispose d’une méthode pour envoyer un e-mail à un
client et d’une autre pour imprimer la commande. Ces deux méthodes durant long-
temps, elles sont toutes les deux annotées par @Asynchronous.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 253

Listing 7.17 : Le bean OrderEJB déclare deux méthodes asynchrones


@Stateless
public class OrderEJB {

@Asynchronous
private void sendEmailOrderComplete(Order order,
Customer customer) {
// Envoie un mail
}

@Asynchronous
private void printOrder(Order order) {
// Imprime une commande
}
}

Lorsqu’un client appelle printOrder() ou sendEmailOrderComplete(), le conte-


neur lui redonne immédiatement le contrôle et continue le traitement de cet appel
dans un thread séparé. Comme vous pouvez le voir dans le Listing 7.17, le type du
résultat de ces deux méthodes est void, mais une méthode asynchrone peut éga-
lement renvoyer un objet de type java.util.concurrent.Future<V>, où V repré-
sente la valeur du résultat. Les objets Future permettent d’obtenir le résultat d’une
méthode qui s’exécute dans un thread distinct : le client peut alors utiliser l’API de
Future pour obtenir ce résultat ou annuler l’appel.

Le Listing 7.18 montre un exemple de méthode renvoyant un objet Future<Integer> :


sendOrderToWorkflow() utilise un workflow pour traiter un objet Order. Supposons
qu’elle appelle plusieurs composants d’entreprise (messages, services web, etc.) et
que chaque étape renvoie un code d’état (un entier) : lorsque le client appelle de
façon asynchrone la méthode sendOrderToWorkflow(), il s’attend donc à recevoir
le code d’état du workflow, qu’il peut récupérer par un appel à la méthode Future.
get(). Si, pour une raison ou pour une autre, il souhaite annuler l’appel, il peut utili-
ser Future.cancel(), auquel cas le conteneur tentera d’annuler l’appel asynchrone
s’il n’a pas encore démarré. Notez que la méthode sendOrderToWorkflow() appelle
SessionContext.wasCancelCalled() pour tester si le client a demandé ou non l’an-
nulation de l’appel. Le résultat est de type javax.ejb.AsyncResult<V>, qui est une
implémentation pratique de Future<V>. En fait, AsyncResult est utilisée pour passer
la valeur du résultat au conteneur au lieu de la passer directement à l’appelant.

Listing 7.18 : Méthode asynchrone renvoyant un Future<Integer>


@Stateless
@Asynchronous
public class OrderEJB {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
254 Java EE 6 et GlassFish 3 

@Resource
SessionContext ctx;

private void sendEmailOrderComplete(Order order,


Customer customer) {
// Envoie un mail
}

private void printOrder(Order order) {


// Imprime une commande
}

private Future<Integer> sendOrderToWorkflow(Order order) {


Integer status = 0;
// Traitement
status = 1;

if (ctx.wasCancelCalled()) {
return new AsyncResult<Integer>(2);

// Traitement
return new AsyncResult<Integer>(status);
}
}

Dans le Listing 7.18, vous remarquerez que l’annotation @Asynchronous est appli-


quée au niveau de la classe, ce qui implique que toutes les méthodes définies sont
asynchrones. Pour récupérer la valeur du résultat d’un appel à sendOrderToWork-
flow(), le client devra appeler Future.get() :
Future<Integer> status = orderEJB.sendOrderToWorkflow (order); Integer
statusValue = status.get();

Conteneurs intégrés

L’avantage des beans de session est qu’ils sont gérés par un conteneur : ce dernier
s’occupe de tous les services (transaction, cycle de vie, asynchronisme, intercep-
teurs, etc.), ce qui permet au développeur de se concentrer sur le code métier. L’in-
convénient est qu’il faut toujours exécuter les EJB dans un conteneur, même pour
les tester. Pour résoudre ce problème, on finit généralement par bricoler le code
métier afin de pouvoir le tester : on ajoute des interfaces distantes alors que l’EJB
n’a besoin que d’un accès local, on crée une façade TestEJB distante qui délègue les
appels aux véritables EJB ou l’on utilise des fonctionnalités spécifiques à un éditeur
– d’une façon ou d’une autre, on doit exécuter un conteneur avec les EJB déployés.
Ce problème a été résolu par EJB 3.1 avec la création d’un conteneur EJB inté-
grable. EJB 3.1 ajoute en effet une API standardisée pour exécuter les EJB dans un
environnement Java SE. Celle-ci (l’API javax.ejb.embeddable) permet à un client

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 255

d’instancier un conteneur EJB qui s’exécutera dans sa propre JVM. Ce conteneur


fournit un environnement géré disposant des mêmes services de base que ceux d’un
conteneur Java EE, mais il ne fonctionne qu’avec l’API EJB Lite (pas de MDB, pas
de beans entités 2.x, etc.).
Le Listing 7.19 montre une classe JUnit qui utilise l’API de "bootstrap" pour lancer
le conteneur (la classe abstraite javax.ejb.embeddable.EJBContainer), rechercher
un EJB et appeler ses méthodes.

Listing 7.19 : Classe de test utilisant un conteneur intégré


public class ItemEJBTest {

private static EJBContainer ec;


private static Context ctx;

@BeforeClass
public static void initContainer() throws Exception {
ec = EJBContainer.createEJBContainer();
ctx = ec.getContext();
}

@AfterClass
public static void closeContainer() throws Exception {
ec.close();
}

@Test
public void createBook() throws Exception {

// Création d’un livre


Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction comedy book");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);

// Recherche de l’EJB
ItemEJB bookEJB = (ItemEJB)
„ ctx.lookup("java:global/chapter07/ItemEJB");

// Stockage du livre dans la base de données


book = itemEJB.createBook(book);
assertNotNull("ID should not be null", book.getId());

// Récupère tous les livres de la base


List<Book> books = itemEJB.findBooks();
assertNotNull(books);
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
256 Java EE 6 et GlassFish 3 

Comme vous pouvez le constater dans la méthode initContainer() du Listing 7.19,


EJBContainer dispose d’une méthode fabrique (createEJBContainer()) permettant
de créer une instance de conteneur. Par défaut, le conteneur intégré recherche les
EJB à initialiser dans le classpath du client. Lorsque le conteneur a été initialisé,
la méthode récupère son contexte (EJBContainer.getContext(), qui renvoie un
javax.naming.Context) pour rechercher le bean ItemEJB (en utilisant la syntaxe
des noms portables JNDI).
Notez qu’ItemEJB (voir Listing 7.1) est un bean de session sans état qui expose ses
méthodes via une vue sans interface. Il utilise l’injection, les transactions gérées
par le conteneur et une entité JPA Book. Le conteneur intégré s’occupe d’injecter un
gestionnaire d’entités et de valider ou d’annuler les transactions. La méthode clo-
seContainer() appelle simplement EJBContainer.close() pour fermer l’instance
du conteneur intégré.
Nous nous sommes servis d’une classe de test dans cet exemple pour vous montrer
comment utiliser un conteneur EJB intégré, mais les EJB peuvent désormais être
employés dans n’importe quel environnement Java  SE  : des classes de tests aux
applications Swing en passant par une simple classe Java avec une méthode public
static void main().

Le service timer

Certaines applications Java EE ont besoin de planifier des tâches pour être averties
à des instants donnés. L’application CD-BookStore, par exemple, veut envoyer tous
les ans un e-mail d’anniversaire à ses clients, afficher les statistiques mensuelles des
ventes, produire des rapports toutes les nuits sur l’état du stock et rafraîchir un cache
toutes les 30 secondes.
Pour ce faire, EJB 2.1 a introduit un service timer car les clients ne pouvaient pas
utiliser directement l’API Thread, mais il était moins riche que d’autres outils ou cer-
tains frameworks (l’utilitaire cron d’Unix, Quartz, etc.). Il a fallu attendre EJB 3.1
pour voir apparaître une amélioration considérable de ce service, qui s’est inspiré de
cron et d’autres outils reconnus. Désormais, il peut répondre à la plupart des besoins
de planification.
Le service timer EJB est un service conteneur qui permet aux EJB de s’enregis-
trer pour être rappelés. Les notifications peuvent être planifiées pour intervenir à
une date ou une heure données, après un certain délai ou à intervalles réguliers.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 257

Le conteneur mémorise tous les timers et appelle la méthode d’instance appropriée


lorsqu’un timer a expiré. La Figure 7.7 montre les deux étapes de l’utilisation de
ce service. L’EJB doit d’abord créer un timer (automatiquement ou explicitement)
et s’enregistrer pour être rappelé, puis le service timer déclenche la méthode d’ins-
tance enregistrée de l’EJB.

<<executionEnvironment>>
Conteneur EJB

<<component>> Enregistrement automatique ou explicite <<component>>


Service timer Bean de session
Prévient le bean à l'expiration du timer

Figure 7.7
Interaction entre le service timer et un bean de session.

Les timers sont destinés aux processus métiers longs et ils sont donc persistants par
défaut, ce qui signifie qu’ils survivent aux arrêts du serveur : lorsqu’il redémarre, les
timers s’exécutent comme s’il ne s’était rien passé. Selon vos besoins, vous pouvez
également demander des timers non persistants.

INFO

Le service de timer peut enregistrer des beans sans état et singletons ainsi que des MDB,
mais pas de beans avec état. Ces derniers ne doivent donc pas utiliser l’API de planification.

Les timers peuvent être créés automatiquement par le conteneur au moment du


déploiement si le bean comprend des méthodes annotées par @Schedule. Ils peuvent
également être créés explicitement par programme et doivent fournir une méthode
de rappel annotée par @Timeout.

Expressions calendaires

Le service timer utilise une syntaxe calendaire inspirée de celle du programme cron
d’Unix. Cette syntaxe est utilisée pour la création des timers par programme (avec
la classe ScheduleExpression) ou pour les créations automatiques (via l’annotation
@Schedule ou le descripteur de déploiement). Le Tableau 7.2 énumère les attributs
de création des expressions calendaires.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
258 Java EE 6 et GlassFish 3 

Tableau 7.2 : Attributs d’une expression calendaire

Valeur par
Attribut Description Valeurs possibles
défaut

second Une ou plusieurs secondes [0,59] 0


dans une minute

minute Une ou plusieurs minutes [0,59] 0


dans une heure

hour Une ou plusieurs heures [0,23] 0


dans une journée

dayOfMonth Un ou plusieurs jours dans [1,31] ou {"1st", "2nd", *


un mois "3rd", . . . ,"30th",
"31st"} ou {"Sun", "Mon",
"Tue", "Wed", "Thu",
"Fri", "Sat"} ou "Last" (le
dernier jour du mois) ou -x (x
jour(s) avant le dernier jour du
mois)

month Un ou plusieurs mois dans [1,12] ou {"Jan", "Feb", *


une année "Mar", "Apr", "May",
"Jun", "Jul", "Aug",
"Sep", "Oct", "Nov",
"Dec"}

dayOfWeek Un ou plusieurs jours dans [0,7] ou {"Sun", "Mon", *


une semaine "Tue", "Wed", "Thu",
"Fri", "Sat"} – "0" et "7"
signifient Dimanche

year Une année particulière Une année sur quatre chiffres *

timezone Une zone horaire particulière Liste des zones horaires fournies
par la base de données zoneinfo
(ou tz)

Chaque attribut d’une expression calendaire (second, minute, hour, etc.) permet
d’exprimer les valeurs sous différentes formes. Vous pouvez, par exemple, avoir une
liste de jours ou un intervalle entre deux années. Le Tableau 7.3 présente les diffé-
rentes formes que peut prendre un attribut.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 259

Tableau 7.3 : Forme d’une expression calendaire

Forme Description Exemple


Valeur simple Une seule valeur possible year = "2009"
month= "May"

Joker Toutes les valeurs possibles d’un attribut second = "*"


dayOfWeek = "*"

Liste Deux valeurs ou plus séparées year = "2008,2012,2016"


par une virgule dayOfWeek = "Sat,Sun"
minute = "0-10,30,40"

Intervalle Intervalle de valeurs séparées par un tiret second="1-10"


dayOfWeek = "Mon-Fri"

Incréments Un point de départ et un intervalle minute = "*/15"


séparés par une barre de fraction second = "30/10"

Cette syntaxe devrait sembler familière à ceux qui connaissent cron, mais elle est
bien plus simple. Comme le montre le Tableau 7.4, elle permet d’exprimer quasi-
ment n’importe quel type d’expression calendaire.

Tableau 7.4 : Exemples d’expressions calendaires

Exemple Expression
Tous les mercredis à minuit dayOfWeek="Wed"

Tous les mercredis à minuit second="0", minute="0", hour="0",


dayOfMonth="*",
month="*", dayOfWeek="Wed", year="*"

Tous les jours de la semaine à 6:55 minute="55", hour="6",


dayOfWeek="Mon- Fri"

Tous les jours de la semaine minute="55", hour="6",


à 6:55 heure de Paris dayOfWeek="Mon- Fri",
timezone="Europe/Paris"

Toutes les minutes minute="*", hour="*"

Toutes les secondes second="*", minute="*", hour="*"

Tous les lundis et vendredis, second="30", hour="12",


30 secondes après midi dayOfWeek="Mon, Fri"

Toutes les cinq minutes minute="*/5", hour="*"

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
260 Java EE 6 et GlassFish 3 

Tableau 7.4 : Exemples d’expressions calendaires (suite)

Exemple Expression
Toutes les cinq minutes minute="0,5,10,15,20,25,30,35,40,45,
50,55", hour="*"

Le dernier lundi de décembre hour="15", dayOfMonth="Last Mon",


à 15 h 00 month="Dec"

Trois jours avant le dernier jour hour="13", dayOfMonth="-3"


de chaque mois à 13 h 00
Toutes les deux heures à partir de midi hour="12/2",
le second mardi de chaque mois dayOfMonth="2nd Tue"

Toutes les 14 minutes de 1 h 00 minute = "*/14", hour="1,2"


et 2 h 00
Toutes les 14 minutes de 1 h 00 minute = "0,14,28,42,56",
et 2 h 00 hour = "1,2"

Toutes les 10 secondes à partir second = "30/10"


de la 30e seconde
Toutes les 10 secondes à partir second = "30,40,50"
de la 30e seconde

Création automatique d’un timer

Le conteneur peut créer automatiquement les timers au moment du déploiement en


utilisant les métadonnées. Il crée un timer pour chaque méthode annotée par @javax.
ejb.Schedule ou @Schedules (ou leur équivalent XML dans le descripteur de déploie-
ment ejb-jar.xml). Par défaut, chaque annotation @Schedule correspond à un seul
timer persistant, mais il est également possible de définir des timers non persistants.
Le Listing  7.20 montre un bean StatisticsEJB qui définit plusieurs méthodes  :
statisticsItemsSold() crée un timer qui appellera la méthode le premier jour de
chaque mois à 05 h 30 ; generateReport() crée deux timers (avec @Schedules) :
l’un pour chaque jour à 02 h 00, l’autre pour chaque mercredi à 14 h 00 ; refres-
hCache() crée un timer non persistant qui rafraîchit le cache toutes les 10 minutes.

Listing 7.20 : Le bean StatisticsEJB enregistre quatre timers


@Stateless
public class StatisticsEJB {

@Schedule(dayOfMonth = "1", hour = "5", minute = "30")

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 261

public void statisticsItemsSold() {


// ...
}

@Schedules({
@Schedule(hour = "2"),
@Schedule(hour = "14", dayOfWeek = "Wed")
})
public void generateReport() {
// ...
}

@Schedule(minute = "*/10", hour = "*", persistent = false)


public void refreshCache() {
// ...
}
}

Création d’un timer par programme

Pour créer un timer par programme, l’EJB doit accéder à l’interface javax.
ejb.TimerService en utilisant soit l’injection de dépendances, soit l’EJBContext
(EJBContext.getTimerService()), soit une recherche JNDI. L’API TimerService
définit plusieurs méthodes permettant de créer quatre sortes de timers :
■■ createTimer crée un timer reposant sur des dates, des intervalles ou des durées.
Ces méthodes n’utilisent pas les expressions calendaires.
■■ createSingleActionTimer crée un timer simple-action qui expire à un instant
donné ou après une certaine durée. Le conteneur supprime le timer après l’appel
à la méthode de rappel.
■■ createIntervalTimer crée un timer intervalle dont la première expiration inter-
vient à un instant donné et les suivantes, après les intervalles indiqués.
■■ createCalendarTimer crée un timer utilisant les expressions calendaires à l’aide
de la classe ScheduleExpression.
La classe ScheduleExpression permet de créer des expressions calendaires par
­programme. Ses méthodes sont liées aux attributs du Tableau  7.2 et permettent
de­ programmer tous les exemples du Tableau 7.4. Voici quelques exemples :
new ScheduleExpression().dayOfMonth("Mon").month("Jan");
new ScheduleExpression().second("10,30,50").minute("*/5").
„ hour("10-14");
new ScheduleExpression().dayOfWeek("1,5").
„ timezone("Europe/Lisbon");

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
262 Java EE 6 et GlassFish 3 

Toutes les méthodes de TimerService (createSingleActionTimer, createCalen-


darTimer, etc.) renvoient un objet Timer contenant des informations sur le timer créé
(date de création, persistant ou non, etc.). Timer permet également à l’EJB d’annu-
ler le timer avant son expiration. Lorsque le timer expire, le conteneur appelle la
méthode annotée par @Timeout correspondante du bean en lui passant l’objet Timer.
Un bean ne peut pas posséder plus d’une méthode @Timeout.
Lorsque CustomerEJB (voir Listing 7.21) ajoute un nouveau client au système (avec
la méthode createCustomer()), il crée également un timer calendaire reposant sur
la date de naissance de ce client : chaque année, le conteneur pourra ainsi déclencher
un bean pour créer et envoyer un courrier électronique afin de souhaiter l’anniver-
saire du client. Pour ce faire, le bean sans état doit d’abord injecter une référence
au service timer (avec @Resource). La méthode createCustomer() stocke le client
dans la base de données et utilise le jour et le mois de sa naissance pour créer un
objet ScheduleExpression qui sert ensuite à créer un timer calendaire avec Timer-
Config – l’appel à new TimerConfig(customer, true) configure un timer persistant
(indiqué par son paramètre true) qui passe l’objet customer représentant le client.

Listing 7.21 : Le bean CustomerEJB crée explicitement un timer


@Stateless
public class CustomerEJB {

@Resource
TimerService timerService;

@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;

public void createCustomer(Customer customer) {


em.persist(customer);
ScheduleExpression birthDay = new ScheduleExpression().
„ dayOfMonth(customer.getBirthDay()).
„ month(customer.getBirthMonth());
timerService.createCalendarTimer(birthDay,
new TimerConfig(customer, true));
}

@Timeout
public void sendBirthdayEmail(Timer timer) {
Customer customer = (Customer) timer.getInfo();
// ...
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 263

Une fois le timer créé, le conteneur invoquera tous les ans la méthode @Timeout
(sendBirthdayEmail()) en lui passant l’objet Timer. Le timer ayant été sérialisé
avec l’objet customer, la méthode peut y accéder en appelant getinfo().

Résumé

Ce chapitre a été consacré aux beans de session et au service timer (les MDB seront
présentés au Chapitre  13, les services web SOAP, au Chapitre  14 et les services
web REST, au Chapitre 15). Les beans de session sont des composants gérés par un
conteneur qui permettent de développer des couches métiers. Il existe trois types de
beans de session : sans état, avec état et singletons. Les beans sans état s’adaptent
facilement car ils ne mémorisent aucune information, sont placés dans un pool et
traitent les tâches qui peuvent être réalisées par un seul appel de méthode. Les beans
avec état sont en relation 1–1 avec un client et peuvent être temporairement ôtés
de la mémoire grâce aux mécanismes de passivation et d’activation. Les singletons
n’ont qu’une seule instance partagée par plusieurs clients et peuvent être initialisés
au lancement de l’application, chaînés ensemble ; en outre, leurs accès concurrents
peuvent s’adapter en fonction des besoins.
Malgré ces différences, tous les beans de session partagent le même modèle de
programmation. Ils peuvent avoir une vue locale, distante ou sans interface, utiliser
des annotations ou être déployés avec un descripteur de déploiement. Les beans de
session peuvent utiliser l’injection de dépendances pour obtenir des références à
plusieurs ressources (sources de données JDBC, contexte persistant, entrées d’envi-
ronnement, etc.) ; ils peuvent également se servir de leur contexte d’environnement
(l’objet SessionContext). Depuis EJB 3.1, vous pouvez appeler des méthodes de
façon asynchrone, rechercher les EJB à l’aide de noms JNDI portables ou utiliser un
conteneur EJB intégré dans l’environnement Java SE. EJB 3.1 a également amélioré
le service timer, qui peut désormais rivaliser avec les autres outils de planification.
Le chapitre suivant présente le cycle de vie des beans de session et explique com-
ment interagir avec les annotations de rappel. Les intercepteurs, qui permettent de
mettre en œuvre la programmation orientée aspect (POA) avec les beans de session,
y sont également détaillés.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
8
Méthodes de rappel
et ­intercepteurs

Le chapitre précédent a montré que les beans sont des composants gérés par un
conteneur. Ils résident dans un conteneur EJB qui encapsule le code métier avec
plusieurs services (injection de dépendances, gestion des transactions, de la sécu-
rité, etc.). La gestion du cycle de vie et l’interception font également partie de ces
services.
Le cycle de vie signifie qu’un bean de session passe par un ensemble d’états bien
précis, qui dépendent du type de bean (sans état, avec état ou singleton). À chaque
phase de ce cycle. le conteneur peut invoquer les méthodes qui ont été annotées
comme méthodes de rappel. Vous pouvez utiliser ces annotations pour initialiser les
ressources de vos beans de session ou pour les libérer avant leur destruction.
Les intercepteurs permettent d’ajouter des traitements transverses à vos beans.
Lorsqu’un client appelle une méthode d’un bean de session, le conteneur peut
intercepter l’appel et traiter la logique métier avant que la méthode du bean ne soit
invoquée.
Ce chapitre présente les différents cycles de vie des beans de session, ainsi que les
annotations de rappel que vous pouvez utiliser pour traiter la logique métier au cours
des différentes phases. Nous verrons également comment intercepter les appels de
méthodes et les encapsuler par notre propre code.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
266 Java EE 6 et GlassFish 3 

Cycles de vie des beans de session

Comme nous l’avons vu au chapitre précédent, un client ne crée pas une instance
d’un bean de session à l’aide de l’opérateur new : il obtient une référence à ce bean
via l’injection de dépendances ou par une recherche JNDI. C’est le conteneur qui
crée l’instance et qui la détruit, ce qui signifie que ni le client ni le bean ne sont res-
ponsables du moment où l’instance est créée, où les dépendances sont injectées et
où l’instance est supprimée. La responsabilité de la gestion du cycle de vie du bean
incombe au conteneur.
Tous les beans de session passent par deux phases évidentes de leur cycle de vie :
leur création et leur destruction. En outre, les beans avec état passent par les phases
de passivation et d’activation que nous avons décrites au chapitre précédent.

Beans sans état et singletons

Les beans sans état et singletons partagent la caractéristique de ne pas mémoriser


l’état conversationnel avec leur client et d’autoriser leur accès par n’importe quel
client – les beans sans état le font en série, instance par instance, alors que les single-
tons fournissent un accès concurrent à une seule instance. Tous les deux partagent le
cycle de vie suivant, représenté par la Figure 8.1 :
1. Le cycle de vie commence lorsqu’un client demande une référence au bean (par
injection de dépendances ou par une recherche JNDI). Le conteneur crée alors
une nouvelle instance de bean de session.
2. Si cette nouvelle instance utilise l’injection de dépendances via des annota-
tions (@Resource, @EJB, @PersistenceContext, etc.) ou des descripteurs de
déploiement, le conteneur injecte toutes les ressources nécessaires.
3. Si l’instance contient une méthode annotée par @PostConstruct, le conteneur
l’appelle.
4. L’instance traite l’appel du client et reste prête pour traiter les appels suivants.
Les beans sans état restent prêts jusqu’à ce que le conteneur libère de la place
dans le pool, les singletons restent prêts jusqu’à la terminaison du conteneur.
5. Le conteneur n’a plus besoin de l’instance. Si celle-ci contient une méthode
annotée par @PreDestroy, il l’appelle et met fin à l’instance.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 267

Figure 8.1 N'existe pas


Cycle de vie des beans
sans état et singletons. 1. Nouvelle instance
2. Injection de dépendances 5. @PreDestroy
3. @PostConstruct

Prêt

4. Appel de méthode

Bien qu’ils partagent le même cycle de vie, les beans sans état et singletons ne sont
pas créés et détruits de la même façon.
Lorsqu’un bean de session sans état est déployé, le conteneur en crée plusieurs ins-
tances et les place dans un pool. Quand un client appelle une méthode de ce bean,
le conteneur choisit une instance dans le pool, lui délègue l’appel de méthode et la
replace dans le pool. Lorsque le conteneur n’a plus besoin de l’instance (parce que,
par exemple, il veut réduire le nombre d’instances du pool), il la supprime.

INFO

GlassFish permet de paramétrer le pool des EJB. Vous pouvez ainsi fixer une taille (nombre
initial, minimal et maximal de beans dans le pool), le nombre de beans à supprimer du pool
lorsque son temps d’inactivité a expiré et le nombre de millisecondes du délai d’expiration
du pool.

La création des beans singletons varie selon qu’ils ont été instanciés dès le démar-
rage (@Startup) ou non, ou qu’ils dépendent (@DependsOn) d’un autre singleton déjà
créé  : dans ce cas, une instance sera créée au moment du déploiement  ; sinon le
conteneur créera l’instance lorsqu’un client appellera une méthode métier. Comme
les singletons durent tout le temps de l’application, leur instance n’est détruite que
lorsque le conteneur se termine.

Beans avec état

Du point de vue du programme, les beans de session avec état ne sont pas très
­différents des beans sans état ou singletons : seules leurs métadonnées changent
(@Stateful au lieu de @Stateless ou @Singleton). La véritable différence réside
dans le fait que les beans avec état mémorisent l’état conversationnel avec leurs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
268 Java EE 6 et GlassFish 3 

clients et qu’ils ont donc un cycle de vie légèrement différent. Le conteneur produit
une instance et ne l’affecte qu’à un seul client. Ensuite, chaque requête de ce client
sera transmise à la même instance. Selon ce principe et en fonction de l’application,
il peut finalement s’établir une relation 1–1 entre un client et un bean avec état (un
millier de clients simultanés peuvent produire un millier de beans avec état). Si un
client n’invoque pas son instance de bean au cours d’une période suffisamment lon-
gue, le conteneur doit le supprimer avant que la JVM ne soit à court de mémoire,
préserver l’état de cette instance dans une zone de stockage permanente, puis la
rappeler lorsque son état redevient nécessaire. Pour ce faire, le conteneur utilise le
mécanisme de passivation et activation.
Comme on l’a expliqué au Chapitre 7, la passivation consiste à sérialiser l’instance
du bean sur un support de stockage permanent (fichier sur disque, base de données,
etc.) au lieu de la maintenir en mémoire. L’activation, qui est l’opération opposée, a
lieu lorsque l’instance est redemandée par le client. Le conteneur désérialise alors le
bean et le replace en mémoire. Ceci signifie donc que les attributs du bean doivent
être sérialisables (donc être d’un type Java primitif ou qui implémente l’interface
java.io.Serializable). Le cycle de vie d’un bean avec état passe donc par les
étapes suivantes, qui sont représentées par la Figure 8.2 :
1. Le cycle de vie démarre lorsqu’un client demande une référence au bean (soit
par injection de dépendances, soit par une recherche JNDI) : le conteneur crée
alors une nouvelle instance du bean de session et la stocke en mémoire.
2. Si la nouvelle instance utilise l’injection de dépendances via des annotations
(@Resource, @EJB, @PersistenceContext, etc.) ou des descripteurs de déploie-
ment, le conteneur injecte les ressources nécessaires.
3. Si l’instance contient une méthode annotée par @PostConstruct, le conteneur
l’appelle.
4. Le bean exécute l’appel demandé et reste en mémoire en attente d’autres
requêtes du client.
5. Si le client reste inactif pendant un certain temps, le conteneur appelle la
méthode annotée par @PrePassivate, s’il y en a une, et stocke le bean sur un
support de stockage permanent.
6. Si le client appelle un bean qui a été passivé, le conteneur le replace en mémoire
et appelle la méthode annotée par @PostActivate, s’il y en a une.
7. Si le client n’invoque pas une instance passivée avant la fin du délai d’expiration
de la session, le conteneur supprime cette instance.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 269

8. Alternative à l’étape 7 : si le client appelle une méthode annotée par @Remove,
le conteneur invoque alors la méthode annotée par @PreDestroy, s’il y en a une,
et met fin au cycle de vie de l’instance.

Figure 8.2 N'existe pas


7. Expiration du délai

Cycle de vie
d’un bean avec état. 1. Nouvelle instance
2. Injection de dépendances 8. @Remove et @Predestroy
3. @PostConstruct

6. PostActivate
Prêt Passivé
5. @PrePassivate

4. Appel de méthode

Dans certains cas, un bean avec état contient des ressources ouvertes comme
des sockets ou des connexions de bases de données. Un conteneur ne pouvant
garder ces ressources ouvertes pour chaque bean, vous devez fermer et rouvrir
ces ressources avant et après la passivation : c’est là que les méthodes de rappel
interviennent.

Méthodes de rappel

Comme nous venons de le voir, le cycle de vie de chaque bean de session est géré par
son conteneur. Ce dernier permet de greffer du code métier aux différentes phases de
ce cycle : les passages d’un état à l’autre sont alors interceptés par le conteneur, qui
appellera les méthodes annotées par l’une des annotations du Tableau 8.1.

Tableau 8.1 : Annotations de rappel du cycle de vie

Annotation Description
@PostConstruct Indique la méthode à appeler immédiatement après la création de
l’instance et l’injection de dépendances par le conteneur. Cette méthode
sert le plus souvent à réaliser les initialisations.
@PreDestroy Indique la méthode à appeler immédiatement avant la suppression de
l’instance par le conteneur. Cette méthode sert le plus souvent à libérer
les ressources utilisées par le bean. Dans le cas des beans avec état, cette
méthode est appelée après la fin de l’exécution de la méthode annotée
par @Remove.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
270 Java EE 6 et GlassFish 3 

Annotation Description
@PrePassivate Indique la méthode à appeler avant que le conteneur passive
l’instance. Elle donne généralement l’occasion au bean de se préparer
à la sérialisation et de libérer les ressources qui ne peuvent pas être
sérialisées (connexions à une base de données, serveur de message,
socket réseau, etc.).
@PostActivate Indique la méthode à appeler immédiatement après la réactivation de
l’instance par le conteneur. Elle lui permet de réinitialiser les ressources
qu’il a fermées au cours de la passivation.

INFO

Les annotations @PrePassivate et @PostActivate sont définies dans le paquetage javax.


ejb et font partie de la spécification EJB 3.1 (JSR 318). @PostConstruct et @PreDestroy font
partie de la spécification Common Annotations 1.0 (JSR 250) et proviennent du paquetage
javax.annotation (comme @Resource et les annotations concernant la sécurité, que nous
présenterons au chapitre suivant).

Une méthode de rappel doit avoir la signature suivante :


void <nom-méthode>();

et respecter les règles suivantes :


■■ Elle ne doit pas prendre de paramètres et doit renvoyer void.
■■ Elle ne doit pas lancer d’exception contrôlée, mais elle peut déclencher une
exception runtime  : dans ce cas, si une transaction est en cours, celle-ci sera
annulée (voir chapitre suivant).
■■ Elle peut avoir un accès public, private, protected ou de niveau paquetage,
mais ne peut être ni static ni final.
■■ Elle peut être annotée par plusieurs annotations (la méthode init() du Lis-
ting  8.2, par exemple, est annotée par @PostConstruct et @PostActivate).
Cependant, il ne peut y avoir qu’une seule annotation du même type dans le bean
(il ne peut pas exister deux annotations @PostConstruct dans le même bean de
session, par exemple).
■■ Elle peut accéder aux entrées d’environnement du bean (voir la section "Contexte
de nommage de l’environnement" du Chapitre 7).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 271

Ces méthodes de rappel servent généralement à allouer et/ou à libérer les ressources
du bean. Le Listing 8.1, par exemple, montre que le bean singleton CacheEJB utilise
une annotation @PostConstruct pour initialiser son cache : immédiatement après la
création de l’unique instance de ce bean, le conteneur invoquera donc la méthode
initCache().

Listing 8.1 : Singleton initialisant son cache avec l’annotation @PostConstruct

@Singleton
public class CacheEJB {

private Map<Long, Object> cache = new HashMap<Long, Object>();

@PostConstruct
private void initCache() {
// Initialise le cache
}

public Object getFromCache(Long id) {


if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}

Le Listing  8.2 présente un extrait de code pour un bean avec état. Le conteneur
gère l’état conversationnel, qui peut contenir des ressources importantes comme une
connexion à une base de données. L’ouverture d’une telle connexion étant coûteuse,
elle devrait être partagée par tous les appels, mais libérée lorsque le bean est inactif
(ou passivé).
Après la création de l’instance du bean, le conteneur injecte la référence d’une
source de données dans l’attribut ds. Il pourra ensuite appeler la méthode annotée
par @PostConstruct (init()), qui crée une connexion vers une base de données. Si
le conteneur passive l’instance, la méthode close() (annotée par @PrePassivate)
sera d’abord invoquée afin de fermer la connexion JDBC, qui ne sert plus pendant
la passivation. Lorsque le client appelle une méthode métier du bean, le conteneur
l’active et appelle à nouveau la méthode init() (car elle est également annotée par
@PostActivate). Si le client invoque la méthode checkout() (annotée par @Remove),
le conteneur supprime l’instance après avoir appelé à nouveau la méthode close()
(car elle est aussi annotée par @PreDestroy).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
272 Java EE 6 et GlassFish 3 

Listing 8.2 : Bean avec état, initialisant et libérant des ressources


@Stateful
public class ShoppingCartEJB {

@Resource private DataSource ds;

private Connection connection;


private List<Item> cartItems = new ArrayList<Item>();

@PostConstruct
@PostActivate
private void init() {
connection = ds.getConnection();
}

@PreDestroy
@PrePassivate
private void close() {
connection.close();
}

// ...

@Remove
public void checkout() {
cartItems.clear();
}
}

Pour plus de lisibilité, la gestion des exceptions SQL a été omise dans les méthodes
de rappel.

Intercepteurs

Avant de présenter les intercepteurs, passons un peu de temps à évoquer la program-


mation orientée aspect (POA). La POA est un paradigme de programmation qui
sépare les traitements transverses (ceux qui apparaissent partout dans l’application)
du code métier. La plupart des applications contiennent du code qui se répète dans
tous les composants. Il peut s’agir de traitements techniques (enregistrer l’entrée et
la sortie de chaque méthode, la durée d’un appel de méthode, les statistiques d’uti-
lisation d’une méthode, etc.) ou de traitements métiers (effectuer des vérifications
supplémentaires si un client achète pour plus de 10 000 € d’articles, envoyer une
demande de réapprovisionnement lorsque l’inventaire est trop bas, etc.). Avec la
POA, ces traitements peuvent s’appliquer automatiquement à toute l’application ou
uniquement à un sous-ensemble de celle-ci.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 273

Les EJB permettent d’utiliser la POA en fournissant la possibilité d’intercepter les


appels de méthodes à l’aide d’intercepteurs, qui seront automatiquement déclen-
chés par le conteneur lorsqu’une méthode EJB est invoquée. Comme le montre la
Figure 8.3, les intercepteurs peuvent être chaînés et sont appelés avant et/ou après
l’exécution d’une méthode.

INFO

Les intercepteurs s’appliquent aux beans de session et aux beans pilotés par messages (MDB).
Aux Chapitres 14 et 15, nous verrons qu’un service web SOAP ou REST peut également être
implémenté comme un EJB (en ajoutant l’annotation @Stateless). En ce cas, ces services
web peuvent également utiliser des intercepteurs.

La Figure  8.3 montre que plusieurs intercepteurs sont appelés entre le client et
l’EJB. En fait, vous pouvez considérer qu’un conteneur EJB est lui-même une
chaîne d’intercepteurs : lorsque vous développez un bean de session, vous vous
concentrez sur le code métier mais, en coulisse, le conteneur intercepte les appels
de méthodes effectués par le client et applique différents services (gestion du cycle
de vie, transactions, sécurité, etc.). Grâce aux intercepteurs, vous pouvez ajou-
ter vos propres traitements transverses et les appliquer au code métier de façon
transparente.

<<executionEnvironment>>
Conteneur EJB

<<component>> Intercepteur 1 Intercepteur n <<component>>


Client Bean de session

Figure 8.3
Conteneur interceptant un appel et invoquant un intercepteur.

Il existe trois types d’intercepteurs (que nous décrirons dans la section suivante) :
■■ intercepteurs autour des appels ;
■■ intercepteurs des méthodes métiers ;
■■ intercepteurs des méthodes de rappel du cycle de vie.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
274 Java EE 6 et GlassFish 3 

INFO

La spécification EJB 3.1 (JSR 318) est composée de deux documents  : la spécification EJB
centrale et le document énonçant les besoins des intercepteurs. Ce dernier définit le fonc-
tionnement des intercepteurs et la façon de les utiliser. La raison de cette séparation est liée
au fait que, dans de prochaines versions, les intercepteurs pourraient être traités indépen-
damment des EJB.

Intercepteurs autour des appels

Le moyen le plus simple de définir un intercepteur consiste à ajouter une anno-


tation @javax.interceptor.AroundInvoke (ou l’élément de déploiement <around-
invoke>) dans le bean lui-même, comme dans le Listing 8.3. CustomerEJB annote la
méthode logMethod(), qui sert à enregistrer un message lorsque l’on entre dans une
méthode et un autre lorsqu’on en sort. Lorsque cet EJB est déployé, tous les appels
aux méthodes createCustomer() ou findCustomerById() seront interceptés et le
code de logMethod() s’appliquera. Notez que la portée de cet intercepteur est limi-
tée au bean. Les intercepteurs autour des appels n’interviennent que dans la même
transaction et dans le même contexte de sécurité que la méthode pour laquelle ils
s’interposent.

Listing 8.3 : CustomerEJB utilise un intercepteur


@Stateless
public class CustomerEJB {

@PersistenceContext(unitName = "chapter08PU")
private EntityManager em;
private Logger logger = Logger.getLogger("com.apress.javaee6");

public void createCustomer(Customer customer) {


em.persist(customer);
}

public Customer findCustomerById(Long id) {


return em.find(Customer.class, id);
}

@AroundInvoke
private Object logMethod(InvocationContext ic)
throws Exception {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 275

logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed();
} finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());

}
}

Bien qu’elle soit annotée par @AroundInvoke, la méthode logMethod() doit avoir
une signature bien précise :
@AroundInvoke
Object <nom-méthode>(InvocationContext ic) throws Exception;
Une méthode intercepteur autour des appels doit respecter les règles suivantes :
■■ Elle peut être public, private, protected ou avoir un accès paquetage, mais ne
peut pas être static ou final.
■■ Elle doit avoir un paramètre javax.interceptor.InvocationContext et renvoyer
un Object, qui est le résultat de l’appel de la méthode cible (si cette méthode
­renvoie void, cet objet vaudra null).
■■ Elle peut lever une exception contrôlée.
L’objet InvocationContext permet aux intercepteurs de contrôler le comporte-
ment de la chaîne des appels. Lorsque plusieurs intercepteurs sont chaînés, c’est
la même instance d’InvocationContext qui est passée à chacun d’eux, ce qui peut
impliquer un traitement de ces données contextuelles par les autres intercepteurs.
Le Tableau 8.2 décrit l’API d’InvocationContext.

Tableau 8.2 : Définition de l’interface InvocationContext

Méthode Description
getContextData Permet de passer des valeurs entre les mêmes méthodes intercepteurs
dans la même instance d’InvocationContext à l’aide d’une Map.
getMethod Renvoie la méthode du bean pour laquelle l’intercepteur a été
invoqué.
getParameters Renvoie les paramètres qui seront utilisés pour invoquer la méthode
métier.
getTarget Renvoie l’instance du bean à laquelle appartient la méthode
interceptée.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
276 Java EE 6 et GlassFish 3 

Méthode Description
getTimer Renvoie le timer associé à une méthode @Timeout.
proceed Appelle la méthode intercepteur suivante de la chaîne. Renvoie le
résultat de la méthode suivante. Si une méthode est de type void,
proceed renvoie null.

setParameters Modifie la valeur des paramètres utilisés pour l’appel de la méthode


cible. Si les types et le nombre de paramètres ne correspondent pas à
la signature de la méthode, l’exception IllegalArgumentException
est levée.

Pour expliquer le fonctionnement du code du Listing 8.3, examinons le diagramme


de séquence de la Figure 8.4 pour voir ce qui se passe lorsqu’un client invoque la
méthode createCustomer(). Tout d’abord, le conteneur intercepte cet appel et, au
lieu d’exécuter directement createCustomer(), appelle la méthode logMethod().
Celle-ci utilise l’interface InvocationContext pour obtenir le nom du bean (ic.
getTarget()) et de la méthode (ic.getMethod()) appelés afin de produire un mes-
sage (logger.entering()). Puis logMethod() appelle la méthode Invocation-
Context.proceed(), qui lui indique qu’elle doit passer à l’intercepteur suivant ou
appeler la méthode métier du bean. Cet appel est très important car, sans lui, la
chaîne des intercepteurs serait rompue et la méthode métier ne serait pas appelée.
Enfin, la méthode createCustomer() est finalement exécutée – lorsqu’elle se ter-
mine, l’intercepteur termine son exécution en enregistrant un message (logger.
exiting()). La même séquence se serait produite si un client avait appelé la méthode
findCustomerById().

Figure 8.4 Client Conteneur CustomerEJB Logger InvocationContext


Chaînage 1 : createCustomer
de différents 2 : logMethod
3 : entering
intercepteurs.

4 : proceed
5 : createCustomer
6 : exiting

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 277

Intercepteurs de méthode

Le Listing  8.3 définit un intercepteur qui n’est disponible que pour CustomerEJB
mais, la plupart du temps, on souhaite isoler un traitement transverse dans une classe
distincte et demander au conteneur d’intercepter les appels de méthodes de plusieurs
beans de session. L’enregistrement de journaux est un exemple typique de situation
dans laquelle on veut enregistrer les entrées et les sorties de toutes les méthodes de
tous les EJB. Pour disposer de ce type d’intercepteur, on doit créer une classe dis-
tincte et informer le conteneur de l’appliquer à un bean précis ou à une méthode de
bean particulière.
Le Listing 8.4 isole la méthode logMethod() du Listing 8.3 dans une classe à part,
LoggingInterceptor, qui est un simple POJO disposant d’une méthode annotée par
@AroundInvoke.

Listing 8.4 : Classe intercepteur enregistrant l’entrée et la sortie d’une méthode


public class LoggingInterceptor {

private Logger logger = Logger.getLogger("com.apress.javaee6");

@AroundInvoke
public Object logMethod(InvocationContext ic) throws Exception {
logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed();
} finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());
}
}
}

LoggingInterceptor peut maintenant être utilisée de façon transparente par n’im-


porte quel EJB souhaitant disposer d’un intercepteur. Pour ce faire, le bean doit
informer le conteneur avec l’annotation @javax.interceptor.Interceptors. Dans
le Listing 8.5, cette annotation est placée sur la méthode createCustomer(), ce qui
signifie que tout appel à cette méthode sera intercepté par le conteneur qui invoquera
la classe .LoggingInterceptor (pour enregistrer un message signalant l’entrée et la
sortie de la méthode).

Listing 8.5 : CustomerEJB utilise un intercepteur sur une méthode


@Stateless
public class CustomerEJB {
@PersistenceContext(unitName = "chapter08PU")

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
278 Java EE 6 et GlassFish 3 

private EntityManager em;

@Interceptors(LoggingInterceptor.class)
public void createCustomer(Customer customer) {
em.persist(customer);
}

public Customer findCustomerById(Long id) {


return em.find(Customer.class, id);
}
}

Dans le Listing 8.5, @Interceptors n’est attachée qu’à la méthode createCustomer(),


ce qui signifie que le conteneur n’interceptera pas un appel à findCustomerById(). Si
vous voulez que ces deux méthodes soient interceptées, vous pouvez placer l’anno-
tation @Interceptors sur chacune de ces méthodes ou sur le bean lui-même (dans ce
dernier cas, l’intercepteur sera déclenché pour toutes les méthodes du bean) :
@Stateless
@Interceptors(LoggingInterceptor.class)
public class CustomerEJB {
public void createCustomer(Customer customer) { ... }
public Customer findCustomerById(Long id) { ... }
}

Si vous voulez que toutes les méthodes, sauf une, soient interceptées, utilisez l’an-
notation javax.interceptor.ExcludeClassInterceptors pour exclure la méthode
concernée. Dans le code suivant, l’appel à updateCustomer() ne sera pas intercepté
alors que les appels à toutes les autres méthodes le seront :
@Stateless
@Interceptors(LoggingInterceptor.class)
public class CustomerEJB {
public void createCustomer(Customer customer) { ... }
public Customer findCustomerById(Long id) { ... }
public void removeCustomer(Customer customer) { ... }
@ExcludeClassInterceptors
public Customer updateCustomer(Customer customer) { ... }
}

Intercepteur du cycle de vie

Dans la première partie de ce chapitre, nous avons vu comment gérer les méthodes
de rappel dans un EJB. Avec une annotation de rappel, vous pouvez demander au
conteneur d’appeler une méthode lors d’une phase précise du cycle de vie (@Post-
Construct, @PrePassivate, @PostActivate et @PreDestroy). Si vous souhaitez, par
exemple, ajouter une entrée dans un journal à chaque fois qu’une instance d’un bean

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 279

est créée, il suffit de placer l’annotation @PostConstruct sur une méthode du bean
et d’ajouter un peu de code pour enregistrer l’entrée dans le journal. Mais comment
faire pour capturer les événements du cycle de vie entre plusieurs types de beans ?
Les intercepteurs du cycle de vie permettent d’isoler du code dans une classe et de
l’invoquer lorsque l’un de ces événements se déclenche.
Les intercepteurs du cycle de vie ressemblent à ce que nous venons de voir dans
le Listing 8.4, sauf que les méthodes utilisent des annotations de rappel au lieu de
@AroundInvoke. Le Listing 8.6 présente une classe ProfileInterceptor avec deux
méthodes : logMethod(), qui sera appelée après la construction d’une instance et
profile(), qui sera invoquée avant la destruction d’une instance.

Listing 8.6 : Intercepteur du cycle de vie définissant deux méthodes


public class ProfileInterceptor {

private Logger logger = Logger.getLogger("com.apress.javaee6");

@PostConstruct
public void logMethod(InvocationContext ic) {
logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed(); }
finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());
}
}

@PreDestroy
public void profile(InvocationContext ic) {
long initTime = System.currentTimeMillis();
try {
return ic.proceed();
} finally {
long diffTime = System.currentTimeMillis() - initTime;
logger.fine(ic.getMethod() + " took " + diffTime +
" millis");
}
}
}

Comme vous pouvez le voir dans le Listing 8.6, les méthodes intercepteurs du cycle
de vie prennent en paramètre un objet InvocationContext, renvoient void au lieu
d’Object (car, comme on l’a expliqué dans la section "Méthodes de rappel", les
méthodes de rappel du cycle de vie renvoient void) et ne peuvent pas lancer d’excep-
tions contrôlées.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
280 Java EE 6 et GlassFish 3 

Pour appliquer l’intercepteur du Listing 8.6, le bean de session doit utiliser l’anno-


tation @Interceptors : dans le Listing 8.7, CustomerEJB précise qu’il s’agit de la
classe ProfileInterceptor. Dès lors, quand l’EJB sera instancié par le conteneur,
la méthode logMethod() de l’intercepteur sera invoquée avant la méthode init().
Les appels aux méthodes createCustomer() ou findCustomerById() ne seront en
revanche pas interceptés, mais la méthode profile() de l’intercepteur sera appelée
avant que le CustomerEJB soit détruit par le conteneur.

Listing 8.7 : CustomerEJB utilisant un intercepteur de rappel


@Stateless
@Interceptors(ProfileInterceptor.class)
public class CustomerEJB {

@PersistenceContext(unitName = "chapter08PU")
private EntityManager em;

@PostConstruct
public void init() {
// ...
}

public void createCustomer(Customer customer) {


em.persist(customer);
}

public Customer findCustomerById(Long id) {


return em.find(Customer.class, id);
}
}

Les méthodes de rappel du cycle de vie et les méthodes @AroundInvoke peuvent être
définies dans la même classe intercepteur.

Chaînage et exclusion d’intercepteurs

Nous venons de voir comment intercepter les appels dans un seul bean (avec
@AroundInvoke) et entre plusieurs beans (avec @Interceptors). EJB  3.1 permet
également de chaîner plusieurs intercepteurs et de définir des intercepteurs par
défaut qui s’appliqueront à tous les beans de session.
En fait, il est possible d’attacher plusieurs intercepteurs avec l’annotation @Inter-
ceptors en lui passant en paramètre une liste d’intercepteurs séparés par des vir-
gules. En ce cas, l’ordre dans lequel ils seront invoqués est déterminé par leur ordre
d’apparition dans cette liste. Le code du Listing 8.8, par exemple, utilise @Intercep-
tors à la fois au niveau du bean et au niveau des méthodes.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et ­intercepteurs 281

Listing 8.8 : CustomerEJB utilisant un intercepteur de rappel


@Stateless
@Interceptors(I1.class, I2.class)
public class CustomerEJB {
public void createCustomer(Customer customer) { ... }
@Interceptors(I3.class, I4.class)
public Customer findCustomerById(Long id) { ... }
public void removeCustomer(Customer customer) { ... }
@ExcludeClassInterceptors
public Customer updateCustomer(Customer customer) { ... }
}

Aucun intercepteur ne sera invoqué lorsqu’un client appelle la méthode updateCus-


tomer() (car elle est annotée par @ExcludeClassInterceptors). Lorsque crea-
teCustomer() est appelée, l’intercepteur I1 s’exécutera, suivi de l’intercepteur I2.
Lorsque findCustomerById() est appelée, les intercepteurs I1, I2, I3 et I4 seront
exécutés dans cet ordre.
Outre les intercepteurs au niveau des méthodes et des classes, EJB 3.1 permet de
créer des intercepteurs par défaut, qui seront utilisés pour toutes les méthodes de
tous les EJB d’une application. Aucune annotation n’ayant la portée d’une applica-
tion, ces intercepteurs doivent être définis dans le descripteur de déploiement (ejb-
jar.xml). Voici, par exemple, la partie XML à ajouter à ce fichier pour appliquer par
défaut l’intercepteur ProfileInterceptor à tous les EJB :
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>
com.apress.javaee6.ProfileInterceptor
</interceptor-class>
</interceptor-binding>
</assembly-descriptor>

Le caractère joker * dans l’élément <ejb-name> signifie que tous les EJB applique-
ront l’intercepteur défini dans l’élément <interceptor-class>. Si vous déployez le
bean CustomerEJB du Listing 8.7 avec cet intercepteur par défaut, le ProfileInter-
ceptor sera invoqué avant tous les autres intercepteurs.

Si plusieurs types d’intercepteurs sont définis pour un même bean de session, le


conteneur les applique dans l’ordre décroissant des portées : le premier sera donc
l’intercepteur par défaut et le dernier, l’intercepteur de méthode. Les règles qui gou-
vernent ces appels sont décrites à la Figure 8.5.
Pour désactiver les intercepteurs par défaut pour un EJB spécifique, il suffit d’ap-
pliquer l’annotation @javax. interceptor.ExcludeDefaultInterceptors sur la
classe ou sur les méthodes, comme le montre le Listing 8.9.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
282 Java EE 6 et GlassFish 3 

<<executionEnvironment>>
Conteneur EJB

<<component>> Intercepteur Intercepteur Intercepteur <<component>>


Client par défaut de classe de méthode Bean de session

Figure 8.5
Chaînage des différents types d’intercepteurs.

Listing 8.9 : EJB excluant les intercepteurs par défaut


@Stateless
@ExcludeDefaultInterceptors @Interceptors(LoggingInterceptor.class)
public class CustomerEJB {
public void createCustomer(Customer customer) { ... }
public Customer findCustomerById(Long id) { ... }
public void removeCustomer(Customer customer) { ... }
@ExcludeClassInterceptors
public Customer updateCustomer(Customer customer) { ... }
}

Résumé

Dans ce chapitre, nous avons vu que les beans de session sans état et singletons par-
tagent le même cycle de vie et que celui des beans avec état est légèrement différent.
En effet, ces derniers mémorisent l’état conversationnel avec leur client et doivent
temporairement sérialiser cet état sur un support de stockage permanent (passiva-
tion). Nous avons également vu que les annotations de rappel permettent d’ajouter
de la logique métier aux beans, qui s’exécutera avant ou après la survenue d’un
événement (@PostConstruct, @PreDestroy, etc.).
Les intercepteurs sont des mécanismes permettant de mettre en œuvre la POA avec les
EJB car ils permettent au conteneur d’invoquer des traitements transverses sur l’appli-
cation. Ils sont simples à utiliser, puissants et peuvent être chaînés pour appliquer plu-
sieurs traitements à la suite. Il est également possible de définir des intercepteurs par
défaut qui s’appliqueront à toutes les méthodes de tous les beans d’une application.
Un conteneur EJB peut lui-même être considéré comme une chaîne d’intercepteurs :
les appels de méthodes sont interceptés par le conteneur, qui applique alors plusieurs
services comme la gestion des transactions et de la sécurité. Le chapitre suivant est
consacré à ces deux services.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
9
Transactions et sécurité

La gestion des transactions et de la sécurité est un problème important pour les


entreprises car elle permet aux applications de disposer de données cohérentes et
de sécuriser les accès à ces données. Ces deux services sont des traitements de bas
niveau dont ne devraient pas se soucier ceux qui développent du code métier. Ils sont
offerts par les EJB de façon très simple : soit par programmation à un haut niveau
d’abstraction, soit de façon déclarative en utilisant les métadonnées.
L’essentiel du travail d’une application d’entreprise consiste à gérer des données :
à les stocker (généralement dans une base de données), à les récupérer, à les traiter,
etc. Ces traitements sont souvent réalisés simultanément par plusieurs applications
qui tentent d’accéder aux mêmes données. Les SGBDR disposent de mécanismes
de bas niveau pour synchroniser les accès concurrents – le verrouillage pessimiste,
par exemple – et utilisent les transactions pour garantir la cohérence des données.
Les EJB utilisent tous ces mécanismes.
La sécurisation des données est également un point important. La couche métier doit
agir comme un pare-feu et autoriser certaines opérations à certains groupes d’utili-
sateurs tout en interdisant l’accès à d’autres (les utilisateurs et les employés peuvent
lire les données, mais seuls les employés sont autorisés à les stocker, par exemple).
La première partie de ce chapitre est consacrée à la gestion des transactions avec EJB 3.1.
Nous présenterons les transactions en général, puis les différents types de transactions
reconnus par les EJB. La seconde partie du chapitre s’intéressera à la sécurité.

Transactions
Les données sont cruciales et elles doivent être correctes, quelles que soient les
opérations effectuées et le nombre d’applications qui y accèdent. Une transaction

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
284 Java EE 6 et GlassFish 3 

sert à garantir que les données resteront dans un état cohérent. Elle représente un
groupe logique d’opérations qui doivent être exécutées de façon atomique – elle
forme donc ce que l’on appelle une unité de traitement. Les opérations qui la consti-
tuent peuvent impliquer le stockage de données dans une ou plusieurs bases, l’envoi
de messages ou l’appel de services web. Les sociétés utilisent quotidiennement les
transactions pour les applications bancaires ou de commerce en ligne, ainsi que pour
les interactions B2B (business-to-business) avec leurs partenaires.
Ces opérations métiers indivisibles s’exécutent en séquence ou en parallèle pendant
une durée relativement courte. Pour qu’une transaction réussisse, toutes ses opéra-
tions doivent réussir (on dit alors que la transaction est validée – committed). Il suf-
fit que l’une des opérations échoue pour que la transaction échoue également (la
transaction est annulée – rolled back). Les transactions doivent garantir un certain
niveau de fiabilité et de robustesse et respecter les propriétés ACID.

ACID

ACID est un acronyme des quatre propriétés qui définissent une transaction fiable :
atomicité, cohérence, isolement et durée (voir Tableau 9.1). Pour expliquer chacune
d’elles, prenons l’exemple classique d’un transfert bancaire dans lequel on débite un
compte épargne pour créditer un compte courant.

Tableau 9.1 : Propriétés ACID

Propriété Description
Atomicité Une transaction est composée d’une ou de plusieurs opérations regroupées dans
une unité de traitement. À la fin de la transaction, soit toutes les opérations se
sont déroulées correctement (transaction validée – commit), soit il s’est passé un
problème inattendu, auquel cas aucune ne sera réalisée (transaction annulée –
rollback).
Cohérence À la fin d’une transaction, les données sont dans un état cohérent.
Isolement L’état intermédiaire d’une transaction n’est pas visible aux applications externes.
Durée Lorsqu’une transaction est validée, les modifications apportées aux données sont
visibles aux autres applications.

On peut imaginer que le transfert d’un compte vers un autre représente une suite
d’accès à la base de données : le compte épargne est débité à l’aide d’une instruction
update de SQL, le compte courant est crédité par une autre instruction update et un

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 285

enregistrement est ajouté dans une autre table afin de garder la trace de ce transfert.
Ces opérations doivent s’effectuer dans la même unité de traitement (atomicité) car
il ne faut pas que le débit ait lieu et qu’il n’y ait pas de crédit correspondant. Du
point de vue d’une application externe interrogeant les comptes, les deux opérations
ne seront visibles que lorsqu’elles se seront toutes les deux correctement réalisées
(isolement). Lorsque la transaction est validée ou annulée, la cohérence des données
est assurée par les contraintes d’intégrité de la base de données (clés primaires,
relations ou champs). Lorsque le transfert est terminé, il est possible d’accéder aux
données par les autres applications (durée).

Transactions locales

Pour que les transactions fonctionnent et respectent les propriétés ACID, plusieurs
composants doivent être mis en place. Commençons par l’exemple le plus simple qui
soit d’une application effectuant plusieurs modifications sur une ressource unique
(une base de données, par exemple). Lorsqu’une seule ressource transactionnelle est
nécessaire, il suffit d’utiliser une transaction locale – on peut utiliser des transac-
tions distribuées à la JTA, mais ce n’est pas strictement nécessaire. La Figure 9.1
représente l’interaction entre une application et une ressource via un gestionnaire de
transactions et un gestionnaire de ressources.

Figure 9.1
Application
Transaction n’impliquant
qu’une seule ressource. JTA
Gestionnaire
de transactions
JTA
Gestionnaire
de ressources

Ressource

Les composants présentés à la Figure  9.1 permettent d’abstraire de l’application


l’essentiel du traitement spécifique à une transaction :
■■ Le gestionnaire de transactions est le composant central de la gestion des opé-
rations transactionnelles. Il crée les transactions pour le compte de l’application,
informe le gestionnaire de ressources qu’il participe à une transaction (opération

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
286 Java EE 6 et GlassFish 3 

de recrutement) et conduit la validation ou l’annulation de cette transaction sur


ce gestionnaire de ressources.
■■ Le gestionnaire de ressources s’occupe de gérer les ressources et de les enregis-
trer auprès du gestionnaire de transactions. Un pilote de SGBDR, une ressource
JMS ou un connecteur Java sont des gestionnaires de ressources.
■■ La ressource est le support de stockage persistant sur lequel on lit ou écrit (une
base de données, etc.).
L’application n’est pas responsable du respect des propriétés ACID : elle se borne
simplement à décider s’il faut valider ou annuler la transaction, et c’est le gestion-
naire de transactions qui prépare toutes les ressources pour que tout se passe bien.
Avec Java EE, ces composants gèrent les transactions via JTA (Java Transaction
API), qui est décrite par la JSR 907. JTA définit un ensemble d’interfaces permettant
à l’application de délimiter des frontières de transactions, ainsi que des API pour
fonctionner avec le gestionnaire de transactions. Ces interfaces sont définies dans le
paquetage javax.transaction ; le Tableau 9.2 décrit les principales.

Tableau 9.2 : Interfaces principales de JTA

Interface Description
UserTransaction Définit les méthodes qu’une application peut utiliser pour contrôler
par programme les frontières de transactions. Les EJB BMT (bean-
managed transaction) s’en servent pour lancer, valider ou annuler une
transaction (voir la section "Transactions gérées par les beans").
TransactionManager Permet au conteneur EJB de délimiter les frontières de transaction du
côté EJB.
Transaction Permet d’effectuer des opérations sur la transaction dans un objet
Transaction.

XAResource Équivalent Java de l’interface standard X/Open XA (voir la section


suivante).

XA et transactions distribuées
Comme nous venons de le voir, une transaction qui n’utilise qu’une seule ressource
(comme à la Figure 9.1) est une transaction locale. Cependant, de nombreuses appli-
cations d’entreprise utilisent plusieurs ressources  : si l’on revient à l’exemple du
transfert de fonds, le compte épargne et le compte courant pourraient se trouver dans
deux bases de données distinctes. Il faut alors gérer les transactions entre ces diffé-
rentes ressources ou entre des ressources distribuées sur le réseau. Ces transactions

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 287

à l’échelle d’une entreprise nécessitent une coordination particulière impliquant XA


et JTS (Java Transaction Service).
La Figure 9.2 représente une application qui utilise une frontière de transaction entre
plusieurs ressources. Elle peut ainsi stocker des données dans une base et envoyer un
message JMS dans la même unité de traitement, par exemple.

Figure 9.2 Application
Transaction XA impliquant
JTA
deux ressources.
Gestionnaire
de transactions
Ressource JTA / XA
Gestionnaire Gestionnaire
de ressources de ressources

Ressource Ressource

Pour disposer d’une transaction fiable entre plusieurs ressources, le gestionnaire de


transactions doit utiliser une interface XA du gestionnaire de ressources, un standard
de l’Open Group (http:// www.opengroup.org) pour le traitement des transactions
distribuées (DTP) qui préserve les propriétés ACID. Cette interface est reconnue
par JTA et permet à des gestionnaires de ressources hétérogènes provenant d’édi-
teurs différents de fonctionner ensemble en passant par une interface commune.
XA utilise une validation de transaction en deux phases pour garantir que toutes les
ressources valideront ou annuleront simultanément chaque transaction.
Dans notre exemple de transfert de fonds, supposons que le compte épargne soit
débité sur une première base de données et que la transaction soit validée. Puis le
compte courant est crédité sur une seconde base, mais la transaction échoue : il fau-
drait donc revenir à la première base et annuler les modifications apportées par la
transaction. Comme le montre la Figure 9.3, pour éviter ce problème d’incohérence
des données, la validation en deux phases effectue une étape supplémentaire avant
la validation finale.
Au cours de la première phase, chaque gestionnaire de ressources est prévenu via
une commande "de préparation" qu’une validation va avoir lieu, ce qui leur permet
d’indiquer s’ils peuvent ou non appliquer leurs modifications. S’ils annoncent tous
qu’ils sont prêts, la transaction peut se poursuivre et on demande à tous les gestion-
naires de ressources de valider leurs transactions dans la seconde phase.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
288 Java EE 6 et GlassFish 3 

Figure 9.3 Phase 1 Préparation Phase 2 Validation


Validation
prépare valide
en deux phases.
Gestionnaire Gestionnaire
de ressources de ressources
prêt validée

Gestionnaire Gestionnaire
de transactions de transactions

prêt validée
Gestionnaire Gestionnaire
de ressources de ressources
prépare valide

La plupart du temps, les ressources sont distribuées sur le réseau (voir Figure 9.4).
Un tel système utilise JTS, qui implémente la spécification OTS (Object Transaction
Service) de l’OMG (Object Management Group) permettant aux gestionnaires de
transactions de participer aux transactions distribuées via IIOP (Internet Inter-ORB
Protocol). JTS est conçu pour les éditeurs qui fournissent les infrastructures de sys-
tèmes de transaction. Les développeurs EJB n’ont pas à s’en soucier : il suffit qu’ils
utilisent JTA, qui s’interface avec JTS à un niveau supérieur.

Figure 9.4 Application
Une transaction
JTA
distribuée XA.
Gestionnaire JTS / OTS Gestionnaire
de transactions de transactions
JTA / XA JTA / XA
Gestionnaire Gestionnaire
de ressources de ressources

Ressource Ressource

Support des transactions avec les EJB

Lorsque l’on développe de la logique métier avec les EJB, il n’est pas nécessaire de
se soucier de la structure interne des gestionnaires de transactions ou de ressources
car JTA abstrait la plus grosse partie de la complexité sous-jacente. Grâce aux EJB,
le développement d’une application transactionnelle est donc très simple car c’est le
conteneur qui implémente les protocoles de bas niveau pour les transactions, comme

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 289

la validation en deux phases ou la propagation du contexte de transaction. Un conte-


neur EJB est donc un gestionnaire de transactions qui utilise à la fois JTA et JTS
pour participer aux transactions distribuées impliquant d’autres conteneurs EJB.
Dans une application Java EE typique, les beans de session établissent les frontières
de la transaction, appellent des entités pour dialoguer avec la base de données ou
envoient des messages JMS dans un contexte de transaction.
Depuis sa création, le modèle EJB a été conçu pour gérer les transactions : elles font
donc partie des EJB et chacune de leurs méthodes est, par défaut, automatiquement
enveloppée dans une transaction. Ce comportement par défaut s’appelle transac-
tion gérée par le conteneur (CMT), ou démarcation de transaction déclarative. Vous
pouvez également choisir de gérer vous-même les transactions en utilisant des tran-
sactions gérées par le bean (BMT), ou démarcation de transaction par programme.
C’est la démarcation de transaction qui détermine quand commencent et finissent
les transactions.

Transactions gérées par le conteneur

Lorsque l’on gère les transactions de façon déclarative, on délègue la politique de


démarcation au conteneur. Il n’est pas nécessaire d’utiliser explicitement JTA dans
le code (même s’il est utilisé en coulisse) ; on peut laisser le conteneur marquer les
frontières de transactions en les ouvrant et en les validant à partir des métadonnées.
Le conteneur EJB fournit les services de gestion des transactions aux beans de session
et aux MDB (voir Chapitre 13).
Au Chapitre 7, nous avons vu plusieurs exemples de beans de session, d’annotations
et d’interfaces, mais rien de spécifique aux transactions. Le Listing 9.1 montre le code
d’un bean sans état utilisant CMT. Comme vous pouvez le constater, aucune anno-
tation particulière n’a été ajoutée et il n’y a pas d’interface spéciale à implémenter
– comme on l’a déjà indiqué, les EJB sont transactionnels par nature. Grâce à la confi-
guration par exception, c’est la gestion des transactions par défaut qui s’applique ici
(comme nous le verrons plus loin, REQUIRED est l’attribut de transaction par défaut).

Listing 9.1 : Bean sans état avec CMT


@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
290 Java EE 6 et GlassFish 3 

private InventoryEJB inventory;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

public Book createBook(Book book) {


em.persist(book);
inventory.addItem(book);
return book;
}
}

Vous pourriez vous demander ce qui rend le code du Listing  9.1 transactionnel  :
la réponse est le conteneur. La Figure 9.5 montre ce qui se passe quand un client
invoque la méthode createBook() : son appel est intercepté par le conteneur, qui
vérifie immédiatement avant l’appel de cette méthode si un contexte de transaction
est associé à cet appel. Dans la négative, le conteneur ouvre par défaut une nou-
velle transaction avant d’entrer dans la méthode, puis invoque celle-ci. À la fin de la
méthode, le conteneur valide automatiquement la transaction (ou l’annule automa-
tiquement si une exception particulière est lancée, comme nous le verrons dans la
section "Traitement des exceptions").

Client Conteneur EJB ItemEJB InventoryEJB Transaction

1 : createBook

2 : begin

3 : createBook

4 : additem

5 : validation
ou annulation

Figure 9.5
Le conteneur gère la transaction.

Dans le Listing  9.1 et à la Figure 9.5, il est intéressant de noter qu’une méthode


métier d’un bean (ItemEJB.createBook()) peut être cliente d’une méthode métier
d’un autre bean (InventoryEJB. AddItem()). Avec le comportement par défaut,
le contexte de transaction utilisé pour createBook() (celui du client ou celui créé
par le conteneur) est appliqué à addItem(). La validation finale a lieu si les deux
méthodes se sont terminées correctement mais ce comportement peut être modifié à

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 291

l’aide de métadonnées (annotations ou descripteur de déploiement XML). L’attribut


de transaction choisi (REQUIRED, REQUIRES_NEW, SUPPORTS, MANDATORY, NOT_SUPPOR-
TED ou NEVER) modifie la façon dont le conteneur démarque la transaction du client :
soit il utilise la transaction du client, soit il exécute la méthode dans une nouvelle
transaction, soit il l’exécute sans transaction, soit il lance une exception. Les attributs
de transactions sont décrits dans le Tableau 9.3.

Tableau 9.3 : Attributs CMT Attributes

Attribut Description
REQUIRED Cet attribut, qui est celui par défaut, signifie qu’une méthode doit
toujours être invoquée dans une transaction. Le conteneur en crée une
nouvelle si la méthode a été appelée par un client non transactionnel.
Si le client dispose d’un contexte de transaction, la méthode métier
s’exécute dans celui-ci. On utilise REQUIRED lorsque l’on modifie des
données et que l’on ne sait pas si le client a lancé ou non une transaction.
REQUIRES_NEW Le conteneur crée toujours une nouvelle transaction avant d’exécuter
une méthode, que le client s’exécute ou non dans une transaction. Si le
client est dans une transaction, le conteneur la suspend temporairement,
en crée une seconde, la valide, puis revient à la première. Ceci signifie
que le succès ou l’échec de la seconde transaction n’a pas d’effet sur la
transaction existante du client. On utilise REQUIRED_NEW lorsque l’on ne
souhaite pas qu’une annulation de la transaction ait un effet sur le client.
SUPPORTS La méthode de l’EJB hérite du contexte de transaction du client. Si ce
contexte est disponible, il est utilisé par la méthode ; sinon le conteneur
invoque la méthode sans contexte de transaction. On utilise SUPPORTS
lorsque l’on a un accès en lecture seule à la table de la base de données.
MANDATORY Le conteneur exige une transaction avant d’appeler la méthode
métier, mais n’en créera pas de nouvelle. Si le client a un contexte
de transaction, celui-ci est propagé ; sinon une exception javax.
ejb.exception" EJBTransactionRequiredException est levée.

NOT_SUPPORTED La méthode de l’EJB ne peut pas être appelée dans un contexte de


transaction. Si le client n’en possède pas, rien ne se passe ; s’il en a un,
le conteneur suspend la transaction du client, appelle la méthode puis
relance la transaction à la fin de l’appel.
NEVER La méthode de l’EJB ne doit pas être appelée par un client
transactionnel. Si le client s’exécute dans un contexte de transition, le
conteneur lève une exception javax.ejb.EJBException.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
292 Java EE 6 et GlassFish 3 

La Figure 9.6 illustre tous les comportements possibles d’un EJB en fonction de la


présence ou non d’un contexte de transaction du client. Si, par exemple, la méthode
createBook() n’a pas de contexte transactionnel et qu’elle appelle addItem() avec
un attribut MANDATORY, une exception est lancée. Le bas de la Figure 9.6 montre les
mêmes combinaisons, mais avec un client disposant d’un contexte transactionnel.

ItemEJB InventoryEJB Attribut CMT Résultat


REQUIRED Nouvelle transaction
REQUIRES NEW Nouvelle transaction
SUPPORTS Pas de transaction
MANDATORY Exception
1. createBook() La méthode X additem
n'est pas appelée NOT SUPPORTED Pas de transaction
dans une transaction. NEVER Pas de transaction

Attribut CMT Résultat


REQUIRED Transaction du client
REQUIRES NEW Nouvelle transaction
additem
1. createBook() La méthode X SUPPORTS Transaction du client
est dans une transaction. MANDATORY Transaction du client
NOT SUPPORTED Pas de transaction
NEVER Exception
Figure 9.6
Deux appels à InventoryEJB avec des politiques de transactions différentes.

Pour appliquer l’un de ces six attributs de démarcation à un bean de session, il


suffit d’utiliser l’annotation @javax.ejb.TransactionAttribute ou le descripteur
de déploiement (l’élément <trans-attribute> du fichier ejb-jar.xml). Ces méta-
données peuvent s’appliquer aux différentes méthodes ou au bean entier – dans ce
cas, toutes les méthodes métiers du bean héritent de la valeur de l’attribut. Dans le
Listing 9.2, ItemEJB utilise une politique de démarcation SUPPORT, sauf la méthode
createBook(), qui utilise REQUIRED.

Listing 9.2 : Bean sans état avec CMT


@Stateless
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB
private InventoryEJB inventory;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 293

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Book createBook(Book book) {
em.persist(book);
inventory.addItem(book);
return book;
}
}

INFO

Le contexte de transaction du client ne se propage pas lors des appels de méthodes asyn-
chrones. En outre, comme nous le verrons au Chapitre  13, les MDB n’autorisent que les
attributs REQUIRED et NOT_SUPPORTED.

Marquage d’un CMT pour annulation


Nous avons vu que le conteneur délimitait automatiquement les transactions et
effectuait à notre place les opérations de lancement, de validation et d’annulation.
En tant que développeur, on peut cependant vouloir empêcher la validation d’une
transaction en cas d’erreur ou d’une condition métier particulière. En outre, il faut
bien comprendre qu’un bean CMT n’est pas autorisé à annuler explicitement la tran-
saction : il faut utiliser le contexte de l’EJB (voir la section "Contexte de session" du
Chapitre 7) pour informer le conteneur de l’annuler.
Comme le montre le Listing  9.3, le bean InventoryEJB dispose d’une méthode
oneItemSold() qui accède à la base de données via le gestionnaire de persistance
et envoie un messsage JMS pour informer la société de transport qu’un article a été
vendu et qu’il doit être livré. Si le niveau du stock est égal à zéro (ce qui signifie
qu’il n’y a plus d’article en stock), la méthode doit explicitement annuler la transac-
tion. Pour ce faire, le bean doit d’abord obtenir la SessionContext via l’injection
de dépendances, puis appeler la méthode setRollbackOnly() de cette interface. Cet
appel n’annule pas immédiatement la transaction mais positionne un indicateur dont
tiendra compte le conteneur lorsqu’il terminera la transaction.

Listing 9.3 : Un bean sans état marque la transaction pour annulation


@Stateless
public class InventoryEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
294 Java EE 6 et GlassFish 3 

private SessionContext ctx;

public void oneItemSold(Item item) {


em.merge(item);
item.decreaseAvailableStock();
sendShippingMessage();
if (inventoryLevel(item) == 0)
ctx.setRollbackOnly();
}
}

Un bean peut également appeler la méthode SessionContext.getRollbackOnly()


pour tester si la transaction courante a été marquée pour annulation.
Un autre moyen d’informer par programme le conteneur qu’il doit annuler une
­transaction consiste à lancer des types d’exceptions précis.

Traitement des exceptions


Le traitement des exceptions en Java est, depuis la création du langage, assez trou-
blant car il utilise la notion d’exception contrôlée non contrôlée. L’association des
transactions et des exceptions dans les EJB est également assez épique... Avant d’al-
ler plus loin, précisons que le lancement d’une exception dans une méthode métier
ne marquera pas toujours la transaction pour annulation – cela dépend du type de
l’exception ou des métadonnées qui la définissent. En fait, la spécification EJB 3.1
met en relief deux types d’exceptions :
■■ Les exceptions d’application. Ce sont les exceptions liées à la logique métier
traitée par l’EJB. Une exception d’application peut, par exemple, être levée si
des paramètres incorrects sont passés à une méthode, si le stock est trop faible
ou si le numéro de carte de crédit est incorrect. Le lancement d’une exception
d’application n’implique pas automatiquement que la transaction soit marquée
pour annulation. Comme on l’explique plus loin dans le Tableau 9.4, le conte-
neur n’annule pas une transaction lorsque des exceptions contrôlées (celles qui
héritent de java.lang.Exception) sont levées – par contre, il le fait pour les
exceptions non contrôlées (qui héritent de RuntimeException).
■■ Les exceptions systèmes. Elles sont causées par des erreurs au niveau système,
comme les erreurs JNDI, les erreurs de la JVM, l’impossibilité d’établir une
connexion avec la base de données, etc. Une exception système peut être une
sous-classe de RuntimeException ou de java.rmi.RemoteException (et donc
une sous-classe de javax.ejb.EJBException). La levée d’une exception système
marque la transaction pour annulation.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 295

Avec cette définition, nous savons maintenant que le conteneur annulera la transaction
s’il détecte une exception système comme ArithmeticException, ClassCastEx-
ception, IllegalArgumentException ou NullPointerException. Les exceptions
d’application dépendent en revanche de nombreux facteurs. À titre d’exemple, le
Listing 9.4 modifie le code du Listing 9.3 et utilise une exception d’application.

Listing 9.4 : Bean sans état levant une exception d’application


@Stateless
public class InventoryEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;

public void oneItemSold(Item item)


throws InventoryLevelTooLowException {
em.merge(item);
item.decreaseAvailableStock();
sendShippingMessage();

if (inventoryLevel(item) == 0)
throw new InventoryLevelTooLowException();
}
}

InventoryLevelTooLowException est une exception d’application car elle est liée à


la logique métier de la méthode oneItemSold(). Selon que l’on veuille ou non annu-
ler la transaction, on peut la faire hériter d’une exception contrôlée ou non contrôlée,
ou l’annoter avec @javax.ejb.ApplicationException (ou l’élément XML équiva-
lent dans le descripteur de déploiement). Cette annotation a un paramètre rollback
qui peut être initialisé à true pour annuler explicitement la transaction. Dans le Lis-
ting 9.5, InventoryLevelTooLowException est une exception annotée et contrôlée.

Listing 9.5 : Exception d’application avec rollback = true


@ApplicationException(rollback = true)
public class InventoryLevelTooLowException extends Exception {

public InventoryLevelTooLowException() { }

public InventoryLevelTooLowException(String message) {


super(message);
}
}

Si le bean InventoryEJB du Listing 9.4 lance l’exception définie dans le Listing 9.5,


la transaction sera marquée pour annulation et c’est le conteneur qui se chargera de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
296 Java EE 6 et GlassFish 3 

cette annulation à la fin de la transaction. Le Tableau 9.4 présente toutes les combi-


naisons possibles d’exceptions d’application. Sa première ligne pourrait être inter-
prétée comme "si l’exception d’application hérite d’Exception et qu’elle ne soit pas
annotée par @ApplicationException, son lancement ne marquera pas la transaction
pour annulation".

Tableau 9.4 : Combinaisons des exceptions d’applications

Hérite de @ApplicationException Description


Exception Pas d’annotation Par défaut, la levée d’une
exception contrôlée ne
marque pas la transaction pour
annulation
Exception rollback = true La transaction est marquée pour
annulation
Exception rollback = false La transaction n’est pas marquée
pour annulation
RuntimeException Pas d’annotation Par défaut, la levée d’une
exception non contrôlée marque
la transaction pour annulation
RuntimeException rollback = true La transaction est marquée pour
annulation
RuntimeException rollback = false La transaction n’est pas marquée
pour annulation

Transactions gérées par le bean

Avec CMT, on laisse au conteneur le soin de réaliser la démarcation des transac-


tions en précisant simplement un attribut et en utilisant le contexte de session ou
des exceptions pour marquer une transaction pour annulation. Dans certains cas,
toutefois, l’approche déclarative de CMT ne permet pas d’obtenir la finesse de
démarcation voulue (une méthode ne peut pas participer à plusieurs transactions,
par exemple). Pour résoudre ce problème, les EJB permettent de gérer les démar-
cations par programme avec BMT (Bean-Managed Transaction), qui autorise la
gestion explicite des frontières de transaction (lancement, validation, annulation)
avec JTA.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 297

Pour désactiver la démarcation CMT par défaut et basculer dans le mode BMT, un
bean doit simplement utiliser l’annotation @javax.ejb.TransactionManagement (ou
son équivalent XML dans le fichier ejb-jar.xml) :
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ItemEJB {
...
}

Avec la démarcation BMT, l’application demande la transaction et le conteneur EJB


crée la transaction physique puis s’occupe uniquement de quelques détails de bas
niveau. En outre, il ne propage pas les transactions d’un EJB BMT vers un autre.
L’interface principale pour mettre en œuvre BMT est javax.transaction.-User-
Transaction. Elle permet au bean de délimiter une transaction, de connaître son sta-
tut, de fixer un délai d’expiration, etc. Cette interface est instanciée par le conteneur
EJB et est rendue disponible via l’injection de dépendances, une recherche JNDI
ou le SessionContext (avec la méthode SessionContext.get-UserTransaction()).
Son API est décrite dans le Tableau 9.5.

Tableau 9.5 : Méthodes de l’interface javax.transaction.UserTransaction

Interface Description
begin Débute une nouvelle transaction et l’associe au thread
courant
commit Valide la transaction attachée au thread courant
rollback Annule la transaction attachée au thread courant
setRollbackOnly Marque la transaction courante pour annulation
getStatus Récupère le statut de la transaction courante
setTransactionTimeout Modifie le délai d’expiration de la transaction courante

Le Listing  9.6 montre comment développer un bean BMT. On commence par


obtenir une référence à l’interface UserTransaction par injection via l’annotation
@Resource. La méthode oneItemSold() débute la transaction, effectue un traitement
métier puis, en fonction d’une condition métier, valide ou annule cette transaction.
Notez également que la transaction est marquée pour annulation dans le bloc catch
(nous avons simplifié le traitement d’exception pour des raisons de lisibilité).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
298 Java EE 6 et GlassFish 3 

Listing 9.6 : Bean sans état avec BMT


@Stateless
public class InventoryEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource
private UserTransaction ut;

public void oneItemSold(Item item) {


try {
ut.begin();

em.merge(item);
item.decreaseAvailableStock();
sendShippingMessage();

if (inventoryLevel(item) == 0)
ut.rollback();
else
ut.commit();

} catch (Exception e) {
ut.setRollbackOnly();
}
sendInventoryAlert();
}
}

Dans le code CMT du Listing 9.3, c’est le conteneur qui débutait la transaction avant
l’exécution de la méthode et la validait immédiatement après. Avec le code BMT du
Listing 9.6, c’est vous qui définissez manuellement les frontières de la transaction
dans la méthode elle-même.

Sécurité

La sécurisation des applications est (ou devrait être) un souci majeur pour les socié-
tés. Ceci peut aller de la sécurisation d’un réseau au chiffrement des transferts de
données, en passant par l’octroi de certaines permissions aux utilisateurs d’un sys-
tème. Au cours de notre navigation quotidienne sur Internet, nous rencontrons de
nombreux sites où nous devons entrer un nom d’utilisateur et un mot de passe pour
avoir accès à certaines parties d’une application. La sécurité est devenue une néces-
sité sur le Web et, en conséquence, Java  EE a défini plusieurs mécanismes pour
sécuriser les applications.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 299

La sécurité nécessite de comprendre plusieurs concepts. L’un d’eux est la liaison


des utilisateurs à un principal et le fait qu’ils peuvent avoir plusieurs rôles. Chaque
rôle donne des permissions pour un ensemble de ressources mais, pour avoir une
identité dans le domaine de sécurité, un utilisateur doit pouvoir être authentifié : la
plate-forme contrôlera alors l’accès en autorisant les ressources en fonction du rôle
de l’utilisateur.

Principal et rôle

Les "principaux" et les rôles tiennent une place importante dans la sécurité logi-
cielle. Un principal est un utilisateur qui a été authentifié (par un nom et un mot de
passe stockés dans une base de données, par exemple). Les principaux peuvent être
organisés en groupes, appelés rôles, qui leur permettent de partager un ensemble de
permissions (accès au système de facturation ou possibilité d’envoyer des messages
dans un workflow, par exemple).
La Figure 9.7 montre comment les utilisateurs peuvent être représentés dans un
système sécurisé. Comme vous pouvez le constater, un utilisateur authentifié est
lié à un principal qui a un identifiant unique et qui peut être associé à plusieurs
rôles. Le principal de l’utilisateur Frank, par exemple, est lié aux rôles Employé et
Admin.

Figure 9.7
Principaux et rôles.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
300 Java EE 6 et GlassFish 3 

Authentification et habilitation

La sécurisation d’une application implique deux fonctions  : l’authentification et


l’habilitation. La première consiste à vérifier l’identité de l’utilisateur (son identi-
fiant et son mot de passe, son OpenID, son empreinte biométrique, etc.) en utilisant
un système d’authentification et en affectant un principal à cet utilisateur. L’habili-
tation consiste à déterminer si un principal (un utilisateur authentifié) a accès à une
ressource particulière (un livre, par exemple) ou à une fonction donnée (supprimer
un livre, par exemple). Selon son rôle, l’utilisateur peut avoir accès à toutes les
r­essources, à aucune ou à certaines d’entre elles.
La Figure 9.8 décrit un scénario de sécurité classique. L’utilisateur doit entrer son
identifiant et son mot de passe via une interface client (web ou Swing). Ces informa-
tions sont vérifiées avec JAAS (Java Authentication and Authorization Service) via
un système d’authentification sous-jacent. Si l’authentification réussit, l’utilisateur
est associé à un principal qui est ensuite lui-même associé à un ou plusieurs rôles.
Lorsque l’utilisateur accède à un EJB sécurisé, le principal est transmis de façon
transparente à l’EJB, qui l’utilise pour savoir si le rôle de l’appelant l’autorise à
accéder aux méthodes qu’il tente d’exécuter.

Figure 9.8 Conteneur web


Principal authentifié
Scénario de sécurité ou Conteneur EJB
AAC par mot de passe
classique avec JAAS.
Authentifie Autorise

JAAS

Authentifie

Système
d'authentification

Comme le montre la Figure 9.8, la sécurité de Java EE repose largement sur l’API


JAAS. En fait, JAAS est l’API utilisée en interne par les couches web et EJB pour
réaliser les opérations d’authentification et d’habilitation. Elle accède également
aux systèmes d’authentification sous-jacents comme LDAP (Lightweight Directory
Access Protocol), Microsoft Active Directory, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 301

Gestion de la sécurité dans EJB

Le but principal du modèle de sécurité EJB est de contrôler l’accès au code métier.
Comme nous venons de le voir, l’authentification est prise en charge par la couche
web (ou une application cliente) ; le principal et ses rôles sont ensuite transmis à la
couche EJB et le service sécurité du conteneur EJB vérifie si le rôle d’un utilisateur
authentifié l’autorise à accéder à une méthode. Comme la gestion des transactions,
celle des habilitations peut s’effectuer de façon déclarative ou par programme.
Dans le cas des habilitations déclaratives, le contrôle des accès est assuré par le
conteneur EJB, tandis qu’avec les habilitations par programme c’est le code qui s’en
charge en utilisant l’API JAAS.

Sécurité déclarative

La politique de sécurité déclarative peut être définie dans le bean à l’aide d’annota-
tions ou dans le descripteur de déploiement XML. Elle consiste à déclarer les rôles,
à affecter des permissions aux méthodes (ou à tout le bean) ou à modifier temporai-
rement une identité de sécurité. Tous ces contrôles s’effectuent par les annotations
du Tableau 9.6, chacune d’elles pouvant porter sur une méthode et/ou sur le bean
entier.

Tableau 9.6 : Annotation de sécurité

Annotation Bean Méthode Description


@PermitAll X X La méthode (ou tout le bean) est accessible
par tout le monde (tous les rôles sont
autorisés).
@DenyAll X Aucun rôle n’est autorisé à exécuter la
méthode (tous les rôles sont refusés).

@RolesAllowed X X Donne la liste des rôles autorisés à exécuter la


méthode (ou tout le bean).
@DeclareRoles X Définit les rôles pour la sécurité.
@RunAs X Affecte temporairement un nouveau rôle au
principal.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
302 Java EE 6 et GlassFish 3 

INFO

Les annotations @TransactionManagement et @TransactionAttribute que nous avons pré-


sentées au début de ce chapitre sont définies dans le paquetage javax.ejb de la spécifi-
cation EJB 3.1 (JSR 318). Les annotations de sécurité (@RolesAllowed, @DenyAll, etc.) font
partie de la spécification Common Annotations 1.0 (JSR 250) et proviennent du paquetage
javax.annotation.security.

L’annotation @RolesAllowed sert à autoriser une liste de rôles à accéder à une


méthode. Elle peut s’appliquer à une méthode particulière ou à l’ensemble du bean
(toutes ses méthodes métier héritent de cet accès). Elle peut prendre en paramètre
un String unique (désignant le seul rôle autorisé) ou un tableau de String (tous les
rôles habilités). L’annotation @DeclareRoles que nous étudierons plus tard permet
de déclarer d’autres rôles.
Dans le Listing 9.7, ItemEJB utilise @RolesAllowed à la fois au niveau du bean et
des méthodes. Ce code indique que toutes les méthodes sont accessibles à un prin-
cipal associé aux rôles utilisateur, employé ou admin. La méthode deleteBook(),
en revanche, redéfinit la configuration du bean pour n’autoriser l’accès qu’au rôle
admin.

Listing 9.7 : Bean sans état autorisant certains rôles


@Stateless
@RolesAllowed({"utilisateur", "employé", "admin"})
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;

public Book findBookById(Long id) {


return em.find(Book.class, id);
}

public Book createBook(Book book) {


em.persist(book);
return book;
}

@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 303

Les annotations @PermitAll et @DenyAll s’appliquent à tous les rôles : vous pouvez
donc utiliser @PermitAll pour annoter un EJB ou une méthode particulière pour
qu’ils puissent être invoqués par n’importe quel rôle. Inversement, @DenyAll interdit
l’accès à une méthode à tous les rôles.
Comme vous pouvez le constater dans le Listing 9.8, la méthode findBookById() est
désormais accessible à n’importe quel rôle, pas simplement à utilisateur, employé
ou admin. Par contre, la méthode findConfidentialBook() n’est pas accessible du
tout.

Listing 9.8 : Bean sans état utilisant les annotations @PermitAll et @DenyAll


@Stateless
@RolesAllowed({"utilisateur", "employé", "admin"})
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;

@PermitAll
public Book findBookById(Long id) {
return em.find(Book.class, id);
}

public Book createBook(Book book) {


em.persist(book);
return book;
}

@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}

@DenyAll
public Book findConfidentialBook(Long secureId){
return em.find(ConfidentialBook.class, id);
}

L’annotation @DeclareRoles est légèrement différente car elle ne sert ni à autoriser


ni à interdire un accès – elle déclare des rôles pour toute l’application. Lorsque
l’EJB du Listing  9.8 est déployé, le conteneur déclare automatiquement les rôles
utilisateur, employé et admin en inspectant @RolesAllowed, mais vous pourriez
vouloir déclarer d’autres rôles dans le domaine de sécurité avec @DeclareRoles.
Cette annotation, qui ne s’applique qu’au niveau d’une classe, prend en paramètre
un tableau de rôles et les déclare dans le domaine. En fait, les rôles peuvent donc
être déclarés à l’aide de l’une de ces deux annotations ou de leur combinaison.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
304 Java EE 6 et GlassFish 3 

Lorsqu’elles sont utilisées toutes les deux, c’est l’ensemble des rôles de @Declare-
Roles et @RolesAllowed qui est déclaré. Ceci dit, les rôles étant généralement décla-
rés pour l’ensemble d’une application d’entreprise, il est plus judicieux de le faire
dans le descripteur de déploiement qu’avec une annotation @DeclareRoles.
Lorsque le bean ItemEJB du Listing 9.9 est déployé, les cinq rôles HR, deptVentes,
utilisateur, employé et admin sont déclarés. Puis, avec l’annotation @Roles­
Allowed, certains d’entre eux permettent d’accéder à certaines méthodes.

Listing 9.9 : Bean sans état déclarant des rôles


@Stateless
@DeclareRoles({"HR", "deptVentes"})
@RolesAllowed({"utilisateur", "employé", "admin"})
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;

public Book findBookById(Long id) {


return em.find(Book.class, id);
}

public Book createBook(Book book) {


em.persist(book);
return book;
}

@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}
}

La dernière annotation, @RunAs, permet d’affecter temporairement un nouveau rôle


à un principal. Ceci peut être utile si, par exemple, on invoque un autre EJB depuis
une méthode et que cet EJB exige un rôle différent.
Dans le Listing  9.10, par exemple, ItemEJB autorise l’accès aux rôles utilisa-
teur, employé et admin. Lorsque l’un de ces rôles accède à une méthode, celle-ci
s’exécute avec le rôle temporaire deptStock (@RunAS("deptStock")), ce qui signifie
que, lorsque la méthode createBook() est exécutée, InventoryEJB.addItem() sera
invoquée avec le rôle deptStock.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 305

Listing 9.10 : Bean sans état s’exécutant avec un rôle différent


@Stateless
@RolesAllowed({"utilisateur", "employé", "admin"}) @RunAS("deptStock")
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB
private InventoryEJB inventory;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

public Book createBook(Book book) {


em.persist(book);
inventory.addItem(book);
return book;
}
}

Comme vous pouvez le constater, la sécurité déclarative permet d’accéder de façon


simple à une politique d’authentification puissante. Mais comment faire si vous
devez fournir une sécurité spéciale à un utilisateur particulier ou appliquer une
logique métier en fonction du rôle courant du principal ? La réponse est la sécurité
par programmation.

Sécurité par programmation

La sécurité déclarative couvre la majeure partie de la sécurité d’une application.


Cependant, on a parfois besoin d’une finesse d’habilitation supplémentaire (pour
autoriser un bloc de code au lieu de la méthode entière, pour autoriser ou interdire
l’accès à une personne particulière, etc.). En ce cas, la gestion des habilitations
par programmation permet d’autoriser ou de bloquer sélectivement l’accès à un
rôle ou à un principal car on dispose alors d’un accès direct à l’interface javax.
security.Principal de JAAS et au contexte de l’EJB pour vérifier le rôle du prin-
cipal dans le code.
L’interface SessionContext définit les méthodes suivantes pour gérer la sécurité :
■■ isCallerInRole() teste si l’appelant a le rôle indiqué.
■■ getCallerPrincipal() renvoie le java.security.Principal qui identifie
l’appelant.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
306 Java EE 6 et GlassFish 3 

Pour comprendre l’utilisation de ces méthodes, étudions le bean ItemEJB du Lis-


ting 9.11 : celui-ci n’utilise aucune annotation de sécurité mais doit quand même
faire certaines vérifications par programme. Le bean doit d’abord obtenir une réfé-
rence à son contexte (via l’annotation @Resource) qui permettra à la méthode dele-
teBook() de vérifier si l’appelant a le rôle admin ou non. S’il ne l’a pas, la méthode
lève java.lang.SecurityException pour prévenir l’utilisateur d’une violation des
autorisations. La méthode createBook() effectue un traitement métier en utilisant
les rôles et le principal : en se servant de la méthode getCallerPrincipal() pour
obtenir l’objet Principal correspondant à l’appelant, elle peut vérifier qu’il s’agit
de paul et ajouter une valeur spéciale à l’entité book.

Listing 9.11 : Bean utilisant une sécurité par programmation


@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource
private SessionContext ctx;

public Book findBookById(Long id) {


return em.find(Book.class, id);
}

public void deleteBook(Book book) {


if (!ctx.isCallerInRole("admin"))
throw new SecurityException("Admins uniquement");
em.remove(em.merge(book));
}

public Book createBook(Book book) {


if (ctx.isCallerInRole("employé") &&
 !ctx.isCallerInRole("admin")) {
book.setCreatedBy("Employés uniquement");
} else if (ctx.getCallerPrincipal().getName().equals("paul")){
book.setCreatedBy("Utilisateur spécial");
}
em.persist(book);
return book;
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 307

Résumé

Dans ce dernier chapitre consacré aux EJB, nous avons vu comment gérer les tran-
sactions et la sécurité. Ces deux services très importants peuvent être définis de
façon déclarative ou par programmation.
Les transactions permettent à la couche métier de maintenir les données dans un
état cohérent, même lorsque plusieurs applications y accèdent de façon concurrente.
Elles respectent les propriétés ACID et peuvent être distribuées entre plusieurs res-
sources (bases de données, destinations JMS, services web, etc.). CMT permet de
personnaliser aisément la démarcation des transactions effectuée par le conteneur
EJB et vous pouvez influencer son comportement en marquant une transaction pour
annulation en vous servant du contexte EJB ou des exceptions. Il est également
possible d’utiliser BMT et JTA si vous avez besoin d’un contrôle plus fin sur la
démarcation des transactions.
Concernant la sécurité, n’oubliez pas que la couche métier n’authentifie pas les uti-
lisateurs  : elle autorise des rôles à accéder aux méthodes. La sécurité déclarative
s’effectue au moyen d’un nombre relativement réduit d’annotations et permet de
traiter la plupart des situations auxquelles sera confrontée une application d’entre-
prise. Là aussi, vous pouvez utiliser une sécurité par programmation et manipuler
directement l’API JAAS.
Les trois chapitres qui suivent expliquent comment développer une couche présen-
tation avec JSF. Les pages JSF utilisent des beans gérés pour invoquer les méthodes
métiers des EJB.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
10
JavaServer Faces

Pour afficher graphiquement les informations provenant du serveur, nous avons


besoin d’une interface utilisateur. Les applications utilisant des interfaces pour inte-
ragir avec l’utilisateur sont de différents types : applications de bureau, applications
web s’exécutant dans un navigateur ou applications mobiles sur un terminal por-
table. Les Chapitres 10 à 12 sont consacrés aux interfaces web.
Initialement, le World Wide Web (WWW) était un moyen de partager des documents
écrits en HTML (Hypertext Markup Language). Le protocole HTTP (Hypertext
Transfer Protocol) a été conçu pour véhiculer ces documents, qui étaient à l’origine
essentiellement statiques (leur contenu n’évoluait pas beaucoup au cours du temps).
Les pages statiques sont composées de HTML pur contenant éventuellement des
graphiques eux aussi statiques (JPG, PNG, par exemple). Les pages dynamiques
sont en revanche composées en temps réel à partir de données calculées à partir des
informations fournies par l’utilisateur.
Pour créer un contenu dynamique, il faut analyser les requêtes HTTP, comprendre
leur signification et créer des réponses dans un format que le navigateur saura traiter.
L’API des servlets simplifie ce processus en fournissant une vue orientée objet du
monde HTTP (HttpRequest, HttpResponse, etc.). Cependant, le modèle des servlets
était de trop bas niveau et c’est la raison pour laquelle on utilise désormais les JSP
(JavaServer Pages) pour simplifier la création des pages dynamiques. En coulisse,
une JSP est une servlet, sauf qu’elle est écrite essentiellement en HTML – avec un
peu de Java pour effectuer les traitements.
JSF (JavaServer Faces, ou simplement Faces) a été créé en réponse à certaines
limitations de JSP et utilise un autre modèle consistant à porter des composants
graphiques vers le Web. Inspiré par le modèle Swing et d’autres frameworks gra-
phiques, JSF permet aux développeurs de penser en termes de composants, d’évé-
nements, de beans gérés et de leurs interactions plutôt qu’en termes de requêtes, de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
310 Java EE 6 et GlassFish 3 

réponses et de langages à marqueurs. Son but est de faciliter et d’accélérer le déve-


loppement des applications web en fournissant des composants graphiques (comme
les zones de texte, les listes, les onglets et les grilles) afin d’adopter une approche
RAD (Rapid Application Development).
Ce chapitre est une introduction à JSF  ; les Chapitres  11 et 12 présentent diffé-
rentes technologies proposées par Java EE 6 pour créer des interfaces web (JSP, EL
et JSTL) et s’intéressent essentiellement à JSF 2.0, qui est la technologie la plus
­puissante et la plus adaptée à la création d’applications web modernes en Java.

Introduction à JSF

Lorsque l’on connaît déjà des frameworks web, l’architecture de JSF est facile à
comprendre (voir Figure 10.1). Les applications JSF sont des applications web clas-
siques qui interceptent HTTP via la servlet Faces et produisent du HTML. En cou-
lisse, cette architecture permet de greffer n’importe quel langage de déclaration de
page (PDL), de l’afficher sur des dispositifs différents (navigateur web, terminaux
mobiles, etc.) et de créer des pages au moyen d’événements, d’écouteurs et de com-
posants, comme en Swing. Ce dernier est un toolkit graphique intégré à Java depuis
sa version 1.6 – c’est un framework permettant de créer des applications de bureau
(pas des applications web) à l’aide de composants graphiques (widgets) et en utili-
sant le modèle événement-écouteur pour traiter les entrées des utilisateurs. JSF four-
nit également un ensemble de widgets standard (boutons, liens hypertextes, cases à
cocher, zones de saisie, etc.) et facilite son extension par l’ajout de composants tiers.
La Figure 10.1 représente son architecture au niveau le plus abstrait.

Figure 10.1 XUL
Requête JSP
Architecture de JSF.
Moteur de rendu

HTTP XHTML
(Ajax) Composant Convertisseur

Réponse Faces Composant


Servlet Composant
HTTP
Validateur

Navigation

Bean géré
faces-config.xml
(facultatif)

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 311

Cette figure représente les parties importantes de JSF qui rendent cette architecture
aussi riche et aussi souple :
■■ FacesServlet et faces-config.xml. FacesServlet est la servlet principale de
l’application et peut éventuellement être configurée par un fichier descripteur
faces-config.xml.
■■ Pages et composants. JSF permet d’utiliser plusieurs PDL (Presentation
­Description Language), comme JSP ou Facelets.
■■ Moteurs de rendu. Ils sont responsables de l’affichage d’un composant et de la
traduction de la valeur saisie par l’utilisateur en valeur pour le composant.
■■ Convertisseurs. Ils effectuent les conversions entre les valeurs de composants
(Date, Boolean, etc.) et les valeurs de marqueurs (String), et réciproquement.
■■ Validateurs. Ils garantissent que la valeur saisie par l’utilisateur est correcte.
■■ Bean géré et navigation. La logique métier s’effectue dans des beans gérés (ou
"managés") qui contrôlent également la navigation entre les pages.
■■ Support d’Ajax. Comme l’explique le Chapitre 12, JSF 2.0 reconnaît nativement
Ajax.

FacesServlet et faces-config.xml

La plupart des frameworks web utilisent le patron de conception MVC (Modèle-


Vue-Contrôleur) – JSF n’y fait pas exception. MVC permet de découpler la vue
(la page) et le modèle (les données affichées dans la vue). Le contrôleur prend en
charge les actions de l’utilisateur qui pourraient impliquer des modifications dans le
modèle et dans les vues. Avec JSF, ce contrôleur est la servlet FacesServlet. Toutes
les requêtes de l’utilisateur passent par cette servlet, qui les examine et appelle les
différentes actions correspondantes du modèle en utilisant des beans gérés.
FacesServlet est intégrée à JSF et le seul moyen de la configurer consiste à utiliser
des métadonnées externes. Jusqu’à JSF 1.2, la seule source de configuration était le
fichier faces-config.xml. À partir de JSF 2.0, ce fichier est facultatif et la plupart
des métadonnées peuvent être définies par des annotations (sur les beans gérés, les
convertisseurs, les composants, les moteurs de rendu et les validateurs).

Pages et composants

Le framework JSF doit envoyer une page sur le dispositif de sortie du client (un
navigateur, par exemple) et exige donc une technologie d’affichage appelée PDL.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
312 Java EE 6 et GlassFish 3 

Une application JSF peut utiliser plusieurs technologies pour son PDL, comme JSP
ou Facelets. Une implémentation conforme à la spécification JSF 2.0 doit inclure
une implémentation complète de JSP, qui était le PDL par défaut de JSF  1.1 et
JSF 1.2 – JSF 2.0 lui préfère désormais Facelets.
JSP et Facelets sont tous les deux formés d’une arborescence de composants (égale-
ment appelés widgets ou contrôles) fournissant des fonctionnalités spécifiques pour
interagir avec l’utilisateur (champs de saisie, boutons, listes, etc.). JSF dispose d’un
ensemble standard de composants et permet de créer facilement les vôtres. Pour
gérer cette arborescence, une page passe par un cycle de vie complexe (initiali­
sation, événements, affichage, etc.).
Le code du Listing  10.1 est une page Facelets en XHTML qui utilise les mar-
queurs JSF (xmlns:h="http:// java.sun.com/jsf/html") pour afficher un formu-
laire avec deux champs de saisie (l’ISBN et le titre d’un livre) et un bouton. Cette
page est composée de plusieurs composants JSF : certains n’ont pas d’apparence
visuelle, comme ceux qui déclarent l’en-tête (<h:head>), le corps (<h:body>) ou
le formulaire (<h:form>). D’autres ont une représentation graphique et affichent
un label (<h:outputLabel>), un champ de saisie (<h:inputText>) ou un bou-
ton (<h:commandButton>). Vous remarquerez que l’on peut également utiliser des
­marqueurs HTML purs (<table>, <tr>, <hr/>, etc.) dans la page.

Listing 10.1 : Extrait d’une page XHTML


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Creates a new book</title>
</h:head>
<h:body>
<h1>Create a new book</h1> <hr/>
<h:form>
<table border="0">

<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{bookController.book.isbn}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Title :"/></td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 313

<td>
<h:inputText value="#{bookController.book.title}"/>
</td>
</tr>

</table>
<h:commandButton value="Create a book"
action="#{bookController.doCreateBook}"
styleClass="submit"/>

</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>

Moteurs de rendu

JSF reconnaît deux modèles de programmation pour afficher les composants  :


l’implémentation directe et l’implémentation déléguée. Avec le modèle direct, les
composants doivent eux-mêmes s’encoder vers une représentation graphique et
réciproquement. Avec le modèle délégué, ces opérations sont confiées à un moteur
de rendu, ce qui permet aux composants d’être indépendants de la technologie d’af-
fichage (navigateur, terminal mobile, etc.) et donc d’avoir plusieurs représentations
graphiques possibles.
Un moteur de rendu s’occupe d’afficher un composant et de traduire la saisie d’un
utilisateur en valeur de composant. On peut donc le considérer comme un traducteur
placé entre le client et le serveur : il décode la requête de l’utilisateur pour initiali-
ser les valeurs du composant et encode la réponse pour créer une représentation du
composant que le client pourra comprendre et afficher.
Les moteurs de rendu sont organisés en kits de rendu spécialisés dans un type spé-
cifique de sortie. Pour garantir la portabilité de l’application, JSF inclut le support
d’un kit de rendu standard et les moteurs de rendu pour HTML 4.01. Les implé-
mentations de JSF peuvent ensuite créer leurs propres kits pour produire du WML
(Wireless Markup Language), du SVG (Scalable Vector Graphics), etc.

Convertisseurs et validateurs

Lorsque la page est affichée, l’utilisateur peut s’en servir pour entrer des données.
Comme il n’y a pas de contraintes sur les types, un moteur de rendu ne peut pas pré-
voir l’affichage de l’objet. Voilà pourquoi les convertisseurs existent : ils traduisent

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
314 Java EE 6 et GlassFish 3 

un objet (Integer, Date, Enum, Boolean, etc.) en chaîne afin qu’il puisse s’afficher et,
inversement, construisent un objet à partir d’une chaîne qui a été saisie. JSF fournit
un ensemble de convertisseurs pour les types classiques dans le paquetage javax.
faces.convert, mais vous pouvez développer les vôtres ou ajouter des types pro-
venant de tierces parties.
Parfois, les données doivent également être validées avant d’être traitées par le back-
end : c’est le rôle des validateurs ; on peut ainsi associer un ou plusieurs validateurs
à un composant unique afin de garantir que les données saisies sont correctes. JSF
fournit quelques validateurs (LengthValidator, RegexValidator, etc.) et vous per-
met d’en créer d’autres en utilisant vos propres classes annotées. En cas d’erreur de
conversion ou de validation, un message est envoyé dans la réponse à afficher.

Beans gérés et navigation

Tous les concepts que nous venons de présenter – qu’est-ce qu’une page, qu’est-ce
qu’un composant, comment sont-ils affichés, convertis et validés – sont liés à une
page unique, mais les applications web sont généralement formées de plusieurs
pages et doivent réaliser un traitement métier (en appelant une couche EJB, par
exemple). Le passage d’une page à une autre, l’invocation d’EJB et la synchronisa-
tion des données avec les composants sont pris en charge par les beans gérés.
Un bean géré est une classe Java spécialisée qui synchronise les valeurs avec les
composants, traite la logique métier et gère la navigation entre les pages. On associe
un composant à une propriété ou à une action spécifique d’un bean géré en utilisant
EL (Expression Language). Voici un extrait de l’exemple précédent :
<h:inputText value="#{bookController.book.isbn}"/>
<h:commandButton value="Create"
action="#{bookController.doCreateBook}"/>

La première ligne lie directement la valeur du champ de saisie à la propriété book.


isbn du bean géré bookController. Cette valeur est synchronisée avec la propriété
du bean géré.
Un bean géré peut également traiter des événements. La seconde ligne associe un
bouton de soumission de formulaire à une action  : lorsqu’on aura cliqué sur ce
bouton, celui-ci déclenchera un événement sur le bean géré, qui exécutera alors une
méthode écouteur (ici, la méthode doCreateBook()).
Le Listing  10.2 contient le code du bean BookController. Cette classe Java est
annotée par @ManagedBean et possède une propriété, book, qui est synchronisée avec

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 315

la valeur du composant de la page. La méthode doCreateBook() invoque un EJB


sans état et renvoie une chaîne permettant de naviguer entre les pages.

Listing 10.2 : Le bean géré BookController


@ManagedBean
public class BookController {

@EJB
private BookEJB bookEJB;

private Book book = new Book();

public String doCreateBook() {


book = bookEJB.createBook(book);
return "listBooks.xhtml";
}
// Getters, setters
}

INFO

Un bean géré est la classe qui agit comme un contrôleur, navigue d’une page à l’autre, ap-
pelle les EJB, etc. Les backing beans sont les objets qui contiennent les propriétés liées aux
composants. Dans cet exemple, nous pourrions donc dire que BookController est un bean
géré et que l’attribut book est le "backing bean".

Support d’Ajax

Une application web doit fournir une interface riche et rapide. Cette réactivité peut
être obtenue en ne modifiant que de petites parties de la page de façon asynchrone,
et c’est exactement pour cela qu’Ajax a été conçu. Les versions précédentes de JSF
n’offraient pas de solution toute prête et des bibliothèques tierces, comme a4jsf,
sont donc venues combler ce manque. À partir de JSF 2.0, le support d’Ajax a été
ajouté sous la forme d’une bibliothèque JavaScript (jsf.js) définie dans la spécifi-
cation. Le code suivant, par exemple, utilise la fonction request pour soumettre un
formulaire de façon asynchrone :
<h:commandButton id="submit" value="Création d’un livre"
onclick="jsf.ajax.request(this, event,
{execute:’isbn title price description nbOfPage
illustrations’, render:’booklist’}); return false;"
actionListener="#{bookController.doCreateBook}" />

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
316 Java EE 6 et GlassFish 3 

Résumé des spécifications de l’interface web

Le développement web en Java a commencé en 1996 avec l’API servlet, un moyen


très rudimentaire de créer du contenu web dynamique. Il fallait alors manipuler une
API HTTP de bas niveau (HttpServletRequest, HttpServletResponse, HttpSes-
sion, etc.) pour rendre les marqueurs HTML à partir d’un code Java. Les JSP sont
apparues en 1999 et ont ajouté un niveau d’abstraction supérieur à celui des servlets.
En 2004, la première version de JSF a vu le jour et sa version 1.2 a été intégrée à
Java EE 5 en 2006. JSF 2.0 fait désormais partie de Java EE 6.

Bref historique des interfaces web

Au début du Web, les pages étaient statiques : un utilisateur demandait une ressource
(une page, une image, une vidéo, etc.) et le serveur la lui renvoyait – simple, mais
très limité. Avec l’augmentation de l’activité commerciale sur le Web, les sociétés se
sont trouvées obligées de fournir du contenu dynamique à leurs clients. La première
solution a donc consisté à utiliser CGI (Common Gateway Interface) : en utilisant
des pages HTML et des scripts CGI écrits dans différents langages (allant de Perl à
Visual Basic), une application pouvait accéder à des bases de données et servir ainsi
du contenu dynamique. Mais CGI était de trop bas niveau (il fallait gérer les en-têtes
HTTP, appeler les commandes HTTP, etc.) et une solution plus élaborée semblait
nécessaire.
En 1995, Java fit son apparition avec une API d’interface utilisateur indépendante
des plates-formes, appelée AWT (Abstract Window Toolkit). Plus tard, avec Java
SE 1.2, AWT, qui reposait sur l’interface utilisateur du système d’exploitation, fut
remplacé par l’API Swing (qui dessine ses propres widgets en utilisant Java 2D).
Dès les premiers jours de Java, le navigateur Netscape Navigator proposa le support
de ce nouveau langage, ce qui marqua le début de l’ère des applets – des applications
qui s’exécutent sur le client, dans un navigateur. Les applets permettent d’écrire
des applications AWT ou Swing et de les intégrer dans une page web, mais leur
utilisation ne décolla jamais vraiment. De son côté, Netscape avait également créé
un langage de script appelé JavaScript qui s’exécutait directement dans le naviga-
teur : malgré certaines incompatibilités entre les navigateurs, ce langage est toujours
très utilisé actuellement car c’est un moyen efficace de créer des applications web
dynamiques.
Après l’échec des applets, Sun présenta les servlets comme un moyen de créer des
clients web dynamiques légers. Les servlets étaient une alternative aux scripts CGI

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 317

car elles offraient une bibliothèque de plus haut niveau pour gérer HTTP, permet-
taient d’accéder à toute l’API de Java (ce qui incluait donc les accès aux bases de
données, les appels distants, etc.) et pouvaient créer une réponse en HTML pouvant
s’afficher chez le client.
En 1999, Sun présenta JSP comme une amélioration du modèle des servlets mais,
comme les JSP mélangeaient du code Java et du code HTML, un framework open-
source, Struts, vit le jour en 2001 et proposa une nouvelle approche. Ce framework
étendait l’API des servlets et encourageait les développeurs à adopter une archi-
tecture MVC. L’histoire récente est remplie d’autres frameworks tentant, chacun,
de combler les lacunes des précédents (Tapestry, Wicket, WebWork, DWR, etc.).
Aujourd’hui, le framework web conseillé pour Java EE 6 est JSF 2.0, qui rivalise
avec Struts et Tapestry dans le monde Java. Rails et Grails rivalisent un peu partout
avec JSF, tout comme Java rivalise avec Ruby et Groovy. Par ailleurs, GWT (Google
Web Toolkit), Flex et JavaFX peuvent être complémentaires de JSF.

JSP 2.2, EL 2.2 et JSTL 1.2

Du point de vue de leur architecture, les JSP sont une abstraction de haut niveau
des servlets et ont été implémentées comme une extension de Servlet 2.1. JSP 1.2
et Servlet 2.3 ont été spécifiées ensemble dans la JSR 53 alors qu’en même temps
JSTL (JSP Standard Tag Library) faisait l’objet de la JSR 52.
Depuis 2002, la spécification JSP 2.0 a évolué séparément dans la JSR 152. En
2006, JSP 2.1 a été ajoutée à Java EE 5 et a facilité l’intégration entre JSF et JSP en
introduisant un langage d’expressions (EL) unifié. Avec Java EE 6, les spécifications
de JSP et EL sont passées à la version 2.0 – l’une des principales modifications est
qu’il est désormais possible d’invoquer une méthode avec EL.

JSF 2.0

JSF est une spécification publiée par le JCP (Java Community Process) et a été
créée en 2001 par la JSR 127. Sa version de maintenance 1.1 est apparue en 2004
et ce n’est qu’en 2006 que JSF 1.2 a été ajoutée dans Java EE par la JSR 252 (avec
Java EE 5). Le plus gros défi de cette version consistait à préserver la compatibilité
ascendante et à intégrer JSP avec un EL unifié. Malgré ces efforts, JSF et JSP ne
fonctionnent pas très bien ensemble et d’autres frameworks comme Facelets ont
donc été introduits pour fournir une alternative aux JSP.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
318 Java EE 6 et GlassFish 3 

JSF 2.0 est une version majeure (JSR 314) et est désormais le choix conseillé pour
le développement web avec Java EE 6 (JSP reste maintenue, mais n’a reçu aucune
amélioration importante avec Java EE  6). JSF  2.0 s’est inspiré de nombreux fra-
meworks web open-source et leur ajoute de nouvelles fonctionnalités.

Nouveautés de JSF 2.0

Avec ses nouvelles fonctionnalités, JSF 2.0 est une évolution de 1.2, mais il va éga-
lement au-delà – les Facelets sont préférées à JSP, par exemple. Parmi les ajouts de
JSF 2.0, citons :
■■ une autre technologie de présentation que JSP, reposant sur Facelets ;
■■ un nouveau mécanisme de gestion des ressources (pour les images, les scripts
JavaScript, etc.) ;
■■ des portées supplémentaires (portée de vue et portée de composant) ;
■■ le développement plus simple grâce aux annotations pour les beans gérés, les
moteurs de rendu, les convertisseurs, les validateurs, etc. ;
■■ la réduction de la configuration XML en exploitant les annotations et la configu-
ration par exception (le fichier faces-config.xml est facultatif) ;
■■ le support d’Ajax ;
■■ le développement de composant facilité.

Implémentation de référence

Mojarra, qui est le nom d’une famille de poissons des Caraïbes, est l’implémenta-
tion de référence open-source de JSF 2.0. Elle est disponible via les mises à jour de
GlassFish V3 et permet de développer des applications web JSF 2.0 en invoquant
une couche métier EJB 3.1 et une couche de persistance JPA 2.0. C’est elle que nous
utiliserons dans notre récapitulatif.

Récapitulatif

Nous allons écrire une petite application web proposant deux pages web  : l’une
qui affiche un formulaire afin de pouvoir créer un livre (newBook.xhtml), l’autre qui

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 319

énumère tous les livres présents dans la base (listBooks.xhtml). Ces deux pages
utilisent le bean géré BookController pour stocker les propriétés nécessaires et pour
la navigation. En utilisant JPA pour la persistance et EJB pour la logique métier, tout
s’emboîte : le bean géré délègue tous les traitements métier à BookEJB, qui contient
deux méthodes, l’une pour stocker un livre dans une base de données (create-
Book()), une autre pour récupérer tous les livres (findBooks()). Ce bean de session
sans état utilise l’API EntityManager pour manipuler une entité Book. La navigation
est très simple : lorsqu’un livre est créé, on affiche la liste. Un lien sur la page de la
liste permet de revenir ensuite à la page newBook.xhtml et de créer un autre livre.
La Figure 10.2 montre l’interaction des composants de cette application ; ceux-ci
assemblés dans un fichier war et déployés sur une instance de GlassFish et une base
de données Derby.

Figure 10.2 <<entity>>
Book
Pages et classes -id : Long
impliquées dans -title : String
-price : Float
l’application newBook -description : String
web. .xhtml <<managed bean>> -nbOfPage : Integer
BookController
-illustrations : Boolean
+doList() : String
+doCreateBook() : String

<<stateless ejb>>
listBook BookEJB
.xhtml -cm : EntityManager
+findBooks() : List<Book>
+createBook(book : Book) : Book

Cette application web utilisant la structure de répertoires de Maven, les classes, les
fichiers et les pages web doivent donc être placés dans les répertoires suivants :
■■ src/main/java contient l’entité Book, l’EJB BookEJB et le bean géré
BookController.

■■ src/main/resources contient le fichier persistence.xml utilisé pour associer


l’entité à la base de données.
■■ src/webapp contient les deux pages web newBook.xhtml et listBooks.xhtml.
■■ src/webapp/WEB-INF contient le fichier web.xml qui déclare la FacesServlet.
■■ pom.xml est un fichier POM (Project Object Model) de Maven décrivant le projet,
ses dépendances et ses extensions.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
320 Java EE 6 et GlassFish 3 

L’entité Book

Nous ne détaillerons pas beaucoup le Listing  10.3 car vous devriez maintenant
comprendre le code de l’entité Book. Outre les annotations de mapping, notez la
requête nommée findAllBooks, qui permet de récupérer les livres à partir de la base
de données.

Listing 10.3 : Entité Book avec une requête nommée


@Entity
@NamedQuery(name = "findAllBooks", query = "SELECT b FROM Book b")
public class Book {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

Comme vous le savez désormais, cette entité doit également être associée à un fichier
persistence.xml que, pour simplifier, nous ne reproduirons pas ici.

L’EJB BookEJB

Le Listing 10.4 représente un bean de session sans état avec une vue sans interface,
ce qui signifie que le client (c’est-à-dire le bean géré) n’a pas besoin d’interface
(locale ou distante) et peut invoquer directement l’EJB. Ce dernier obtient par injec-
tion une référence à un gestionnaire d’entités grâce auquel il peut rendre persistante
une entité Book (avec la méthode createBook()) et récupérer tous les livres de la
base (avec la requête nommée findAllBooks). Cet EJB n’a besoin d’aucun descripteur
de déploiement.

Listing 10.4 : EJB sans état créant et récupérant des livres


@Stateless
public class BookEJB {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 321

@PersistenceContext(unitName = "chapter10PU")
private EntityManager em;

public List<Book> findBooks() {


Query query = em.createNamedQuery("findAllBooks");
return query.getResultList();
}

public Book createBook(Book book) {


em.persist(book);
return book;
}
}

Le bean géré BookController

L’un des rôles d’un bean géré consiste à interagir avec les autres couches de l’ap-
plication (la couche EJB, par exemple) ou à effectuer des validations. Dans le Lis-
ting 10.5, BookController est un bean géré car il est annoté par @ManagedBean. La
seconde annotation, @RequestScoped, définit la durée de vie du bean : ici, il vivra
le temps de la requête (on peut également choisir d’autres portées). Ce bean géré
contient deux attributs qui seront utilisés par les pages :
■■ bookList est la liste des livres récupérés à partir de la base de données, qui doit
s’afficher dans la page listBooks.xhtml.
■■ book est l’objet qui sera associé au formulaire (dans la page newBook.xhtml) et
rendu persistant.
Tout le traitement métier (création et récupération des livres) s’effectue via BookEJB.
Le bean géré obtient une référence à l’EJB par injection, via l’annotation @EJB, et
dispose de deux méthodes qui seront invoquées par les pages :
■■ doNew(). Cette méthode n’effectue aucun traitement mais permet de naviguer
vers newBook.xhtml. Comme nous le verrons au Chapitre 12, il existe plusieurs
moyens de naviguer de page en page : le plus simple consiste à renvoyer le nom
de la page cible.
■■ doCreateBook(). Cette méthode permet de créer un livre en invoquant l’EJB
sans état et en lui passant l’attribut book. Puis elle appelle à nouveau l’EJB pour
obtenir tous les livres de la base et stocke la liste dans l’attribut bookList du
bean géré. Ensuite, la méthode renvoie le nom de la page vers laquelle elle doit
naviguer.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
322 Java EE 6 et GlassFish 3 

Le Listing 10.5 contient le code de BookController. Pour plus de lisibilité, nous


avons omis les getters et les setters, mais ils sont nécessaires pour chaque attribut
(book et bookList).

Listing 10.5 : Le bean géré BookController qui invoque l’EJB


@ManagedBean
@RequestScoped
public class BookController {

@EJB
private BookEJB bookEJB;

private Book book = new Book();


private List<Book> bookList = new ArrayList<Book>();

public String doNew() {


return "newBook.xhtml";
}

public String doCreateBook() {


book = bookEJB.createBook(book);
bookList = bookEJB.findBooks();
return "listBooks.xhtml";
}

// Getters, setters
}

La page newBook.xhtml

La page newBook.xhtml du Listing  10.6 est un formulaire permettant à l’utilisa-


teur de saisir les informations nécessaires à la création d’un livre (ISBN, titre, prix,
­description, nombre de pages et illustrations).

Listing 10.6 : La page newBook.xhtml


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Creates a new book</title>
</h:head>
<h:body>
<h1>Create a new book</h1>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 323

<hr/>
<h:form>
<table border="0">

<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{bookController.book.isbn}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Title :"/></td>
<td>
<h:inputText value="#{bookController.book.title}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Price : "/></td>
<td>
<h:inputText value="#{bookController.book.price}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Description : "/></td>
<td><h:inputTextarea
value="#{bookController.book.description}"
cols="20" rows="5"/></td>
</tr>

<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText value="#{bookController.book.nbOfPage}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Illustrations : "/></td>
<td><h:selectBooleanCheckbox
value="#{bookController.book.illustrations}"/></td>
<tr>
</table>
<h:commandButton value="Create a book"
action="#{bookController.doCreateBook}"/>
</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
324 Java EE 6 et GlassFish 3 

Comme le montre la Figure 10.3, la plupart des informations sont entrées dans des
champs de saisie, sauf la description, qui utilise une zone de texte et les illustrations
qui sont indiquées par une case à cocher.

Figure 10.3
Create a new book
La page newBook.xhtml.
ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :
Create a book

APress Beginning Java EE 6

Un clic sur le bouton Create a book provoque l’appel de la méthode doCreate-


Book() du bean géré et l’EJB stocke alors le livre dans la base de données.

Bien que ce code ait été simplifié, il contient l’essentiel. Il déclare d’abord l’espace de
noms h pour les composants HTML de JSF : pour les utiliser, il faudra donc les pré-
fixer par cet espace de noms (<h:body>, <h:outputText>, <h:commandButton>, etc.).
Le langage d’expressions  EL permet ensuite de lier dynamiquement la valeur du
composant à la propriété correspondante du bean géré. Le code suivant, par exemple :
<h:inputText value="#{bookController.book.isbn}"/>

lie la valeur de l’attribut isbn de book avec le contenu de ce composant inputText


lors de la soumission du formulaire. bookController étant le nom par défaut du
bean géré, ce code est donc équivalent à celui-ci :
bookController.getBook().setISBN("ce qui a été saisi")

La page utilise différents composants graphiques dont voici un bref résumé :


■■ <h:form> permet de créer un formulaire dont les valeurs seront envoyées au
­serveur lorsqu’il sera soumis.
■■ <h:outputLabel> affiche un label à partir d’une chaîne fixe (comme
value="ISBN : ") ou en liant un bean à la propriété.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 325

■■ <h:inputTextarea> affiche une zone de texte et lie sa valeur à l’attribut description


du livre.
■■ <h:selectBooleanCheckbox> affiche une case à cocher et la lie à l’attribut illus-
trations (un Boolean).
■■ <h:commandButton> affiche un bouton de soumission de formulaire qui,
lorsqu’on cliquera dessus, invoquera la méthode doCreateBook() du bean géré
(action="#{bookController.doCreateBook}").

La page listBooks.xhtml

La méthode doCreateBook() du bean géré est appelée lors du clic sur le bouton de
soumission de la page newBook.xhtml (voir Figure 10.3) ; elle stocke le livre dans la
base et, si aucune exception n’a été lancée, renvoie le nom de la page à afficher ensuite,
listBooks.xhtml, qui affiche tous les livres de la base (voir Figure 10.4). Un lien sur
cette page permet ensuite de revenir à newBook.xhtml pour créer un autre livre.

Figure 10.4
List of the books
La page
listBooks.xhtml.
ISBN Title Price Description Number Of Pages Illustrations
1234 234 H2G2 12.0 Scifi IT book 241 false
564 694 Robots 18.5 Asimov Best seller 317 true
256 6 56 Dune 23.25 The trilogy 529 false
Create a new book

APress Beginning Java EE 6

Le code de la page listBooks.xhtml (voir Listing 10.7) utilise des composants dif-


férents, mais le principe est le même que celui de la page précédente. Le composant
le plus important est celui qui affiche les données sous la forme d’un tableau :
<h:dataTable value="#{bookController.bookList}" var="bk">

L’élément <h:dataTable> est lié à l’attribut bookList du bean géré (une ArrayList
de livres) et déclare la variable bk qui permettra de parcourir cette liste. Dans cet
élément, on peut ensuite utiliser des expressions comme #{bk.isbn} pour obtenir
l’attribut isbn d’un livre. Chaque colonne du tableau est définie par un élément
<h:column>. Le marqueur <h:commandLink> en bas de la page crée un lien qui,
lorsqu’on clique dessus, appelle la méthode doNew() du bean géré (celle-ci permet
de revenir à la page newBook.xhtml).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
326 Java EE 6 et GlassFish 3 

Listing 10.7 : La page listBooks.xhtml


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>List of the books</title>
</h:head>
<h:body>
<h1>List of the books</h1>
<hr/>

<h:dataTable value="#{bookController.bookList}" var="bk">

<h:column>
<f:facet name="header">
<h:outputText value="ISBN"/>
</f:facet>
<h:outputText value="#{bk.isbn}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{bk.title}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Price"/>
</f:facet>
<h:outputText value="#{bk.price}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet> <h:outputText value="#{bk.description}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Number Of Pages"/>
</f:facet>
<h:outputText value="#{bk.nbOfPage}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Illustrations"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 327

</f:facet>
<h:outputText value="#{bk.illustrations}"/>
</h:column>

</h:dataTable>

<h:form>
<h:commandLink action="#{bookController.doNew}">
Create a new book
</h:commandLink>
</h:form>

<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>

Configuration avec web.xml

Les applications web sont généralement configurées à l’aide d’un descripteur de


déploiement web.xml. Nous avons écrit "généralement" car ce fichier est devenu
facultatif avec la nouvelle spécification Servlet 3.0. Cependant, JSF 2.0 reposant sur
Servlet 2.5 (et non sur Servlet 3.0), nous devons quand même déployer notre appli-
cation web avec un descripteur.
Les applications JSF ont besoin d’une servlet nommée FacesServlet qui agit
comme un contrôleur frontal pour toute l’application. Cette servlet et son associa-
tion doivent être définies dans le fichier web.xml, comme le montre le Listing 10.8.

Listing 10.8 : Fichier web.xml déclarant une FacesServlet


<?xml version=’1.0’ encoding=’UTF-8’?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
</web-app>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
328 Java EE 6 et GlassFish 3 

Le descripteur de déploiement associe à la servlet les requêtes d’URL se terminant


par .faces, ce qui signifie que toute demande d’une page se terminant par .faces
sera traitée par FacesServlet.

Compilation et assemblage avec Maven

L’application web doit être compilée et assemblée dans un fichier war (<packaging>war
</packaging>). Le fichier pom.xml du Listing 10.9 déclare toutes les dépendances
nécessaires à la compilation du code (jsf-api, javax.ejb et javax.persistence)
et précise que cette compilation utilisera la version 1.6 du JDK. Avec JSF 2.0, le
fichier faces-config.xml n’est plus obligatoire et nous ne l’utilisons pas ici.

Listing 10.9 : Fichier pom.xml de Maven pour compiler et assembler l’application web


<?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/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter10</artifactId>
<packaging>war</packaging>
<version>1.0</version>

<dependencies>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 329

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Pour compiler et assembler les classes, il suffit d’ouvrir un interpréteur en ligne de


commande dans le répertoire contenant le fichier pom.xml et d’entrer la commande
Maven suivante :
mvn package

Cette commande crée le fichier chapter10-1.0.war dans le répertoire cible.


Ouvrez-le et vous constaterez qu’il contient l’entité Book, le bean BookEJB, le bean
géré Book­Controller, les deux descripteurs de déploiement (persistence.xml et
web.xml) et les deux pages web (newBook.xhtml et listBooks.xhtml).

Déploiement dans GlassFish

L’application web assemblée doit ensuite être déployée dans GlassFish. Après avoir
vérifié que Derby s’exécute et écoute sur son port par défaut, ouvrez un interpréteur
en ligne de commande, placez-vous dans le répertoire target contenant le fichier
chapter10-1.0.war et entrez la commande suivante :

asadmin deploy chapter10-1.0.war

Si le déploiement réussit, la commande qui suit devrait renvoyer le nom et le type


de l’application. Ici, il y a deux types : web car c’est une application web et ejb car
elle contient un EJB :
asadmin list-components
chapter10-1.0 <ejb, web>

Exécution de l’application

Lorsque l’application a été déployée, ouvrez votre navigateur et faites-le pointer


vers l’URL suivante :
http://localhost:8080/chapter10-1.0/newBook.faces

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
330 Java EE 6 et GlassFish 3 

Le fichier pointé est newBook.faces, pas newBook.xhtml, car avec l’extension .faces
JSF sait qu’il doit traiter la page avant de l’afficher (voir l’association de.faces avec
FacesServlet dans le Listing 10.8). Lorsque la page newBook s’affiche, saisissez les
informations et cliquez sur le bouton d’envoi du formulaire pour être redirigé sur la
page listBooks.

Résumé

Aujourd’hui, la compétition entre les interfaces utilisateurs continue de plus belle


avec la prolifération des RDA (Rich Desktop Application), des RIA (Rich Internet
Application), des applications pour terminaux mobiles, etc. JSF est entré dans la
course il y a quelques années déjà et continue de tenir son rang grâce aux nouvelles
fonctionnalités de JSF 2.0.
L’architecture de JSF repose sur des composants et une API riche permettant de
développer des moteurs de rendu, des convertisseurs, des validateurs, etc. Elle
reconnaît plusieurs langages, bien que le langage de déclaration de page (PDL) pré-
féré de JSF 2.0 soit Facelets.
Les annotations ont été introduites avec JSF 2.0 et sont désormais utilisées dans la
plupart des spécifications de Java EE 6. Le Chapitre 11 s’intéresse à la partie pré-
sentation de Java EE 6 et couvre les spécifications JSP 2.2, EL 2.2 et JSTL 1.2, il
introduit également Facelets et se concentre principalement sur JSF. Le Chapitre 12
aborde tous les aspects dynamiques de la spécification. Vous y apprendrez le fonc-
tionnement de la navigation, les beans gérés et comment écrire votre propre conver-
tisseur et validateur.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
11
Pages et composants

Nous vivons dans le monde de l’Internet. Munis d’un backend transactionnel qui
traite des milliers de requêtes et communique avec des systèmes hétérogènes au
moyen de services web, nous avons maintenant besoin d’une couche de présenta-
tion pour interagir avec les utilisateurs – de préférence une interface qui s’exécute
dans un navigateur car les navigateurs sont partout et parce que les interfaces web
sont plus riches, plus dynamiques et plus simples à utiliser. Les RIA (Rich Internet
Applications) sont de plus en plus appréciées car les utilisateurs peuvent profiter de
leur connaissance de leurs navigateurs : ils ont besoin de consulter des catalogues de
livres et de CD, mais ils veulent également accéder au courrier électronique et à des
documents, recevoir des notifications par courrier ou voir une partie de leur naviga-
teur se mettre à jour en fonction des informations reçues du serveur. Ajoutons à cela
que la philosophie du Web 2.0 est de faire partager toutes sortes d’informations à des
groupes d’amis qui peuvent interagir les uns avec les autres et l’on comprend que les
interfaces web soient de plus en plus compliquées à développer.
Aux premiers jours de Java, les développeurs émettaient directement du HTML à
partir des servlets. Puis nous sommes passés des servlets à JSP (Java Server Pages),
qui utilise des marqueurs personnalisés. Désormais, Java EE 6 et sa nouvelle version
de JSF simplifie encore plus le développement des interfaces web.
Dans ce chapitre, nous présenterons différentes technologies utilisées par Java EE 6
pour créer des pages web. Nous expliquerons d’abord quelques concepts de base
comme HTML, CSS et JavaScript, puis nous passerons à JSP, EL et JSTL. Nous
introduirons alors Facelets, le langage de présentation (PDL) conseillé pour JSF. Le
reste du chapitre s’intéressera à la création d’interfaces web avec JSF ou des com-
posants personnalisés. Le chapitre suivant expliquera comment naviguer entre les
pages et interagir avec un backend pour afficher des données dynamiques.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
332 Java EE 6 et GlassFish 3 

Pages web

Lorsque l’on crée une application web, on affiche généralement un contenu dyna-
mique : une liste d’articles d’un catalogue (des CD et des livres, par exemple), les infor-
mations associées à un identifiant de client, un panier virtuel contenant les articles
que l’utilisateur veut acheter, etc. Inversement, le contenu statique, comme l’adresse
d’un éditeur et les FAQ expliquant comment acheter ou se faire livrer les articles,
change rarement, voire jamais – ce contenu peut également être une image, une
vidéo ou un dessin.
Le but ultime de la création d’une page est son affichage dans un navigateur. Elle
doit donc utiliser les langages compris par les navigateurs : HTML, XHTML, CSS
et JavaScript.

HTML

Hypertext Markup Language (HTML) est le langage qui prédomine dans les pages
web. Il repose sur SGML (Standard Generalized Markup Language), un métalan-
gage standard permettant de définir des langages à marqueurs. HTML utilise des
balises, ou marqueurs, pour structurer le texte en paragraphes, listes, liens, boutons,
zones de texte, etc.
Une page HTML est un document texte utilisé par les navigateurs pour présenter du
texte et des images : ce sont des fichiers texte portant souvent l’extension .html ou
.htm. Une page web est formée d’un contenu, de marqueurs permettant de changer
certains aspects de ce contenu et d’objets externes comme des images, des vidéos,
du code JavaScript ou des fichiers CSS.
La section "Récapitulatif" du chapitre précédent a montré deux pages JSF, dont
l’une affichait un formulaire pour créer un nouveau livre. Le Listing 11.1 montre
cette page écrite en HTML pur, sans utiliser aucun marqueur JSF.

Listing 11.1 : La page newBook.html


<h1>Create a new book</h1>
<hr>
<table border=0>
<TR>
<TD>ISBN :</TD>
<TD><input type=text/></td>
</tr>
<tr>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 333

<td>Title :</td>
<td><input type=text/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type=text/></td>
</tr>
<tr>
<td>Description :
<td><textarea name=textarea cols=20 rows=5></textarea>
</tr>
<TR>
<TD>Number of pages :
<td><input type=text/>
</tr>
<tr>
<td>Illustrations :
<td><input type=checkbox/>
</tr>
</table>
<input type=submit value=Create>
<hr>
<i>APress - Beginning Java EE 6</i>

Normalement, une page HTML valide commence par un marqueur <html> qui agit
comme un conteneur du document. Il est suivi des marqueurs <head> et <body>. Ce
dernier contient la partie visible – ici, un tableau constitué de labels et de champs
de saisie, et un bouton. Comme vous pouvez le constater, le fichier newBook.html
du Listing 11.1 ne respecte pas ces règles mais les navigateurs peuvent afficher des
pages HTML non valides (jusqu’à un certain point). Le résultat affiché ressemblera
donc à la Figure 11.1.

Figure 11.1
Create a new book
Représentation graphique
de la page newBook.html.
ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :
Create

APress Beginning Java EE 6

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
334 Java EE 6 et GlassFish 3 

La représentation graphique de la Figure 11.1 est celle que l’on attendait ; pourtant,


le Listing 11.1 n’est pas correctement formaté en termes de XML :
■■ La page n’a pas de marqueurs <html>, <head> ou <body>.
■■ Le marqueur <input type=submit value=Create> n’est pas fermé.
■■ Les marqueurs mélangent les majuscules et les minuscules (<TR> et </tr> appa-
raissent dans le code).
La plupart des navigateurs autorisent ce type d’erreur et afficheront correctement le
formulaire. En revanche, si vous voulez traiter ce document avec des parsers XML,
par exemple, le traitement échouera. Pour en comprendre la raison, étudions une
page web qui utilise une structure XML stricte avec XHTML (eXtensible Hypertext
Markup Language).

XHTML

XHTML a été créé peu de temps après HTML 4.01. Ses racines puisent dans HTML,
mais avec une reformulation en XML strict. Ceci signifie qu’un document XHTML
est un document XML qui respecte un certain schéma et peut être représenté gra-
phiquement par les navigateurs – un fichier XHTML (qui porte l’extension .xhtml)
peut être directement utilisé comme du XML ou être affiché dans un navigateur. Par
rapport à HTML, il a l’avantage de permettre une validation et une manipulation du
document à l’aide d’outils XML standard (XSL ou eXtensible Stylesheet Language ;
XSLT ou XSL Transformations ; etc.).
Le Listing 11.2 montre la version XHTML de la page web du Listing 11.1.

Listing 11.2 : La page newBook.xhtml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<head>
<title>Creates a new book</title>
</head>
<body>
<h1>Create a new book</h1>
<hr/>
<table border="0">
<tr>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 335

<td>ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Description :</td>
<td><textarea name="textarea" cols="20"
rows="5"></textarea></td>
</tr>
<tr>
<td>Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>

Notez les différences entre les Listings 11.1 et 11.2 : ce dernier respecte une struc-
ture stricte et contient les marqueurs <html>, <head> et <body> ; tous les marqueurs
sont fermés, même les vides (chaque <td> est fermé et on utilise <hr/> au lieu de
<hr>) ; les valeurs des attributs sont toujours entre apostrophes ou entre guillemets
(<table border="0"> ou <table border=’0’>, mais pas <table border=0>) ; tous
les marqueurs sont en minuscules (<tr> au lieu de <TR>).
Le respect strict des règles syntaxiques de XML et les contraintes de schéma ren-
dent XHTML plus facile à maintenir et à traiter que HTML, et c’est la raison pour
laquelle il est désormais le langage préféré pour les pages web.

CSS

Les navigateurs utilisent des langages côté client comme HTML, XHTML, CSS et
JavaScript. CSS (Cascading Style Sheets) sert à décrire la présentation d’un docu-
ment écrit en HTML ou en XHTML. Il permet de définir les couleurs, les polices, la

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
336 Java EE 6 et GlassFish 3 

disposition et les autres aspects de la présentation d’un document et, donc, de séparer
son contenu (écrit en XHTML) de sa présentation (écrite en CSS). Comme HTTP,
HTML et XHTML, les spécifications de CSS sont édictées par le W3C (World Wide
Web Consortium).
Supposons, par exemple, que vous vouliez modifier les labels de la page newBook.
xhtml pour qu’ils soient tous en italique (font-style: italic;), de couleur bleue
(color: #000099;) et dans une taille de police plus grande (font-size: 22px;). Au
lieu de répéter ces modifications pour chaque marqueur, il suffit de définir un style
CSS (dans un marqueur <style type="text/css">) et de lui donner un alias (title
et row, par exemple) : la page appliquera alors ce style pour tous les éléments qui
utilisent cet alias afin de modifier leur présentation (<h1 class="title">).

Listing 11.3 : La page newBook.xhtml avec des styles CSS


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<head>
<title>Creates a new book</title>
<style type="text/css">
.title {
font-family: Arial, Helvetica, sans-serif;
font-size: 22px; color: #000099; font-style: italic;
}
.row {
font-family: Arial, Helvetica, sans-serif;
color: #000000; font-style: italic;
}
</style>
</head>
<body>
<h1 class="title">Create a new book</h1>
<hr/>
<table border="0">
<tr>
<td class="row">ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Price :</td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 337

<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Description :</td>
<td><textarea name="textarea" cols="20"
rows="5"></textarea></td>
</tr>
<tr>
<td class="row">Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>

Dans cet exemple, le code CSS est intégré à la page XHTML mais, dans une vraie
application, tous les styles seraient placés dans un fichier distinct qui serait importé
par la page web. Le webmestre peut ainsi créer un ou plusieurs fichiers CSS pour
différents groupes de pages et les contributeurs de contenu peuvent écrire ou modi-
fier leurs pages sans être concernés par l’aspect final de leurs documents.
À la Figure  11.2, tous les labels sont désormais en italique et le titre de la page
apparaît en bleu.

Figure 11.2
Create a new book
Représentation graphique
de la page newBook.xhtml.
ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

Create

APress Beginning Java EE 6

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
338 Java EE 6 et GlassFish 3 

DOM

Une page XHTML est un document XML et a donc une représentation DOM (Docu-
ment Object Model). DOM est une spécification du W3C pour accéder et modifier le
contenu et la structure des documents XML ainsi qu’une API abstraite pour interro-
ger, parcourir et manipuler ce type de document – il peut être considéré comme une
représentation arborescente de la structure d’un document. La Figure 11.3 montre
une représentation DOM de la page newBook.xhtml : la racine est le marqueur html,
ses deux fils sont head et body et ce dernier a lui-même un fils table avec une liste
de fils tr.

Figure 11.3
Représentation
arborescente de la page
newBook.xhtml.

DOM fournit un moyen standard d’interaction avec les documents XML. Grâce à
lui, vous pouvez parcourir l’arbre d’un document et modifier le contenu d’un nœud.
Moyennant un peu de code JavaScript, il est possible d’ajouter un comportement
dynamique à une page web. Comme nous le verrons au chapitre suivant, Ajax utilise
JavaScript sur la représentation DOM d’une page.

JavaScript

Les langages que nous avons évoqués jusqu’à maintenant permettent de représen-
ter le contenu statique et les aspects graphiques d’une page web. Cependant, une
page doit souvent interagir avec l’utilisateur en affichant du contenu dynamique. Ce
contenu dynamique peut être traité par des technologies côté serveur comme JSP ou
JSF, mais les navigateurs peuvent également en produire de leur côté en exécutant
du code JavaScript.
JavaScript est un langage de script pour le développement web côté client. Contrai-
rement à ce que son nom pourrait laisser supposer, il n’a rien à voir avec le langage

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 339

de programmation Java car c’est un langage interprété et faiblement typé. Avec Java­
Script, il est possible de créer des applications web dynamiques en écrivant des fonc-
tions qui agissent sur le DOM d’une page. Il a été standardisé par l’ECMA (European
Computer Manufacturers Association) sous le nom d’ECMAScript. Toute page écrite
en respectant les standards XHTML, CSS et JavaScript devrait s’afficher et se com-
porter de façon quasiment identique avec tout navigateur respectant ces normes.
Le Listing 11.4 contient un exemple de code JavaScript manipulant le DOM de la
page newBook.xhtml qui affiche un formulaire permettant de saisir des informations
sur un livre. Le prix du livre doit être fourni par l’utilisateur côté client avant d’at-
teindre le serveur : une fonction JavaScript (priceRequired()) permet de valider ce
champ en testant s’il est vide ou non.

Listing 11.4 : La page newBook.xhtml avec du JavaScript


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<head>
<title>Creates a new book</title>
<script type="text/JavaScript">
function priceRequired() {
if (document.getElementById("price").value == "") {
document.getElementById("priceError").innerHTML =
"Please, fill the price !";
}
</script>
</head>
<body>
<h1>Create a new book</h1>
<hr/>
<table border="0">
<tr>
<td>ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Price :</td>
<td><input id="price" type="text"
onblur="JavaScript:priceRequired()"/>
<span id="priceError"/>
</td>
</tr>
<tr>
<td>Description :</td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
340 Java EE 6 et GlassFish 3 

<td><textarea name="textarea" cols="20"


rows="5"></textarea></td>
</tr>
<tr>
<td>Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>

Dans le Listing 11.4, la fonction priceRequired() est intégrée dans la page au moyen


d’un marqueur <script> et est appelée lorsque le champ de saisie du prix perd le
focus (représenté par l’événement onblur). Elle utilise l’objet document implicite
qui représente le DOM du document XHTML. L’appel getElementById("price")
recherche un élément ayant un identifiant price (<input id="price">) : on récu-
père sa valeur et l’on teste si elle est vide. Si c’est le cas, la fonction recherche
un autre élément appelé priceError (getElementById("priceError")) et fixe sa
valeur à "Please, fill the price !". Cette procédure de validation affichera donc
le message de la Figure 11.4 si le prix n’a pas été indiqué.

Figure 11.4
Create a new book
La page newBook.html
affiche un message
ISBN :
d’erreur.
Tiltle :

Price : Please, fill the price !

Description :

Number of pages :

Illustrations :
Create

APress Beginning Java EE 6

JavaScript est un langage puissant : nous n’en avons présenté qu’une petite partie
pour montrer son interaction avec DOM mais il est important de comprendre qu’une

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 341

fonction JavaScript peut accéder à un nœud de la page (par son nom ou son identi-
fiant) et modifier dynamiquement son contenu côté client. Nous donnerons plus de
détails dans la section "Ajax" du prochain chapitre.

Java Server Pages

Nous venons de présenter des technologies et des langages, comme XHTML ou


CSS, qui représentent le contenu et l’aspect visuel d’une page web. Pour ajouter
de l’interactivité et modifier dynamiquement des parties d’une page, vous pouvez
utiliser des fonctions JavaScript qui s’exécuteront dans le navigateur mais, la plupart
du temps, vous devrez faire appel à une couche métier d’EJB pour afficher des infor-
mations provenant d’une base de données. Ce contenu dynamique peut être obtenu à
l’aide de JSP (ou JSF avec JSP ou Facelets, comme nous le verrons plus loin).
JSP a été ajouté à J2EE 1.2 en 1999 et permet de créer dynamiquement des pages
web en réponse à une requête d’un client. Les pages sont traitées sur le serveur et
compilées sous forme de servlets. Les pages JSP ressemblent à des pages HTML
ou XHTML, sauf qu’elles contiennent des marqueurs spéciaux pour effectuer des
traitements sur le serveur et appeler du code Java côté serveur.
La plupart du travail de JSP repose sur l’API servlet. Les servlets ont été créées
pour permettre à un serveur d’accepter des requêtes HTTP des clients et de créer
des réponses dynamiques. Comme JSP, elles peuvent se servir de n’importe quelle
ressource serveur comme les EJB, les bases de données, les services web et d’autres
composants. Les JSP sont dynamiques parce qu’elles exécutent du code Java pour
former une réponse en fonction d’une requête.
Les JSP s’exécutent sur un serveur dans un conteneur de servlets et répondent aux
requêtes des clients, qui sont des utilisateurs accédant à une application web au
moyen d’un navigateur via HTTP, le même protocole que celui qu’ils utilisent pour
demander des pages XHTML au serveur. Le conteneur de servlets gère le cycle de
vie d’une JSP en :
■■ compilant le code JSP dans une servlet ;
■■ chargeant et initialisant la JSP ;
■■ traitant les requêtes des clients et les faisant suivre à la JSP ;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
342 Java EE 6 et GlassFish 3 

■■ renvoyant les réponses aux clients (ces réponses ne contiennent que des marqueurs
HTML ou XHTML pour pouvoir s’afficher dans un navigateur) ;
■■ déchargeant la JSP et arrêtant de lui envoyer des requêtes (lorsque le serveur
s’arrête, par exemple).
Une page JSP pouvant produire du code HTML ou XHTML, vous pouvez utili-
ser des extensions différentes pour l’indiquer – .jsp pour HTML et .jspx pour
XHTML, par exemple. Examinons le code suivant :
<html>
<head>
<title>Lists all the books</title>
</head>
<body>
<h1>Lists all the books</h1>
<hr/>
</body>
</html>

Comme vous pouvez le constater, une JSP valide peut ne contenir que des mar-
queurs HTML : vous pourriez sauvegarder ce code dans un fichier listBooks.jsp
et le déployer dans un conteneur de servlets qui renverrait alors une simple page
HTML. En fait, une page JSP ressemble à du HTML, mais elle peut également
contenir des marqueurs supplémentaires qui permettent d’ajouter du contenu dyna-
mique afin que les réponses produites dépendent des requêtes. La spécification JSP
définit les éléments suivants :
■■ directives ;
■■ scripts ;
■■ actions.
Comme nous le verrons, il existe deux syntaxes pour ces éléments : la syntaxe XML
pour les pages XHTML (<jsp:directive attributs/>) et la syntaxe JSP, qui n’est
pas conforme à XML (<%@ attributs %>).

Directives

Les directives fournissent des informations sur la JSP et ne produisent rien. Il existe
trois directives : page, include et taglib. Les deux syntaxes possibles sont :
<%@ directive attributs %>
<jsp:directive attributs />

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 343

La directive page sert à indiquer les attributs de page tels que le langage de program-
mation de la page (Java, ici), le type MIME, l’encodage des caractères de la réponse,
si la JSP est une page d’erreur, etc.
<%@ page contentType=" text/html; ISO-8859-1" language="java" %>
<jsp:directive.page contentType="text/html; ISO-8859-1"
language="java"/>

La directive include sert à inclure une autre page (HTML, XHTML ou JSP) dans la
page courante. Vous pouvez l’utiliser pour inclure une page standard (un en-tête ou
un pied de page, par exemple) dans plusieurs JSP.
<%@ include file="header.jsp"%>
<jsp:directive.include file="header.jsp" />

La section "Bibliothèque des marqueurs JSP standard" montre que l’on peut étendre
les JSP à l’aide d’une bibliothèque de marqueurs. La directive taglib déclare qu’une
page utilise l’une de ces bibliothèques en l’identifiant de façon unique par une URI
et un préfixe. Avec la syntaxe XML, ces deux informations sont regroupées dans un
espace de noms unique (xmlns). Dans l’exemple suivant, la bibliothèque de mar-
queurs http://java.sun.com/jstl/core est disponible pour la page via le préfixe c :
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<jsp:root xmlns:c="http://java.sun.com/jstl/core">

Scripts

Les scripts incluent du code Java permettant de manipuler des objets et d’effectuer
des traitements affectant le contenu. Ils peuvent utiliser les deux syntaxes suivantes :
<%! déclaration %>
<jsp:declaration>ceci est une déclaration</jsp:declaration>
<% scriptlet %>
<jsp:scriptlet>ceci est un scriptlet</jsp:scriptlet>
<%= expression %>
<jsp:expression>ceci est une expression</jsp:expression>

Les déclarations permettent de déclarer les variables ou les méthodes qui seront dispo-
nibles pour tous les autres scripts de la page. La déclaration n’apparaît que dans la JSP
traduite (c’est-à-dire dans la servlet), pas dans ce qui est envoyé au client. Le code sui-
vant, par exemple, déclare une instance d’ArrayList qui sera globale à toute la page :
<%! ArrayList books = new ArrayList(); %>
<jsp:declaration>
ArrayList books = new ArrayList();
</jsp:declaration>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
344 Java EE 6 et GlassFish 3 

Les scriptlets contiennent du code Java permettant de décrire les actions à réaliser
en réponse aux requêtes. Ils peuvent servir à effectuer des itérations ou à exécuter
conditionnellement d’autres éléments de la JSP. Comme les déclarations, le code
d’un scriptlet n’apparaît que dans la JSP traduite (la servlet). Le code suivant, par
exemple, ajoute un objet Book à l’ArrayList déclarée plus haut :
<%
books.add(new Book("H2G2", 12f, "Scifi IT book",
"1234-234", 241, true));
%>
<jsp:scriptlet>
books.add(new Book("H2G2", 12f, "Scifi IT book",
"1234-234", 241, true));
</jsp:scriptlet>

Les expressions servent à envoyer la valeur d’une expression Java au client. Elles
sont évaluées au moment de la réponse et leur résultat est converti en chaîne de
caractères puis inséré dans le flux affiché par le navigateur. Le fragment de code
suivant, par exemple, affichera l’ISBN d’un livre :
<%= book.getIsbn()%> <jsp:expression>book.getIsbn()</jsp:expression>

Les déclarations, les scriptlets et les expressions doivent contenir du code Java cor-
rect. Si vous choisissez d’utiliser la syntaxe XML, leur contenu doit également être
du XML valide. Le code suivant, par exemple, déclare une ArrayList de livres en
utilisant une classe générique :
<%! ArrayList<Book> books = new ArrayList<Book>(); %>

Si vous voulez faire la même déclaration dans un format XML strict, vous ne pou-
vez pas utiliser les symboles < et > car ils sont réservés à l’ouverture et à la ferme-
ture des marqueurs XML. Vous devez donc utiliser une section CDATA (qui signifie
­Character DATA) afin que le parser XML ne tente pas de l’analyser :
<jsp:declaration><![CDATA[
ArrayList<Book> books = new ArrayList<Book>();
]]></jsp:declaration>

Actions

Les actions standard sont définies par la spécification JSP et forcent la page à effec-
tuer certaines actions (inclure des ressources externes, faire suivre une requête vers
une autre page ou utiliser les propriétés d’objets Java). Elles ressemblent à des mar-
queurs HTML car elles sont représentées par des éléments XML préfixés par jsp

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 345

(<jsp:useBean>, <jsp:include>, etc.). Le Tableau 11.1 énumère toutes les actions


disponibles.

Tableau 11.1 : Éléments des actions JSP

Action Description
useBean Associe une instance d’objet à une portée donnée et à un identifiant.
setProperty Fixe la valeur d’une propriété d’un bean.
getProperty Affiche la valeur d’une propriété d’un bean.
include Permet d’inclure des ressources statiques et dynamiques dans le même
contexte que celui de la page courante.
forward Fait suivre la requête courante à une ressource statique, une JSP ou une
servlet dans le même contexte que celui de la page courante.
param Utilisé avec les éléments include, forward et params. La page incluse ou
transférée verra l’objet requête initial avec les paramètres originaux, plus
les nouveaux.
plugin Permet à une JSP de produire du HTML contenant des constructions
spécifiques au navigateur (OBJECT ou EMBED), qui provoquera le
téléchargement d’une extension.
params Passe des paramètres. Fait partie de l’action plugin.
element Définit dynamiquement la valeur du marqueur d’un élément XML.
attribute Définit un attribut XML. Fait partie de l’action element.
body Définit le corps d’un élément XML. Fait partie de l’action element.

Récapitulatif

Tous ces éléments permettent d’invoquer du code Java et toutes sortes de compo-
sants (EJB, bases de données, services web, etc.). À titre d’exemple, nous allons
créer une page qui affichera une liste de livres stockés dans une ArrayList. Ici, nous
n’accéderons pas à une base de données : nous nous contenterons d’une ArrayList
initialisée avec un nombre déterminé d’objets Book, que nous parcourrons pour
afficher les attributs de chaque livre (ISBN, titre, description, etc.). La Figure 11.5
montre le résultat attendu.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
346 Java EE 6 et GlassFish 3 

Figure 11.5
List of the books
La page listBooks.jsp
affiche une liste de livres.
ISBN Title Price Description Number Of Pages Illustrations
1234 234 H2G2 12.0 Scifi IT book 241 true
56 694 Robots 18.5 Best seller 317 true
256 6 56 Dune 23.25 The trilogy 529 true

APress Beginning Java EE 6

Nous avons besoin de plusieurs éléments pour construire cette page. Comme le
montre le Listing  11.5, il faut importer les classes java.util.ArrayList et Book
avec une directive (<%@ page import="java.util.ArrayList" %>). Puis on déclare
un attribut books, instance d’ArrayList, afin qu’il soit accessible à toute la page
(<%! ArrayList<Book> books = new ArrayList<Book>(); %>). Ensuite, un scri-
plet ajoute des objets livres dans une ArrayList et un autre parcourt cette liste avec
une instruction for. Pour afficher les attributs de chaque livre, nous utilisons des
éléments expression (<%= book. getTitle()%>). Le Listing 11.5 présente le code
complet de cette page.

Listing 11.5 : La page listBooks.jsp


<%@ page import="com.apress.javaee6.chapter11.Book" %>
<%@ page import="java.util.ArrayList" %>
<%!
ArrayList<Book> books = new ArrayList<Book>();
%>
<html>
<head>
<title>List all the books</title>
</head>
<body>
<h1>Lists all the books</h1>
<hr/>
<%
books.add(new Book("H2G2", 12f, "Scifi IT book",
"1234-234", 241, true));
books.add(new Book("Robots", 18.5f, "Best seller",
"564-694", 317, true));
books.add(new Book("Dune", 23.25f, "The trilogy",
"256-6-56", 529, true));
%>
<table border="1">
<tr>
<td>ISBN</td>
<td>Title</td>
<td>Price</td>
<td>Description</td>
<td>Number of pages</td>
<td>Illustrations</td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 347

</tr>
<%
Book book;
for (int i = 0; i < books.size(); i++) {
book = books.get(i);
%>
<tr>
<td><%= book.getIsbn()%></td>
<td><%= book.getTitle()%></td>
<td><%= book.getPrice()%></td>
<td><%= book.getDescription()%></td>
<td><%= book.getNbOfPage()%></td>
<td><%= book.getIllustrations()%></td>
</tr>
<%
} // fin de l’instruction for
%>
</table>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>

Vous remarquerez que l’on peut librement entrelacer du code Java, des marqueurs
HTML et du texte. Tout ce qui est dans un scriptlet (entre <% et %>) est du code Java
qui sera exécuté sur le serveur et tout ce qui est à l’extérieur est du texte qui sera affi-
ché dans la page de réponse. Notez également que le bloc de l’instruction for com-
mence et se termine dans des scriptlets différents. Une JSP peut donc rapidement
devenir difficile à relire si l’on commence à trop mélanger des marqueurs HTML
avec du code Java. En outre, il n’y a pas de séparation entre la logique métier et la
présentation, ce qui complique la maintenance des pages car on mélange deux lan-
gages destinés à deux catégories d’intervenants : Java pour les développeurs métiers
et XHTML/CSS pour les concepteurs web.
Les JSP peuvent utiliser des bibliothèques de marqueurs et le langage d’expressions
(EL). JSTL (JSP Standard Tag Library) standardise un certain nombre d’actions clas-
siques en utilisant un langage à marqueurs familier pour les développeurs web, tandis
qu’EL utilise une syntaxe plus simple pour effectuer certaines actions des scripts JSP.

Langage d’expressions (EL)

Nous venons de voir comment utiliser des scripts pour intégrer du code dans une
page JSP. Les instructions EL fournissent une syntaxe plus simple pour effectuer des
actions similaires et elles sont plus faciles à utiliser pour ceux qui ne développent
pas en Java. Elles permettent d’afficher les valeurs des variables ou d’accéder aux

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
348 Java EE 6 et GlassFish 3 

attributs des objets et disposent d’un grand nombre d’opérateurs mathématiques,


logiques et relationnels. La syntaxe de base d’une instruction EL est de la forme :
${expr}

où expr est une expression valide qui est analysée et interprétée. À partir de JSP 2.1,
vous pouvez également utiliser la syntaxe suivante :
#{expr}

Avec les JSP, ${expr} et #{expr} seront analysées et interprétées exactement de


la même manière. Avec JSF, en revanche, leur traitement sera différent car, comme
nous le verrons, le cycle de vie des pages JSF n’est pas le même que celui des JSP.
${expr} sera évaluée immédiatement (l’expression est compilée en même temps
que la JSP et n’est évaluée qu’une fois : lorsque la JSP s’exécute) alors que #{expr}
est évaluée plus tard (lorsque sa valeur sera nécessaire). Ces deux EL ayant été
unifiés, une page JSP et une page JSF peuvent utiliser les deux moyennant les diffé-
rences de leurs cycles de vie.
Les expressions EL peuvent utiliser la plupart des opérateurs Java habituels :
■■ arithmétiques, +, -, *, / (div), % (mod) ;

■■ relationnels, == (eq), != (ne), < (lt), > (gt), <= (le), >= (ge) ;

■■ logiques, && (and), || (or), ! (not) ;

■■ autres, (), empty, [], ..


Notez que certains opérateurs ont à la fois une forme symbolique et une forme litté-
rale (> est équivalent à gt, / à div, etc.), ce qui permet de rendre une JSP conforme à
XML sans avoir besoin d’utiliser des références d’entités (comme &lt; pour <). Un
"inférieur à" peut ainsi être représenté par #{2 lt 3} au lieu de #{2 < 3}.
L’opérateur empty teste si un objet est null ou s’il référence un objet String, List,
Map null ou un tableau null. Une JSP qui utilise une déclaration pour définir un objet
Book (<%! Book book = new Book(); %>) peut donc tester si l’objet ou l’un de ses
attributs est null :
#{empty book}
#{empty book.isbn}

L’opérateur point permet d’accéder à un attribut d’un objet. Une autre syntaxe pos-
sible consiste à utiliser l’opérateur []. On peut donc accéder à l’attribut isbn à l’aide
de ces deux syntaxes :
#{book.isbn}
#{book[isbn]}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 349

Avec EL 2.2, il est désormais possible d’appeler des méthodes. Le fragment de code
qui suit montre comment acheter un livre en appelant la méthode book.buy() et
comment lui passer un paramètre (la monnaie utilisée) :
#{book.buy}

#{book.buy(’EURO’)}

Les instructions EL peuvent être employées avec les pages JSP et JSF, le fichier
faces-config.xml, les scriptlets ou JSTL.

La bibliothèque de marqueurs standard de JSP (JSTL)

JSTL est un ensemble de marqueurs standard permettant d’éviter le mélange du


code Java et des marqueurs XHTML : grâce à elle, vous pouvez manipuler les don-
nées dynamiques contenues dans une JSP en utilisant des marqueurs XML au lieu
de passer par du code Java. Les actions possibles vont de l’affectation d’une valeur
à un objet à la capture des exceptions en passant par le contrôle du flux avec des
conditions et des itérateurs et par l’accès aux bases de données.
Une bibliothèque de marqueurs est une collection de fonctions pouvant être utilisées
dans une page JSP ou JSF. Le Tableau 11.2 énumère ces fonctions, les URI permet-
tant de référencer les bibliothèques et les préfixes associés (ce sont ceux que l’on
utilise le plus souvent, mais ils peuvent être modifiés).

Tableau 11.2 : Bibliothèques de marqueurs JSTL

Domaine URI Préfixe classique


Noyau http://java.sun.com/jsp/jstl/core c

Traitement XML http://java.sun.com/jsp/jstl/xml x

I18N et formatage http://java.sun.com/jsp/jstl/fmt fmt

Accès aux BD http://java.sun.com/jsp/jstl/sql sql

Fonctions http://java.sun.com/jsp/jstl/functions fn

Avant de pouvoir utiliser ces actions, la JSP doit importer l’URI de la bibliothèque
et choisir un préfixe, soit en utilisant une directive JSP avec le système de marqueurs
de JSP, soit en utilisant une syntaxe XML :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
350 Java EE 6 et GlassFish 3 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


// ou
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jstl/core" version="1.2">

Avec cette déclaration, vous pouvez ensuite utiliser toutes les actions de la biblio-
thèque des marqueurs fondamentaux en utilisant le préfixe c :
<c:set var="upperLimit" value="20"/>

Ce code initialise la variable upperLimit avec la valeur 20.

Actions fondamentales

Les actions fondamentales, énumérées dans le Tableau 11.3, fournissent des mar-


queurs pour manipuler des variables, traiter les erreurs, effectuer des tests et exécuter
des boucles et des itérations.

Tableau 11.3 : Actions fondamentales

Action Description
<c:out> Évalue une expression et affiche son résultat.
<c:set> Initalise la valeur d’un objet.
<c:remove> Supprime une variable.
<c:catch> Capture une exception java.lang.Throwable lancée par l’une de ses
actions imbriquées.
<c:if> Teste si une expression est vraie.
<c:choose> Fournit plusieurs alternatives exclusives.
<c:when> Représente une alternative dans une action <c:choose>.
<c:otherwise> Représente la dernière alternative d’une action <c:choose>.
<c:forEach> Répète son corps pour chaque élément d’une collection ou un nombre fixé
de fois.
<c:forTokens> Itère sur une liste de tokens (lexèmes) séparés par des virgules.
<c:import> Importe une ressource.
<c:url> Encode une URL.
<c:param> Ajoute des paramètres de requête à une URL.
<c:redirect> Redirige vers une URL précise.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 351

Pour illustrer le fonctionnement de certains de ces marqueurs, le Listing 11.6 pré-


sente une JSP qui boucle sur les nombres de 3 à 15 en affichant, pour chacun d’eux,
s’il est pair ou impair.

Listing 11.6 : JSP affichant une liste de nombres pairs et impairs


<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jstl/core" version="1.2">
<html>
<body>
<c:set var="upperLimit" value="20"/>
<c:forEach var="i" begin="3" end="${upperLimit – 5}">

<c:choose>
<c:when test="${i%2 == 0}">
<c:out value="${i} is even"/><br/>
</c:when>
<c:otherwise>
<c:out value="${i} is odd"/><br/>
</c:otherwise>
</c:choose>

</c:forEach>
</body>
</html>

Pour utiliser la bibliothèque des marqueurs fondamentaux, la page doit importer son
URI avec un préfixe. Puis elle affecte la valeur 20 à la variable upperLimit à l’aide
du marqueur <c:set> et itère sur tous les nombres compris entre 3 et 15 (nous avons
volontairement ajouté une expression arithmétique ${upperLimit – 5}). La valeur
de l’indice (la variable i) est testée à chaque tour de boucle pour savoir si elle est
paire ou impaire (<c:when test="${i%2 == 0}">). Le marqueur <c:out> affiche
ensuite la valeur de l’indice, accompagnée du texte "est pair" ou "est impair".
Vous pouvez remarquer que tout le traitement s’effectue grâce aux marqueurs, que
cette page est conforme à XML et qu’elle peut être comprise par les développeurs
qui ne connaissent pas Java.

Actions de formatage

Les actions de formatage, énumérées dans le Tableau 11.4, permettent de formater


des dates, des nombres, des valeurs monétaires et des pourcentages. Elles reconnais-
sent aussi l’internationalisation (i18n) : vous pouvez obtenir ou modifier les locales
(variables de langue) et les zones horaires et obtenir l’encodage de la page web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
352 Java EE 6 et GlassFish 3 

Tableau 11.4 : Actions de formatage

Action Description
<fmt:message> Internationalise une JSP en extrayant un message en
fonction de la langue.
<fmt:param> Fournit un paramètre à <fmt:message>.
<fmt:bundle> Indique le paquetage contenant les messages par langue.
<fmt:setLocale> Fixe la langue à utiliser.
<fmt:requestEncoding> Fixe l’encodage des caractères de la requête.
<fmt:timeZone> Précise la zone horaire du format de l’heure.
<fmt:setTimeZone> Stocke la zone horaire indiquée dans une variable.
<fmt:formatNumber> Formate une valeur numérique (nombre, monnaie,
pourcentage) selon la locale.
<fmt:parseNumber> Analyse la représentation textuelle des nombres, des valeurs
monétaires et des pourcentages.
<fmt:formatDate> Formate les dates et les heures selon la langue.
<fmt:parseDate> Analyse la représentation textuelle des dates et des heures.

Le Listing 11.7 montre comment utiliser ces marqueurs.

Listing 11.7 : JSP formatant des dates et des nombres


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <html>
<body>
Dates
<c:set var="now" value="<%=new java.util.Date()%>"/>
<fmt:formatDate type="time" value="${now}"/>
<fmt:formatDate type="date" value="${now}"/>
<fmt:formatDate type="both" dateStyle="short"
timeStyle="short" value="${now}"/>
<fmt:formatDate type="both" dateStyle="long"
timeStyle="long" value="${now}"/>

Currency
<fmt:setLocale value="en_us"/>
<fmt:formatNumber value="20.50" type="currency"/>
<fmt:setLocale value="en_gb"/>
<fmt:formatNumber value="20.50" type="currency"/>

</body>
</html>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 353

La page importe les bibliothèques des marqueurs fondamentaux et de formatage à


l’aide de directives <%@ taglib>. La ligne <c:set var="now" value="<%=new java.
util.Date()%>"/> initialise la variable now avec la date courante, et le marqueur
<fmt:formatDate> la formate selon différents critères : uniquement l’heure, unique-
ment la date et les deux ensemble. La ligne <fmt:setLocale value="en_us"/> fixe
la langue américaine et formate la valeur monétaire 20.50 pour obtenir $20.50. La
locale est ensuite modifiée pour la Grande-Bretagne afin que cette valeur soit exprimée
en livres sterling. Cette JSP produit donc le résultat suivant :
Dates
11:31:12
14 may 2009
14/02/09 11:31
14 may 2009 11:31:12 CET

Currency
$20.50
£20.50

Actions SQL

Les actions SQL de la JSTL permettent d’effectuer des requêtes sur une base de
données (insertions, modifications et suppressions), d’accéder aux résultats de ces
requêtes et même de mettre en place un contexte transactionnel. Nous avons déjà vu
comment accéder à une base de données avec les entités JPA et les EJB mais, pour
des applications spécifiques, on a parfois besoin d’accéder à une base à partir d’une
page web (pour une application web d’administration non critique utilisée occasion-
nellement par un unique utilisateur, par exemple) : dans ce cas, les marqueurs de la
bibliothèque SQL (voir Tableau 11.5) peuvent se révéler utiles.

Tableau 11.5  : Actions SQL

Action Description
<sql:query> Interroge une base de données.
<sql:update> Exécute une instruction SQL INSERT, UPDATE ou DELETE.
<sql:transaction> Établit un contexte transactionnel pour les marqueurs
<sql:query> et <sql:update>.

<sql:setDataSource> Indique la source de données.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
354 Java EE 6 et GlassFish 3 

Action Description
<sql:param> Fixe les valeurs des marqueurs d’emplacements (?) d’une
instruction SQL.
<sql:dateParam> Fixe les valeurs des marqueurs d’emplacements (?) d’une
instruction SQL pour les valeurs de type java.util.Date.

La page JSP du Listing 11.8 accède à une base de données, récupère toutes les lignes
de la table BOOK et les affiche.

Listing 11.8 : JSP accédant à une base de données pour récupérer tous les livres
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<html>
<head>
<title>Lists all the books</title>
</head>
<body>
<h1>Lists all the books</h1>
<hr/>
<sql:setDataSource dataSource="jdbc/__default"/>
<sql:query var="books">
select * from book
</sql:query>

<table border="1">
<tr>
<th>ISBN</th>
<th>Title</th>
<th>Price</th>
<th>Description</th>
<th>Number of pages</th>
<th>Illustrations</th>
</tr>
<c:forEach var="row" items="${books.rows}">
<tr>
<td><c:out value="${row.isbn}"/></td>
<td><c:out value="${row.title}"/></td>
<td><c:out value="${row.price}"/></td>
<td><c:out value="${row.description}"/></td>
<td><c:out value="${row.nbOfPage}"/></td>
<td><c:out value="${row.illustrations}"/></td>
</tr>
</c:forEach>
</table>
<hr/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 355

<i>APress - Beginning Java EE 6</i>


</body>
</html>

Cette JSP doit d’abord importer la bibliothèque sql avec une directive <%@ taglib>
puis indiquer la source de données (ici, la source par défaut jdbc/__default fournie
avec GlassFish). Le résultat de l’exécution de la requête select * from book est
stocké dans la variable books et toutes les lignes obtenues se trouvent dans la collec-
tion books.rows, que l’on parcourt avec le marqueur <c:forEach>. Le résultat sera
identique à celui que nous avons déjà présenté à la Figure 11.5.

Actions XML

Par certains aspects, la bibliothèque de marqueurs XML ressemble à la biblio-


thèque des marqueurs fondamentaux  : elle permet d’effectuer une analyse XML,
d’itérer sur les éléments des collections, d’effectuer des opérations reposant sur les
expressions Xpath et de réaliser des transformations à l’aide de documents XSL.
Le Tableau 11.6 énumère les actions de cette bibliothèque.

Tableau 11.6 : Actions XML

Action Description
<x:parse> Analyse un document XML.
<x:out> Évalue une expression XPATH et produit son résultat.
<x:set> Évalue une expression XPATH et stocke son résultat dans une
variable.
<x:if> Évalue l’expression XPATH si l’expression est vraie.
<x:choose> Fournit plusieurs alternatives exclusives.
<x:when> Représente une alternative d’une action <x:choose>.
<x:otherwise> Représente la dernière alternative d’une action <x:choose>.
<x:forEach> Évalue une expression XPATH et répète son contenu sur le résultat.
<x:transform> Applique une feuille de style XSLT à un document XML.
<x:param> Fixe les paramètres de la transformation <x:transform>.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
356 Java EE 6 et GlassFish 3 

Nous avons besoin d’un document XML pour effectuer les transformations réalisées
par ces marqueurs. Le fichier books.xml du Listing 11.9 contient une liste de livres
stockés sous forme d’éléments et d’attributs XML.

Listing 11.9 : Le fichier books.xml


<?xml version=’1.0’ encoding=’UTF-8’?>
<books>
<book isbn=’1234-234’ price=’12’ nbOfPage=’241’
illustrations=’true’>
<title>H2G2</title>
<description>Scifi IT book</description>
</book>
<book isbn=’564-694’ price=’18.5’ nbOfPage=’317’
illustrations=’true’>
<title>Robots</title>
<description>Best seller</description>
</book>
<book isbn=’256-6-56’ price=’23.25’ nbOfPage=’529’
illustrations=’false’>
<title>Dune</title>
<description>The trilogy</description>
</book>
</books>

Ce fichier doit être déployé avec la JSP ou être importé par une URL. La page JSP
du Listing 11.10 l’analyse et affiche tous les livres.

Listing 11.10 : JSP analysant le fichier books.xml et affichant son contenu


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %>
<html>
<body>
<table border="1">
<tr>
<th>ISBN</th>
<th>Title</th>
<th>Price</th>
<th>Description</th>
<th>Number of pages</th>
<th>Illustrations</th>
</tr>

<c:import url="books.xml" var="bookUrl"/>


<x:parse xml="${bookUrl}" var="doc"/>

<x:forEach var="b" select="$doc/books/book">


<tr>
<td><x:out select="$b/@isbn"/></td>
<td><x:out select="$b/title"/></td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 357

<td><x:out select="$b/@price"/></td>
<td><x:out select="$b/description"/></td>
<td><x:out select="$b/@nbOfPage"/></td>
<td><x:out select="$b/@illustrations"/></td>
</tr>
</x:forEach>
</table>
</body>
</html>

Pour commencer, cette JSP doit importer la bibliothèque de marqueurs XML (avec
la directive <%@ taglib>) puis charger le fichier books.xml dans la variable ­bookUrl
à l’aide du marqueur <c:import>. bookUrl contient le texte brut, qui doit être ana-
lysé par le marqueur <x:parse> – le DOM résultant sera stocké dans la variable
doc. Une fois que le document a été analysé, nous pouvons le parcourir et afficher
les valeurs en utilisant des expressions XPATH avec <x:out> (/@isbn représente un
attribut XML et /title, un élément). Le résultat obtenu sera identique à celui de la
Figure 11.5.

Fonctions

Les fonctions ne sont pas des marqueurs mais sont quand même définies dans la
spécification JSTL. Elles peuvent être utilisées avec EL et sont principalement
employées pour traiter les chaînes de caractères :
${fn:contains("H2G2", "H2")}

Ce code teste si une chaîne contient une sous-chaîne particulière : ici, cet appel ren-
verra true car H2G2 contient H2. L’appel suivant renvoie la longueur d’une chaîne
ou d’une collection : dans cet exemple précis son résultat sera 4.
${fn:length("H2G2")}

Une JSP peut afficher les résultats des fonctions (avec un marqueur <c:out>) ou les
utiliser dans un test ou une boucle :
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fn" prefix="fn" %>
<html>
<body>
<c:out value="${fn:toLowerCase(sentence)}" />
<c:if test="${fn:length(’H2G2’) == 4}">
H2G2 is four caracters long
</c:if>
</body>
</html>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
358 Java EE 6 et GlassFish 3 

Le Tableau 11.7 énumère toutes les fonctions fournies par la bibliothèque.


Tableau 11.7 : Fonctions

Fonction Description
fn:contains Teste si une chaîne contient la sous-chaîne indiquée.
fn:containsIgnoreCase Idem, sans tenir compte de la casse.
fn:endsWith Teste si une chaîne se termine par le suffixe indiqué.
fn:escapeXml Protège les caractères pouvant être interprétés comme du XML.
fn:indexOf Renvoie l’indice de la première occurrence d’une sous-chaîne
dans une chaîne.
fn:join Joint tous les éléments d’un tableau pour former une chaîne.
fn:length Renvoie le nombre d’éléments d’une collection ou le nombre
de caractères d’une chaîne.
fn:replace Renvoie une chaîne où toutes les occurrences de la sous-
chaîne indiquée ont été remplacées par une autre chaîne.
fn:split Découpe une chaîne pour obtenir un tableau de sous-chaînes.
fn:startsWith Teste si une chaîne commence par le préfixe indiqué.
fn:substring Renvoie une sous-chaîne.
fn:substringAfter Renvoie la sous-chaîne située après la sous-chaîne indiquée.
fn:substringBefore Renvoie la sous-chaîne située avant la sous-chaîne indiquée.
fn:toLowerCase Convertit une chaîne en minuscules.
fn:toUpperCase Convertit une chaîne en majuscules.
fn:trim Supprime les espaces aux deux extrémités d’une chaîne.

Facelets

Lorsque JSF a été créé, le but consistait à réutiliser JSP comme PDL principal car
elle faisait déjà partie de Java EE. JSP utilisait EL et JSTL et l’idée consistait donc
à réutiliser toutes ces technologies avec JSF. JSP est un langage de page et JSF, une
couche de composants située au-dessus. Cependant, les cycles de vie de JSP et de
JSF ne s’accordent pas. Pour produire une réponse, les marqueurs de JSP sont traités
de haut en bas, dans l’ordre où ils apparaissent ; alors que le cycle de vie de JSF est
un peu plus compliqué puisque la production de l’arborescence des composants et

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 359

leur traitement ont lieu dans des phases différentes. Facelets entre donc en jeu pour
correspondre au cycle de vie des JSF.
Facelets est une alternative open-source à JSP. À la différence de JSP, EL et JSTL,
Facelets n’a pas de JSR et ne fait pas partie de Java EE : c’est un remplaçant de JSP
qui fournit une alternative XML (XHTML) pour les pages d’une application JSF.
Facelets ayant été conçu en tenant compte de JSF, il fournit un modèle de program-
mation plus simple que celui de JSP.
Facelets dispose d’une bibliothèque de marqueurs permettant d’écrire l’interface
utilisateur et reconnaît en partie les marqueurs JSTL. Bien que la bibliothèque de
fonctions soit intégralement disponible, seuls quelques marqueurs fondamentaux
(c:if, c:forEach, c:catch et c:set) sont reconnus. La caractéristique essentielle de
Facelets est son mécanisme de templates de pages, qui est bien plus souple que celui
de JSP. Il permet également de créer des composants personnalisés utilisables dans
le modèle arborescent de JSF.
La bibliothèque des marqueurs Facelets est définie par l’URI http://java.sun.
com/jsf/facelets et utilise généralement le préfixe ui. Ces marqueurs sont présentés
dans le Tableau 11.8.

Tableau 11.8 : Marqueurs Facelets

Marqueur Description
<ui:composition> Définit une composition qui utilise éventuellement un template.
Plusieurs compositions peuvent utiliser le même template.
<ui:component> Crée un composant.
<ui:debug> Capture les informations de débogage.
<ui:define> Définit le contenu inséré dans une page par un template.

<ui:decorate> Décore une partie du contenu d’une page.


<ui:fragment> Ajoute un fragment de page.
<ui:include> Encapsule et réutilise un contenu dans plusieurs pages XHTML,
comme le marqueur <jsp:include> de JSP.
<ui:insert> Insère un contenu dans un template.
<ui:param> Passe des paramètres à un fichier inclus par <ui:include> ou à un
template.
<ui:repeat> Alternative à <c:forEach>.
<ui:remove> Supprime du contenu d’une page.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
360 Java EE 6 et GlassFish 3 

Les pages Facelets sont écrites en XHTML et ressemblent à ce que nous avons déjà
vu avec le Listing 11.2. Nous verrons comment utiliser les templates dans la section
qui leur est consacrée dans ce chapitre.

JavaServer Faces

Nous avons commencé ce chapitre en présentant JSP, JSTL et Facelets car ces tech-
nologies sont essentielles pour comprendre JSF 2.0. Vous devez choisir un PDL pour
écrire une page JSF : jusqu’à JSF 1.2, le PDL préféré était JSP mais, les cycles de
vie des pages JSP et JSF étant différents, Facelets (pages en XHTML) est désormais
le PDL conseillé. Ceci ne signifie pas que vous ne puissiez pas utiliser JSP mais, si
vous le faites, vous serez très limité en termes de marqueurs et de fonctionnalités
(vous n’aurez pas accès aux marqueurs de Facelets, notamment).
Le cycle de vie des JSP est relativement simple. Un source JSP est compilé en ser-
vlet, à laquelle le conteneur web passera les requêtes HTTP qu’il reçoit. La servlet
traite la requête puis renvoie la réponse au client.
JSF a un cycle de vie plus complexe, et c’est la raison pour laquelle, dans la section
"Langage d’expressions (EL)", nous avons présenté deux syntaxes différentes : l’une
utilisant le symbole Dollar (${expression}), l’autre, le symbole dièse (#{expres-
sion}). $ est utilisé pour les expressions qui peuvent s’exécuter immédiatement
(lorsque l’on sait que les objets de l’expression sont disponibles) alors que # sert aux
expressions différées (qui doivent être évaluées plus tard dans le cycle de vie). Avec
JSF 1.2, les deux syntaxes étaient unifiées, mais cela impliquait trop de c­onfusions
et beaucoup d’erreurs.
Le Tableau 11.9 énumère toutes les bibliothèques de marqueurs auxquelles a accès
une page utilisant Facelets comme PDL. On y retrouve la bibliothèque fondamen-
tale et celle des fonctions que nous avons présentées dans la section consacrée à
JSTL, les marqueurs Facelets (avec le préfixe ui) et les nouveaux marqueurs fonda-
mentaux, html et composites de JSF. Les autres marqueurs JSTL (formatage, SQL
et XML) ne sont pas reconnus par Facelets.
Intéressons-nous aux composants HTML de JSF permettant de créer des interfaces
web riches. Le Chapitre 12 présentera l’essentiel de la bibliothèque fondamentale
de JSF avec les convertisseurs et les validateurs, mais examinons d’abord le cycle
de vie d’une page JSF.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 361

Tableau 11.9 : Bibliothèques de marqueurs autorisés avec le PDL Facelets

URI Préfixe classique Description


http://java.sun.com/jsf/html h Contient les composants et leurs
rendus HTML (h:commandButton,
h:commandLink, h:inputText, etc.).

http://java.sun.com/jsf/core f Contient les actions personnalisées


indépendantes d’un rendu particulier
(f:selectItem, f:validateLength,
f:convertNumber, etc.).

http://java.sun.com/jsf/facelets ui Marqueurs pour le support des


templates.
http://java.sun.com/jsf/composite composite Sert à déclarer et à définir des
composants composites.
http://java.sun.com/jsp/jstl/core c Les pages Facelets peuvent utiliser
certains marqueurs fondamentaux
(c:if, c:forEach et c:catch).
http://java.sun.com/jsp/jstl/ fn Les pages Facelets peuvent utiliser tous
functions les marqueurs de fonctions.

Cycle de vie

Une page JSF est une arborescence de composants avec un cycle de vie spécifique
qu’il faut bien avoir compris pour savoir à quel moment les composants sont validés
ou quand le modèle est mis à jour. Un clic sur un bouton provoque l’envoi d’une
requête du navigateur vers le serveur et cette requête est traduite en événement qui
peut être traité par l’application sur le serveur. Toutes les données saisies par l’uti-
lisateur passent par une étape de validation avant que le modèle soit mis à jour et
que du code métier soit appelé. JSF se charge alors de vérifier que chaque compo-
sant graphique (composants parent et fils) est correctement rendu par le naviga-
teur. Les différentes phases du cycle de vie d’une page JSF sont représentées par la
Figure 11.6.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
362 Java EE 6 et GlassFish 3 

Figure 11.6 Fin de Fin de


la réponse la réponse
Cycle de vie
de JSF. Requête Restauration
Application Traitement Traitement
des valeurs des Validations des
de la vue de la requête événements événements

Fin de Fin de
la réponse la réponse

Fin de
la réponse Traitement Traitement Modification
Appel de des valeurs
Réponse des l'application des
événements événements du modèle

Erreurs de conversion ou de validation

Le cycle de vie de JSF se divise en six phases :

1. Restauration de la vue. JSF trouve la vue cible et lui applique les entrées de
l’utilisateur. S’il s’agit de la première visite, JSF crée la vue comme un com-
posant UIViewRoot (racine de l’arborescence de composants, qui constitue une
page particulière). Pour les requêtes suivantes, il récupère l’UIViewRoot précé-
demment sauvegardée pour traiter la requête HTTP courante.
2. Application des valeurs de la requête. Les valeurs fournies avec la requête
(champs de saisie, d’un formulaire, valeurs des cookies ou à partir des en-têtes
HTTP) sont appliquées aux différents composants de la page. Seuls les compo-
sants UI modifient leur état, non les objets métiers qui forment le modèle.
3. Validations. Lorsque tous les composants UI ont reçu leurs valeurs, JSF tra-
verse l’arborescence de composants et demande à chacun d’eux de s’assurer
que la valeur qui leur a été soumise est correcte. Si la conversion et la validation
réussissent pour tous les composants, le cycle de vie passe à la phase suivante.
Sinon il passe à la phase de Rendu de la réponse avec les messages d’erreur de
validation et de conversion appropriés.
4. Modification des valeurs du modèle. Lorsque toutes les valeurs des compo-
sants ont été affectées et validées, les beans gérés qui leur sont associés peuvent
être mis à jour.
5. Appel de l’application. On peut maintenant exécuter la logique métier. Les
actions qui ont été déclenchées seront exécutées sur le bean géré. La navigation
entre en jeu car c’est la valeur qu’elle renvoie qui déterminera la réponse.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 363

6. Rendu de la réponse. Le but principal de cette phase consiste à renvoyer la


réponse à l’utilisateur. Son but secondaire est de sauvegarder l’état de la vue pour
pouvoir la restaurer dans la phase de restauration si l’utilisateur redemande la vue.
Le thread d’exécution d’un cycle requête/réponse peut passer ou non par chacune de
ces étapes en fonction de la requête et de ce qui se passe au cours de son traitement :
en cas d’erreur, notamment, le flux d’exécution passe directement à la phase de
Rendu de la réponse. Quatre de ces étapes peuvent produire des messages d’erreur :
Application des valeurs de la requête (2), Validations (3), Modification des valeurs
du modèle (4) et Appel de l’application (5). Avec ou sans erreur, la phase de Rendu
de la réponse (6) renvoie toujours le résultat à l’utilisateur.

Composants HTML standard

L’architecture JSF est conçue pour être indépendante de tout protocole ou langage
à marqueurs particulier et pour écrire des applications pour les clients HTML qui
communiquent via HTTP. Une interface utilisateur pour une page web donnée est
créée en assemblant des composants qui fournissent des fonctionnalités spécifiques
afin d’interagir avec l’utilisateur (labels, cases à cocher, etc.) – JSF met à disposition
un certain nombre de classes composants couvrant la plupart des besoins classiques.
Une page est une arborescence de classes héritant de javax.faces.component.
UIComponent et ayant des propriétés, des méthodes et des événements. La racine de
l’arbre est une instance de UIViewRoot et tous les autres composants respectent une
relation d’héritage. Intéressons-nous à ces composants dans une page web.

Commandes
Les commandes sont les contrôles sur lesquels l’utilisateur peut cliquer pour déclen-
cher une action. Ces composants sont généralement représentés sous forme de bou-
tons ou de liens hypertextes, indiqués par les marqueurs du Tableau 11.10.

Tableau 11.10 : Marqueurs de commandes

Marqueur Description
<h:commandButton> Représente un élément HTML pour un bouton de type submit ou
reset.

<h:commandLink> Représente un élément HTML pour un lien agissant comme un


bouton submit. Ce composant doit être placé dans un formulaire.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
364 Java EE 6 et GlassFish 3 

Le code suivant crée des boutons de soumission et de réinitialisation du formulaire,


des images cliquables ou des liens permettant de déclencher un événement :
<h:commandButton value="A submit button"/>
<h:commandButton type="reset" value="A reset button"/>
<h:commandButton image="javaee6.gif"
title="A button with an image"/>
<h:commandLink>A hyperlink</h:commandLink>

Par défaut, un commandButton est de type submit. Pour utiliser une image comme
bouton, utilisez non pas l’attribut value (qui est le nom du bouton) mais l’attribut
image pour indiquer le chemin d’accès du fichier image que vous souhaitez afficher.
Voici le résultat graphique que produira ce code :

Les boutons et les liens ont tous les deux un attribut action permettant d’appeler
une méthode d’un bean géré. Voici comment invoquer la méthode doNew() du boo-
kController, par exemple :

<h:commandLink action="#{bookController.doNew}">
Create a new book
</h:commandLink>

Entrées
Les entrées sont des composants qui affichent leur valeur courante et permettent à
l’utilisateur de saisir différentes informations textuelles. Il peut s’agir de champs de
saisie, de zones de texte ou de composants pour entrer un mot de passe ou des données
cachées. Leurs marqueurs sont énumérés dans le Tableau 11.11.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 365

Tableau 11.11 : Marqueurs d’entrées

Marqueur Description
<h:inputHidden> Représente un élément d’entrée HTML de type caché (non affiché).
<h:inputSecret> Représente un élément d’entrée HTML de type mot de passe. Pour
des raisons de sécurité, tout ce qui a été saisi ne sera pas affiché,
sauf si la propriété redisplay vaut true.
<h:inputText> Représente un élément d’entrée HTML de type texte.
<h:inputTextarea> Représente une zone de texte HTML.

De nombreuses pages web contiennent des formulaires pour que l’utilisateur puisse
saisir des données ou se connecter en fournissant un mot de passe. En outre, les
composants d’entrée utilisent plusieurs attributs permettant de modifier leur longueur,
leur contenu ou leur aspect :
<h:inputHidden value="Hidden data"/>
<h:inputSecret maxlength="8"/>
<h:inputText value="An input text"/>
<h:inputText size="40" value="A longer input text"/>
<h:inputTextarea rows="4" cols="20" value="A text area"/>

Tous les composants ont un attribut value pour fixer leur valeur par défaut. L’attribut
maxLength permet de s’assurer que le texte saisi ne dépasse pas une longueur don-
née et l’attribut size modifie la taille par défaut du composant. Le code précédent
­produira donc le résultat suivant :

An input text
A longer input text

A text area

Sorties
Les composants de sortie affichent une valeur qui peut éventuellement avoir été obte-
nue à partir d’un bean géré, une expression valeur ou un texte littéral. L’utilisateur
ne peut pas modifier ce contenu car il n’est qu’en lecture seule. Le Tableau 11.12
énumère les marqueurs de sortie disponibles.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
366 Java EE 6 et GlassFish 3 

Tableau 11.12 : Marqueurs de sortie

Marqueur Description
<h:outputLabel> Produit un élément <label> de HTML
<h:outputLink> Produit un élément <a>
<h:outputText> Produit un texte littéral

La plupart des pages web affichent du texte. Pour ce faire, vous pouvez utiliser des
éléments HTML classiques mais, grâce à EL, les marqueurs de sortie de JSF per-
mettent d’afficher le contenu d’une variable liée à un bean géré. Vous pouvez ainsi
afficher du texte avec <h:outputText> et des liens hypertextes avec <h:outputLink>.
Notez que les marqueurs <h:commandLink> et <h:outputLink> sont différents car le
dernier affiche le lien sans invoquer de méthode lorsqu’on clique dessus – il crée
simplement un lien externe ou une ancre.
<h:outputLabel value="#{bookController.book.title}"/> <h:outputText value="A
text"/>
<h:outputLink value="http://www.apress.com/">
A link
</h:outputLink>

Ce code n’a pas de représentation graphique particulière, il produira le code HTML


suivant :
<label>The title of the book</label>
A text
<a href="http://www.apress.com/">A link</a>

Sélections
Les composants de sélection (voir Tableau 11.13) permettent de choisir une ou plu-
sieurs valeurs dans une liste. Graphiquement, ils sont représentés par des cases à
cocher, des boutons radio, des listes ou des combo box.

Tableau 11.13 : Marqueurs de sélection

Marqueur Description
<h:selectBooleanCheckbox> Produit une case à cocher représentant une valeur
booléenne unique. Cette case sera initialement cochée ou
décochée selon la valeur de sa propriété checked.
<h:selectManyCheckbox> Produit une liste de cases à cocher.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 367

Tableau 11.13 : Marqueurs de sélection (suite)

Marqueur Description
<h:selectManyListbox> Produit un composant à choix multiples, dans lequel on
peut choisir une ou plusieurs options.
<h:selectManyMenu> Produit un élément <select> HTML.
<h:selectOneListbox> Produit un composant à choix unique, dans lequel on ne
peut choisir qu’une seule option.
<h:selectOneMenu> Produit un composant à choix unique, dans lequel on ne
peut choisir qu’une seule option. N’affiche qu’une option
à la fois.
<h:selectOneRadio> Produit une liste de boutons radio.

Les marqueurs de ce tableau ont une représentation graphique mais ont besoin
d’imbriquer d’autres marqueurs (<f:selectItem> ou <f:selectItems>) pour
contenir les options disponibles. Pour représenter une combo box contenant une
liste de genres littéraires, par exemple, il faut imbriquer un ensemble de marqueurs
<f:selectItem> dans un marqueur <h:selectOneMenu> :

<h:selectOneMenu>
<f:selectItem itemLabel="History" />
<f:selectItem itemLabel="Biography"/>
<f:selectItem itemLabel="Literature"/>
<f:selectItem itemLabel="Comics"/>
<f:selectItem itemLabel="Child"/>
<f:selectItem itemLabel="Scifi"/>
</h:selectOneMenu>

La Figure 11.7 montre toutes les représentations possibles de ces marqueurs. Cer-


taines listes sont à choix multiples, d’autres n’autorisent qu’un seul choix ; comme
tous les autres composants, vous pouvez directement lier la valeur d’un bean géré
(List, Set, etc.) à l’une de ces listes.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
368 Java EE 6 et GlassFish 3 

Marqueur Représentation graphique


h:selectBooleanCheckbox

h:selectManyCheckbox History Biography Literature Comics Child Scifi

History
Biography
Literature
h:selectManyListbox
Comics
Child
Scifi
h:selectManyMenu History
History
Biography
Literature
h:selectOneListbox
Comics
Child
Scifi
h:selectOneMenu History

h:selectOneRadio History Biography Literature Comics Child Scifi

Figure 11.7
Les différentes représentations graphiques des listes.

Graphiques
Il n’existe qu’un seul composant pour afficher les images : <h:graphicImage>. Ce
marqueur utilise un élément HTML <img> pour afficher une image que les utili-
sateurs n’auront pas le droit de manipuler. Ses différents attributs permettent de
modifier la taille de l’image, de l’utiliser comme image cliquable, etc. Une image
peut être liée à une propriété d’un bean géré et provenir d’un fichier sur le système ou
d’une base de données. Le code suivant, par exemple, affiche une image en modifiant
sa taille :
<h:graphicImage value="book.gif" height="200" width="320"/>

Grilles et tableaux
Les données doivent très souvent être affichées sous forme de tableau. JSF four-
nit donc le marqueur <h:dataTable> permettant de parcourir une liste d’éléments
afin de produire un tableau (reportez-vous au code de la page listBooks.xhtml qui
apparaît dans le Listing 10.7 du chapitre précédent). Les tableaux permettent éga-
lement de créer une interface utilisateur "en grille". Dans ce cas, vous pouvez utili-
ser les marqueurs <h:panelGrid> et <h:panelGroup> pour disposer les composants
(voir Tableau 11.14).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 369

Tableau 11.14 : Marqueurs de grilles et de tableaux

Marqueur Description
<h:dataTable> Représente un ensemble de données qui seront affichées dans un élément
<table> de HTML.

<h:column> Produit une colonne de données dans un composant <h:dataTable>.


<h:panelGrid> Produit un élément <table> HTML.
<h:panelGroup> Conteneur de composants pouvant s’imbriquer dans un <h:panelGrid>.

À la différence de <h:dataTable>, le marqueur <h:panelGrid> n’utilise pas de


modèle de données sous-jacent pour produire les lignes de données – c’est un conte-
neur permettant de produire les autres composants JSF dans une grille de lignes et
de colonnes. Vous pouvez préciser le nombre de colonnes : <h:panelGrid> détermi-
nera le nombre de lignes nécessaire (l’attribut column indique le nombre de colonnes
à produire avant de débuter une nouvelle ligne). Le code suivant, par exemple,
­produira une grille de trois colonnes sur deux lignes :
<h:panelGrid columns="3" border="1">
<h:outputLabel value="One"/>
<h:outputLabel value="Two"/>
<h:outputLabel value="Three"/>
<h:outputLabel value="Four"/>
<h:outputLabel value="Five"/>
<h:outputLabel value="Six"/>
</h:panelGrid>

Pour combiner plusieurs composants dans la même colonne, utilisez un <h:panel-


Group> qui produira ses fils comme un seul composant. Vous pouvez également
définir un en-tête et un pied à l’aide du marqueur spécial <f:facet>.
<h:panelGrid columns="3" border="1">
<f:facet name="header">
<h:outputText value="Header"/>
</f:facet>
<h:outputLabel value="One"/>
<h:outputLabel value="Two"/>
<h:outputLabel value="Three"/>
<h:outputLabel value="Four"/>
<h:outputLabel value="Five"/>
<h:outputLabel value="Six"/>
<f:facet name="footer">
<h:outputText value="Footer"/>
</f:facet>
</h:panelGrid>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
370 Java EE 6 et GlassFish 3 

Les deux grilles que nous venons de décrire auront les représentations graphiques
suivantes. La première n’aura ni en-tête ni pied ; la seconde aura les deux :

One Two Three


Four Five Six

Header
One Two Three
Four Five Six
Footer

Messages d’erreur
Les applications peuvent parfois lancer des exceptions en réponse à des données mal
formatées ou pour certaines raisons techniques. Dans ce cas, il ne faut afficher dans
l’interface utilisateur que ce qui est nécessaire afin d’attirer son attention et pour qu’il
puisse corriger le problème. Le mécanisme de gestion des messages d’erreur passe
par l’utilisation des marqueurs <h:message> et <h:messages> (voir Tableau 11.15).
<h:message> est lié à un composant précis, tandis que <h:messages> permet de défi-
nir un message global pour tous les composants de la page.

Tableau 11.15 : Marqueurs de messages

Marqueur Description
<h:message> Affiche un seul message d’erreur.
<h:messages> Affiche tous les messages d’erreur en attente.

Les messages peuvent avoir des importances différentes (INFO, WARN, ERROR et FATAL)
correspondant chacune à un style CSS (respectivement infoStyle, warnStyle,
errorStyle et fatalStyle)  : chaque type de message sera donc affiché dans un
style différent. Le code suivant, par exemple, affichera tous les messages en rouge :
<h:messages style="color:red"/>
<h:form>
Enter a title:
<h:inputText value="#{bookController.title}" required="true"/>
<h:commandButton action="#{bookController.save}" value="Save"/>
</h:form>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 371

Cette page affichera un champ de saisie lié à une propriété d’un bean géré. Ici, cette
propriété est obligatoire : un message d’erreur s’affichera si l’utilisateur clique sur
le bouton Save alors que ce champ est vide.

Validation Error : Value is required.

Enter a tiltle : Save

Informations complémentaires
Les marqueurs énumérés dans le Tableau  11.16 n’ont pas de représentation gra-
phique mais possèdent un équivalent HTML. Bien que les marqueurs natifs de
HTML puissent être utilisés directement sans problème, les marqueurs JSF ont
des attributs supplémentaires qui facilitent le développement. Vous pouvez, par
exemple, ajouter une bibliothèque JavaScript à l’aide du marqueur HTML standard
<script type="text/JavaScript">, mais le marqueur <h:outputScript> de JSF
permet d’utiliser la nouvelle gestion des ressources, comme nous le verrons dans la
section "Gestion des ressources".

Tableau 11.16 : Marqueurs divers

Marqueur Description
<h:body> Produit un élément <body> HTML.
<h:head> Produit un élément <head> HTML.
<h:form> Produit un élément <form> HTML.
<h:outputScript> Produit un élément <script> HTML.
<h:outputStylesheet> Produit un élément <link> HTML.

Templates
Une application web typique contient plusieurs pages partageant toutes le même
aspect, un en-tête, un pied de page, un menu, etc. Facelets permet de définir une dis-
position de page dans un fichier template qui pourra être utilisé par toutes les pages :
ce fichier définit les zones (avec le marqueur <ui:insert>) dont le contenu sera
remplacé grâce aux marqueurs <ui:component>, <ui:composition>, <ui:fragment>
ou <ui:decorate> des pages clientes. Le Tableau 11.17 énumère les marqueurs de
templates.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
372 Java EE 6 et GlassFish 3 

Tableau 11.17 : Marqueurs de templates

Marqueur Description
<ui:composition> Définit une composition utilisant éventuellement un template. Le
même template peut être utilisé par plusieurs compositions.
<ui:define> Définit un contenu qui sera inséré dans l’élément <ui:insert>
correspondant du template.
<ui:decorate> Permet de décorer le contenu d’une page.
<ui:fragment> Ajoute un fragment à une page.
<ui:insert> Définit un point d’insertion dans un template dans lequel on pourra
ensuite insérer un contenu placé dans un marqueur <ui:define>.

À titre d’exemple, réutilisons la page qui affichait un formulaire pour créer un livre
(voir Figure 11.1). Nous pourrions considérer que le titre est l’en-tête de la page
et que le texte "Apress - Beginning Java EE 6" est le pied de page. Le contenu du
­template layout.xml ressemblerait donc au code du Listing 11.11.

Listing 11.11 : Le fichier layout.xml est un template Facelets


<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xml:lang="en" lang="en">
<head>
<title>
<ui:insert name="title">Default title</ui:insert>
</title>
</head>
<body>
<h1>
<ui:insert name="title">Default title</ui:insert>
</h1>
<hr/>

<ui:insert name="content">Default content</ui:insert>

<hr/>
<i>APress - Beginning Java EE 6</i>

</body>
</html>

Le template doit d’abord définir la bibliothèque de marqueurs nécessaire


(xmlns:ui="http://java.sun.com/ jsf/facelets"). Puis il utilise un marqueur

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 373

<ui:insert> pour insérer un attribut title dans les marqueurs HTML <title> et
<h1>. Le corps de la page sera inséré dans l’attribut content.

Pour utiliser ce template, la page newBook.xhtml présentée dans le Listing  11.12


doit le déclarer (<ui:composition template="layout.xhtml">). Puis le prin-
cipe consiste à lier les attributs définis par les marqueurs <ui:define> de la page
à ceux des marqueurs <ui:insert> du template. Dans notre exemple, le titre de
la page, "Create a new book", est stocké dans la variable title (avec <ui:define
name="title">) et sera donc lié au marqueur correspondant dans le template
(<ui:insert name="title">). Il en va de même pour le reste de la page, qui est
inséré dans la variable content (<ui:define name="content">).

Listing 11.12 : La page newBook.xhtml utilise le template layout.xml


<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xml:lang="en" lang="en">

<ui:composition template="layout.xhtml">

<ui:define name="title">Create a new book</ui:define>

<ui:define name="content">

<table border="0">
<tr>
<td>ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Description :</td>
<td><textarea name="textarea" cols="20" rows="5">
</textarea></td>
</tr>
<tr>
<td>Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
374 Java EE 6 et GlassFish 3 

<td>Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>

</ui:define>

</ui:composition>
</html>

La Figure 11.8 montre que le résultat obtenu est identique à celui de la Figure 11.1.

Figure 11.8
Create a new book
La page newBook.html
avec le template
ISBN :
layout.xhtml.
Tiltle :

Price :

Description :

Number of pages :

Illustrations :
Create a book

APress Beginning Java EE 6

Gestion des ressources

La plupart des composants ont besoin de ressources externes pour s’afficher correc-
tement : <h:graphicImage> a besoin d’une image, <h:commandButton> peut égale-
ment afficher une image pour représenter le bouton, <h:outputScript> référence
un fichier JavaScript et les composants peuvent également appliquer des styles CSS.
Avec JSF, une ressource est un élément statique qui peut être transmis aux éléments
afin d’être affiché (images) ou traité (JavaScript, CSS) par le navigateur.
Les versions précédentes de JSF ne fournissaient pas de mécanisme particulier pour
servir les ressources : lorsque l’on voulait en fournir une, il fallait la placer dans
le répertoire WEB-INF pour que le navigateur du client puisse y accéder. Pour la
modifier, il fallait remplacer le fichier et, pour gérer les ressources localisées (une
image avec un texte anglais et une autre avec un texte français, par exemple),

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 375

il fallait utiliser des répertoires différents. JSF 2.0 permet désormais d’assembler


directement les ressources dans un fichier jar séparé, avec un numéro de version et
une locale, et de le placer à la racine de l’application web, sous le répertoire suivant :
resources/<identifiant_ressource>

ou :
META-INF/resources/<identifiant_ressource>

<identifiant_ressource> est formé de plusieurs sous-répertoires indiqués sous la


forme :
[locale/][nomBib/][versionBib/]nomRessource[/versionRessource]

Tous les éléments entre crochets sont facultatifs. La locale est le code du langage,
suivi éventuellement d’un code de pays (en, en_US, pt, pt_BR). Comme l’indique
cette syntaxe, vous pouvez ajouter un numéro de version à la bibliothèque ou à la
ressource elle-même. Voici quelques exemples :
book.gif
en/book.gif
en_us/book.gif
en/myLibrary/book.gif
myLibrary/book.gif
myLibrary/1_0/book.gif
myLibrary/1_0/book.gif/2_3.gif

Vous pouvez ensuite utiliser une ressource – l’image book.gif, par exemple – direc-
tement dans un composant <h:graphicImage> ou en précisant le nom de la biblio-
thèque (library="myLibrary"). La ressource correspondant à la locale du client
sera automatiquement choisie.
<h:graphicImage value="book.gif" />
<h:graphicImage value="book.gif" library="myLibrary" />
<h:graphicImage value="#{resource[’book.gif’]}" />
<h:graphicImage value="#{resource[’myLibrary:book.gif’]}" />

Composants composites

Tous les composants que nous venons de présenter font partie de JSF et sont dis-
ponibles dans toutes les implémentations qui respectent la spécification. En outre,
comme elle repose sur des composants réutilisables, JSF fournit le moyen de créer
et d’intégrer aisément dans les applications ses propres composants ou des compo-
sants provenant de tierces parties.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
376 Java EE 6 et GlassFish 3 

Nous avons déjà mentionné le fait que tous les composants héritaient, directement
ou indirectement, de la classe javax.faces.component.UIComponent. Avant JSF 2.0,
pour créer son propre composant il fallait étendre la classe component la plus proche
du nouveau composant (UICommand, UIGraphic, UIOutput, etc.), la déclarer dans le
fichier faces-config.xml et fournir un descripteur de marqueur et une représenta-
tion. Ces étapes étaient complexes : d’autres frameworks comme Facelets ont alors
montré qu’il était possible de créer plus simplement des composants puissants. Le
but des composants composites est de permettre aux développeurs de créer de vrais
composants graphiques réutilisables sans avoir besoin d’écrire du code Java ou de
mettre en place une configuration XML.
Cette nouvelle approche consiste à créer une page XHTML contenant les compo-
sants, puis de l’utiliser comme composant dans d’autres pages. Cette page XHTML
est alors vue comme un véritable composant supportant des validateurs, des conver-
tisseurs et des écouteurs. Les composants composites peuvent contenir n’importe
quel marqueur valide et utiliser des templates. Ils sont traités comme des ressources
et doivent donc se trouver dans les nouveaux répertoires standard des ressources.
Le Tableau 11.18 énumère les marqueurs permettant de les créer et de les définir.

Tableau 11.18 : Marqueurs pour la déclaration et la définition des composants composites

Marqueur Description
<composite:interface> Déclare le contrat d’un composant.
<composite:implementation> Définit l’implémentation d’un composant.
<composite:attribute> Déclare un attribut pouvant être fourni à
une instance du composant. Un marqueur
<composite:interface> peut en contenir
plusieurs.
<composite:facet> Déclare que ce composant supporte une facet.
<composite:insertFacet> Utilisé dans un marqueur
<composite:implementation>. La facet
insérée sera représentée dans le composant.
<composite:insertChildren> Utilisé dans un marqueur
<composite:implementation>. Tous les
composants fils ou les templates seront insérés
dans la représentation de ce composant.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 377

Tableau 11.18 : Marqueurs pour la déclaration et la définition des composants composites


(suite)

Marqueur Description
<composite:valueHolder> Le composant dont le contrat est déclaré par
le marqueur <composite:interface> dans
lequel est imbriqué cet élément devra exposer
une implémentation de ValueHolder.
<composite:editableValueHolder> Le composant dont le contrat est déclaré par
le marqueur <composite:interface> dans
lequel est imbriqué cet élément devra exposer
une implémentation d’editableValueHolder.
<composite:actionSource> Le composant dont le contrat est déclaré
par le marqueur <composite:interface>
dans lequel est imbriqué cet élément devra
exposer une implémentation de l’interface
actionSource.

Étudions un exemple montrant la facilité avec laquelle on peut créer un composant


graphique et l’utiliser dans d’autres pages. Dans les chapitres précédents, l’applica-
tion CD-BookStore vendait deux sortes d’articles : des livres et des CD. Au Cha-
pitre 3, nous les avons représentés comme trois objets différents : Book et CD héritaient
d’Item. Ce dernier contenait les attributs communs (title, price et description)
alors que Book et CD contenaient des attributs spécialisés (isbn, publisher, nbOf-
Page et illustrations pour Book ; musicCompany, numberOfCDs, totalDuration et
gender pour CD). Pour que l’application web puisse créer de nouveaux livres et de
nouveaux CD, on a donc besoin de deux formulaires différents, mais les attributs
d’Item pourraient être dans une page distincte qui agirait comme un composant à
part entière. La Figure 11.9 montre ces deux formulaires.
Nous allons donc créer un composant composite contenant deux champs de saisie
(pour le titre et le prix) et une zone de texte (pour la description). L’écriture d’un
composant avec JSF 2.0 est relativement proche de celle que l’on utilise pour Java :
on écrit d’abord une interface, <composite:interface> (voir Listing 11.13), qui sert
de point d’entrée pour le composant – elle décrit les noms et les paramètres qu’il uti-
lise. Puis on passe à l’implémentation : <composite:implementation> est le corps
du composant écrit en XHTML avec des marqueurs JSF ou des templates.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
378 Java EE 6 et GlassFish 3 

Create a new CD Create a new book


Tiltle : Tiltle :

Price : Price :

Description : Description :

Music company : ISBN :

Number of CDs : Number of pages :

Total duration : Illustrations :

Gender : Create a book

Create a cd
APress Beginning Java EE 6

APress Beginning Java EE 6

Figure 11.9
Deux formulaires : l’un pour créer un CD, l’autre pour créer un livre.

L’interface et l’implémentation se trouvent dans la même page. Ici, notre implémen-


tation utilise les éléments <tr> et <td> car nous supposons que le composant sera
placé dans un tableau <table> de deux colonnes.

Listing 11.13 : La page newItem.xhtml contient un composant composite


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">

<composite:interface>
<composite:attribute name="item" required="true"/>
<composite:attribute name="style" required="false"/>
</composite:interface>

<composite:implementation>
<tr style="#{compositeComponent.attrs.style}">
<td>Title :</td>
<td>
<h:inputText value="#{compositeComponent.attrs.item.title}"/>
</td>
</tr>
<tr style="#{compositeComponent.attrs.style}">

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 379

<td>Price :</td>
<td>
<h:inputText value="#{compositeComponent.attrs.item.price}"/>
</td>
</tr>
<tr style="#{compositeComponent.attrs.style}">
<td>Description :</td>
<td>
<h:inputTextarea
value="#{compositeComponent.attrs.item.description}"
cols="20" rows="5"/>
</td>
</tr>
</composite:implementation>
</html>

Ce composant déclare une interface avec deux attributs  : item représente l’entité
Item (et les sous-classes Book et CD) et style est une feuille de style CSS utili-
sée pour la présentation. Ces attributs sont ensuite utilisés par l’implémentation du
­composant à l’aide de la syntaxe suivante :
#{compositeComponent.attrs.style}

Ce code indique un appel de la méthode getAttributes() du composant composite


courant ; le code recherche ensuite dans l’objet Map qu’elle renvoie la valeur corres-
pondant à la clé style.
Avant d’expliquer comment utiliser ce composant, il faut se rappeler les principes
de la gestion des ressources et la notion de configuration par exception : le compo-
sant doit être stocké dans un fichier situé dans une bibliothèque de ressources. Ici,
par exemple, ce fichier s’appelle newItem.xhtml et a été placé dans le répertoire
/resources/apress. Si l’on se fie au comportement par défaut, l’utilisation du
composant nécessite simplement de déclarer une bibliothèque appelée apress et
de lui associer un espace de noms XML :
<html xmlns:ago="http://java.sun.com/jsf/composite/apress">

Puis on appelle le composant newItem (le nom de la page) en lui passant les para-
mètres qu’il attend : item désigne l’entité Item et style est le paramètre facultatif
désignant une feuille de style CSS :
<ago:newItem item="#{itemController.book}" style="myCssStyle"/>
<ago:newItem item="#{itemController.cd}"/>

Le Listing  11.14 montre la page newBook.xhtml représentant le formulaire pour


entrer les informations sur un livre. Elle inclut le composant newItem et ajoute des

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
380 Java EE 6 et GlassFish 3 

champs de saisie pour l’ISBN et le nombre de pages, ainsi qu’une case à cocher pour
indiquer si le livre contient, ou non, des illustrations.

Listing 11.14 : La page newBook.xhtml utilise le composant newItem


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ago="http://java.sun.com/jsf/composite/apress">

<h:head>
<title>Creates a new book</title>
</h:head>

<h:body>
<h1>Create a new book</h1>
<hr/>
<h:form>
<table border="0">

<ago:newItem item="#{itemController.book}"/>

<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{itemController.book.isbn}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText value="#{itemController.book.nbOfPage}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Illustrations : "/></td>
<td>
<h:selectBooleanCheckbox
value="#{itemController.book.illustrations}"/>
</td>
</tr>

</table>

<h:commandButton value="Create a book"


action="#{itemController.doCreateBook}"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 381

</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>

Objets implicites

L’implémentation du composant composite que nous venons de créer utilise un objet


compositeComponent – pourtant, on ne l’a déclaré nulle part : il est simplement là
pour permettre d’accéder aux attributs du composant. Ces types d’objets sont appe-
lés objets implicites (ou variables implicites) : ce sont des identificateurs spéciaux
qui correspondent à des objets spécifiques souvent utilisés. Ils sont implicites parce
qu’une page y a accès et peut les utiliser sans avoir besoin de les déclarer ou de les
initialiser explicitement. Ces objets (énumérés dans le Tableau 11.19) sont utilisés
dans des expressions EL.

Tableau 11.19 : Objets implicites

Objet implicite Description Type renvoyé


application Représente l’environnement de l’application Object
web. Sert à obtenir les paramètres de
configuration de cette application.
applicationScope Associe les noms d’attributs de l’application Map
à leurs valeurs.
component Désigne le composant courant. UIComponent

compositeComponent Désigne le composant composite courant. UIComponent

cookie Désigne un Map contenant les noms des Map


cookies (clés) et des objets Cookie.
facesContext Désigne l’instance FacesContext de cette FacesContext
requête.
header Fait correspondre chaque nom d’en-tête HTTP Map
à une seule valeur de type String.
headerValue Fait correspondre chaque nom d’en-tête HTTP Map
à un tableau String[] contenant toutes les
valeurs de cet en-tête.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
382 Java EE 6 et GlassFish 3 

Tableau 11.19 : Objets implicites (suite)

Objet implicite Description Type renvoyé


initParam Fait correspondre les noms des paramètres Map
d’initialisation du contexte à leurs valeurs de
type String.
param Fait correspondre chaque nom de paramètre à Map
une seule valeur de type String.
paramValues Fait correspondre chaque nom de paramètre Map
à un tableau String[] contenant toutes les
valeurs de ce paramètre.
request Représente l’objet requête HTTP. Object

requestScope Fait correspondre les noms des attributs Map


de la requête à leurs valeurs.
resource Indique l’objet ressource. Object

session Représente l’objet session http. Object

sessionScope Fait correspondre les noms des attributs Map


de la session à leurs valeurs.
view Représente la vue courante. UIViewRoot

viewScope Fait correspondre les noms des attributs Map


de la vue à leurs valeurs.

Tous ces objets implicites sont de vrais objets avec des interfaces  : vous pouvez
accéder à leurs attributs avec EL (consultez la spécification). #{view. Locale}, par
exemple, permet d’obtenir la locale de la vue courante (en_US, pt_PT, etc.). Si vous
stockez un livre dans la portée de la session, par exemple, vous pouvez y accéder par
#{sessionScope.book}. Vous pouvez même utiliser un algorithme plus élaboré pour
afficher tous les en-têtes HTTP et leurs valeurs :
<h3>headerValues</h3>
<c:forEach var="parameter" items="#{headerValues}">
<h:outputText value="#{parameter.key}"/> =
<c:forEach var="value" items="#{parameter.value}">
<h:outputText value="#{value}" escape="false"/>
<br/>
</c:forEach>
</c:forEach>

Si vous exécutez cette page, vous obtiendrez le résultat suivant :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 383

Résumé

Ce chapitre a présenté les différents moyens de créer des pages web statiques à
l’aide de langages comme HTML, XHTML ou CSS et dynamiques avec JavaScript
ou les technologies côté serveur. Pour créer des interfaces web dynamiques avec
Java EE  6, vous avez le choix entre plusieurs spécifications. JSP  2.2, EL  2.2 et
JSTL 1.2 ont été créées en pensant aux servlets, leurs pages sont formées d’infor-
mations HTML et de code Java compilés dans une servlet qui renvoie une réponse
à une requête donnée.
Bien que l’on puisse se servir de JSP comme PDL (Presentation Description Lan-
guage) pour JSF, il est préférable d’utiliser Facelets afin de disposer de la puissance
de l’architecture des composants JSF et de son cycle de vie élaboré. JSF fournit un
ensemble de widgets standard (boutons, liens, cases à cocher, etc.) et un nouveau
modèle pour créer ses propres composants (composants composites). JSF 2.0 dis-
pose également d’un nouveau mécanisme de gestion des ressources permettant de
gérer de façon simple les locales et les versions des ressources externes.
JSF 2.0 utilise une architecture de composants UI sophistiquée  ; ses composants
peuvent être convertis et validés, et interagir avec les beans gérés, qui sont présentés
dans le prochain chapitre.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
12
Traitement et navigation

Au chapitre précédent, nous avons vu comment créer des pages web avec différentes
technologies (HTML, JSP, JSTL, etc.) en insistant sur le fait que JSF est la spécifi-
cation conseillée pour écrire des applications web modernes avec Java EE. Cepen-
dant, créer des pages contenant des composants graphiques ne suffit pas : ces pages
doivent interagir avec un backend (un processus en arrière-plan), il faut pouvoir
naviguer entre les pages et valider et convertir les données. JSF est une spécifica-
tion très riche : les beans gérés permettent d’invoquer la couche métier, de naviguer
dans votre application, et, grâce à un ensemble de classes, vous pouvez convertir les
valeurs des composants ou les valider pour qu’ils correspondent aux règles métiers.
Grâce aux annotations, le développement de convertisseurs et de validateurs person-
nalisés est désormais chose facile.
JSF 2.0 apporte la simplicité et la richesse aux interfaces utilisateurs dynamiques.
Il reconnaît nativement Ajax en fournissant une bibliothèque JavaScript permettant
d’effectuer des appels asynchrones vers le serveur et de rafraîchir une page par parties.
La création d’interfaces utilisateurs, le contrôle de la navigation dans l’application
et les appels synchrones ou asynchrones de la logique métier sont possibles parce
que JSF utilise le modèle de conception MVC (Modèle-Vue-Contrôleur). Chaque
partie est donc isolée des autres, ce qui permet de modifier l’interface utilisateur
sans conséquence sur la logique métier et vice versa.

Le modèle MVC

JSF et la plupart des frameworks web encouragent la séparation des problèmes en


utilisant des variantes du modèle MVC. Ce dernier est un modèle d’architecture
permettant d’isoler la logique métier de l’interface utilisateur car la première ne se

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
386 Java EE 6 et GlassFish 3 

mélange pas bien avec la seconde : leur mélange produit des applications plus diffi-
ciles à maintenir et qui supportent moins bien la montée en charge. Dans la section
"JavaServer Pages" du chapitre précédent, nous avons vu une page JSP qui contenait
à la fois du code Java et des instructions SQL : bien que ce soit techniquement cor-
rect, imaginez la difficulté de maintenir une telle page... Elle mélange deux types de
développement différents (celui de concepteur graphique et celui de programmeur
métier) et pourrait finir par utiliser bien plus d’API encore (accès aux bases de don-
nées, appels d’EJB, etc.), par gérer les exceptions ou par effectuer des traitements
métiers complexes. Avec MVC, l’application utilise un couplage faible, ce qui faci-
lite la modification de son aspect visuel ou des règles métiers sous-jacentes sans
pour autant affecter l’autre composante.
Comme le montre la Figure 12.1, la partie "modèle" de MVC représente les données
de l’application ; la "vue" correspond à l’interface utilisateur et le "contrôleur" gère
la communication entre les deux.

Figure 12.1 Client
Server
Requête HTTP
Le modèle Contrôleur
de conception MVC. (FacesServlet)
Navigateur Réponse HTTP
crée et gère

manipule et redirige Modèle


(backing bean)

accède
Vue
(pages XHTML)

Le modèle est représenté par le contenu, qui est souvent stocké dans une base de
données et affiché dans la vue ; il ne se soucie pas de l’aspect que verra l’utilisateur.
Avec JSF, il peut être formé de backing beans, d’appels EJB, d’entités JPA, etc.
La vue JSF est la véritable page XHTML (XHTML est réservé aux interfaces web,
mais il pourrait s’agir d’un autre type de vue, comme WML pour les dispositifs
mobiles). Comme au chapitre précédent, une vue fournit une représentation gra-
phique d’un modèle et un modèle peut avoir plusieurs vues pour afficher un livre
sous forme de formulaire ou de liste, par exemple.
Lorsqu’un utilisateur manipule une vue, celle-ci informe un contrôleur des modifi-
cations souhaitées. Ce contrôleur se charge alors de rassembler, convertir et valider
les données, appelle la logique métier puis produit le contenu en XHTML. Avec JSF,
le contrôleur est un objet FacesServlet.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 387

FacesServlet

FacesServlet est une implémentation de javax.servlet.Servlet qui sert de contrô-


leur central par lequel passent toutes les requêtes. Comme le montre la Figure 12.2,
la survenue d’un élément (lorsque l’utilisateur clique sur un bouton, par exemple)
provoque l’envoi d’une notification au serveur via HTTP ; celle-ci est interceptée
par javax.faces.webapp.FacesServlet, qui examine la requête et exécute diffé-
rentes actions sur le modèle à l’aide de beans gérés.

Figure 12.2
Cycle de vie
Interactions de 3. Traitement en 6 étapes
FacesServlet.
2. Passe le contrôle au cycle de vie FacesContext

1. Crée un FaceContext

Bouton Événement FacesServlet

En coulisse, la FacesServlet prend les requêtes entrantes et donne le contrôle à


l’objet javax.faces.lifecycle.Lifecycle. À l’aide d’une méthode fabrique, elle
crée un objet javax.faces.context.FacesContext qui contient et traite les infor-
mations d’état de chaque requête. L’objet Lifecycle utilise ce FacesContext en six
étapes (décrites au chapitre précédent) avant de produire la réponse.
Les requêtes qui doivent être traitées par la FacesServlet sont redirigées à l’aide
d’une association de servlet dans le descripteur de déploiement. Les pages web, les
beans gérés, les convertisseurs, etc. doivent être assemblés avec le fichier web.xml
du Listing 12.1.

Listing 12.1 : Fichier web.xml définissant la FacesServlet


<?xml version=’1.0’ encoding=’UTF-8’?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
388 Java EE 6 et GlassFish 3 

<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
</web-app>

Ce fichier définit la javax.faces.webapp.FacesServlet en lui donnant un nom


(Faces Servlet, ici) et une association. Dans cet exemple, toutes les requêtes por-
tant l’extension .faces sont associées pour être gérées par la servlet – toute requête
de la forme http://localhost:8080/ chapter10-1.0/newBook.faces sera donc
traitée par JSF.
Vous pouvez également configurer quelques paramètres spécifiques à JSF dans
l’élément <context-param> (voir Tableau 12.1).

Tableau 12.1 : Paramètres de configuration spécifiques à JSF

Paramètre Description
javax.faces.CONFIG_FILES Définit une liste de chemins de ressources liées au
contexte dans laquelle JSF recherchera les ressources.
javax.faces.DEFAULT_SUFFIX Permet de définir une liste de suffixes possibles pour les
pages ayant du contenu JSF (.xhtml, par exemple).
javax.faces.LIFECYCLE_ID Identifie l’instance LifeCycle utilisée pour traiter les
requêtes JSF.
javax.faces.STATE_SAVING_ Définit l’emplacement de sauvegarde de l’état. Les
METHOD valeurs possibles sont server (valeur par défaut qui
indique que l’état sera généralement sauvegardé dans un
objet HttpSession) et client (l’état sera sauvegardé
dans un champ caché lors du prochain envoi de
formulaire).
javax.faces.PROJECT_STAGE Décrit l’étape dans laquelle se trouve cette application
JSF dans le cycle de vie (Development, UnitTest,
SystemTest ou Production). Cette information peut être
utilisée par une implémentation de JSF pour améliorer les
performances lors de la phase de production en utilisant
un cache pour les ressources, par exemple.
javax.faces.DISABLE_FACELET_ Désactive Facelets comme langage de déclaration de
JSF_VIEWHANDLER page (PDL).
javax.faces.LIBRARIES Liste des chemins qui seront considérés comme une
bibliothèque de marqueurs Facelets.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 389

FacesContext

JSF définit la classe abstraite javax.faces.context.FacesContext pour représenter


les informations contextuelles associées au traitement d’une requête et à la produc-
tion de la réponse correspondante. Cette classe permet d’interagir avec l’interface
utilisateur et le reste de l’environnement JSF.
Pour y accéder, vous devez soit utiliser l’objet implicite facesContext dans vos
pages (les objets implicites ont été présentés au chapitre précédent), soit obtenir
une référence dans vos beans gérés à l’aide de la méthode statique getCurrentIns-
tance() : celle-ci renverra l’instance de FacesContext pour le thread courant et vous
pourrez alors invoquer les méthodes du Tableau 12.2.

Tableau 12.2 : Quelques méthodes de FacesContext

Méthode Description
addMessage Ajoute un message d’erreur.
getApplication Renvoie l’instance Application associée à cette application
web.
getAttributes Renvoie un objet Map représentant les attributs associés à
l’instance FacesContext.
getCurrentInstance Renvoie l’instance FacesContext pour la requête traitée par le
thread courant.
getMaximumSeverity Renvoie le niveau d’importance maximal pour tout
FacesMessage mis en file d’attente.

getMessages Renvoie une collection de FacesMessage.


getViewRoot Renvoie le composant racine associé à la requête.
release Libère les ressources associées à cette instance de FacesContext.
renderResponse Signale à l’implémentation JSF que le contrôle devra être
transmis à la phase Render response dès la fin de l’étape de
traitement courante de la requête, en ignorant les étapes qui n’ont
pas encore été exécutées.
responseComplete Signale à l’implémentation JSF que la réponse HTTP de cette
requête a déjà été produite et que le cycle de vie du traitement de
la requête doit se terminer dès la fin de l’étape en cours.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
390 Java EE 6 et GlassFish 3 

Configuration de Faces

La FacesServlet est interne aux implémentations de JSF ; bien que vous n’ayez
pas accès à son code, vous pouvez la configurer avec des métadonnées. Vous savez
désormais qu’il existe deux moyens d’indiquer des métadonnées avec Java EE 6 :
les annotations et les descripteurs de déploiement XML (/WEB-INF/faces-config.
xml). Avant JSF 2.0, le seul choix possible était XML mais, désormais, les beans
gérés, les convertisseurs, les moteurs de rendu et les validateurs pouvant utiliser les
annotations, les fichiers de configuration XML sont devenus facultatifs.
Nous conseillons l’emploi des annotations mais, pour montrer à quoi ressemble
un fichier faces-config.xml, le Listing  12.2 définit une locale et un ensemble de
messages pour l’internationalisation et certaines règles de navigation. Nous verrons
ensuite comment naviguer avec et sans faces-config.xml.

Listing 12.2 : Extrait d’un fichier faces-config.xml


<?xml version=’1.0’ encoding=’UTF-8’?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">

<application>
<locale-config>
<default-locale>fr</default-locale>
</locale-config>
<resource-bundle>
<base-name>messages</base-name>
<var>msg</var>
</resource-bundle>
</application>

<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>doCreateBook-success</from-outcome>
<to-view-id>/listBooks.htm</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>

Beans gérés

Comme on l’a indiqué plus haut, le modèle MVC encourage la séparation entre le
modèle, la vue et le contrôleur. Avec Java EE, les pages JSF forment la vue et la

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 391

FacesServlet est le contrôleur. Les beans gérés, quant à eux, sont une passerelle
vers le modèle.
Les beans gérés sont des classes Java annotées. Ils constituent le cœur des applications
web car ils exécutent la logique métier (ou la délèguent aux EJB, par exemple), gèrent
la navigation entre les pages et stockent les données. Une application JSF typique
contient un ou plusieurs beans gérés qui peuvent être partagés par plusieurs pages.
Les données sont stockées dans les attributs du bean géré, qui, en ce cas, est égale-
ment appelé "backing bean". Un backing bean définit les données auxquelles est lié
un composant de l’interface utilisateur (la cible d’un formulaire, par exemple). Pour
établir cette liaison, on utilise EL, le langage d’expressions.

Écriture d’un bean géré

Écrire un bean géré est aussi simple qu’écrire un EJB ou une entité JPA puisqu’il
s’agit simplement de créer une classe Java annotée par @ManagedBean (voir Lis-
ting 12.3) – il n’y a nul besoin de créer des entrées dans faces-config.xml, de créer
des classes utilitaires ou d’hériter d’une classe quelconque : JSF 2.0 utilisant égale-
ment le mécanisme de configuration par exception, une seule annotation suffit pour
utiliser tous les comportements par défaut et pour déployer une application web
utilisant un bean géré.

Listing 12.3 : Bean géré simple


@ManagedBean
public class BookController {
private Book book = new Book();
public String doCreateBook() {
createBook(book);
return "listBooks.xhtml";
}

// Constructeurs, getters, setters


}

Le Listing  12.3 met en évidence le modèle de programmation d’un bean géré  :


il stocke l’état (l’attribut book), définit les méthodes d’action (doCreateBook())
­utilisées par une page et gère la navigation (return "listBooks.xhtml").

Modèle d’un bean géré

Les beans gérés sont des classes Java prises en charge par la FacesServlet. Les com-
posants de l’interface utilisateur sont liés aux propriétés du bean (backing bean) et

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
392 Java EE 6 et GlassFish 3 

peuvent invoquer des méthodes d’action. Un bean géré doit respecter les contraintes
suivantes :
■■ La classe doit être annotée par @javax.faces.model.ManagedBean ou son équi-
valent dans le descripteur de déploiement XML faces-config.xml.
■■ La classe doit avoir une portée (qui vaut par défaut @RequestScoped).
■■ La classe doit être publique et non finale ni abstraite.
■■ La classe doit fournir un constructeur public sans paramètre qui sera utilisé par
le conteneur pour créer les instances.
■■ La classe ne doit pas définir de méthode finalize().
■■ Pour être liés à un composant, les attributs doivent avoir des getters et des setters
publics.
Bien qu’un bean géré puisse être un simple POJO annoté, sa configuration peut être
personnalisée grâce aux éléments de @ManagedBean et @ManagedProperty (ou leurs
équivalents XML).

@ManagedBean
La présence de l’annotation @javax.faces.model.ManagedBean sur une classe l’en-
registre automatiquement comme un bean géré. La Figure 12.4 présente l’API de
cette annotation, dont tous les éléments sont facultatifs.

Listing 12.4 : API de l’annotation ManagedBean


@Target(TYPE)
@Retention(RUNTIME)
public @interface ManagedBean {
String name() default "";
boolean eager() default false;
}

L’élément name indique le nom du bean géré (par défaut, ce nom est celui de la
classe commençant par une minuscule). Si l’élément eager vaut true, le bean géré
est instancié dès le démarrage de l’application.
Les composants de l’interface utilisateur étant liés aux propriétés d’un bean géré,
changer son nom par défaut a des répercussions sur la façon d’appeler une propriété
ou une méthode. Le code du Listing 12.5, par exemple, renomme le bean géré Book–
Controller en myManagedBean.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 393

Listing 12.5 : Changement du nom par défaut d’un bean géré


@ManagedBean(name = "myManagedBean")
public class BookController {

private Book book = new Book();


public String doCreateBook() {
createBook(book);
return "listBooks.xhtml";
}

// Constructeurs, getters, setters


}

Pour invoquer les attributs ou les méthodes de ce bean, vous devez donc utiliser son
nouveau nom :
<h:outputText value="#{myManagedBean.book.isbn}"/>
<h:form>
<h:commandLink action="#{myManagedBean.doCreateBook}">
Create a new book
</h:commandLink>
</h:form>

Portées
Les objets créés dans le cadre d’un bean géré ont une certaine durée de vie et peu-
vent ou non être accessibles aux composants de l’interface utilisateur ou aux objets
de l’application. Cette durée de vie et cette accessibilité sont regroupées dans la
notion de portée. Cinq annotations permettent de définir la portée d’un bean géré :
■■ @ApplicationScoped. Il s’agit de l’annotation la moins restrictive, avec la plus
longue durée de vie. Les objets créés sont disponibles dans tous les cycles requête/
réponse de tous les clients utilisant l’application tant que celle-ci est active. Ces
objets peuvent être appelés de façon concurrente et doivent donc être thread-
safe (c’est-à-dire utiliser le mot-clé synchronized). Les objets ayant cette portée
­peuvent utiliser d’autres objets sans portée ou avec une portée d’application.
■■ @SessionScoped. Ces objets sont disponibles pour tous les cycles requête/réponse
de la session du client. Leur état persiste entre les requêtes et dure jusqu’à la fin
de la session. Ils peuvent utiliser d’autres objets sans portée, avec une portée de
session ou d’application.
■■ @ViewScoped. Ces objets sont disponibles dans une vue donnée jusqu’à sa modi-
fication. Leur état persiste jusqu’à ce que l’utilisateur navigue vers une autre vue,

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
394 Java EE 6 et GlassFish 3 

auquel cas il est supprimé. Ils peuvent utiliser d’autres objets sans portée, avec
une portée de vue, de session ou d’application.
■■ @RequestScoped. Il s’agit de la portée par défaut. Ces objets sont disponibles
du début d’une requête jusqu’au moment où la réponse est envoyée au client.
Un client pouvant exécuter plusieurs requêtes tout en restant sur la même vue,
la durée de @ViewScoped est supérieure à celle de @RequestScoped. Les objets
ayant cette portée peuvent utiliser d’autres objets sans portée, avec une portée de
requête, de vue, de session ou d’application.
■■ @NoneScoped. Les beans gérés ayant cette portée ne sont visibles dans aucune
page JSF ; ils définissent des objets utilisés par d’autres beans gérés de l’appli-
cation. Ils peuvent utiliser d’autres objets avec la même portée.
La portée des beans gérés doit être judicieusement choisie  : vous ne devez leur
donner que la portée dont ils ont besoin. Des beans ayant une portée trop grande (@
ApplicationScoped, par exemple) augmentent l’utilisation mémoire et l’utilisation
du disque pour leur persistance éventuelle. Il n’y a aucune raison de donner une
portée d’application à un objet qui n’est utilisé que dans un seul composant. Inverse-
ment, un objet ayant une portée trop restreinte ne sera pas disponible dans certaines
parties de l’application.
Le code du Listing 12.6 définit un bean géré avec une portée d’application. Il sera
instancié dès le lancement de l’application (eager = true) et initialise l’attribut
defaultBook dès qu’il est construit (@PostConstruct). Il pourrait donc être le bean
idéal pour initialiser des parties de l’application web ou pour être référencé par des
propriétés d’autres beans gérés.

Listing 12.6 : Bean géré avec une portée d’application et une instanciation eager
@ManagedBean(eager = true)
@ApplicationScoped
public class InitController {

private Book defaultBook;


@PostConstruct
private void init() {
defaultBook=new Book("default title", 0,
"default description", "0000-000",
100, true);
}

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 395

@ManagedProperty
Dans un bean géré, vous pouvez demander au système d’injecter une valeur dans
une propriété (un attribut avec des getters et/ou des setters) en utilisant le fichier
faces-config.xml ou l’annotation @javax.faces.model.ManagedProperty, dont
l’attribut value peut recevoir une chaîne ou une expression  EL. Le Listing  12.7
montre quelques exemples d’initialisations.

Listing 12.7 : Initialisation des propriétés d’un bean géré


@ManagedBean
public class BookController {

@ManagedProperty(value = "#{initController.defaultBook}")
private Book book;

@ManagedProperty(value = "this is a title")


private String aTitle;

@ManagedProperty(value = "999")
private Integer aPrice;

// Constructeurs, getters, setters & méthodes


}

Dans le Listing 12.7, les propriétés aTitle et aPrice sont initialisées avec une valeur
de type String. L’attribut aTitle, de type String, sera initialisé avec "this is a title"
et l’attribut aPrice, qui est un Integer, sera initialisé avec le nombre 999 (bien
que "999" soit une chaîne, celle-ci sera convertie en Integer). Les propriétés étant
évaluées lors de l’exécution (généralement lorsqu’une vue est affichée), celles qui
font référence à d’autres beans gérés peuvent aussi être initialisées. Ici, par exemple,
book est initialisé par une expression utilisant la propriété defaultBook du bean géré
initController (#{initController.defaultBook}) que nous avons présenté plus
haut. Le Listing 12.6 montre que defaultBook est un attribut de type Book initialisé
par le bean InitController  : lorsque BookController est initialisé, l’implémen-
tation JSF injectera donc cet attribut. Il est généralement conseillé d’initialiser les
littéraux dans le fichier faces-config.xml et d’utiliser les annotations pour les réfé-
rences croisées entre les beans gérés (en utilisant EL).

Annotation du cycle de vie et des méthodes de rappel


Le chapitre précédent a expliqué le cycle de vie d’une page (qui compte six phases,
de la réception de la requête à la production de la réponse), mais le cycle de vie des

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
396 Java EE 6 et GlassFish 3 

beans gérés (voir Figure 12.3) est totalement différent : en fait, il est identique à
celui des beans de session sans état.

Figure 12.3 N'existe pas


Cycle de vie
@PostConstruct @PreDestroy
d’un bean géré.
Prêt

Appel de méthode

Les beans gérés qui s’exécutent dans un conteneur de servlet peuvent utiliser les
annotations @PostConstruct et @PreDestroy. Après avoir créé une instance de bean
géré, le conteneur appelle la méthode de rappel @PostConstruct s’il y en a une.
Puis le bean est lié à une portée et répond à toutes les requêtes de tous les utilisa-
teurs. Avant de supprimer le bean, le conteneur appelle la méthode @PreDestroy.
Ces méthodes permettent donc d’initialiser les attributs ou de créer et libérer les
ressources externes.

Navigation

Les applications web sont formées de plusieurs pages entre lesquelles vous devez
naviguer. Selon les cas, il peut exister différents niveaux de navigation avec des flux
de pages plus ou moins élaborés. JSF dispose de plusieurs options de navigation et
vous permet de contrôler le flux page par page ou pour toute l’application.
Les composants <h:commandButton> et <h:commandLink> permettent de passer sim-
plement d’une page à une autre en cliquant sur un bouton ou sur un lien sans effec-
tuer aucun traitement. Il suffit d’initialiser leur attribut action avec le nom de la
page vers laquelle vous voulez vous rendre :
<h:commandButton value="Create" action="listBooks.xhtml"/>

Cependant, la plupart du temps, ceci ne suffira pas car vous aurez besoin d’accéder
à une couche métier ou à une base de données pour récupérer ou traiter des données.
En ce cas, vous aurez besoin d’un bean géré. Dans la section "Récapitulatif" du
Chapitre 10, une première page (newBook.xhtml) affichait un formulaire permettant
de créer un livre. Lorsque l’on cliquait sur le bouton Create, le livre était créé puis
le bean géré passait à la page listBooks.xhtml, qui affichait tous les livres. Cette
page contenait un lien Create a new book permettant de revenir à la page précédente
(voir Figure 12.4).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 397

newBook.xhtml listBooks.xhtml

Create a new book List of the books


<h:commandButton>

ISBN ISBN Title Price Description Number Of Pages Illustrations


Tiltle 1234 234 H2G2 12 0 Scifi IT book 241 false
Price 564 694 Robots 18 5 Asimov Best seller 317 true
256 6 56 Dune 23 25 The trilogy 529 false
<h:commandLink>
Create a new book
Description
APress Beginning Java EE 6

Number of pages

Illustrations
Create

APress Beginning Java EE 6

Figure 12.4
Navigation entre newBook.xhtml et listBooks.xhtml.

Le flux des pages est simple, mais ces deux pages ont pourtant besoin d’un bean
géré (BookController) pour traiter la logique métier et la navigation. Elles utilisent
les composants bouton et lien pour naviguer et interagir avec ce bean.
La page newBook.xhtml utilise un bouton pour appeler la méthode doCreateBook()
du bean géré :
<h:commandButton value="Create"
action="#{bookController.doCreateBook}"/>

La page listBooks.xhtml utilise un lien pour appeler la méthode doNewBookForm() :


<h:commandLink action="#{bookController.doNewBookForm}">
Create a new book
</h:commandLink>

Les composants bouton et lien n’appellent pas directement la page vers laquelle ils
doivent se rendre : ils appellent des méthodes du bean géré qui prennent en charge
cette navigation et laissent le code décider de la page qui sera chargée ensuite. La
navigation utilise un ensemble de règles qui définissent tous les chemins de naviga-
tion possibles de l’application. Dans le Listing 12.8, le code du bean géré utilise la
forme la plus simple de ces règles de navigation : chaque méthode définit la page
vers laquelle elle doit aller.

Listing 12.8 : Bean géré définissant explicitement la navigation


@ManagedBean
public class BookController {

@EJB
private BookEJB bookEJB;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
398 Java EE 6 et GlassFish 3 

private Book book = new Book();


private List<Book> bookList = new ArrayList<Book>();

public String doNewBookForm() {


return "newBook.xhtml";
}

public String doCreateBook() {


book = bookEJB.createBook(book);
bookList = bookEJB.findBooks();
return "listBooks.xhtml";
}

// Constructeurs, getters, setters


}

Quand le <h:commandButton> invoque la méthode doCreateBook(), celle-ci crée un


livre (à l’aide d’un bean de session sans état) et renvoie le nom de la page vers
laquelle naviguer ensuite : listBooks.xhtml. La FacesServlet redirigera alors le
flux de page vers la page désirée.
La chaîne renvoyée peut prendre plusieurs formes. Ici, nous avons utilisé la plus
simple  : le nom de la page. L’extension de fichier par défaut étant .xhtml, nous
aurions même pu simplifier le code en supprimant l’extension :
public String doNewBookForm() {
return "newBook";
}

Avec JSF, le flux de navigation peut être défini en externe via faces-config.xml, à
l’aide d’éléments <navigation-rule> qui identifient la page de départ, une condi-
tion et la page vers laquelle naviguer lorsque la condition sera vérifiée. Celle-ci
utilise un nom logique au lieu du nom physique de la page. Comme le montre le
Listing 12.9, le code précédent aurait pu utiliser, par exemple, le nom success.

Listing 12.9 : Extrait d’un bean géré utilisant des noms logiques


@ManagedBean
public class BookController {
// ...

public String doNewBookForm() {


return "success";
}

public String doCreateBook() {


book = bookEJB.createBook(book);
bookList = bookEJB.findBooks();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 399

return "success";
}

// Constructeurs, getters, setters


}

Les deux méthodes renvoyant le même nom logique, le fichier faces-config.xml


doit donc faire correspondre ce nom à la page newBook.xhtml dans un cas et à la
page listBooks.xhtml dans l’autre. Le Listing 12.10 montre la structure de faces-
config.xml : l’élément <from-view-id> définit la page dans laquelle a lieu l’action.
Dans le premier cas, on part de newBook.xhtml avant d’appeler le bean géré : si le
nom logique renvoyé est success (<from-outcome>), la FacesServlet fera suivre
l’appel à la page listBooks.xhtml (<to-view-id>).

Listing 12.10 : Fichier faces-config.xml définissant la navigation


<?xml version=’1.0’ encoding=’UTF-8’?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">

<navigation-rule>
<from-view-id>newBook.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>listBooks.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>listBooks.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>newBook.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>

La navigation pouvant avoir lieu directement dans les beans gérés ou au moyen de
faces-config.xml, quelle solution utiliser plutôt qu’une autre ? La première moti-
vation pour renvoyer directement le nom de la page dans les beans gérés est la
simplicité : le code Java est explicite et il n’y a pas besoin de passer par un fichier
XML supplémentaire. Si, en revanche, le flux des pages d’une application web est
complexe, il peut être judicieux de le décrire à un seul endroit afin que les modifica-
tions soient centralisées au lieu d’être dispersées dans plusieurs pages. Vous pouvez
également mélanger ces deux approches et effectuer une partie de la navigation dans
vos beans et une autre dans le fichier faces-config.xml.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
400 Java EE 6 et GlassFish 3 

Il existe un cas où une configuration XML est très utile : c’est lorsque plusieurs pages
contiennent des liens globaux (lorsque, par exemple, la connexion ou la déconnexion
peuvent être appelées à partir de toutes les pages de l’application) car il serait assez
lourd de devoir les définir dans chaque page. Avec XML, vous pouvez définir des
règles de navigation globales (ce qui n’est pas possible avec les beans gérés) :
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>logout</from-outcome>
<to-view-id>logout.xhtml</to-view-id>
</navigation-case>
</navigation-rule>

Si une action s’applique à toutes les pages d’une application, vous pouvez utiliser un
élément <navigation- rule> sans <from-view-id> ou utiliser un joker (*). Le code
précédent indique que, quelle que soit la page où il se trouve, l’utilisateur sera dirigé
vers la page logout.xhtml si la méthode du bean géré renvoie le nom logique logout.
Les exemples précédents ont montré une navigation simple où une page n’avait
qu’une seule règle de navigation et une seule page destination. Ce n’est pas un cas
si fréquent : les utilisateurs peuvent généralement être redirigés vers des pages dif-
férentes, en fonction de certaines conditions. Cette navigation, là encore, peut être
mise en place dans les beans gérés et dans le fichier faces-config.xml. Le code sui-
vant utilise une instruction switch pour rediriger l’utilisateur vers trois pages pos-
sibles. Si l’on renvoie la valeur null, l’utilisateur reviendra sur la page sur laquelle
il se trouve déjà.
public String doNewBookForm() {
switch (value) {
case 1: return "page1.xhtml"; break;
case 2: return "page2.xhtml"; break;
case 3: return "page3.xhtml"; break;
default: return null; break;
}
}

Gestion des messages

Les beans gérés traitent la logique métier, appellent les EJB, utilisent les bases de
données, etc. Parfois, cependant, un problème peut survenir et, en ce cas, l’uti-
lisateur doit en être informé par un message qui peut être un message d’erreur
de l’application (concernant la logique métier ou la connexion à la base ou au
réseau) ou un message d’erreur de saisie (un ISBN incorrect ou un champ vide, par
exemple). Les erreurs d’application peuvent produire une page particulière demandant

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 401

à l’utilisateur de réessayer dans un moment, par exemple, alors que les erreurs de
saisie peuvent s’afficher dans la même page avec un texte décrivant l’erreur. On peut
également utiliser des messages pour informer l’utilisateur qu’un livre a été correc-
tement ajouté à la base de données.
Au chapitre précédent, nous avons utilisé des marqueurs pour afficher des messages
sur les pages (<h:message> et <h:messages>). Pour produire ces messages, JSF vous
permet de les placer dans une file d’attente en appelant la méthode FacesContext.
addMessage() dans les beans gérés. Sa signature est la suivante :

void addMessage(String clientId, FacesMessage message)

Cette méthode ajoute un FacesMessage à l’ensemble des messages à afficher. Son


premier paramètre est l’identifiant d’un client qui désigne le composant d’interface
auquel le message est rattaché. S’il vaut null, ceci signifie que le message n’est lié
à aucun composant particulier et qu’il est global à toutes les pages. Un message est
formé d’un texte résumé, d’un texte détaillé et d’un niveau d’importance (fatal,
error, warning et info). Les messages peuvent également être internationalisés par
des ensembles de textes localisés (message bundles).
FacesMessage(Severity severity, String summary, String detail)

Le code suivant est un extrait d’un bean géré qui crée un livre. Selon que cette créa-
tion réussit ou qu’une exception survient, un message d’information ou d’erreur est
ajouté à la file d’attente des messages à afficher. Notez que ces deux messages sont
globaux car l’identifiant du client vaut null :
FacesContext ctx = FacesContext.getCurrentInstance();
try {
book = bookEJB.createBook(book);
ctx.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Book has been created",
"The book" + book.getTitle() +
" has been created with id=" +
book.getId())
);
} catch (Exception e) {
ctx.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Book hasn’t been created",
e.getMessage())
);
}
}

Ces messages étant globaux, on peut les afficher dans une page à l’aide d’un simple
marqueur <h:messages>. On peut également préférer afficher un message à un

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
402 Java EE 6 et GlassFish 3 

endroit précis pour un composant spécifique (ce qui est généralement le cas avec les
erreurs de validation ou de conversion). La Figure 12.5, par exemple, montre une
page avec un message spécifiquement destiné au champ de saisie du prix.

Figure 12.5
Create a new book
Page affichant un message
pour un composant
ISBN :
d’interface précis.
Tiltle :

Price : Please, fill the price !

Description :

Number of pages :

Illustrations :
Create

APress Beginning Java EE 6

Dans cette page, le champ de saisie du prix a un identifiant (id="priceId") auquel


fait référence le marqueur <h:message> (for="priceId"). En conséquence, ce
message précis ne s’affichera que pour ce composant :
<h:inputText id="priceId" value="#{bookController.book.price}"/> <h:message
for="priceId"/>

Si le champ du prix n’a pas été rempli, un message s’affiche à côté du champ de sai-
sie. Le code qui suit vérifie le prix saisi et crée un message d’avertissement associé
à l’identifiant du composant si la valeur n’est pas correcte :
if (book.getPrice() == null || "".equals(book.getPrice())) {
ctx.addMessage("priceId", new FacesMessage(SEVERITY_WARN,
"Please, fill the price !", "Enter a number value"));

JSF utilise également ce mécanisme de message pour les convertisseurs et les


validateurs.

Conversion et validation
Nous venons de voir comment gérer les messages pour informer l’utilisateur sur les
actions à entreprendre. L’une d’elles consiste à corriger une saisie incorrecte (un
ISBN non valide, par exemple). JSF fournit un mécanisme standard de conversion

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 403

et de validation permettant de traiter les saisies des utilisateurs afin d’assurer l’inté-
grité des données. Lorsque vous invoquez des méthodes métiers, vous pouvez donc
vous fier à des données valides : la conversion et la validation permettent aux déve-
loppeurs de se concentrer sur la logique métier au lieu de passer du temps à vérifier
que les données saisies ne sont pas null, qu’elles appartiennent bien à un intervalle
précis, etc.
La conversion a lieu lorsque les données saisies par l’utilisateur doivent être trans-
formées de String en un objet et vice versa. Elle garantit que les informations sont
du bon type – en convertissant, par exemple, un String en java.util.Date, un
String en Integer ou des dollars en euros. Comme pour la validation, elle garantit
que les données contiennent ce qui est attendu (une date au format jj/mm/aaaa, un
réel compris entre 3,14 et 3,15, etc.).
Comme le montre la Figure  12.6, la conversion et la validation interviennent au
cours des différentes phases du cycle de vie de la page (que nous avons présenté au
chapitre précédent).

Valeurs des composants Valeurs des composants validées


converties en objets • validation standard
• conversion par défaut • validation personnalisée
• conversion personnalisée
• appel de la méthode getAsObject()

Application Traitement Traitement


Restauration des valeurs
de la vue des validations des
de la requête événements événements

Valeurs des composants


reconverties pour l'affichage
• appel de la méthode getAsString()

Affichage Traitement Traitement Mise à jour


Appel de des valeurs
de la des l'application des
réponse événements événements du modèle

Erreurs de conversion ou de validation

Figure 12.6
Conversion et validation au cours du cycle de vie d’une page.

Au cours de la phase Application des valeurs de la requête de la Figure 12.6, la


valeur du composant de l’interface est convertie dans l’objet cible puis validée au
cours de la phase Traitement des validations. Il est logique que la conversion et
la validation interviennent avant que les données du composant ne soient liées

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
404 Java EE 6 et GlassFish 3 

au backing bean (ce qui a lieu au cours de la phase Mise à jour des valeurs du
modèle). En cas d’erreur, des messages d’erreur seront ajoutés et le cycle de vie
sera écourté afin de passer directement à l’Affichage de la réponse (les messages
seront alors affichés sur l’interface utilisateur avec <h:messages>). Au cours
de⁄cette phase, les propriétés du backing bean sont reconverties en chaînes pour
pouvoir être affichées.
JSF fournit un ensemble de convertisseurs et de validateurs standard et vous permet
de créer les vôtres très facilement.

Convertisseurs

Lorsqu’un formulaire est affiché par un navigateur, l’utilisateur remplit les champs
et appuie sur un bouton ayant pour effet de transporter les données vers le serveur
dans une requête HTTP formée de chaînes. Avant de mettre à jour le modèle du bean
géré, ces données textuelles doivent être converties dans les objets cibles (Float,
Integer, BigDecimal, etc.). L’opération inverse aura lieu lorsque les données seront
renvoyées au client dans la réponse pour être affichées par le navigateur.
JSF fournit des convertisseurs pour les types classiques comme les dates et les
nombres. Si une propriété du bean géré est d’un type primitif (Integer, int, Float,
float, etc.), JSF convertira automatiquement la valeur du composant d’interface dans
le type adéquat et inversement. Si elle est d’un autre type, vous devrez fournir votre
propre convertisseur. Le Tableau 12.3 énumère tous les convertisseurs standard du
paquetage javax.faces.convert.

Tableau 12.3 : Convertisseurs standard

Convertisseur Description
BigDecimalConverter Convertit un String en java.math.BigDecimal et vice versa.
BigIntegerConverter Convertit un String en java.math.BigInteger et vice versa.
BooleanConverter Convertit un String en Boolean (et boolean) et vice versa.
ByteConverter Convertit un String en Byte (et byte) et vice versa.
CharacterConverter Convertit un String en Character (et char) et vice versa.
DateTimeConverter Convertit un String en java.util.Date et vice versa.
DoubleConverter Convertit un String en Double (et double) et vice versa.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 405

Tableau 12.3 : Convertisseurs standard (suite)

Convertisseur Description
EnumConverter Convertit un String en Enum (et enum) et vice versa.
FloatConverter Convertit un String en Float (et float) et vice versa.
IntegerConverter Convertit un String en Integer (et int) et vice versa.
LongConverter Convertit un String en Long (et long) et vice versa.
NumberConverter Convertit un String en classe abstraite java.lang.Number et
vice versa.
ShortConverter Convertit un String en Short (et short) et vice versa.

JSF convertira automatiquement les valeurs saisies en nombre lorsque la propriété


du bean géré est d’un type numérique primitif et en date ou en heure lorsque la
propriété est d’un type date. Si ces conversions automatiques ne conviennent pas,
vous pouvez les contrôler explicitement via les marqueurs standard convertNumber
et convertDateTime. Pour ce faire, vous devez imbriquer le convertisseur dans un
marqueur d’entrée ou de sortie. Il sera appelé par JSF au cours du cycle de vie.
Le marqueur convertNumber possède des attributs permettant de convertir la valeur
d’entrée en nombre (comportement par défaut), en valeur monétaire ou en pourcen-
tage. Vous pouvez préciser un symbole monétaire ou un nombre de chiffres après la
virgule, ainsi qu’un motif déterminant le format du nombre et la façon dont il sera
analysé :
<h:inputText value="#{bookController.book.price}">
<f:convertNumber currencySymbol="$" type="currency"/>
</h:inputText>

Le marqueur convertDateTime convertit les dates dans différents formats (date,


heure ou les deux). Il possède plusieurs attributs pour contrôler cette conversion
ainsi que les zones horaires. L’attribut pattern permet d’indiquer le format de la
chaîne de date à convertir :
<h:inputText value="#{bookController.book.publishedDate}">
<f:convertDateTime pattern="MM/dd/yy"/>
</h:inputText>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
406 Java EE 6 et GlassFish 3 

Convertisseurs personnalisés

Parfois, la conversion de nombres, de dates, d’énumérations, etc. ne suffit pas et


nécessite une conversion adaptée à la situation.
Il suffit pour cela d’écrire une classe qui implémente l’interface javax.faces.
convert.Converter et de lui associer des métadonnées. Cette interface expose deux
méthodes :
Object getAsObject(FacesContext ctx, UIComponent component,
String value)
String getAsString(FacesContext ctx, UIComponent component,
Object value)

La méthode getAsObject() convertit la valeur chaîne d’un composant d’interface


utilisateur dans le type correspondant et renvoie la nouvelle instance  ; elle lance
une exception ConverterException si la conversion échoue. Inversement, getAs­
String() convertit l’objet en chaîne afin qu’il puisse être affiché à l’aide d’un lan-
gage à marqueurs (comme XHTML).
Pour utiliser ce convertisseur dans l’application web, il faut l’enregistrer  : une
méthode consiste à le déclarer dans le fichier faces-config.xml, l’autre, à utiliser
l’annotation @FacesConverter.
Le Listing 12.11 montre comment écrire un convertisseur personnalisé pour conver-
tir un prix en dollars en valeur en euros. On commence par associer ce convertis-
seur au nom euroConverter (value = "euroConverter") à l’aide de l’annotation @
FacesConverter, puis on implémente l’interface Converter. Cet exemple ne redéfi-
nit que la méthode getAsString() pour qu’elle renvoie une représentation textuelle
d’un prix en euros.

Listing 12.11 : Convertisseur en euros


@FacesConverter(value = "euroConverter")
public class EuroConverter implements Converter {

@Override
public Object getAsObject(FacesContext ctx,
UIComponent component,
String value) {
return value;
}

@Override
public String getAsString(FacesContext ctx,
UIComponent component,
Object value) {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 407

float amountInDollars = Float.parseFloat(value.toString());


double ammountInEuros = amountInDollars * 0.8;
DecimalFormat df = new DecimalFormat("###,##0.##");
return df.format(ammountInEuros);
}
}

Pour utiliser ce convertisseur, on utilise soit l’attribut converter d’un marqueur, soit
le marqueur <f:converter> : dans les deux cas, il faut fournir le nom du convertisseur
défini par l’annotation @FacesConverter (euroConverter, ici). Le code suivant affiche
deux textes, l’un représentant le prix en dollars, l’autre ce prix converti en euros :
<h:outputText value="#{book.price}"/>
<h:outputText value="#{book.price}">
<f:converter converterId="euroConverter"/>
</h:outputText>

Vous pouvez également utiliser l’attribut converter du marqueur outputText :


<h:outputText value="#{book.price}" converter="euroConverter"/>

Validateurs

Les applications web doivent garantir que les données saisies par les utilisateurs
sont appropriées. Cette vérification peut avoir lieu côté client avec JavaScript ou
côté serveur avec des validateurs. JSF simplifie la validation des données en four-
nissant des validateurs standard et en permettant d’en créer de nouveaux, adaptés
à vos besoins. Les validateurs agissent comme des contrôles de premier niveau en
validant les valeurs des composants de l’interface utilisateur avant qu’elles ne soient
traitées par le bean géré.
Généralement, les composants d’interface mettent en œuvre une validation simple,
comme vérifier qu’une valeur est obligatoire. Le marqueur suivant, par exemple,
exige qu’une valeur soit entrée dans le champ de saisie :
<h:inputText value="#{bookController.book.title}"
required="true"/>

Si aucune valeur n’est saisie, JSF renvoie la page avec un message indiquant qu’il
faut en fournir une (la page doit avoir un marqueur <h:messages>) en utilisant le
même mécanisme de messages que nous avons déjà décrit. Mais JSF fournit égale-
ment un ensemble de validateurs plus élaborés (voir Tableau 12.4) définis dans le
paquetage javax.faces.validator.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
408 Java EE 6 et GlassFish 3 

Tableau 12.4 : Validateurs standard

Convertisseur Description
DoubleRangeValidator Compare la valeur du composant aux valeurs minimales et
maximales indiquées (de type double).
LengthValidator Teste le nombre de caractères de la valeur textuelle du
composant.
LongRangeValidator Compare la valeur du composant aux valeurs minimales et
maximales indiquées (de type long).
RegexValidator Compare la valeur du composant à une expression régulière.

Ces validateurs permettent de traiter les cas génériques comme la longueur d’une
chaîne ou un intervalle de valeurs ; ils peuvent être associés facilement à un com-
posant, de la même façon que les convertisseurs (un même composant peut contenir
les deux). Le code suivant, par exemple, garantit que le titre d’un livre fait entre 2 et
20 caractères et que son prix varie de 1 à 500 dollars :
<h:inputText value="#{bookController.book.title}" required="true">
<f:validateLength minimum="2" maximum="20"/>
</h:inputText>
<h:inputText value="#{bookController.book.price}">
<f:validateLongRange minimum="1" maximum="500"/>
</h:inputText>

Validateurs personnalisés

Les validateurs standard de JSF peuvent ne pas convenir à vos besoins : vous avez
peut-être des données qui respectent certains formats métier, comme un code postal
ou une adresse de courrier électronique. En ce cas, vous devrez créer votre propre
validateur. Comme les convertisseurs, un validateur est une classe qui doit implé-
menter une interface et redéfinir une méthode. Dans le cas des validateurs, cette
interface est javax.faces.validator.Validator, qui n’expose que la méthode
validate() :

void validate(FacesContext context, UIComponent component,


Object value)

Le paramètre value est celui qui doit être vérifié en fonction d’une certaine logique
métier. S’il passe le test de validation, vous pouvez simplement sortir de cette
méthode et le cycle de la page se poursuivra. Dans le cas contraire, vous pouvez lancer
une exception ValidatorException et inclure un FacesMessage avec des messages

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 409

r­ ésumés et détaillés pour décrire l’erreur de validation. Ce validateur doit être enre-
gistré dans le fichier faces-config.xml ou à l’aide de l’annotation @FacesValidator.
Le Listing  12.12, par exemple, contient le code d’un validateur qui garantit que
l’ISBN saisi par l’utilisateur est au bon format.

Listing 12.12 : Validateur d’ISBN


@FacesValidator(value = "isbnValidator")
public class IsbnValidator implements Validator {

private Pattern pattern;


private Matcher matcher;

@Override
public void validate(FacesContext context,
UIComponent component,
Object value) throws ValidatorException {
String componentValue = value.toString();
pattern = Pattern.compile("(?=[-0-9xX]{13}$)");
matcher = pattern.matcher(componentValue);
if (!matcher.find()) {
String message =
MessageFormat.format("{0} is not a valid isbn format",
componentValue);
FacesMessage facesMessage =
new FacesMessage(SEVERITY_ERROR, message, message);
throw new ValidatorException(facesMessage);
}
}
}

Le code du Listing 12.12 commence par associer le validateur au nom isbnVali-


dator afin que l’on puisse l’utiliser dans une page. Puis il implémente l’interface
Validator en ajoutant le code de validation à la méthode validate(), qui utilise
une expression régulière pour vérifier que l’ISBN est au bon format – dans le cas
contraire, il ajoute un message au contexte et lance une exception. Dans ce cas, JSF
terminera automatiquement le cycle de vie de la page, la rappellera et affichera le
message d’erreur. Vous pouvez utiliser ce validateur dans vos pages en passant par
l’attribut validator ou par un marqueur <f:validator> imbriqué :
<h:inputText value="#{book.isbn}" validator="isbnValidator"/>
// ou
<h:inputText value="#{book.isbn}">
<f:validator validatorId="isbnValidator" />
</h:inputText>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
410 Java EE 6 et GlassFish 3 

Ajax
Le protocole HTTP repose sur un mécanisme requête/réponse : un client a besoin
d’une information, il envoie une requête et reçoit une réponse du serveur – généra-
lement une page web complète. La communication va toujours dans ce sens : c’est
le client qui est à l’initiative de la requête. Cependant, les applications web doivent
produire des interfaces riches et réactives et répondre aux événements du serveur,
mettre à jour des parties d’une page, agréger des widgets, etc. Dans un cycle requête/
réponse classique, le serveur devrait envoyer toute la page web, même s’il ne faut
en modifier qu’une petite partie : si la taille de cette page n’est pas négligeable, ceci
consomme de la bande passante et le temps de réponse sera médiocre car le navi-
gateur devra recharger toute la page. Pour améliorer la réactivité du navigateur et
fournir plus de fluidité à l’utilisateur, il ne faut modifier que de petites parties de la
page, et c’est là qu’Ajax entre en jeu.
Ajax (acronyme d’Asynchronous JavaScript and XML) est un ensemble de techniques
de développement web permettant de créer des applications web interactives. Grâce
à lui, les applications récupèrent de façon asynchrone des portions de données à
partir du serveur sans interférer avec l’affichage et le comportement de la page en
cours de consultation. Lorsque les données sont reçues par le client, seules les par-
ties qui ont besoin d’être modifiées le seront : pour cela, on utilise le DOM de la
page et du code JavaScript. Il existe également un mécanisme appelé Reverse Ajax
(ou programmation Comet) pour pousser les données du serveur vers le navigateur.
Ces mécanismes sont utilisés dans la plupart de nos applications web quotidiennes
et sont désormais intégrés à JSF 2.0.

Concepts généraux

Le terme Ajax a été inventé en 2005 pour désigner un ensemble d’alternatives per-
mettant de charger des données de façon asynchrone dans les pages web. En 1999,
Microsoft avait créé l’objet XMLHttpRequest comme un contrôle ActiveX dans Inter-
net Explorer 5. En 2006, le W3C produisit le premier draft de la spécification de
l’objet XMLHttpRequest, qui est désormais reconnu par la plupart des navigateurs.
Au même moment, plusieurs sociétés réfléchirent au moyen de garantir qu’Ajax
devienne un standard reposant sur des technologies ouvertes. Le résultat de ce tra-
vail fut la création de l’OpenAjax Alliance, composée d’éditeurs de logiciels, de
projets open-source et de sociétés utilisant les technologies Ajax.
Comme le montre la Figure 12.7, dans les applications web traditionnelles le naviga-
teur doit demander des documents HTML complets au serveur. L’utilisateur clique

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 411

sur un bouton pour envoyer ou recevoir l’information, attend la réponse du serveur


puis reçoit l’intégralité de la page qui se charge dans le navigateur. Ajax, par contre,
utilise des transferts de données asynchrones entre le navigateur et le serveur, ce
qui permet aux pages de demander des petits morceaux d’information (données
au format JSON ou XML). L’utilisateur reste sur la même page pendant que du
code JavaScript demande ou envoie des données au serveur de façon asynchrone, et
seules des parties de cette page seront rafraîchies, ce qui a pour effet de produire des
applications plus réactives et des interfaces plus fluides.

Figure 12.7 Appel HTTP Appel Ajax

Appels HTTP classiques vs. Navigateur Navigateur


appels HTTP Ajax.
Page web Page web

Appel JavaScript XML

Bibliothèque Ajax

Requête HTTP Page XHTML

Requête XMLHttp XML

Serveur Serveur

En principe, Ajax repose sur les technologies suivantes :


■■ XHTML et CSS pour la présentation ;
■■ DOM pour l’affichage dynamique et l’interaction avec les données ;
■■ XML et XSLT pour les échanges, la manipulation et l’affichage des données
XML ;
■■ l’objet XMLHttpRequest pour la communication asynchrone ;
■■ JavaScript pour relier toutes ces technologies.
XMLHttpRequest joue un rôle important dans Ajax car c’est une API DOM utilisée
par JavaScript pour transférer du XML du navigateur vers le serveur. Les données
renvoyées en réponse doivent être récupérées sur le client pour modifier dynamique-
ment les parties de la page avec JavaScript. Ces données peuvent être dans différents
formats, comme XHTML, JSON, voire du texte brut.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
412 Java EE 6 et GlassFish 3 

Ajax étant disponible nativement dans JSF  2.0, vous n’avez plus besoin d’écrire
de code JavaScript pour gérer l’objet XMLHttpRequest : il suffit d’utiliser la biblio-
thèque JavaScript qui a été spécifiée et qui est disponible dans les implémentations
de JSF 2.0.

Ajax et JSF

Les versions précédentes de JSF n’offrant pas de solution native pour Ajax, des
bibliothèques tierces sont venues combler ce manque, ce qui augmentait la com-
plexité du code au détriment des performances. À partir de JSF 2.0, tout est beau-
coup plus simple puisque Ajax a été ajouté à la spécification et est désormais intégré
dans toutes les implémentations.
La bibliothèque JavaScript jsf.js permet de réaliser les interactions Ajax, ce qui
signifie qu’il n’est plus nécessaire d’écrire ses propres scripts pour manipuler direc-
tement les objets XMLHttpRequest : vous pouvez vous servir d’un ensemble de fonc-
tions standard pour envoyer des requêtes asynchrones et recevoir les données. Pour
utiliser cette bibliothèque dans vos pages, il suffit d’ajouter la ressource jsf.js avec
la ligne suivante :
<h:outputScript name="jsf.js" library="javax.faces"
target="head"/>

Le marqueur <h:outputScript> produit un élément <script> faisant référence au


fichier jsf.js de la bibliothèque javax.faces (l’espace de noms de premier niveau
javax est enregistré par l’OpenAjax Alliance). Cette API JavaScript sert à lancer les
interactions côté client. La fonction utilisée directement dans les pages s’appelle
request : c’est elle qui est responsable de l’envoi d’une requête Ajax au serveur.
Sa signature est la suivante :
jsf.ajax.request(ELEMENT, |EVENT|, { |OPTIONS| });

ELEMENT est le composant JSF ou l’élément XHTML qui déclenchera l’événement


– pour la soumission d’un formulaire, il s’agira généralement d’un bouton. EVENT
est l’événement JavaScript supporté par cet élément, comme onmousedown, onclick,
onblur, etc. Le paramètre OPTIONS est un tableau pouvant contenir les paires nom/
valeur suivantes :
■■ execute: ’<liste identifiants de composants interface>’. Envoie au ser-
veur la liste des identifiants de composants pour qu’ils soient traités au cours de
la phase d’exécution de la requête.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 413

■■ render:’<liste identifiants de composants interface>’. Traduit la liste des


identifiants de composants qui sont à mettre à jour au cours de la phase de rendu
de la requête.
Le code suivant, par exemple, affiche un bouton qui appelle la fonction jsf.
ajax.request lorsqu’on clique dessus (événement onclick). Le paramètre this
désigne l’élément lui-même (le bouton) et les options désignent les identifiants des
composants :
<h:commandButton id="submit" value="Create a book"
onclick="jsf.ajax.request(this, event,
{execute:’isbn title price description nbOfPage illustrations’,
render:’booklist’}); return false;"
actionListener="#{bookController.doCreateBook}" />

Lorsque le client fait une requête Ajax, le cycle de vie de la page sur le serveur
reste le même (il passe par les mêmes six phases). L’avantage principal est que
la réponse ne renvoie au navigateur qu’un petit extrait de données au lieu d’une
page HTML complète. La phase Application de la requête détermine si la requête
adressée est "partielle" ou non : l’objet PartialViewContext est utilisé tout au long
du cycle de vie de la page et contient les méthodes et les propriétés pertinentes per-
mettant de traiter une requête partielle et de produire une réponse partielle. À la
fin du cycle de vie, la réponse Ajax (ou, à proprement parler, la réponse partielle)
est envoyée au client au cours de la phase Rendu de la réponse – elle est générale-
ment formée de XHTML, XML ou JSON, qui sera analysé par le code JavaScript
s’exécutant côté client.

Récapitulatif

L’exemple du Listing 12.13 montre comment utiliser Ajax et son support par JSF.
La section "Récapitulatif" du Chapitre 10 a montré comment insérer de nouveaux
livres dans la base de données à l’aide d’un bean géré nommé BookController. La
navigation était simple : dès que le livre était créé, l’utilisateur était redirigé vers la
page affichant la liste de tous les livres ; en cliquant sur un lien, il pouvait revenir
sur le formulaire de création. Nous allons reprendre cet exemple pour lui ajouter
quelques fonctionnalités Ajax.
Nous voulons maintenant afficher sur la même page le formulaire et la liste des
livres (voir Figure 12.8). À chaque fois qu’un livre est créé en cliquant sur le bouton
du formulaire, la liste sera rafraîchie afin de faire apparaître ce nouveau livre.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
414 Java EE 6 et GlassFish 3 

Figure 12.8
Create a new book
Une seule page
pour créer et afficher
ISBN : 256 6 56
tous les livres.
Tiltle : Dune

Price : 23.25

The trilogy

Description :

Number of pages : 529

Illustrations :
Create a book

List of the books


ISBN Title Price dollar Description Number Of Pages Illustrations
1234 234 H2G2 12.0 Scifi IT book 241 false
564 694 Robots 18.5 Best seller 317 true

APress Beginning Java EE 6

Le formulaire en haut de la page ne change pas  : seule la liste a besoin d’être


rafraîchie. Le code du Listing 12.13 contient le code du formulaire. Pour intégrer
Ajax, la page doit d’abord définir la bibliothèque jsf.js à l’aide du marqueur
<h:outputScript> – rien ne change vraiment par rapport au code du Chapitre 10. La
variable bookCtrl fait référence au bean géré BookController, qui est responsable
de toute la logique métier (en invoquant un EJB pour stocker et récupérer les livres).
On accède aux attributs de l’entité Book via le langage d’expression (#{bookCtrl.
book.isbn} est lié à l’ISBN). Chaque composant d’entrée possède un identifiant
(id="isbn", id="title", etc.) : ceci est très important car cela permet d’identifier
chaque nœud du DOM ayant besoin d’interagir de façon asynchrone avec le ser-
veur. Ces identifiants doivent être uniques pour toute la page car l’application doit
­pouvoir faire correspondre les données à un composant spécifique.

Listing 12.13 : La partie formulaire de la page newBook.xhtml


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 415

<h:head>
<title>Create a new book</title>
</h:head>
<h:body>

<h:outputScript name="jsf.js"
library="javax.faces" target="head"/>

<h1>Create a new book</h1>


<hr/>

<h:form id="form" prependId="false">


<table border="0">

<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText id="isbn" value="#{bookCtrl.book.isbn}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Title :"/></td>
<td>
<h:inputText id="title"
value="#{bookCtrl.book.title}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Price : "/></td>
<td><h:inputText id="price"
value="#{bookCtrl.book.price}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Description : "/></td>
<td>
<h:inputTextarea id="description"
value="#{bookCtrl.book.description}" cols="20"
rows="5"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText id="nbOfPage"
value="#{bookCtrl.book.nbOfPage}"/>
</td>
</tr>

<tr>
<td><h:outputLabel value="Illustrations : "/></td>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
416 Java EE 6 et GlassFish 3 

<td>
<h:selectBooleanCheckbox id="illustrations"
value="#{bookCtrl.book.illustrations}"/>
</td>
</tr>

</table>

<h:commandButton id="submit" value="Create a book"


onclick="jsf.ajax.request(this, event,
{execute:’isbn title price description nbOfPage
„ illustrations’,
render:’booklist’}); return false;"
actionListener="#{bookCtrl.doCreateBook}" />
</h:form>

Le marqueur <h:commandButton> représente le bouton qui déclenche l’appel Ajax.


Lorsque l’utilisateur clique dessus (événement onclick), la fonction jsf.ajax.
request est invoquée avec, pour paramètres, les identifiants des composants (isbn,
title, etc.). Grâce à eux, les valeurs des composants correspondants sont alors pos-
tées vers le serveur. La méthode doCreateBook() du bean géré est appelée, le nouveau
livre est créé et la liste des livres est récupérée. L’affichage de cette liste côté client est
effectué avec Ajax grâce à la bibliothèque JS de JSF et la fonction jsf.ajax.request,
appelée par l’événement onclick. L’élément render fait référence à booklist, qui est
l’identifiant du tableau qui affiche tous les livres (voir Listing 12.14).

Listing 12.14 : La partie liste de la page newBook.xhtml


<hr/>
<h1>List of the books</h1>
<hr/>
<h:dataTable id="booklist" value="#{bookCtrl.bookList}"
var="bk">
<h:column>
<f:facet name="header">
<h:outputText value="ISBN"/>
</f:facet>
<h:outputText value="#{bk.isbn}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{bk.title}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Price dollar"/>
</f:facet>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 417

<h:outputText value="#{bk.price}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:outputText value="#{bk.description}" />
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Number Of Pages"/>
</f:facet>
<h:outputText value="#{bk.nbOfPage}"/>
</h:column>

<h:column>
<f:facet name="header">
<h:outputText value="Illustrations"/>
</f:facet>
<h:outputText value="#{bk.illustrations}"/>
</h:column>

</h:dataTable>

<i>APress - Beginning Java EE 6</i>


</h:body>
</html>

La réponse partielle du serveur contient la portion XHTML à modifier. Le code


JavaScript recherche l’élément booklist de la page et applique les modifications
nécessaires. Le code de cette réponse dans le Listing 12.15 est assez simple à com-
prendre : il précise qu’il faut modifier le composant identifié par booklist (<update
id="booklist">). Le corps de l’élément <update> est le fragment XHTML qui doit
remplacer les données courantes du tableau.

Listing 12.15 : La réponse partielle reçue par le client


<partial-response>
<changes>
<update id="booklist">
<table id="booklist" border="1">
<tr>
<th scope="col">ISBN</th>
<th scope="col">Title</th>
<th scope="col">Price</th>
<th scope="col">Description</th>
<th scope="col">Number Of Pages</th>
<th scope="col">Illustrations</th>
</tr>
<tr>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
418 Java EE 6 et GlassFish 3 

<td>1234-234</td>
<td>H2G2</td>
<td>12.0</td>
<td>Scifi IT book</td>
<td>241</td>
<td>false</td>
</tr>
<tr>
<td>564-694</td>
<td>Robots</td>
<td>18.5</td>
<td>Best seller</td>
<td>317</td>
<td>true</td>
</tr>
</table>
</update>
</changes>
</partial-response>

Résumé
Le chapitre précédent avait étudié l’aspect graphique de JSF alors que ce chapitre
s’est intéressé à sa partie dynamique. JSF met en œuvre le modèle de conception
MVC : sa spécification s’étend de la création des interfaces utilisateurs à l’aide de
composants au traitement des données avec les beans gérés.
Les beans gérés sont au cœur de JSF car ce sont eux qui traitent la logique métier,
appellent les EJB, les bases de données, etc. et qui permettent de naviguer entre
les pages. Ils ont une portée et un cycle de vie (qui ressemble à celui des beans de
session sans état), et ils déclarent des méthodes et des propriétés qui sont liées aux
composants d’interface grâce au langage d’expression. Les annotations et la confi-
guration par exception ont permis de beaucoup simplifier JSF 2.0 car la plupart des
configurations XML sont désormais facultatives.
Nous avons montré comment chaque composant d’entrée peut gérer les conversions
et les validations. JSF définit un ensemble de convertisseurs et de validateurs pour
les situations les plus courantes, mais vous pouvez également créer et enregistrer les
vôtres de façon très simple.
Ajax existe depuis quelques années et JSF 2.0 dispose maintenant pour cette techno-
logie d’un support natif qui permet aux pages web d’invoquer de façon asynchrone
des beans gérés. JSF 2.0 définit une bibliothèque JavaScript standard afin que les
développeurs n’aient plus besoin d’écrire de scripts et puissent simplement utiliser
des fonctions pour rafraîchir des portions de pages.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
13
Envoi de messages

La plupart des communications entre les composants que nous avons vus jusqu’à
présent sont synchrones : une classe en appelle une autre, un bean géré invoque un
EJB, qui appelle une entité, etc. Dans ces cas-là, l’appelant et la cible doivent être
en cours d’exécution pour que la communication réussisse et l’appelant doit attendre
que la cible termine son exécution avant de pouvoir continuer. À l’exception des
appels asynchrones dans EJB, la plupart des composants de Java EE utilisent des
appels synchrones (locaux ou distants). Lorsque nous parlons de messages, nous pen-
sons à une communication asynchrone, faiblement couplée, entre les composants.
MOM (Middleware Orienté Messages) est un logiciel (un fournisseur) qui autorise les
messages asynchrones entre des systèmes hétérogènes. On peut le considérer comme
un tampon placé entre les systèmes, qui produisent et consomment les messages à leurs
propres rythmes (un système peut, par exemple, tourner 24 heures sur 24, 7 jours sur 7
alors qu’un autre ne tourne que la nuit). Il est faiblement couplé car ceux qui envoient
les messages ne savent pas qui les recevra à l’autre extrémité du canal de communi-
cation. Pour communiquer, l’expéditeur et le récepteur ne doivent pas nécessairement
fonctionner en même temps – en fait, ils ne se connaissent même pas puisqu’ils uti-
lisent un tampon intermédiaire. De ce point de vue, le MOM est totalement différent
des technologies comme RMI (Remote Method Invocation), qui exigent qu’une appli-
cation connaisse les signatures des méthodes d’une application distante.
Aujourd’hui, une société typique utilise plusieurs applications, souvent écrites dans
des langages différents, qui réalisent des tâches bien définies. Le MOM leur permet
de fonctionner de façon indépendante tout en faisant partie d’un workflow. Les mes-
sages sont une bonne solution pour intégrer les applications existantes et nouvelles
en les couplant faiblement, de façon asynchrone, pourvu qu’émetteur et destinataire
se mettent d’accord sur le format du message et sur le tampon intermédiaire.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
420 Java EE 6 et GlassFish 3 

Cette communication peut être locale à une organisation ou distribuée entre plusieurs
services externes.

Présentation des messages

Le MOM, qui existe déjà depuis un certain temps, utilise un vocabulaire spécial.
Lorsqu’un message est envoyé, le logiciel qui le stocke et l’expédie est appelé un
fournisseur (ou, parfois, un broker). L’émetteur du message est appelé producteur
et l’emplacement de stockage du message, une destination. Le composant qui reçoit
le message est appelé consommateur – tout composant intéressé par un message à
cette destination précise peut le consommer. La Figure 13.1 illustre ces concepts.

Figure 13.1 Fournisseur
de messages
Architecture du MOM. Mess

Producteur Destination Consommateur


envoie reçoit

Avec Java EE, tous ces concepts sont pris en charge par l’API JMS (Java Mes-
sage Service), qui propose un ensemble d’interfaces et de classes permettant de se
connecter à un fournisseur, de créer un message, de l’envoyer et de le recevoir. JMS
ne transporte pas les messages : il a besoin d’un fournisseur qui s’occupe de le faire.
Lorsqu’ils s’exécutent dans un conteneur, les MDB (Message-Driven Beans) peuvent
servir à recevoir les messages de façon gérée par le conteneur.

JMS

JMS est une API standard de Java permettant aux applications de créer, d’envoyer,
de recevoir et de lire les messages de façon asynchrone. Elle définit un ensemble
d’interfaces et de classes que les programmes peuvent utiliser pour communiquer
avec d’autres fournisseurs de messages. JMS ressemble à JDBC  : cette dernière
permet de se connecter à plusieurs bases de données (Derby, MySQL, Oracle, DB2,
etc.), alors que JMS permet de se connecter à plusieurs fournisseurs (OpenMQ,
MQSeries, SonicMQ, etc.).
L’API JMS couvre toutes les fonctionnalités nécessaires aux messages, c’est-à-dire
leur envoi et leur réception via des destinations :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 421

■■ Les producteurs de messages. L’API permet aux clients de générer un message


(émetteurs et éditeurs).
■■ Les consommateurs de messages. Elle permet aux applications clientes de
consommer des messages (récepteurs ou abonnés).
■■ Les messages. Un message est formé d’un en-tête, de propriétés et d’un corps
contenant différentes informations (texte, objets, etc.).
■■ Les connexions et les destinations. L’API fournit plusieurs fabriques permettant
d’obtenir des fournisseurs et des destinations (files d’attente et sujets).

MDB

Les MDB sont des consommateurs de messages asynchrones qui s’exécutent dans
un conteneur EJB. Comme on l’a vu aux Chapitres 6 à 9, le conteneur prend en
charge plusieurs services (transaction, sécurité, concurrence, acquittement des
messages, etc.) ; ce qui permet au MDB de se consacrer à la consommation des
messages JMS. Les MDB étant sans état, le conteneur EJB peut avoir de nom-
breuses instances s’exécutant de façon concurrente pour traiter les messages pro-
venant de différents producteurs JMS. Bien que les MDB ressemblent aux beans
sans état, on ne peut pas y accéder directement par les applications : la seule façon
de communiquer avec eux consiste à envoyer un message à la destination que le
MDB surveille.
En général, les MDB surveillent une destination (file d’attente ou sujet) et consom-
ment et traitent les messages qui y arrivent. Ils peuvent également déléguer la
logique métier à d’autres beans de session sans état de façon transactionnelle. Étant
sans état, les MDB ne mémorisent pas l’état d’un message à l’autre. Ils répondent
aux messages JMS reçus du conteneur, alors que les beans de session sans état
répondent aux requêtes clientes via une interface appropriée (locale, distante ou sans
interface).

Résumé de la spécification des messages

En Java, les messages sont essentiellement représentés par JMS, qui peut être utili-
sée par les applications qui s’exécutent dans un environnement Java SE ou Java EE.
MDB est l’extension entreprise d’un consommateur JMS et est lié à la spécification
EJB.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
422 Java EE 6 et GlassFish 3 

Bref historique des messages

Jusqu’à la fin des années 1980, les sociétés ne disposaient pas de moyens simples
pour lier leurs différentes applications : les développeurs devaient écrire des adapta-
teurs logiciels pour que les systèmes traduisent les données des applications sources
dans un format reconnu par les applications destinataires (et réciproquement). Pour
gérer les différences de puissance et de disponibilité de serveurs, il fallait créer des
tampons pour faire attendre les traitements. En outre, l’absence de protocoles de
transport homogènes exigeait de créer des adaptateurs de protocoles de bas niveau.
Le middleware a commencé à émerger à la fin des années 1980 afin de résoudre
ces problèmes d’intégration. Les premiers MOM furent créés comme des compo-
sants logiciels séparés que l’on plaçait au milieu des applications pour "faire de la
plomberie" entre les systèmes. Ils étaient capables de gérer des plates-formes, des
langages de programmation, des matériels et des protocoles réseaux différents.

JMS 1.1

La spécification JMS fut publiée en août 1998. Elle a été mise au point par les princi-
paux éditeurs de middleware afin d’ajouter à Java les fonctionnalités des messages.
La JSR 914 apporta quelques modifications mineures (JMS 1.0.1, 1.0.2 et 1.0.2b)
pour finalement atteindre la version 1.1 en avril 2002. Depuis, cette spécification n’a
pas changé. JMS 1.1 a été intégrée dans J2EE 1.2 et fait depuis partie de Java EE.
Cependant, JMS et les MDB ne font pas partie de la spécification Web Profile que
nous avons décrite au premier chapitre, ce qui signifie qu’ils ne seront disponibles
que sur les serveurs d’applications qui implémentent l’intégralité de la plate-forme
Java EE 6.
Bien que JMS soit une API touffue et de bas niveau, elle fonctionne très bien.
­Malheureusement, elle n’a pas été modifiée ou améliorée dans Java EE 5 ni dans
Java EE 6.

EJB 3.1

Les MDB ont été introduits avec EJB 2.0 et améliorés avec EJB 3.0 et par le para-
digme général de simplification de Java EE 5. Ils n’ont pas été modifiés en interne
car ils continuent d’être des consommateurs de messages, mais l’introduction des
annotations et de la configuration par exception facilite beaucoup leur écriture. La
nouvelle spécification EJB 3.1 (JSR 318) n’apporte pas de modifications notables
aux MDB.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 423

Comme on l’a vu au Chapitre 7, les appels asynchrones sont désormais possibles


avec les beans de session sans état (en utilisant l’annotation @Asynchronous) et les
threads ne sont pas autorisés dans les EJB. Dans les versions précédentes de Java EE,
les appels asynchrones entre EJB étant impossibles, la seule solution consistait alors
à passer par JMS et les MDB, ce qui était coûteux puisqu’il fallait utiliser de nom-
breuses ressources (destinations, connexions, fabriques JMS, etc.) simplement pour
appeler une méthode de façon asynchrone. Avec EJB 3.1, les appels asynchrones
étant devenus possibles entre les beans de session, nous pouvons utiliser les MDB
pour réaliser ce pourquoi ils ont été initialement créés : intégrer des systèmes grâce
aux messages.

Implémentation de référence

L’implémentation de référence de JMS est OpenMQ (Open Message Queue), qui est
open-source depuis 2006 et peut être utilisée dans des applications JMS autonomes
ou intégrées dans un serveur d’applications. OpenMQ est le fournisseur de mes-
sages par défaut de GlassFish et, à la date où ce livre est écrit, en est à sa version 4.4.
Elle respecte évidemment la spécification JMS et lui ajoute de nombreuses fonction-
nalités supplémentaires comme UMS (Universal Message Service), les clusters et
bien d’autres choses encore.

Envoi et réception d’un message

Étudions un exemple simple pour nous faire une idée de JMS. Comme on l’a déjà
évoqué, JMS utilise des producteurs, des consommateurs et des destinations  : le
producteur envoie un message à la destination, qui est surveillée par le consomma-
teur qui attend qu’un message y arrive. Les destinations sont de deux types : les files
d’attente (pour les communications point à point) et les sujets (pour les communica-
tions de type Publication-Abonnement). Dans le Listing 13.1, un producteur envoie
un message de texte à une file surveillée par le consommateur.

Listing 13.1 : La classe Sender produit un message dans une Queue


public class Sender {

public static void main(String[] args) {

// Récupération du contexte JNDI


Context jndiContext = new InitialContext();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
424 Java EE 6 et GlassFish 3 

// Recherche des objets administrés


ConnectionFactory connectionFactory = (ConnectionFactory)
„ jndiContext.lookup("jms/javaee6/ConnectionFactory");
Queue queue = (Queue) jndiContext.lookup("jms/javaee6/Queue");

// Création des artéfacts nécessaires pour se connecter


// à la file
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
„ Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);

// Envoi d’un message de texte à la file


TextMessage message = session.createTextMessage();
message.setText("This is a text message");
producer.send(message);

connection.close();
}
}

Le code du Listing  13.1 représente une classe Sender qui ne dispose que d’une
méthode main(). Celle-ci commence par instancier un contexte JNDI pour obtenir
des objets ConnectionFactory et Queue. Les fabriques de connexions et les destina-
tions (files d’attente et sujets) sont appelées objets administrés : ils doivent être créés
et déclarés dans le fournisseur de messages (OpenMQ, ici). Tous les deux ont un
nom JNDI (la file d’attente, par exemple, s’appelle jms/javaee6/Queue) et doivent
être recherchés dans l’arborescence JNDI.
Lorsque les deux objets administrés ont été obtenus, la classe Sender utilise l’ob-
jet ConnectionFactory pour créer une connexion (objet Connection) afin d’obtenir
une session. Cette dernière est utilisée pour créer un producteur (objet Message-
Producer) et un message (objet TextMessage) sur la file de destination (session.
createProducer(queue)). Le producteur envoie ensuite ce message de type texte.
Bien que ce code soit largement commenté, nous avons délibérément omis le
­traitement des exceptions JNDI et JMS.
Le code pour recevoir le message est quasiment identique. En fait, les premières
lignes de la classe Receiver du Listing 12.2 sont exactement les mêmes : on crée un
contexte JNDI, on recherche la fabrique de connexion et la file d’attente, puis on se
connecte. Les seules différences tiennent au fait que l’on utilise un MessageConsumer
au lieu d’un MessageProducer et que le récepteur entre dans une boucle sans fin pour
surveiller la file d’attente (nous verrons plus tard que l’on peut éviter cette boucle en
utilisant un écouteur de messages, ce qui est une technique plus classique). Lorsque
le message arrive, on le consomme et on affiche son contenu.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 425

Listing 13.2 : La classe Receiver consomme un message d’une file d’attente


public class Receiver {

public static void main(String[] args) {

// Récupération du contexte JNDI


Context jndiContext = new InitialContext();

// Recherche des objets administrés


ConnectionFactory connectionFactory = (ConnectionFactory)
„ jndiContext.lookup("jms/javaee6/ConnectionFactory");
Queue queue = (Queue) jndiContext.lookup("jms/javaee6/Queue");

// Création des artéfacts nécessaires pour se connecter


// à la file
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
„ Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(queue);
connection.start();

// Boucle pour recevoir les messages


while (true) {
TextMessage message = (TextMessage) consumer.receive();
System.out.println("Message received: " + message.getText());
}

}
}

Nous pouvons maintenant nous intéresser à l’API de JMS, définir les objets admi-
nistrés et les classes nécessaires et voir comment traduire tout ceci dans un MDB.

Java Messaging Service

Au niveau le plus haut, l’architecture de JMS est formée des composants suivants
(voir Figure 13.2) :
■■ Un fournisseur. JMS n’est qu’une spécification et a donc besoin d’une implé-
mentation sous-jacente pour router les messages, c’est-à-dire un fournisseur.
Celui-ci gère le tampon et la livraison des messages en fournissant une implé-
mentation de l’API JMS.
■■ Clients. Un client est une application ou un composant Java qui utilise l’API
JMS pour consommer ou produire un message JMS. On parle de client JMS car
c’est un client du fournisseur sous-jacent. "Client" est le terme générique pour

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
426 Java EE 6 et GlassFish 3 

désigner un producteur, un émetteur, un éditeur, un consommateur, un récepteur


ou un abonné.
■■ Messages. Ce sont les objets que les clients envoient et reçoivent du fournisseur
JMS.
■■ Objets administrés. Pour qu’un fournisseur implémente totalement JMS, les
objets administrés (fabriques de connexion et destinations) doivent être placés
dans une arborescence JNDI et on doit pouvoir y accéder par des recherches
JNDI.

Figure 13.2 produit un message consomme un message


Fournisseur JMS
Architecture
de JMS.

Producteur Consommateur

Annuaire JNDI
recherche les objets administrés recherche les objets administrés

Le fournisseur de messages permet de mettre en place une communication asyn-


chrone en offrant une destination où seront stockés les messages en attendant de
pouvoir être délivrés à un client. Il existe deux types de destinations correspondant
chacun à un modèle d’architecture spécifique :
■■ Le modèle point à point (P2P). Dans ce modèle, la destination qui stocke les
messages est appelée file d’attente. Lorsqu’on utilise des messages point à point,
un client place un message dans une file et un autre client reçoit le message.
Lorsque le message a été acquitté, le fournisseur le supprime de la file.
■■ Le modèle publication-abonnement. Ici, la destination s’appelle un sujet. Avec
ce modèle, un client publie un message dans un sujet et tous les abonnés le
reçoivent.
La spécification JMS fournit un ensemble d’interfaces pouvant être utilisées par
les deux modèles. Le Tableau 13.1 énumère le nom générique de chaque interface
(Session, par exemple) et son nom spécifique pour chaque modèle (QueueSession,
TopicSession). Vous remarquerez également que les vocabulaires sont différents :
un consommateur est appelé récepteur dans le modèle P2P et abonné dans le modèle
publication/abonnement.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 427

Tableau 13.1 : Interfaces utilisées en fonction du type de destination

Générique Point à Point Publication-abonnement


Destination Queue Topic

ConnectionFactory QueueConnectionFactory TopicConnectionFactory

Connection QueueConnection TopicConnection

Session QueueSession TopicSession

MessageConsumer QueueReceiver TopicSubscriber

MessageProducer QueueSender TopicPublisher

Point à point

Dans le modèle P2P, un unique message va d’un unique producteur (le point A) à
un unique consommateur (le point B). Ce modèle repose sur le concept de files de
messages, d’émetteurs et de récepteurs (voir Figure 13.3). Une file stocke les mes-
sages envoyés par l’émetteur jusqu’à ce qu’ils soient consommés – les timings de
l’émetteur et du récepteur sont indépendants, ce qui signifie que l’émetteur peut pro-
duire des messages et les envoyer dans la file lorsqu’il le désire et qu’un récepteur
peut les consommer quand il le souhaite. Lorsqu’un récepteur est créé, il reçoit tous
les messages envoyés dans la file, même ceux qui ont été envoyés avant sa création.

Figure 13.3
Le modèle P2P. Mess Mess

Émetteur Récepteur
envoie reçoit

Chaque message est envoyé dans une file spécifique d’où le récepteur extrait les
messages. Les files conservent tous les messages tant qu’ils n’ont pas été consommés
ou jusqu’à ce qu’ils expirent.
Le modèle P2P est utilisé lorsqu’il n’y a qu’un seul récepteur pour chaque mes-
sage – une file peut avoir plusieurs consommateurs mais, une fois qu’un récepteur a
consommé un message, ce dernier est supprimé de la file et aucun autre consomma-
teur ne peut donc le recevoir. À la Figure 13.4, un seul émetteur produit trois mes-
sages et deux récepteurs consomment chacun un message qui ne sera pas disponible

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
428 Java EE 6 et GlassFish 3 

pour l’autre. JMS garantit également qu’un message ne sera délivré qu’une seule
fois.

Figure 13.4
Plusieurs
récepteurs. Mess Mess Mess Récepteur 1 Mess Mess
1 2 3 3 2
Émetteur
Récepteur 2 Mess
1

Notez que P2P ne garantit pas que les messages seront délivrés dans un ordre parti-
culier. En outre, s’il y a plusieurs récepteurs possibles pour un message, le choix du
récepteur est aléatoire.

Publication-abonnement

Dans ce modèle, un unique message envoyé par un seul producteur peut être reçu
par plusieurs consommateurs. Ce modèle repose sur le concept de sujets, d’éditeurs
et d’abonnés (voir Figure 13.5). Les consommateurs sont appelés abonnés car ils
doivent d’abord s’abonner à un sujet. C’est le fournisseur qui gère le mécanisme
d’abonnement/désabonnement, qui a lieu de façon dynamique.

Figure 13.5
Modèle
Publication- Mess s'abonne
abonnement. Éditeur Abonné

publie reçoit

Mess

Le sujet conserve les messages jusqu’à ce qu’ils aient été distribués à tous les abon-
nés. À la différence du modèle P2P, les timings entre éditeurs et abonnés sont liés :
ces derniers ne reçoivent pas les messages envoyés avant leur abonnement et un
abonné resté inactif pendant une période donnée ne recevra pas les messages publiés
entre-temps lorsqu’il redeviendra actif. Comme nous le verrons plus tard, ceci peut
être évité car l’API JMS dispose du concept d’abonné durable.
Plusieurs abonnés peuvent consommer le même message. Ce modèle peut donc être
utilisé par les applications de type "diffusion", dans lesquelles un même message est

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 429

délivré à plusieurs consommateurs. À la Figure 13.6, par exemple, l’éditeur envoie


trois messages qui seront reçus par tous les abonnés.

Figure 13.6
Plusieurs
Abonné 1
abonnés. Mess Mess Mess Mess Mess Mess
3 2 1 3 1 2
Éditeur
Abonné 2 Mess Mess Mess
1 3 2

API JMS

L’API JMS se trouve dans le paquetage javax.jms et fournit des classes et des inter-
faces aux applications qui ont besoin d’un système de messages (voir Figure 13.7).
Elle autorise les communications asynchrones entre les clients en fournissant une
connexion à un fournisseur et une session au cours de laquelle les messages peuvent
être créés, envoyés et reçus. Ces messages peuvent contenir du texte ou différents
types d’objets.

Figure 13.7 <<Interface>>
ConnectionFactory
API JMS (inspirée
de la Figure 2.1
crée
de la spécification
JMS 1.1). <<Interface>>
Connection

crée

crée <<Interface>> crée


Session

<<Interface>> <<Interface>> <<Interface>>


MessageProducer Message MessageConsumer

<<Interface>>
Destination
envoie à reçoit de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
430 Java EE 6 et GlassFish 3 

Objets administrés
Les objets administrés sont des objets qui ne sont pas configurés par programma-
tion : le fournisseur de messages les rend disponibles dans l’espace de noms JNDI.
Comme les sources de données JDBC, les objets administrés ne sont créés qu’une
seule fois. Il en existe deux types pour JMS :
■■ Les fabriques de connexion. Ces objets sont utilisés par les clients pour créer
une connexion vers une destination.
■■ Les destinations. Ces objets sont des points de distribution qui reçoivent,
­stockent et distribuent les messages. Les destinations peuvent être des files
­d’attente (P2P) ou des sujets (Publication-abonnement).
Les clients JMS accèdent à ces objets via des interfaces portables en les recherchant
dans l’espace de noms JNDI. GlassFish fournit plusieurs moyens d’en créer  : en
utilisant la console d’administration, avec l’outil en ligne de commande asadmin ou
avec l’interface REST.

Fabriques de connexion
Ce sont les objets administrés qui permettent à une application de se connecter
à un fournisseur en créant par programme un objet Connection. Une fabrique de
connexion encapsule les paramètres de configuration définis par un administrateur.
Il en existe trois types :
■■ javax.jms.ConnectionFactory est une interface pouvant être utilisée à la fois
par les communications P2P et Publication-abonnement.
■■ javax.jms.QueueConnectionFactory est une interface qui hérite de Connection­
Factory et qui ne sert qu’aux communications P2P.
■■ javax.jms.TopicConnectionFactory est une interface qui hérite de Connection­
Factory et qui ne sert qu’aux communications Publication-abonnement.
Le programme doit d’abord obtenir une fabrique de connexion en effectuant une
recherche JNDI. Le fragment de code suivant, par exemple, obtient un objet Ini-
tialContext et s’en sert pour rechercher un QueueConnectionFactory et un Topic-
ConnectionFactory par leurs noms JNDI :

Context ctx = new InitialContext();


QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) ctx.lookup("QConnFactory");
TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory) ctx.lookup("TConnFactory");

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 431

Les fabriques de files d’attente/sujets sont nécessaires lorsque l’on a besoin d’ac-
céder à des détails spécifiques de ces modèles. Dans le cas contraire, vous pouvez
directement utiliser une ConnectionFactory générique pour les deux situations :
Context ctx = new InitialContext();
ConnectionFactory ConnectionFactory =
(QueueConnectionFactory) ctx.lookup("GenericConnFactory");

La seule méthode disponible dans ces trois interfaces est la méthode createConnec-
tion(), qui renvoie un objet Connection. Cette méthode est surchargée pour pouvoir
créer une connexion en utilisant l’identité de l’utilisateur par défaut ou en précisant
un nom d’utilisateur et un mot de passe (voir Listing 13.3).

Listing 13.3 : Interface ConnectionFactory


public interface ConnectionFactory {

Connection createConnection() throws JMSException;


Connection createConnection(String userName, String password)
throws JMSException;
}

Destinations
Une destination est un objet administré contenant des informations spécifiques à
un fournisseur, comme l’adresse de destination. Cependant, ces informations sont
cachées au client JMS par l’interface standard javax.jms.Destination. Il existe
deux types de destinations, représentées par deux interfaces héritant de Destination :
■■ javax.jms.Queue, utilisée pour les communications P2P ;
■■ javax.jms.Topic, utilisée pour les communications Publication-abonnement.
Ces interfaces ne contiennent qu’une seule méthode, qui renvoie le nom de la des-
tination. Comme pour les fabriques de connexion, ces objets s’obtiennent par une
recherche JNDI.

Injection
Les fabriques de connexion et les destinations sont des objets administrés qui rési-
dent dans un fournisseur de messages et qui doivent être déclarés dans l’espace
de noms JNDI. Lorsque le code client s’exécute dans un conteneur (EJB, servlet,
client d’application), vous pouvez utiliser l’injection des dépendances à la place
des recherches JNDI grâce à l’annotation @Resource (voir la section "Injection de
dépendances" du Chapitre 7), ce qui est bien plus simple.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
432 Java EE 6 et GlassFish 3 

Le Tableau 13.2 énumère les éléments de cette annotation.

Tableau 13.2 : API de l’annotation @javax.annotation.Resource

Élément Description
name Nom JNDI de la ressource.
type Type Java de la ressource (javax.sql.DataSource ou javax.
jms.Topic, par exemple).

authenticationType Type d’authentification à utiliser pour la ressource (soit le


conteneur, soit l’application
shareable Indique si la ressource peut être ou non partagée.
mappedName Nom spécifique auquel associer la ressource.
description Description de la ressource.

Pour tester cette annotation, nous utiliserons une classe Receiver avec une méthode
main() qui reçoit des messages textuels. Dans le Listing  13.2, la fabrique de
connexions et la file d’attente étaient obtenues par des recherches JNDI ; dans le
Listing 13.4, les noms JNDI sont indiqués dans les annotations @Resource : si cette
classe Receiver s’exécute dans un conteneur, elle recevra donc par injection des
références ConnectionFactory et Queue lors de son initialisation.

Listing 13.4 : La classe Receiver obtient par injection des références à des ressources JMS
public class Receiver {

@Resource(name = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(name = "jms/javaee6/Queue")
private static Queue queue;

public static void main(String[] args) {

// Crée les artéfacts nécessaires à la connexion à la file


Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(queue);
connection.start();

// Boucle pour recevoir les messages


while (true) {
TextMessage message = (TextMessage) consumer.receive();

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 433

System.out.println("Message received: " +


message.getText());
}
}
}

Pour simplifier, nous avons omis le traitement des exceptions. Notez que l’annota-
tion @Resource porte sur des attributs privés : l’injection de ressources peut s’ap-
pliquer à différents niveaux de visibilité (privée, protégée, paquetage ou publique).

Connexion
L’objet javax.jms.Connection que l’on crée avec la méthode createConnection()
de la fabrique de connexion encapsule une connexion au fournisseur JMS. Les
connexions sont thread-safe et conçues pour être partagées car l’ouverture d’une
connexion est une opération coûteuse en terme de ressources. Une session (javax.
jms.Session), par contre, fournit un contexte mono-thread pour envoyer et recevoir
les messages ; une connexion permet de créer une ou plusieurs sessions.
Comme les fabriques de connexion, les connexions peuvent être de trois types en uti-
lisant l’interface générique Connection ou les interfaces QueueConnection et Topic-
Connection qui dérivent de celle-ci. La création de l’objet "connexion" dépend du
type de la fabrique dont vous disposez :
Connection connection = connFactory.createConnection(); QueueConnection
connection =
queueConnFactory.createQueueConnection();
TopicConnection connection =
topicConnFactory.createTopicConnection();

Dans le Listing 13.4, le récepteur doit appeler la méthode start() avant de pouvoir


consommer les messages. La méthode stop() permet d’arrêter temporairement d’en
recevoir sans pour autant fermer la connexion.
connection.start();
connection.stop();
Les connexions qui ont été créées doivent être fermées lorsque l’application se ter-
mine. La fermeture d’une connexion ferme également ses sessions, ses producteurs
ou ses consommateurs.
connection.close();

Session
On crée une session en appelant la méthode createSession() de la connexion.
Une session fournit un contexte transactionnel qui permet de regrouper plusieurs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
434 Java EE 6 et GlassFish 3 

­ essages dans une unité de traitement atomique : si vous envoyez plusieurs mes-
m
sages au cours de la même session, JMS garantira qu’ils arrivent tous à destination
dans leur ordre d’émission, ou qu’aucun n’arrivera. Ce comportement est mis en
place lors de la création de la session :
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);

Le premier paramètre de cette méthode indique si la session est transactionnelle ou


non. Une valeur true précise que la requête d’envoi des messages ne sera pas réali-
sée tant que l’on n’a pas appelé la méthode commit() de la session ou que la session
est fermée. Si ce paramètre vaut false, la session ne sera pas transactionnelle et les
messages seront envoyés dès l’appel de la méthode send(). Le second paramètre
indique que la session accuse automatiquement réception des messages lorsqu’ils
ont été correctement reçus.
Une session s’exécute dans un seul thread et permet de créer des messages, des pro-
ducteurs et des consommateurs. Comme tous les objets que nous avons vus jusqu’à
présent, il existe deux variantes de sessions : QueueSession et TopicSession. Un
objet Session générique permet d’utiliser l’une ou l’autre via une interface unique.

Messages

Figure 13.8
Structure En-tête Propriétés Corps
JMSMessageID <name><value> BytesMessage
d’un message JMS. JMSCorrelationID <name><value> TextMessage
JMSDeliveryMode <name><value> ObjectMessage
JMSDestination MapMessage
JMSExpiration StreamMessage
JMSPriority
JMSRedelivered
JMSReplyTo
JMSTimestamp

Les clients échangent des messages pour communiquer ; un producteur envoie un


message à une destination où un client le recevra. Les messages sont des objets qui
encapsulent les informations et qui sont divisés en trois parties (voir Figure 13.8) :
■■ Un en-tête, qui contient les informations classiques pour identifier et acheminer
le message.
■■ Les propriétés, qui sont des paires nom/valeur que l’application peut lire ou
écrire. Elles permettent également aux destinations de filtrer les messages selon
les valeurs de ces propriétés.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 435

■■ Un corps, qui contient le véritable message et peut utiliser plusieurs formats


(texte, octets, objet, etc.).

En-tête
L’en-tête contient des paires nom/valeur prédéfinies, communes à tous les mes-
sages et que les clients et les fournisseurs utilisent pour identifier et acheminer
les messages. Elles peuvent être considérées comme les métadonnées du message
car elles fournissent des informations sur le message lui-même. Chaque champ a
des méthodes getter et setter qui lui sont associées dans l’interface javax.jms.
Message. Bien que certains champs de l’en-tête soient prévus pour être initialisés
par un client, la plupart sont automatiquement fixés par la méthode send() ou
publish().

Le Tableau 13.3 décrit tous les champs de l’en-tête d’un message JMS.

Tableau 13.3 : Champs de l’en-tête1

Champ Description Initialisé par


JMSDestination Destination à laquelle est envoyé le send() ou publish()
message.
JMSDeliveryMode JMS reconnaît deux modes de délivrance send() ou publish()
des messages. Le mode PERSISTENT
demande au fournisseur de garantir que
le message ne sera pas perdu lors de
son transfert à cause d’une erreur. Le
mode NON_PERSISTENT est le mode de
délivrance le moins coûteux car il n’exige
pas d’enregistrer le message sur un
support persistant.
JMSMessageID Valeur identifiant de manière unique send() ou publish()
chaque message envoyé par un
fournisseur.
JMSTimestamp Horodatage indiquant l’instant où le send() ou publish()
message a été passé à un fournisseur pour
être envoyé.

1 Spécification de JMS 1.1, "3.4: Message Header Fields" (http://jcp.org/en/jsr/detail?id=914).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
436 Java EE 6 et GlassFish 3 

Tableau 13.3 : Champs de l’en-tête (suite)

Champ Description Initialisé par


JMSCorrelationID Un client peut utiliser ce champ pour Le client
lier un message à un autre : pour lier un
message de réponse à son message de
requête, par exemple.
JMSReplyTo Contient la destination à laquelle Le client
renvoyer la réponse au message.
JMSRedelivered Valeur booléenne initialisée par le Le fournisseur
fournisseur pour indiquer si un message
a été à nouveau délivré.
JMSType Identifiant du type du message. Le client
JMSExpiration Date d’expiration d’un message envoyé, send() ou publish()
calculée et initialisée en fonction de la
valeur time-to-live passée à la méthode send().
JMSPriority JMS définit dix niveaux de priorités, 0 send() ou publish()
étant la plus faible et 9, la plus forte.

Vous pouvez accéder à ces champs via l’interface Message :


message.getJMSCorrelationID();
message.getJMSMessageID();
message.setJMSPriority(6);

Propriétés
Outre les champs d’en-tête, l’interface javax.jms.Message permet de gérer les
valeurs des propriétés, qui sont exactement comme les en-têtes, mais créées expli-
citement par l’application au lieu d’être standard pour tous les messages. Elles per-
mettent d’ajouter des champs d’en-têtes optionnels à un message, que le client peut
choisir de recevoir ou non via des sélecteurs. Leurs valeurs peuvent être de type
boolean, byte, short, int, long, float, double et String. Le code permettant de les
initialiser et de les lire est de la forme :
message.setFloatProperty("orderAmount", 1245.5f); message.getFloatProperty("or
derAmount");

Corps
Le corps d’un message est facultatif et contient les données à envoyer ou à rece-
voir. Selon l’interface utilisée, il peut contenir des données dans différents formats,
­énumérés dans le Tableau 13.4.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 437

Tableau 13.4 : Types de messages

Interface Description
StreamMessage Message contenant un flux de valeurs primitives Java. Il est rempli et lu
séquentiellement.
MapMessage Message contenant un ensemble de paires nom/valeur où les noms sont
des chaînes et les valeurs sont d’un type primitif.
TextMessage Message contenant une chaîne (du XML, par exemple).
ObjectMessage Message contenant un objet sérialisable ou une collection d’objets
sérialisables.
BytesMessage Message contenant un flux d’octets.

En dérivant l’interface javax.jms.Message, vous pouvez créer votre propre format


de message. Notez que lorsqu’un message est reçu, son corps est en lecture seule.
Selon le type du message, des méthodes différentes permettent d’accéder à son
contenu. Un message textuel disposera des méthodes getText() et setText(), un
message d’objet disposera des méthodes getObject() et setObject(), etc.
textMessage.setText("This is a text message");
textMessage.getText();
bytesMessage.readByte();
objectMessage.getObject();

MessageProducer
Un producteur de messages est un objet créé par une session pour envoyer des mes-
sages à une destination. L’interface générique javax.jms.MessageProducer peut
être utilisée pour obtenir un producteur spécifique disposant d’une interface unique.
Dans le cas du modèle P2P, un producteur de messages est appelé émetteur et implé-
mente l’interface QueueSender. Avec le modèle Publication-abonnement, il s’ap-
pelle éditeur et implémente TopicPublisher. Selon l’interface utilisée, un message
créé est envoyé (P2P) ou publié (Publication-abonnement) :
messageProducer.send(message);
queueSender.send(message);
topicPublisher.publish(message);

Un producteur peut préciser un mode de livraison par défaut, une priorité et un délai
d’expiration des messages qu’il envoie. Les étapes suivantes expliquent comment
créer un éditeur qui publie un message dans un sujet (voir Listing 13.5) :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
438 Java EE 6 et GlassFish 3 

1. Obtenir une fabrique de connexions et un sujet par injection (ou par une
recherche JNDI).
2. Créer un objet Connection avec la fabrique de connexions.
3. Créer un objet Session avec la connexion.
4. Créer un MessageProducer (ici, nous aurions pu choisir un TopicPublisher) en
utilisant l’objet Session.
5. Créer un ou plusieurs messages d’un type quelconque (ici, nous avons utilisé
un TextMessage) en utilisant l’objet Session. Après sa création, remplir le mes-
sage avec les données (nous nous sommes servis ici de la méthode setText()).
6. Envoyer un ou plusieurs messages dans le sujet à l’aide de la méthode Message-
Producer.send() (ou TopicPublisher.publish()).

Listing 13.5 : La classe Sender envoie un message dans un sujet


public class Sender {

@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;

public static void main(String[] args) {

// Crée les artéfacts nécessaires à la connexion au sujet.


Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);

// Envoie un message texte dans le sujet


TextMessage message = session.createTextMessage();
message.setText("This is a text message");
producer.send(message);

connection.close();
}
}

MessageConsumer
Un client utilise un MessageConsumer pour recevoir les messages provenant d’une
destination. Cet objet est créé en passant une Queue ou un Topic à la méthode
createConsumer() de la session. Dans le cas du modèle P2P, un consommateur

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 439

de messages peut implémenter l’interface QueueReceiver et, pour le modèle Publi­


cation-abonnement, l’interface TopicSubscriber.
Les messages sont intrinsèquement asynchrones car les timings des producteurs
et des consommateurs sont totalement indépendants. Cependant, un client peut
consommer les messages de deux façons :
■■ De façon synchrone. Un récepteur récupère explicitement le message de la
destination en appelant la méthode receive(). Les exemples précédents, par
exemple, utilisent une boucle sans fin qui se bloque jusqu’à ce que le message
arrive.
■■ De façon asynchrone. Un récepteur s’enregistre auprès d’un événement qui est
déclenché à l’arrivée du message. Il doit alors implémenter l’interface Messa-
geListener : à chaque fois qu’un message arrive, le fournisseur lui délivrera en
appelant la méthode onMessage().
Ces deux types de consommateurs sont illustrés à la Figure 13.9.

Figure 13.9
Consommateurs
synchrones et Mess Mess
asynchrones. Émetteur Récepteur
envoie collecte synchrone

Mess enregistre
Émetteur Récepteur
prévient asynchrone
envoie
collecte

Mess

Livraison synchrone
Un récepteur synchrone doit lancer une connexion, boucler en attendant qu’un
message arrive et demander le message arrivé à l’aide de l’une de ses méthodes
receive() : leurs différentes variantes permettent au client de demander ou d’at-
tendre le prochain message. Les étapes suivantes expliquent comment créer un
récepteur synchrone qui consomme un message d’un sujet (voir Listing 13.6) :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
440 Java EE 6 et GlassFish 3 

1. Obtenir une fabrique de connexions et un sujet en utilisant l’injection (ou une


recherche JNDI).
2. Créer un objet Connection à l’aide de la fabrique.
3. Créer un objet Session à l’aide de la connexion.
4. Créer un MessageConsumer (ici, il pourrait également s’agir d’un TopicSub–
scriber) à l’aide de l’objet Session.

5. Lancer la connexion.
6. Boucler et appeler la méthode receive() sur l’objet consommateur. Cette
méthode se bloque si la file est vide et attend qu’un message arrive. Ici, la
boucle sans fin attend que d’autres messages arrivent.
7. Traiter le message renvoyé par receive() en utilisant la méthode TextMessage.
getText() (si c’est un message texte).

Listing 13.6 : Le Receiver consomme les messages de façon synchrone


public class Receiver {

@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;

public static void main(String[] args) {

// Crée les artéfacts nécessaires à la connexion au sujet


Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(topic);
connection.start();

// Boucle pour recevoir les messages


while (true) {
TextMessage message = (TextMessage) consumer.receive();
System.out.println("Message received: " +
message.getText());
}
}
}

Livraison asynchrone
La consommation asynchrone repose sur la gestion des événements. Un client
peut enregistrer un objet (y compris lui-même) qui implémente l’interface

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 441

­ essageListener. Un écouteur de messages est un objet qui se comporte comme un


M
gestionnaire d’événements asynchrones pour les messages : lorsque les messages
arrivent, le fournisseur les délivre en appelant la méthode onMessage() de l’écou-
teur, qui prend un seul paramètre de type Message. Grâce à ce modèle événementiel,
le consommateur n’a pas besoin de boucler indéfiniment en attente d’un message :
les MDB utilisent ce modèle.
Les étapes suivantes décrivent la création d’un écouteur de messages asynchrone
(voir Listing 13.7) :
1. La classe implémente l’interface javax.jms.MessageListener, qui définit une
seule méthode nommée onMessage().
2. Obtenir une fabrique de connexions et un sujet en utilisant l’injection (ou une
recherche JNDI).
3. Créer un objet Connection à l’aide de la fabrique et un objet Session à l’aide de
la connexion. Créer un MessageConsumer à l’aide de l’objet Session.
4. Appeler la méthode setMessageListener() en lui passant une instance de l’in-
terface MessageListener (dans le Listing 13.7, la classe Listener implémente
elle-même l’interface MessageListener).
5. Après l’enregistrement de l’écouteur de messages, appeler la méthode start()
pour lancer la surveillance de l’arrivée d’un message – si l’on appelle start()
avant d’enregistrer l’écouteur, on risque de perdre des messages.
6. Implémenter la méthode onMessage() et traiter le message reçu. À chaque fois
qu’un message arrive, le fournisseur appellera cette méthode en lui passant le
message.

Listing 13.7 : Le consommateur est un écouteur de messages


public class Listener implements MessageListener {

@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;

public static void main(String[] args) {

// Crée les artéfacts nécessaires à la connexion au sujet e


Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
442 Java EE 6 et GlassFish 3 

MessageConsumer consumer = session.createConsumer(topic);


consumer.setMessageListener(new Listener());
connection.start();
}

public void onMessage(Message message) {


System.out.println("Message received: " +
((TextMessage) message).getText());
}
}

Sélecteurs

Certaines applications doivent filtrer les messages qu’elles reçoivent. Lorsqu’un


message est diffusé à de nombreux clients, par exemple, il peut être utile de fixer des
critères afin qu’ils ne soient consommés que par certains récepteurs : ceci permet
d’éviter que le fournisseur gaspille son temps et sa bande passante à transporter des
messages à des clients qui n’en ont pas besoin.
Nous avons vu que les messages étaient constitués de trois parties : un en-tête, des
propriétés et un corps. L’en-tête contient un nombre déterminé de champs (qui for-
ment les métadonnées du message) et les propriétés sont un ensemble de paires
nom/valeur personnalisées que l’application peut utiliser pour fixer des valeurs dont
elle a besoin. Ces deux parties peuvent servir à mettre en place une sélection : les
émetteurs fixent une ou plusieurs propriétés ou des valeurs de champs dans l’en-tête,
et le récepteur précise les critères de choix d’un message en utilisant des expressions
de sélection (sélecteurs). Seuls les messages correspondant à ces sélecteurs seront
alors délivrés. C’est le fournisseur JMS, plutôt que l’application, qui filtrera les
messages.
Un sélecteur de message est une chaîne contenant une expression dont la syntaxe
s’inspire de celle des expressions conditionnelles de SQL92 :
session.createConsumer(topic, "JMSPriority < 6"); session.
createConsumer(topic, "JMSPriority < 6
„ AND orderAmount < 200");
session.createConsumer(topic, "orderAmount BETWEEN 1000
„ AND 2000");

Le code précédent crée un consommateur en lui passant un sélecteur. Cette chaîne


peut utiliser les champs de l’en-tête (JMSPriority < 6) ou des propriétés propres à
l’application (orderAmount < 200). De son côté, le producteur fixe ce champ d’en-
tête et cette propriété dans le message de la façon suivante :
message.setIntProperty("orderAmount", 1530); message.setJMSPriority(5);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 443

Les expressions de sélections peuvent utiliser des opérateurs logiques (NOT, AND,
OR), de comparaison (=, >, >=, <, <=, <>), des opérateurs arithmétiques (+, -, *, /), des
expressions ([NOT] BETWEEN, [NOT] IN, [NOT] LIKE, IS [NOT] NULL), etc.

Mécanismes de fiabilité

Nous avons vu comment se connecter à un fournisseur, créer différents types de mes-


sages, les envoyer à des files d’attentes ou dans des sujets et comment les recevoir
tous ou les filtrer à l’aide de sélecteurs. Mais qu’en est-il de la fiabilité ? JMS définit
différents mécanismes pour garantir la délivrance des messages, même lorsque le
fournisseur tombe en panne ou est surchargé, ou que les destinations sont remplies
de messages qui auraient dû expirer. Les mécanismes qui permettent de fiabiliser la
livraison des messages sont les suivants :
■■ Délai d’expiration pour les messages. Les messages ne seront pas délivrés s’ils
sont obsolètes.
■■ Persistance des messages. Les messages sont stockés dans l’éventualité d’une
panne du fournisseur.
■■ Accusé de réception. Différents niveaux d’acquittement des messages sont
possibles.
■■ Abonnements durables. Garantit que les messages seront délivrés à un abonné
indisponible (modèle Publication-abonnement).
■■ Priorités. Fixe la priorité de livraison d’un message.

Utilisation d’un délai d’expiration


Lorsque la charge est importante, un délai d’expiration attribué aux messages permet
de garantir que le fournisseur les supprimera de la destination lorsqu’ils deviendront
obsolètes. Ce délai d’expiration est mis en place en utilisant l’API MessageProducer
ou en configurant le champ d’en-tête JMSExpiration.
Un objet MessageProducer dispose d’une méthode setTimeToLive() prenant un
nombre de millisecondes en paramètre. On peut également l’indiquer dans l’appel à
la méthode send() de chaque message :
MessageProducer producer = session.createProducer(topic); producer.
setTimeToLive(1000);
// ou
producer.send(message, DeliveryMode.NON_PERSISTENT, 2, 1000);

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
444 Java EE 6 et GlassFish 3 

Le premier appel fixe globalement le TTL (Time To Live) pour le producteur et


tous les messages qu’il délivre, alors que le second envoie un message en précisant
son mode de livraison, sa priorité et son délai d’expiration. Lorsqu’un message est
envoyé, son délai d’expiration est calculé comme étant la somme des TTL. Indiquer
cette valeur dans le champ d’en-tête JMSExpiration fera que les messages obsolètes
ne seront pas délivrés :
message.setJMSExpiration(1000);

Persistance des messages


JMS reconnaît deux modes de livraison des messages : persistant et non persistant.
Le premier garantit qu’un message ne sera délivré qu’une seule fois à un consom-
mateur, tandis que le second exige qu’un message ne soit délivré qu’une fois au plus.
La livraison persistante (mode par défaut) est plus fiable mais plus coûteuse car elle
fait en sorte d’éviter la perte d’un message en cas de panne du fournisseur.
Le mode de livraison peut être précisé de deux façons : en utilisant la méthode set-
DeliveryMode() de l’interface MessageProducer ou en passant un paramètre à la
méthode send() :
MessageProducer producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// ou
producer.send(message, DeliveryMode.NON_PERSISTENT, 2, 1000);

Le premier appel fixe le mode global de livraison pour le producteur et tous les mes-
sages qu’il délivre, tandis que le second ne l’indique que pour le message concerné.

Contrôle des accusés de réception


Jusqu’à maintenant, les scénarios que nous avons étudiés supposaient qu’un mes-
sage était envoyé et reçu sans aucun accusé de réception. Parfois, cependant, on
souhaite que le récepteur acquitte le message qu’il a reçu (voir Figure 13.10). Une
phase d’acquittement peut être lancée soit par le fournisseur JMS, soit par le client
en fonction du mode choisi.
Dans des sessions transactionnelles, l’acquittement a automatiquement lieu
lorsqu’une transaction est validée. Si elle est annulée, tous les messages consom-
més sont à nouveau délivrés. Dans les sessions non transactionnelles, en revanche,
­l’accusé de réception doit être explicite :
■■ AUTO_ACKNOWLEDGE. La session accuse automatiquement réception du message.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 445

■■ CLIENT_ACKNOWLEDGE. Un client accuse réception d’un message en appelant


explicitement la méthode Message.acknowledge().
■■ DUPS_OK_ACKNOWLEDGE. Demande à la session d’acquitter les messages de façon
paresseuse. Ceci impliquera probablement la livraison de messages dupliqués si
le fournisseur JMS tombe en panne, aussi cette option ne doit-elle être utilisée
que par les consommateurs qui tolèrent les messages en double. Lorsque le mes-
sage est délivré une nouvelle fois, le fournisseur fixe à true la valeur du champ
d’en-tête JMSRedelivered.

Figure 13.10
Un récepteur Mess
accuse réception Mess
d’un message. Émetteur Abonné 1
reçoit
envoie
accuse réception

Le code suivant utilise l’acquittement explicite du client en appelant la méthode


acknowledge() :

// Producteur
connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
producer = session.createProducer(topic);
message = session.createTextMessage();
producer.send(message);
// Consommateur
message.acknowledge();

Abonnements durables
L’inconvénient du modèle Publication-abonnement est qu’il faut qu’un consomma-
teur de messages s’exécute lorsque les messages sont envoyés au sujet ; sinon il ne
les recevra pas. En utilisant des abonnés durables, l’API JMS fournit un moyen de
conserver les messages dans le sujet jusqu’à ce que tous les consommateurs abonnés
les aient reçus. Grâce à l’abonnement durable, le récepteur peut donc être déconnecté :
lorsqu’il se reconnectera, il recevra les messages arrivés entre-temps. Pour cela, le
client crée un abonné durable en utilisant la session :
session.createDurableSubscriber(topic,
"javaee6DurableSubscription");

À partir de là, le programme client lance la connexion et reçoit les messages. Le


nom javaee6DurableSubscription sert d’identifiant pour l’abonnement durable :

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
446 Java EE 6 et GlassFish 3 

il doit être unique et provoque la déclaration d’une fabrique de connexions unique


pour chaque abonné durable.

Priorités
Les priorités permettent de faire en sorte que le fournisseur JMS délivre en pre-
mier les messages urgents. JMS définit dix priorités, de 0 (la plus faible) à 9 (la
plus forte). Vous pouvez indiquer la valeur de la priorité par un appel à la méthode
­ etPriority() ou en la passant en paramètre à la méthode send() :
s

MessageProducer producer = session.createProducer(topic);


producer. setPriority(2);
// ou
producer.send(message, DeliveryMode.NON_PERSISTENT, 2, 1000);

MDB : Message-Driven Beans

Ce chapitre montre comment les messages asynchrones de JMS autorisent un cou-


plage faible entre les systèmes dans un environnement autonome avec des classes
principales, des recherches JNDI ou l’injection de ressources avec un conteneur
client d’application. Les MDB offrent la même chose pour les applications profes-
sionnelles qui s’exécutent dans un conteneur EJB.
Un MDB est un consommateur asynchrone qui est appelé par le conteneur lorsqu’un
message arrive. Du point de vue du producteur du message, un MDB n’est qu’un
simple consommateur caché derrière la destination qu’il surveille.
Les MDB font partie de la spécification EJB et leur modèle ressemble à celui des
beans de session sans état car ils n’ont pas d’état et s’exécutent dans un conteneur
EJB. Le conteneur surveille une destination et délègue l’appel au MDB lorsqu’un
message arrive. Comme n’importe quel EJB, les MDB peuvent accéder aux res-
sources gérées par le conteneur (autres EJB, connexions JDBC, ressources JMS,
etc.).
Pourquoi utiliser les MDB alors que l’on peut utiliser des clients JMS autonomes
comme on l’a fait jusqu’à présent ? À cause du conteneur, qui gère les threads, la
sécurité et les transactions, ce qui simplifie beaucoup le code du consommateur JMS.
Le conteneur gère également les messages entrants en les répartissant entre les diffé-
rentes instances des MDB (placées dans un pool), qui ne gèrent pas elles-mêmes le

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 447

multithread. Dès qu’un nouveau message atteint la destination, une instance MDB
est retirée du pool pour gérer le message.

Création d’un MDB

Les MDB peuvent être transactionnels, multithreads et, naturellement, ils peuvent
consommer des messages JMS. Avec l’API JMS, vous pourriez vous attendre à
devoir utiliser des fabriques, des connexions, des consommateurs, des transactions,
etc., comme précédemment. Pourtant, l’aspect d’un MDB simple comme celui du
Listing 13.8 risque de vous surprendre.

Listing 13.8 : Un MDB simple


@MessageDriven(mappedName = "jms/javaee6/Topic")
public class BillingMDB implements MessageListener {

public void onMessage(Message message) {


TextMessage msg = (TextMessage)message;
System.out.println("Message received: " + msg.getText());
}
}

Le code du Listing 13.8 (qui omet le traitement des exceptions pour plus de clarté)
montre que les MDB libèrent le programmeur de tous les aspects techniques du
traitement des différents types de messages que nous avons présentés jusqu’ici. Un
MDB implémente l’interface MessageListener et la méthode onMessage(), mais
aucun autre code n’est nécessaire pour se connecter au fournisseur ou pour lancer la
consommation des messages. Les MDB utilisent également le mécanisme de confi-
guration par exception, ce qui implique que seul un petit nombre d’annotations suffit
pour les faire fonctionner (voir l’annotation @MessageDriven).

Le modèle des MDB

Les MDB sont différents des beans de session car ils n’implémentent pas d’interface
métier locale ou distante ; ils implémentent à la place javax.jms.MessageListener.
Les clients ne peuvent pas appeler directement les méthodes des MDB ; cependant,
comme les beans de session, les MDB utilisent un modèle de programmation éla-
boré, avec un cycle de vie, des annotations de rappel, des intercepteurs, l’injection
et les transactions. Les applications qui tirent parti de ce modèle disposent donc de
nombreuses fonctionnalités.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
448 Java EE 6 et GlassFish 3 

Il est important de se rappeler que les MDB ne font pas partie du nouveau modèle
EJB Lite, ce qui signifie qu’on ne peut pas les déployer dans une application web
simple (dans un fichier war) : ils ont besoin d’un assemblage entreprise (archive ear).
Une classe MDB doit respecter les règles suivantes :
■■ Elle doit être annotée par @javax.ejb.MessageDriven ou son équivalent XML
dans un descripteur de déploiement.
■■ Elle doit implémenter, directement ou non, l’interface MessageListener.
■■ Elle doit être publique et ne doit être ni finale ni abstraite.
■■ Elle doit proposer un constructeur public sans paramètre qui servira à créer les
instances du MDB.
■■ Elle ne doit pas définir de méthode finalize().
La classe MDB peut implémenter d’autres méthodes, invoquer d’autres ressources,
etc. Les MDB sont déployés dans un conteneur et peuvent éventuellement être
assemblés avec un fichier ejb-jar.xml. Avec le modèle simplifié de Java EE 6, un
MDB peut être un simple POJO annoté et faire l’économie de la majeure partie
de sa configuration. Cependant, vous pouvez utiliser les éléments des annotations
@MessageDriven et @ActivationConfigProperty (ou leurs équivalents XML) pour
adapter la configuration de JMS à vos besoins.

@MessageDriven
Les MDB font partie des EJB les plus simples à développer car ils utilisent un mini-
mum d’annotations. @MessageDriven (ou son équivalent XML) est obligatoire car c’est
une métadonnée dont a besoin le conteneur pour savoir que la classe Java est un MDB.
L’API de cette annotation, présentée dans le Listing 13.9, est très simple et tous ses
éléments sont facultatifs.

Listing 13.9 : API de l’annotation @MessageDriven


@Target(TYPE) @Retention(RUNTIME)
public @interface MessageDriven {
String name() default "";
Class messageListenerInterface default Object.class;
ActivationConfigProperty[] activationConfig() default {};
String mappedName();
String description();
}

L’élément name précise le nom du MDB (qui est, par défaut, celui de la classe). mes-
sageListenerInterface précise l’écouteur de message que le MDB implémente

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 449

(s’il implémente plusieurs interfaces, cet élément indique au conteneur EJB quelle
est l’interface MessageListener). L’élément mappedName est le nom JNDI de la des-
tination que doit surveiller le MDB. description est une simple chaîne qui décrit le
MDB lorsqu’il est déployé. L’élément activationConfig sert à préciser les proprié-
tés de configuration ; il ne prend pas de paramètre et renvoie un tableau d’annotations
@ActivationConfigProperty.

@ActivationConfigProperty
JMS permet de configurer certaines propriétés comme les sélecteurs de messages, le
mode d’acquittement, les abonnements durables, etc. Dans un MDB, ces propriétés
peuvent être configurées via l’annotation facultative @ActivationConfigProperty.
Celle-ci peut être passée en paramètre à l’annotation @MessageDriven ; par rapport
à son équivalent JMS, elle est très simple puisqu’elle est formée d’une paire nom/
valeur (voir Listing 13.10).

Listing 13.10 : API de l’annotation ActivationConfigProperty


@Target({})
@Retention(RUNTIME)
public @interface ActivationConfigProperty {
String propertyName();
String propertyValue();
}

Cette annotation permet d’utiliser une configuration spécifique du système de mes-


sages, ce qui signifie que les propriétés ne sont pas portables entre les fournisseurs
JMS. Le code du Listing 13.11, par exemple, configure le mode d’acquittement et le
sélecteur de message pour OpenMQ.

Listing 13.11 : Configuration de propriétés avec les MDB


@MessageDriven(mappedName = "jms/javaee6/Topic",
activationConfig = {
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "messageSelector",
propertyValue = "orderAmount < 3000")
})
public class BillingMDB implements MessageListener {

public void onMessage(Message message) {


TextMessage msg = (TextMessage)message;
System.out.println("Message received: " + msg.getText());
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
450 Java EE 6 et GlassFish 3 

Chaque propriété d’activation est une paire nom/valeur interprétée par le fournisseur
de messages sous-jacent, qui l’utilise pour configurer le MDB. Le Tableau 13.5 énu-
mère quelques-unes des propriétés reconnues par OpenMQ (consultez la documen-
tation de votre fournisseur JMS si vous utilisez une autre implémentation).

Tableau 13.5 : Propriétés d’activation d’OpenMQ

Propriété Description
destinationType Type de destination, qui peut être TOPIC ou QUEUE.
destination Nom de la destination.
messageSelector Chaîne de sélecteur utilisée par le MDB.
acknowledgeMode Mode d’acquittement (AUTO_ACKNOWLEDGE par défaut).
subscriptionDurability Persistance de l’abonnement (NON_DURABLE par défaut).
subscriptionName Nom d’abonné du consommateur.

Comme dans l’API de la classe javax.jms.Session, il y a trois modes d’acquittement,


mais les MDB ne peuvent en utiliser que deux :
■■ AUTO_ACKNOWLEDGE. La session accuse automatiquement réception d’un message.

■■ DUPS_OK_ACKNOWLEDGE. Demande à la session d’acquitter la livraison des mes-


sages de façon paresseuse (ce qui provoque parfois des livraisons en double).
■■ CLIENT_ACKNOWLEDGE. Ce mode n’est pas autorisé car les MDB ne doivent pas
accéder directement à l’API JMS pour l’acquittement des messages. Cet acquit-
tement est géré automatiquement par le conteneur.

Injection des dépendances

Comme tous les autres EJB que nous avons présentés au Chapitre 6, les MDB peu-
vent utiliser l’injection des dépendances pour obtenir des références à des ressources
comme des sources de données JDBC, des EJB ou d’autres objets. L’injection est
le mécanisme par lequel le conteneur insère automatiquement une référence d’objet
pour les attributs annotés. Ces ressources doivent être disponibles dans le conteneur
ou dans le contexte de l’environnement. Le code suivant est donc autorisé dans un
MDB :
@PersistenceContext
private EntityManager em;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 451

@EJB
private InvoiceBean invoice;
@Resource(name = "jms/javaee6/ConnectionFactory")
private ConnectionFactory connectionFactory;

Le contexte MDB peut également être injecté à l’aide de l’annotation @Resource :


@Resource
private MessageDrivenContext context;

Contexte MDB
L’interface MessageDrivenContext donne accès au contexte d’exécution que fournit
le conteneur à une instance MDB. Le conteneur passe l’interface MessageDriven-
Context à cette instance qui reste associée toute la durée de vie du MDB, ce qui lui
donne la possibilité d’annuler explicitement une transaction, de connaître le prin-
cipal de l’utilisateur, etc. Cette interface hérite de javax.ejb.EJBContext sans lui
ajouter de méthode supplémentaire.
En injectant une référence à son contexte, le MDB aura accès aux méthodes du
Tableau 13.6.

Tableau 13.6 : Méthodes de l’interface MessageDrivenContext

Méthode Description
getCallerPrincipal Renvoie l’objet java.security.Principal associé à l’appel.
getRollbackOnly Teste si la transaction courante a été marquée pour annulation.
getTimerService Renvoie l’interface javax.ejb.TimerService.
getUserTransaction Renvoie l’interface javax.transaction.UserTransaction à
utiliser pour délimiter les transactions. Seuls les MDB avec des
transactions gérées par les beans (BMT) peuvent appeler cette
méthode.
isCallerInRole Teste si l’appelant a le rôle indiqué.
Lookup Permet au MDB de rechercher des variables d’environnement
qui lui sont propres dans le contexte des noms JNDI.
setRollbackOnly Permet à l’instance de marquer la transaction courante pour
annulation. Seuls les MDB avec des transactions gérées par les
beans (BMT) peuvent appeler cette méthode.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
452 Java EE 6 et GlassFish 3 

Cycle de vie et annotations de rappels


Le cycle de vie des MDB (voir Figure  13.11) est identique à celui des beans de
session sans état : soit le MDB existe et est prêt à consommer des messages, soit il
n’existe pas. Avant de se terminer, le conteneur crée d’abord une instance du MDB et
injecte au besoin les ressources nécessaires indiquées par les annotations (@Resource,
@Ejb, etc.) ou le descripteur de déploiement. Puis le conteneur appelle la méthode de
rappel @PostConstruct du bean, s’il en a une. Le MDB est alors dans l’état "prêt" et
attend de consommer tout message entrant. La méthode de rappel @PreDestroy est
appelée lorsque le MDB est supprimé du pool ou détruit.

Figure 13.11 N'existe pas


Cycle de vie d’un MDB.
@PostConstruct @PreDestroy

Prêt

onMessage()

Ce comportement est identique à celui des beans de session sans état (voir Cha-
pitre 8 pour plus de détails sur les méthodes de rappel et les intercepteurs) et vous
pouvez, comme avec les autres EJB, ajouter des intercepteurs à l’aide de l’annotation
@javax.ejb.AroundInvoke.

MDB comme consommateur

Comme on l’a expliqué dans la section "API JMS", plus haut dans ce chapitre, les
consommateurs peuvent recevoir un message de façon synchrone en bouclant en
attendant qu’il arrive ou de façon asynchrone en implémentant l’interface Message-
Listener. Par nature, les MDB sont conçus pour fonctionner comme des consom-
mateurs asynchrones : ils implémentent une interface d’écoute des messages qui est
déclenchée par le conteneur lorsqu’un message arrive.
Un MDB peut également consommer les messages de façon synchrone, mais ce n’est
pas conseillé : les consommateurs synchrones bloquent et occupent les ressources
du serveur (les EJB ne feront rien pendant qu’ils bouclent et le conteneur ne pourra
pas les libérer). Les MDB, comme les beans de session sans état, sont placés dans un
pool d’une certaine taille. Lorsque le conteneur a besoin d’une instance, il en extrait
une du pool et l’utilise. Si chaque instance entrait dans une boucle infinie, le pool

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 453

finirait par se vider et toutes les instances disponibles seraient occupées à boucler.
Le conteneur EJB pourrait alors créer plus d’instances de MDB et ainsi augmenter
la taille du pool et consommer de plus en plus de mémoire. Pour cette raison, les
beans de session et les MDB ne doivent pas être utilisés comme des consommateurs
synchrones. Le Tableau 13.7 présente les différents modes de réception des MDB et
des beans de session.

Tableau 13.7 : Comparaison des MDB et des beans de session

Beans Producteur Récepteur synchrone Récepteur asynchrone


Beans de session Oui Déconseillé Impossible
MDB Oui Déconseillé Oui

MDB comme producteur

Les MDB peuvent devenir des producteurs de message, ce qui arrive souvent
lorsqu’ils sont utilisés dans un workflow puisqu’ils reçoivent un message d’une des-
tination, le traitent et l’envoient à une autre destination. Pour ajouter cette ­possibilité,
il faut utiliser l’API JMS.
Une destination et une fabrique de connexions peuvent être injectées avec une
annotation @Resource ou par une recherche JNDI, puis des appels aux méthodes de
l’objet javax.jms.Session permettent de créer et d’envoyer un message. Le code
de BillingMDB (voir Listing 13.12) surveille un sujet (jms/javaee6/Topic), reçoit
des messages (méthode onMessage()) et envoie un nouveau message dans une file
­d’attente (jms/ javaee6/Queue).

Listing 13.12 : Un MDB consommateur et producteur de messages


@MessageDriven(mappedName = "jms/javaee6/Topic",
activationConfig = {
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "messageSelector",
propertyValue = "orderAmount < 3000")
})
public class BillingMDB implements MessageListener {

@Resource(name = "jms/javaee6/Queue")
private Destination printingQueue;

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

454 Java EE 6 et GlassFish 3 

@Resource(name = "jms/javaee6/ConnectionFactory")
private ConnectionFactory connectionFactory;
private Connection connection;

@PostConstruct
private void initConnection() {
connection = connectionFactory.createConnection();
}

@PreDestroy
private void closeConnection() {
connection.close();
}

public void onMessage(Message message) {


TextMessage msg = (TextMessage)message;
System.out.println("Message received: " + msg.getText());
sendPrintingMessage();
}

private void sendPrintingMessage() throws JMSException {


Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer =
session.createProducer(printingQueue);
TextMessage message = session.createTextMessage();
message.setText("This message has been received and sent
„ again");
producer.send(message);
session.close();
}
}

Ce MDB utilise la plupart des concepts que nous avons présentés. Il se sert d’abord
de l’annotation @MessageDriven pour définir le nom JNDI du sujet qu’il surveille
(mappedName = "jms/javaee6/Topic"). Dans cette même annotation, il définit un
ensemble de propriétés comme le mode d’acquittement et un sélecteur de message à
l’aide d’un tableau d’annotations @ActivationConfigProperty ; enfin, il implémente
MessageListener et sa méthode onMessage().

Ce MDB doit également produire un message. Il reçoit donc par injection les deux
objets administrés nécessaires : une fabrique de connexions et une destination (la
file nommée jms/ javaee6/Queue). Il peut alors créer et fermer une instance par-
tagée de javax.jms.Connection en utilisant des méthodes de rappel du cycle de
vie. La création d’une connexion étant coûteuse, placer ce code dans les méthodes
annotées par @PostConstruct et @PreDestroy garantit qu’elles n’auront lieu qu’à la
création et à la destruction du MDB.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 455

Enfin, la méthode métier qui envoie les messages (la méthode sendPrintingMes-
sage()) ressemble à ce que nous avons déjà vu : une session JMS est créée et sert
à créer un message texte et un producteur, puis le message est envoyé. Pour plus de
clarté, nous avons omis le traitement des exceptions.

Transactions

Les MDB étant des EJB (voir Chapitre 9 pour plus d’informations), ils peuvent donc
utiliser des transactions gérées par les beans (BMT) ou par le conteneur (CMT),
annuler explicitement une transaction avec la méthode MessageDrivenContext.
setRollbackOnly(), etc. Ceci dit, les MDB ont quelques spécificités qui méritent
d’être connues.
Lorsque l’on parle de transactions, on pense toujours aux bases de données rela-
tionnelles, mais d’autres ressources sont également transactionnelles : les systèmes
de messages, notamment. Deux opérations ou plus qui doivent réussir ou échouer
ensemble forment une transaction  : avec les messages, si deux messages ou plus
sont envoyés, ils doivent tous réussir ou tous échouer. En pratique, le conteneur
lancera une transaction avant l’appel de la méthode onMessage() et la validera au
retour de cette méthode (sauf si la transaction a été marquée pour annulation avec
setRollbackOnly()).
Bien que les MDB soient transactionnels, ils ne peuvent pas s’exécuter dans le
contexte de transaction du client car ils n’ont pas de client  ; personne n’appelle
explicitement leurs méthodes  : ils se contentent de surveiller une destination et
de consommer les messages. Aucun contexte n’est passé d’un client à un MDB,
notamment pas le contexte transactionnel du client pour la méthode onMessage().
Le Tableau 13.8 compare les CMT avec les beans de session et les MDB.
Tableau 13.8 : Transactions MDB comparées avec celles des beans de session

Attribut de transaction Beans de session MDB


NOT_SUPPORTED Oui Oui
REQUIRED Oui Oui
MANDATORY Oui Non
REQUIRES_NEW Oui Non
SUPPORTS Oui Non
NEVER Oui Non

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
456 Java EE 6 et GlassFish 3 

Avec les CMT, les MDB peuvent utiliser l’annotation @javax.


ejb.TransactionAttribute sur les méthodes métiers avec les deux attributs
suivants :
■■ REQUIRED (valeur par défaut). Si le MDB invoque d’autres beans d’entreprise, le
conteneur passe le contexte transactionnel avec l’appel. Le conteneur tente de
valider la transaction lorsque la méthode d’écoute des messages s’est terminée.
■■ NOT_SUPPORTED. Si le MDB invoque d’autres beans d’entreprise, le conteneur ne
passe pas de contexte transactionnel avec l’appel.

Gestion des exceptions

Dans ce chapitre, nous avons omis le traitement des exceptions dans les extraits
de code car l’API JMS permettant de les gérer est très verbeuse. Elle définit douze
exceptions différentes qui héritent toutes de javax.jms.JMSException  : tous les
appels de méthodes des connexions, des sessions, des consommateurs, des mes-
sages ou des producteurs lancent une JMSException ou l’une de ses sous-classes en
cas de problème.
Il est important de noter que JMSException est une exception contrôlée. Comme on
l’a déjà évoqué, la spécification EJB distingue deux types d’exceptions :
■■ Les exceptions d’applications. Ce sont des exceptions contrôlées qui héri-
tent d’Exception et qui ne provoquent pas l’annulation d’une transaction par le
conteneur.
■■ Les exceptions systèmes. Ce sont des exceptions non contrôlées qui héritent
de RuntimeException et qui provoquent l’annulation de la transaction par le
conteneur.
Le déclenchement d’une JMSException ne provoque donc pas l’annulation d’une
transaction. Si cette annulation est nécessaire, il faut appeler explicitement la méthode
setRollBackOnly() ou relancer une exception système (comme EJBException) :

public void onMessage(Message message) {


TextMessage msg = (TextMessage)message;
try {
System.out.println("Message received: " + msg.getText());
sendPrintingMessage();
} catch (JMSException e) {
context.setRollBackOnly();
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 457

Récapitulatif

Les concepts les plus importants des messages sont les modèles P2P et Publica-
tion-abonnement, les objets administrés (fabriques de connexion et destinations), la
façon de se connecter à un fournisseur et de produire ou consommer les messages,
les mécanismes de fiabilité utilisés par JMS et l’utilisation des composants gérés
par le conteneur (MDB) pour surveiller les destinations. Nous allons ici étudier
comment ces concepts fonctionnent ensemble à travers un exemple, le compiler et
l’assembler avec Maven et le déployer sur GlassFish.
Cet exemple utilise une classe autonome (OrderSender) qui envoie des messages
dans une file d’attente (jms/javaee6/Queue). Ces messages sont des objets représen-
tant une commande de livres et de CD (OrderDTO) qui ont plusieurs attributs, dont le
montant total de la commande. À l’autre extrémité de la file, un MDB (OrderMDB) ne
consomme que les commandes dont le montant excède 1 000 €. Le montant d’une
commande est passé en paramètre à la classe OrderSender.
Maven devant structurer le code en fonction des artéfacts finaux, la classe OrderSen-
der sera déployée dans un fichier jar, OrderMDB dans une autre et la classe OrderDTO
sera commune à ces deux fichiers.

OrderDTO
L’objet qui sera envoyé dans le message JMS est un POJO qui doit implémen-
ter l’interface Serializable. La classe OrderDTO présentée dans le Listing 13.13
donne quelques informations sur la commande, dont son montant total ; ce sont ces
objets qui seront placés dans un ObjectMessage JMS et envoyés d’OrderSender à
OrderMDB.

Listing 13.13 : Un objet OrderDTO sera passé dans un ObjectMessage JMS

public class OrderDTO implements Serializable {

private Long orderId;


private Date creationDate;
private String customerName;
private Float totalAmount;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
458 Java EE 6 et GlassFish 3 

OrderSender
La classe OrderSender du Listing  13.14 est un client autonome qui utilise l’API
JMS pour envoyer un ObjectMessage à la file jms/javaee6/Queue. Elle reçoit par
injection la fabrique de connexions ainsi que la destination ; sa méthode main() crée
une instance de la classe OrderDTO – notez que le totalAmount de la commande est
un paramètre passé à la classe (dans args[0]). La Session crée un ObjectMessage
et place la commande dans le corps du message avec message.setObject(order).
Le montant total est placé dans une propriété (message.setFloatProperty()) pour
être utilisé plus tard par un sélecteur.

Listing 13.14 : La classe OrderSender envoie un OrderDTO dans un message


public class OrderSender {

@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Queue")
private static Queue queue;

public static void main(String[] args) {

// Crée un orderDto avec le montant total en paramètre


Float totalAmount = Float.valueOf(args[0]);
OrderDTO order = new OrderDTO(1234l, new Date(),
"Serge Gainsbourg", totalAmount);

try {
// Crée les artéfacts nécessaires à la connexion à la file
Connection connection= connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);

// Envoie un message dans la file


ObjectMessage message = session.createObjectMessage();
message.setObject(order);

message.setFloatProperty("orderAmount", totalAmount);
producer.send(message);
connection.close();

} catch (Exception e) {
e.printStackTrace();
}
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 459

OrderMDB

La classe OrderMDB (voir Listing  13.15) est un MDB annoté par @MessageDriven
qui surveille la destination jms/javaee6/Queue. Il ne s’intéresse qu’aux commandes
supérieures à 1  000  € en utilisant pour cela un sélecteur (orderAmount > 1000).
Lorsqu’un message arrive, la méthode onMessage() le consomme, le transtype
en ObjectMessage et récupère son corps (msg.getObject()). Ici, on se contente
­d’afficher le message, mais d’autres traitements sont possibles.

Listing 13.15 : OrderMDB reçoit un ObjectMessage


@MessageDriven(mappedName = "jms/javaee6/Queue",
activationConfig = {
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "messageSelector",
propertyValue = "orderAmount > 1000")
})
public class OrderMDB implements MessageListener {

public void onMessage(Message message) {


try {
ObjectMessage msg = (ObjectMessage) message;
OrderDTO order = (OrderDTO) msg.getObject();
System.out.println("Order received: " + order.toString());
} catch (JMSException e) {
e.printStackTrace();
}
}
}

Compilation et assemblage avec Maven

L’émetteur et le MDB doivent être assemblés dans des fichiers jar distincts et uti-
liser des structures de répertoires et des fichiers pom.xml différents. Le fichier pom.
xml d’OrderMDB présenté dans le Listing  13.16 est quasiment identique à celui
d’OrderSender.
Les MDB utilisant des annotations du paquetage EJB (@MessageDriven), il faut
déclarer la dépendance javax.ejb. La dépendance javax.jms, quant à elle, est
nécessaire pour accéder à l’API JMS (sessions, messages, etc.). Toutes les deux ont
une portée provided car GlassFish, en tant que conteneur EJB et fournisseur JMS,
fournit ces API lors de son exécution. Vous devez également informer Maven que
vous utilisez Java SE 6 en configurant en conséquence maven-compiler-plugin.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
460 Java EE 6 et GlassFish 3 

Listing 13.16 : Fichier pom.xml pour compiler et assembler le MDB


<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter13-MDB</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.jms</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>

</project>

Pour compiler et assembler les classes, ouvrez un interpréteur de commandes dans


le répertoire qui contient le fichier pom.xml et lancez la commande suivante :
mvn package

Le répertoire target devrait maintenant contenir le fichier chapter13-MDB-1.0.jar.


Si vous l’ouvrez, vous pourrez constater qu’il contient le fichier class d’OrderMDB.
Pour OrderSender, vous obtiendrez un autre fichier jar, chapter13-Sender-1.0.jar.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 461

Création des objets administrés

JMS doit créer les objets administrés nécessaires à l’envoi et à la réception des mes-
sages. Chacun d’eux possède un nom JNDI afin que les clients puissent en obtenir
une référence (ici, nous nous servons de l’injection) :
■■ La fabrique de connexions s’appelle jms/javaee6/ConnectionFactory.
■■ La file d’attente s’appelle jms/javaee6/Queue.
Ces objets étant administrés, GlassFish doit être en cours d’exécution pour qu’ils
soient créés car OpenMQ s’exécute dans GlassFish. Si la commande asadmin est
dans votre path, lancez la commande suivante à l’invite de l’interpréteur :
asadmin create-jms-resource -–restype javax.jms.ConnectionFactory
„ jms/javaee6/ConnectionFactory
asadmin create-jms-resource --restype javax.jms.Queue
„ jms/javaee6/Queue

La console web de GlassFish permet également de mettre en place la fabrique de


connexions et la file d’attente mais, d’après mon expérience, le moyen le plus simple
et le plus rapide d’administrer GlassFish consiste à passer par asadmin. L’une de
ses commandes permet d’énumérer toutes les ressources JMS et de vérifier que les
objets administrés ont bien été créés :
asadmin list-jms-resources
jms/javaee6/Queue
jms/javaee6/ConnectionFactory

Déploiement du MDB dans GlassFish


Lorsque le MDB est assemblé dans un jar, il reste à le déployer dans GlassFish. Il
y a plusieurs moyens de le faire, notamment par la console d’administration web.
Cependant, la ligne de commande asadmin permet de le faire très simplement  :
ouvrez une fenêtre de commandes, placez-vous dans le répertoire target où se
trouve le fichier chapter13-MDB-1.0.jar, vérifiez que GlassFish s’exécute et tapez
la commande suivante :
asadmin deploy chapter13-MDB-1.0.jar

Si le déploiement a réussi, la commande qui suit devrait renvoyer le nom et le type


du jar déployé (ejb-module, ici) :
asadmin list-components
chapter13-MDB-1.0 <ejb-module>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
462 Java EE 6 et GlassFish 3 

Exécution de l’exemple

Le MDB est déployé dans GlassFish et surveille la destination jms/javaee6/Queue


en attendant qu’un message arrive  : il est temps de lancer le client OrderSender.
Bien qu’il s’agisse d’une application autonome qui s’exécute à l’extérieur de Glass-
Fish, il faut lui injecter des ressources (la fabrique de connexions et la file d’attente).
Pour l’exécuter, on peut utiliser un ACC (Application Client Container), qui est un
conteneur enveloppant un fichier jar pour lui donner accès aux ressources du serveur
d’applications. Pour lancer l’ACC, utilisez la commande appclient fournie avec
GlassFish en lui passant en paramètre le nom du fichier jar ainsi que les paramètres
de l’application (le montant de la commande). La commande suivante, par exemple,
envoie un message avec un montant de 2 000 € :
appclient -client chapter13-Sender-1.0.jar 2000

Ce montant étant supérieur à 1 000 € (la limite définie dans le sélecteur de message),
l’OrderMDB devrait le recevoir et afficher le message (ce que vous pourrez vérifier en
consultant le journal de GlassFish). Si vous passez un montant inférieur à 1 000 €,
le MDB ne recevra pas le message :
appclient -client chapter13-Sender-1.0.jar 500

Résumé

Ce chapitre a montré que les messages sont une forme asynchrone, faiblement cou-
plée, de communication entre les composants. Le MOM peut être considéré comme
un tampon placé entre les systèmes qui ont besoin de produire et de consommer les
messages à leurs propres rythmes. C’est donc une architecture différente de celle de
RPC (comme RMI) où les clients doivent connaître les méthodes du service qu’ils
utilisent.
La première section de ce chapitre s’est intéressée à l’API JMS et à son vocabulaire.
Le modèle asynchrone est une API très puissante qui peut être utilisée dans les envi-
ronnements Java SE ou Java EE. Elle repose sur les modèles P2P ou Publication-
abonnement, sur les fabriques de connexions, les destinations, les connexions, les
sessions, les messages (en-têtes, propriétés, corps) de différents types (texte, objet,
dictionnaires, flux, octets), les sélecteurs et les mécanismes de fiabilité comme
­l’acquittement ou la persistance.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 463

Java EE dispose de composants d’entreprise spéciaux pour consommer les mes-


sages  : les MDB. La seconde section du chapitre a montré comment utiliser ces
MDB comme consommateurs asynchrones et comment ils délèguent plusieurs
­services à leur conteneur (cycle de vie, intercepteurs, transactions, sécurité, concur-
rence, acquittement des messages, etc.).
Cette section a également montré comment rassembler toutes ces pièces avec Maven,
GlassFish et OpenMQ en présentant un exemple d’émetteur autonome et de MDB
récepteur.
Les chapitres suivants présenteront d’autres technologies d’interactions avec les
systèmes externes : les services web SOAP et REST.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
14
Services web SOAP

Auparavant considéré comme un terme à la mode, SOA (Service-Oriented Architec-


ture) fait aujourd’hui partie de la vie quotidienne des architectures. Bien qu’elle soit
souvent confondue avec les services web, SOA est une architecture reposant prin-
cipalement sur des applications orientées services qui peuvent être implémentées à
l’aide de services web, mais également avec d’autres technologies.
On dit que les services web sont "faiblement couplés" car leurs clients n’ont pas
besoin de connaître les détails d’implémentation (le langage utilisé pour les déve-
lopper ou les signatures des méthodes, notamment). Le consommateur peut invo-
quer un service web à l’aide d’une interface intuitive décrivant les méthodes métiers
disponibles (paramètres et valeur de retour). L’implémentation sous-jacente peut
être réalisée avec n’importe quel langage de programmation (Visual Basic, C#, C,
C++, Java, etc.). Avec un couplage faible, un consommateur et un service peuvent
quand même échanger des données : en utilisant des documents XML. Un consom-
mateur envoie une requête à un service web sous la forme d’un document XML et,
éventuellement, reçoit une réponse également en XML.
Les services web concernent également la distribution. Les logiciels distribués exis-
tent depuis longtemps mais, à la différence des systèmes distribués existants, les
services web sont conçus pour le Web : leur protocole réseau par défaut est HTTP,
un protocole sans état bien connu et robuste.
Les services web sont partout et peuvent s’exécuter sur des machines de bureau
ou intervenir dans une intégration B2B (business-to-business) pour que des opéra-
tions qui nécessitaient auparavant une intervention manuelle s’exécutent automa-
tiquement. Ils intègrent des applications utilisées par différentes organisations sur
Internet ou au sein de la même société – désignées par le terme EAI (Enterprise
Application Integration). Dans tous les cas, les services web constituent un moyen
standard de connecter différents composants logiciels.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
466 Java EE 6 et GlassFish 3 

Présentation des services web


Pour résumer, les services web sont une sorte de logique métier offerte à une appli-
cation cliente (c’est-à-dire un consommateur de service) via une interface de service.
À la différence des objets ou des EJB, les services web fournissent une interface
faiblement couplée en se servant de XML. Les standards précisent que l’interface
à laquelle on envoie un message doit définir le format du message de requête et de
réponse, ainsi que les mécanismes pour publier et pour découvrir les interfaces du
service (une base de registres).
La Figure  14.1 représente l’interaction d’un service web de façon très abstraite.
Le service peut éventuellement enregistrer son interface dans une base de registres
(UDDI) afin qu’un consommateur puisse le trouver. Une fois que le consommateur
connaît l’interface du service et le format du message, il peut envoyer une requête
et recevoir une réponse.

Figure 14.1
<<registry>>
Le consommateur UDDI
découvre le service via un
registre.
XML/HTTP
Consommateur Service web

Les services web nécessitent plusieurs technologies et protocoles pour transporter


et transformer les données d’un client vers un service de façon standard. Les plus
courants sont les suivants :
■■ UDDI (Universal Description Discovery and Integration) est une base de
registres et un mécanisme de découverte qui ressemble aux pages jaunes. Il sert
à stocker et à classer les interfaces des services web.
■■ WSDL (Web Services Description Language) définit l’interface du service web,
les données et les types des messages, les interactions et les protocoles.
■■ SOAP (Simple Object Access Protocol) est un protocole d’encodage des mes-
sages reposant sur les technologies XML. Il définit une enveloppe pour la commu-
nication des services web.
■■ Les messages sont échangés à l’aide d’un protocole de transport. Bien que HTTP
(Hypertext Transfer Protocol) soit le plus utilisé, d’autres comme SMTP ou JMS
sont également possibles.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 467

■■ XML (Extensible Markup Language) est la base sur laquelle sont construits et
définis les services web (SOAP, WSDL et UDDI).
Grâce à ces technologies standard, les services web ont un potentiel quasiment illi-
mité. Les clients peuvent appeler un service qui peut être associé à n’importe quel
programme et accommoder n’importe quel type et structure de données pour échanger
des messages via XML.

UDDI

Les programmes qui interagissent avec un autre via le Web doivent pouvoir trouver
les informations leur permettant de s’interconnecter. UDDI fournit pour cela une
approche standardisée permettant de trouver les informations sur un service web et
sur la façon de l’invoquer.
UDDI est une base de registres de services web en XML, un peu comme les profes-
sionnels peuvent enregistrer leurs services dans les pages jaunes. Cet enregistrement
inclut le type du métier, sa localisation géographique, le site web, le numéro de
téléphone, etc. Les autres métiers peuvent ensuite parcourir cette base et retrouver
les informations sur un service web spécifique, qui contiennent des métadonnées
supplémentaires décrivant son comportement et son emplacement. Ces informations
sont stockées sous la forme de document WSDL : les clients peuvent lire ce document
afin d’obtenir l’information et invoquer le service.

WSDL

La base de registres UDDI pointe vers un fichier WSDL sur Internet, qui peut être
téléchargé par les consommateurs potentiels. WSDL est un langage de définition
d’interfaces (IDL) permettant de définir les interactions entre les consommateurs et
les services (voir Figure 14.2). C’est donc le composant central d’un service web
puisqu’il décrit le type du message, le port, le protocole de communication, les opé-
rations possibles, son emplacement et ce que le client peut en attendre. Vous pouvez
considérer WSDL comme une interface Java, mais écrite en XML.

Figure 14.2 Conteneur de service web


JVM Cliente
Interface
WSDL entre le <<component>>
Protocole de transport <<component>>
consommateur Consommateur Message SOAP
Service web

et le service web. WSDL

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
468 Java EE 6 et GlassFish 3 

Pour garantir l’interopérabilité, l’interface standard du service web doit être standar-
disée, afin qu’un consommateur et un producteur puissent partager et comprendre
un message. C’est le rôle de WSDL ; SOAP, de son côté, définit la façon dont le
message sera envoyé d’un ordinateur à l’autre.

SOAP

SOAP est le protocole standard des services web. Il fournit le mécanisme de com-
munication permettant de connecter les services qui échangent des données au for-
mat XML au moyen d’un protocole réseau – HTTP le plus souvent. Comme WSDL,
SOAP repose fortement sur XML  : un message SOAP est un document XML
contenant plusieurs éléments (une enveloppe, un corps, etc.).
SOAP est conçu pour fournir un protocole indépendant et abstrait, permettant de
connecter des services distribués. Ces services connectés peuvent être construits à
l’aide de n’importe quelle combinaison de matériels et de logiciels reconnaissant un
protocole de transport donné.

Protocole de transport

Pour qu’un consommateur puisse communiquer avec un service web, il faut qu’ils
puissent s’envoyer des messages. Les services web étant essentiellement utilisés sur
le Web, ils utilisent généralement HTTP, mais d’autres protocoles, comme HTTPS
(Secure HTTP), TCP/IP, SMTP (Simple Mail Transport Protocol), FTP (File Transfer
Protocol), etc. sont également possibles.

XML

XML est utilisé par la plate-forme Java EE pour les descripteurs de déploiement,
les métadonnées, etc. Pour les services web, XML sert également de technologie
d’intégration pour résoudre les problèmes d’indépendance des données et d’intero-
pérabilité. Il est utilisé non seulement pour le format des messages mais également
pour définir les services (WSDL) ou la façon dont ils sont échangés (SOAP). Des
schémas sont associés à ces documents XML pour valider les données échangées.

Résumé de la spécification des services web

La persistance est essentiellement couverte par la spécification JPA. Pour les services
web, la situation est plus complexe car il faut prendre en compte de nombreuses

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 469

‑spécifications provenant de standards différents. En outre, les services web étant


utilisés par de nombreux autres langages de programmation, ces spécifications ne
sont pas directement liées au JCP (Java Community Process).

Bref historique des services web

Les services web sont un moyen standard permettant aux entreprises de communi-
quer sur un réseau. Leurs précurseurs s’appellent CORBA (Common Object Request
Broker Architecture), initialement utilisé par les systèmes Unix et DCOM (Distribu-
ted Component Object Model), son rival Microsoft. À un niveau plus bas, on trouve
RPC (Remote Procedure Call) et, plus près du monde Java, RMI (Remote Method
Invocation).
Avant le Web, il était difficile de mettre d’accord les acteurs majeurs du logiciel sur
un protocole de transport. Lorsque HTTP est devenu un standard, il a également
obtenu petit à petit le statut de support de communication universel. À peu près en
même temps, XML est devenu un standard officiel lorsque le W3C (World Wide Web
Consortium) a annoncé que XML 1.0 pouvait être déployé dans les applications. En
1998, ces deux ingrédients – HTTP et XML – étaient prêts à travailler ensemble.
SOAP 1.0, lancé en 1998 par Microsoft, fut finalement livré à la fin de 1999. Il
permettait alors de modéliser les références typées et les tableaux dans un schéma
XML. En 2000, IBM commença à travailler sur SOAP 1.1 et WSDL fut soumis au
W3C en 2001. UDDI a été créé en 2000 par OASIS (Organization for the Advan-
cement of Structured Information Standards) afin de permettre aux entreprises de
publier et de retrouver les services web. Avec SOAP, WSDL et UDDI, les standards
de facto pour la création de services web étaient désormais en place.
En juin 2002, Java a introduit les services web avec JAX-RPC 1.0 (Java API for
XML-based RPC 1.0) et JAX-RPC 1.1 a été ajouté à J2EE 1.4 en 2003, mais cette
spécification était très touffue et difficile à utiliser. Avec l’arrivée de Java EE  5,
une toute nouvelle spécification JAX-WS 2.0 (Java API for XML-based Web Ser-
vices 2.0) vit le jour et devint le modèle préféré pour les services web. Actuellement,
Java EE 6 est fourni avec JAX-WS 2.2.

Spécifications Java EE

La maîtrise de tous les standards des services web exige de passer un peu de temps
à lire tout un lot de spécifications provenant du W3C, du JCP et d’OASIS.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
470 Java EE 6 et GlassFish 3 

Le W3C est un consortium qui développe et gère les technologies web comme
HTML, XHTML, RDF, CSS, etc. Il nous intéresse ici car il s’occupe également des
technologies des services web : XML, XML Schema, SOAP et WSDL.
OASIS héberge plusieurs standards liés aux services web, comme UDDI, WS-Addres-
sing, WS-Security, WS-Reliability et bien d’autres.
Si l’on revient à Java, le JCP propose un ensemble de spécifications faisant partie
de Java EE  6 et Java SE  6 –  notamment JAX-WS (JSR  224), Web Services  1.2
(JSR 109), JAXB 2.2 (JSR 222), Web Services Metadata 2.0 (JSR 181) et JAXR 1.0
(JSR 93). Mises ensemble, ces spécifications sont généralement désignées informel-
lement par le terme JWS (Java Web Services).
Au premier abord, ces listes de spécifications pourraient vous faire croire que
l’écriture d’un service web en Java est un exercice compliqué, notamment lorsqu’il
s’agit d’utiliser toutes ces API. En réalité, vous n’avez pas à vous soucier de toutes
ces technologies sous-jacentes (XML, WSDL, SOAP, HTTP, etc.) car JWS s’en
­occupera pour vous.

JAX-WS 2.2
JAX-WS (JSR 224) est le nouveau nom de JAX-RPC, qui a été élagué de Java EE 6,
ce qui signifie que l’on a proposé sa suppression de Java EE 7.
JAX-WS 2.2 définit un ensemble d’API et d’annotations permettant de construire et
de consommer des services web en Java. Elle fournit les outils pour envoyer et rece-
voir des requêtes de services web via SOAP en masquant la complexité du proto-
cole. Ni le consommateur ni le service n’ont donc besoin de produire ou d’analyser
des messages SOAP car JAX-WS s’occupe du traitement de bas niveau. JAX-WS
dépend d’autres spécifications comme JAXB (Java Architecture for XML Binding).

Web Services 1.2


La JSR 109 ("Implementing Enterprise Web Services") définit le modèle de pro-
grammation et le comportement d’exécution des services web dans le conteneur
Java EE. Elle définit également l’assemblage permettant d’assurer la portabilité des
services web entre les différentes implémentations des serveurs d’applications.

JAXB 2.2
Les services web envoient des requêtes et des réponses en échangeant des messages
XML. En Java, il existe plusieurs API de bas niveau pour traiter les documents XML

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 471

et les schémas XML. La spécification JAXB fournit un ensemble d’API et d’annota-


tions pour représenter les documents XML comme des artéfacts Java, ce qui permet
aux développeurs de manipuler des objets Java représentant des documents XML.
JAXB (JSR  222) facilite la désérialisation des documents XML en objets et leur
sérialisation en documents XML. Même si cette spécification peut être utilisée pour
n’importe quel traitement XML, elle est fortement intégrée à JAX-WS.

WS-Metadata 2.0
Web Services Metadata (WS-Metadata, spécification JSR 181) fournit des annota-
tions qui facilitent la définition et le déploiement des services web. Le but principal de
la JSR 181 consiste à simplifier le développement des services web : elle fournit des
outils permettant d’associer WSDL avec les interfaces Java et vice versa au moyen
d’annotations. Ces dernières peuvent être utilisées avec des classes Java ou des EJB.

JAXR 1.0
La spécification JAXR (Java API for XML Registries) définit un ensemble standard
d’API permettant aux clients Java d’accéder à UDDI. Comme JAX-RPC, JAXR
est une spécification élaguée, dont la suppression a été proposée pour la prochaine
version de Java EE. Si cette suppression est acceptée, JAXR continuera d’évoluer,
mais en dehors de Java EE.

Implémentation de référence
Metro est non pas une spécification Java EE mais une implémentation de référence
open-source des spécifications des services web Java. Elle est formée de JAX-WS
et de JAXB et reconnaît également les API JAX-RPC historiques. Metro permet
de créer et de déployer des services web et des consommateurs sécurisés, fiables,
transactionnels et interopérables. Bien que la pile Metro soit produite par la commu-
nauté GlassFish, elle peut être utilisée en dehors de celui-ci, dans un environnement
Java EE ou Java SE.

Appel d’un service web

Malgré tous ces concepts, spécifications, standards et organisations, l’écriture et la


consommation d’un service web sont très simples. Le Listing  14.1, par exemple,
présente le code d’un service web qui valide une carte de crédit.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
472 Java EE 6 et GlassFish 3 

Listing 14.1 : Le service web CardValidator


@WebService
public class CardValidator {

public boolean validate(CreditCard creditCard) {


String lastDigit = creditCard.getNumber().substring(
„ creditCard.getNumber().length() - 1,
„ creditCard.getNumber().length());
if (Integer.parseInt(lastDigit) % 2 != 0) {
return true;
} else {
return false;
}
}
}

Comme les entités ou les EJB, un service web utilise le modèle de POJO annoté
avec une politique de configuration par exception. Si tous les choix par défaut vous
conviennent, ceci signifie qu’un service web peut se réduire à une simple classe Java
annotée par @javax.jws.WebService. Le service CardValidator n’a qu’une seule
méthode pour valider une carte de crédit :validate() prend une carte de crédit en
paramètre et renvoie true ou false selon qu’elle est valide ou non. Ici, nous suppo-
sons que les cartes de crédit ayant un numéro impair sont valides et que celles avec
un numéro pair ne le sont pas.
Un objet CreditCard (voir Listing 14.2) est échangé entre le consommateur et le
service web. Lorsque nous avons décrit l’architecture d’un service web, nous avons
vu que les données échangées devaient être des documents XML : on a donc besoin
d’une méthode pour transformer un objet Java en XML et c’est là que JAXB entre en
jeu avec ses annotations et son API. L’objet CreditCard doit simplement être annoté
par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme
en XML et réciproquement.

Listing 14.2 : La classe CreditCard avec une annotation JAXB


@XmlRootElement
public class CreditCard {

private String number;


private String expiryDate;
private Integer controlNumber;
private String type;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 473

Grâce aux annotations JAXB, il n’est pas nécessaire d’écrire de code de bas niveau
pour effectuer l’analyse XML car elle a lieu en coulisse –  le service web et le
consommateur manipulent un objet Java. Le consommateur peut être une classe
Java qui crée une instance de CreditCard puis invoque le service web, comme dans
le Listing 14.3.

Listing 14.3 : Un consommateur invoque le service web


public class Main {

public static void main(String[] args) {

CreditCard creditCard = new CreditCard();


creditCard.setNumber("12341234");
creditCard.setExpiryDate("10/10");
creditCard.setType("VISA");
creditCard.setControlNumber(1234);

CardValidator cardValidator =
new CardValidatorService().getCardValidatorPort();

cardValidator.validate(creditCard);

}
}

Le consommateur n’invoque pas directement le service CardValidator : il utilise


une classe CardValidatorService et appelle la méthode getCardValidatorPort()
pour obtenir une référence à un CardValidator. Grâce à elle, il peut ensuite appeler
la méthode validate() en lui passant la carte de crédit à tester.
Bien que ce code soit très simple à comprendre, beaucoup de choses se passent en
arrière-plan. Pour que tout ceci fonctionne, plusieurs artéfacts ont été produits : un
fichier WSDL et des stubs clients qui contiennent toutes les informations pour se
connecter à l’URL du service web, la sérialisation de l’objet CreditCard en XML,
l’appel du service web et la récupération du résultat.
La partie visible des services web en Java ne manipule pas directement XML, SOAP
ou WSDL et est donc très simple à comprendre. Cependant, certaines parties invi-
sibles sont très importantes pour l’interopérabilité.

JAXB : Java Architecture for XML Binding

Comme vous l’avez compris, XML est utilisé pour échanger les données et définir
les services web via WSDL et les enveloppes SOAP. Pourtant, dans le Listing 14.3,

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
474 Java EE 6 et GlassFish 3 

un consommateur invoquait un service web sans qu’il n’y ait trace de XML car ce
consommateur ne manipulait que des interfaces et des objets Java distants qui, à
leur tour, gèrent toute la plomberie XML et les connexions réseau. On manipule des
classes Java à un endroit de la chaîne et des documents XML à un autre – le rôle de
JAXB est de faciliter cette correspondance bidirectionnelle.
Java fournit plusieurs moyens de manipuler du XML, qui vont des API classiques
(javax.xml.stream.XmlStreamWriter et java.beans.XMLEncoder) à des modèles
de bas niveau plus complexes comme SAX (Simple API for XML), DOM (Docu-
ment Object Model) ou JAXP (Java API for XML Processing). JAXB, qui repose
sur les annotations, offre un niveau d’abstraction supérieur à celui de SAX et DOM.
JAXB définit un standard permettant de lier les représentations Java à XML et réci-
proquement. Il gère les documents XML et les définitions des schémas XML (XSD)
de façon transparente et orientée objet, qui masque la complexité du langage XSD.
À part l’annotation @XmlRootElement, le code du Listing 14.4 est celui d’une classe
Java normale. Grâce à cette annotation et à un mécanisme de sérialisation. JAXB
est capable de créer une représentation XML d’une instance de CreditCard, comme
celle qui est présentée dans le Listing 14.5.

Listing 14.4 : La classe CreditCard avec une annotation JAXB


@XmlRootElement
public class CreditCard {

private String number;


private String expiryDate;
private Integer controlNumber;
private String type;

// Constructeurs, getters, setters


}

Listing 14.5 : Document XML représentant les données d’une carte de crédit


<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <creditCard>
<controlNumber>6398</controlNumber>
<expiryDate>12/09</expiryDate>
<number>1234</number>
<type>Visa</type>
</creditCard>

La sérialisation, ici, consiste à transformer un objet en XML, mais JAXB permet éga-
lement de faire l’inverse : la désérialisation prendrait ce document XML en entrée et

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 475

créerait un objet CreditCard à partir des valeurs de ce document. JAXB peut aussi
produire automatiquement le schéma qui validerait automatiquement la structure
XML de la carte de crédit afin de garantir qu’elle est correcte et que les types des
données conviennent. Le Listing 14.6 montre la définition du schéma XML (XSD)
de la classe CreditCard.

Listing 14.6 : Schéma XML validant le document XML précédent


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="creditCard type="creditCard"/>

<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="controlNumber" type="xs:int"
minOccurs="0"/>
<xs:element name="expiryDate" type="xs:string"
minOccurs="0"/>
<xs:element name="number" type="xs:string" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Ce schéma est constitué d’éléments simples (controlNumber, expiryDate, etc.) et


d’un type complexe (creditCard). Les types complexes modélisent le contenu de
l’élément : ils déterminent l’ensemble des éléments utilisés dans un document (une
carte de crédit, ici).
Vous remarquerez que tous les marqueurs sont préfixés par xs (xs:element,
xs:string, etc.) : ce préfixe est un espace de noms et est défini dans l’élément xmlns
(XML namespace) du marqueur d’en-tête du document.
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

Les espaces de noms créent des préfixes uniques pour les éléments des documents
ou des applications qui sont utilisés ensemble. Leur but principal consiste à éviter
les conflits qui pourraient survenir lorsqu’un même nom d’élément apparaît dans
plusieurs documents (le marqueur <element>, par exemple, pourrait apparaître dans
plusieurs documents et avoir des significations différentes). Ceci pose un problème
non négligeable pour les services web car ils manipulent plusieurs documents en
même temps (l’enveloppe SOAP, le document WSDL, etc.) : les espaces de noms
sont donc très importants pour les services web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
476 Java EE 6 et GlassFish 3 

Liaison

L’API JAXB, définie dans le paquetage javax.xml.bind, fournit un ensemble d’in-


terfaces et de classes permettant de produire des documents XML et des classes
Java – en d’autres termes, elle relie les deux modèles. Le framework d’exécution
de JAXB implémente les opérations de sérialisation et de désérialisation. La séria-
lisation (ou marshalling) consiste à convertir les instances des classes annotées par
JAXB en représentations XML. Inversement, la désérialisation (unmarshalling)
consiste à convertir une représentation XML en arborescence d’objets.
Les données XML sérialisées peuvent être validées par un schéma XML – JAXB peut
produire automatiquement ce schéma à partir d’un ensemble de classes et vice versa.
La Figure 14.3 montre les interactions possibles entre une application et JAXB.

Figure 14.3 Schéma XML Classes


<xs:schema>
Architecture JAXB. <xs:element/> Compilateur de schémas
<xs:complexType>
xjc
<xs:element>
...
</xs:sequence>
</xs:complexType Générateur de schémas
</xs:schema> schemagen

respecte instancie

d'exécution
Framework
<creditCard>
<controlNumber> Désérialisation
<expiryDate> ( +validation)
<number>
<type>VISA</type>
</creditCard> Sérialisation
(+validation)
Document XML Objets

Le cœur de l’API JAXB est la classe javax.xml.bind.JAXBContext. Cette classe


abstraite gère la liaison entre les documents XML et les objets Java. Elle fournit :
■■ une classe Unmarshaller qui transforme un document XML en graphe d’objets
et, éventuellement, valide le code XML ;
■■ une classe Marshaller qui transforme un graphe d’objets en document XML.
Pour transformer un objet CreditCard en document XML, par exemple, le Lis-
ting 14.4 utilise la méthode Marshaller.marshal(). Cette méthode prend un objet

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 477

en paramètre et le sérialise sur différents supports (StringWriter pour une repré-


sentation du document XML sous forme de chaîne ou FileOutputStream pour le
stocker dans un fichier).
Le code du Listing 14.7 crée une instance de JAXBContext à l’aide de sa méthode
statique newInstance(), à laquelle il passe la classe racine qui doit être sérialisée
(CreditCard.class). Puis il appelle la méthode marshal() de l’objet Marshaller
pour produire une représentation XML (celle du Listing 14.5) de l’objet carte de cré-
dit dans un StringWriter afin de l’afficher. On pourrait utiliser la même approche
pour désérialiser un document XML en objets à l’aide de la méthode Unmarshaller.
unmarshal().

Listing 14.7 : Classe sérialisant un objet CreditCard


public class Main {

public static void main(String[] args) {

CreditCard creditCard = new CreditCard("1234", "12/09",


6398, "Visa");
StringWriter writer = new StringWriter();

JAXBContext context =
JAXBContext.newInstance(CreditCard.class);
Marshaller m = context.createMarshaller();
m.marshal(creditCard, writer);

System.out.println(writer.toString());
}
}

JAXB fournit également un compilateur de schémas (xjc) et un générateur de sché-


mas (schemaGen) – alors que la sérialisation/désérialisation manipule des objets et
des documents XML, ce compilateur et ce générateur de schémas manipulent des
classes et des schémas XML. Ces outils peuvent être utilisés en ligne de commande
(ils sont fournis avec Java SE 6) ou comme buts Maven.
Avec JAXB, les liaisons peuvent suivre deux scénarios :
■■ Partir des classes Java. Les classes existent et servent à produire un schéma
XML.
■■ Partir d’un schéma XML. Le schéma existe et les classes Java sont créées à
l’aide d’un compilateur de schémas.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
478 Java EE 6 et GlassFish 3 

Ces deux scénarios sont très importants pour les services web – un service web peut
produire ses fichiers WSDL, en fonction desquels un consommateur pourra produire
un ensemble de classes Java de façon transparente et portable.

Annotations

Par bien des aspects, JAXB ressemble à JPA. Cependant, au lieu de faire correspondre
les objets à une base de données, JAXB les lie à un document XML. Comme JPA,
JAXB définit un certain nombre d’annotations (dans le paquetage javax.xml.bind.
annotation) afin de configurer cette association et s’appuie sur la configuration par
exception pour alléger le travail du développeur. Comme le montre le Listing 14.8,
l’équivalent de @Entity des objets persistants est l’annotation @XmlRootElement de
JAXB.

Listing 14.8 : Une classe CreditCard personnalisée


@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class CreditCard {

@XmlAttribute (required = true)


private String number;
@XmlElement(name = "expiry-date", defaultValue = "01/10")
private String expiryDate; private String type;
@XmlElement(name = "control-number")
private Integer controlNumber;

// Constructeurs, getters, setters


}

L’annotation @XmlRootElement prévient JAXB que la classe CreditCard (présentée


plus haut dans le Listing  14.4) est l’élément racine du document XML. Si cette
annotation est absente, JAXB lancera une exception lorsqu’il tentera de sérialiser la
classe. Cette dernière est ensuite associée au schéma du Listing 14.6 en utilisant les
associations par défaut de JAXB (chaque attribut est traduit en un élément de même
nom).
Avec un objet Marshaller, on obtient aisément une représentation XML d’un objet
CreditCard (voir Listing 14.5). L’élément racine <creditCard> représente l’objet
CreditCard et inclut la valeur de chaque attribut.

JAXB permet de personnaliser et de contrôler cette structure XML. Un document


XML étant composé d’éléments (<element>valeur</element>) et d’attributs

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 479

(<element attribute="valeur"/>), JAXB utilise deux annotations pour les dif-


férencier  : @XmlAttribute et @XmlElement. Chacune d’elles reconnaît un certain
nombre de paramètres permettant de renommer un attribut, d’autoriser ou non les
valeurs null, d’attribuer des valeurs par défaut, etc. Le Listing 14.8 les utilise pour
transformer le numéro de carte en attribut (au lieu d’un élément) et pour renommer
la date d’expiration et le code de contrôle.
Cette classe sera liée à un schéma différent dans lequel le numéro de carte est un
<xs:attribute> obligatoire et la date d’expiration renommée a une valeur par défaut
de 01/10 (voir Listing 14.9).

Listing 14.9 : Schéma de carte de crédit avec des attributs et des valeurs par défaut
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="creditCard" type="creditCard"/>

<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="expiry-date" type="xs:string"
default="01/10" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
<xs:element name="control-number" type="xs:int"
minOccurs="0"/>
<xs:sequence>
<xs:attribute name="number" type="xs:string" use="required"/>
</xs:complexType>
</xs:schema>

Comme le montre le Listing 14.10, la représentation XML sera également modifiée.

Listing 14.10 : Document XML représentant un objet CreditCard personnalisé


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<creditCard number="1234">
<expiry-date>12/09</expiry-date>
<type>Visa</type>
<control-number>6398</control-number>
</creditCard>

Le Tableau 14.1 énumère les principales annotations de JAXB ; la plupart avec les


éléments auxquels elles s’appliquent ; certaines peuvent annoter des attributs (getters),
d’autres, des classes et certaines peuvent s’appliquer à tout un paquetage (@Xml–
Schema, par exemple).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
480 Java EE 6 et GlassFish 3 

Tableau 14.1 : Annotations JAXB

Annotation Description
@XmlAccessorType Contrôle si les attributs ou les getters doivent être mis en
correspondance (FIELD, NONE, PROPERTY, PUBLIC_MEMBER).
@XmlAttribute Traduit un attribut ou un getter en attribut XML de type simple
(String, Boolean, Integer, etc.).
@XmlElement Traduit un attribut ou un getter non statique et non transitoire en
élément XML.
@XmlElements Agit comme un conteneur de plusieurs annotations @XmlElement.
@XmlEnum Traduit une énumération en XML.
@XmlEnumValue Identifie une constante d’énumération.
@XmlID Identifie le champ clé d’un élément XML (de type String) qui pourra
servir à désigner un élément avec l’annotation @XmlIDREF (concepts
d’ID et d’IDREF des schémas XML).
@XmlIDREF Traduit une propriété en IDREF XML dans le schéma.
@XmlList Traduit une propriété en liste.
@XmlMimeType Identifie une représentation textuelle du type MIME d’une propriété.
@XmlNs Identifie un espace de noms XML.
@XmlRootElement Annotation exigée pour toute classe liée à l’élément racine du
document XML.
@XmlSchema Traduit un nom de paquetage en espace de noms XML.
@XmlTransient Demande à JAXB de ne pas lier un attribut (analogue au mot-clé
transient de Java ou à l’annotation @Transient de JPA).

@XmlType Marque une classe comme étant un type complexe dans le schéma
XML.
@XmlValue Permet de traduire une classe en un contenu ou un type de schéma
simple.

Grâce à ces annotations, vous pouvez traduire des objets en un schéma XML spé-
cifique, ce qui est parfois nécessaire avec les services web existants. JPA définit
un ensemble d’annotations permettant d’adapter chaque partie de la traduction
(colonnes, tables, clés étrangères, etc.) pour pouvoir associer des entités à une base
de données déjà établie, et il en va de même avec les services web qui sont décrits
par un fichier WSDL écrit en XML : s’il s’agit d’un service existant, son WSDL ne

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 481

peut pas être modifié – les annotations JAXB permettent alors de le faire corres-
pondre à des objets.

INFO

Dans cette section, nous avons plusieurs fois mentionné JPA car les technologies JPA et JAXB
reposent fortement sur les annotations et permettent de traduire des objets vers différents
supports (bases de données ou XML). En termes d’architecture, les entités ne devraient servir
qu’à associer des données à une base de données et les classes JAXB, à traduire des données
en XML. Parfois, cependant, le même objet a besoin d’avoir une représentation sous forme
de base de données et sous forme XML : il est alors techniquement possible d’annoter la
même classe avec @Entity et @XmlRootElement, même si cela n’est pas vraiment conseillé.

La partie immergée de l’iceberg


Même si l’on ne manipule pas explicitement des documents SOAP et WSDL lorsque
l’on développe avec JAW-WS, il est important de comprendre un peu leur structure.
Les services web fournissent deux ingrédients essentiels : un langage de définition
d’interface (WSDL) et un standard de messages (SOAP). Lorsque l’on développe
des services web, on peut se servir de WSDL pour définir leur interface, c’est-à-dire
définir les paramètres d’entrée et de sortie du service en termes de schéma XML.
Les messages SOAP, quant à eux, permettent de transporter les paramètres d’entrée
et de sortie précisés par le document WSDL.
Lorsqu’un consommateur voulant invoquer le service web CardValidator (voir
Figure 14.4) récupère son WSDL pour connaître son interface, il demande à valider
la carte (le message validate de SOAP) et reçoit une réponse (le message validate­
Response de SOAP).

Figure 14.4 Conteneur de service web


JVM Cliente
Consommateur SOAP / HTTP
WSDL
invoquant un <<component>> <<component>>
service web. Consommateur validate Service web

validateResponse

WSDL
Les documents WSDL sont hébergés dans le conteneur de service web et utilisent
XML pour décrire ce que fait un service, comment appeler ses opérations et où le

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
482 Java EE 6 et GlassFish 3 

trouver. Ce document XML respecte une structure bien établie, formée de plusieurs
parties (voir Listing 14.11). Le service web CardValidator, par exemple, utilise les
éléments suivants :
■■ <definitions> est l’élément racine de WSDL. Il précise les déclarations des
espaces de noms visibles dans tout le document.
■■ <types> définit les types de données utilisés par les messages. Ici, c’est la défi-
nition du schéma XML (CardValidatorService?xsd=1) qui décrit le type des
paramètres de la requête adressée au service (un objet CreditCard) et le type de
la réponse (un Boolean).
■■ <message> définit le format des données échangées entre un consommateur du
service web et le service lui-même. Ici, il s’agit de la requête (la méthode vali-
date) et de la réponse (validateResponse).

■■ <portType> précise les opérations du service (la méthode validate).


■■ <binding> décrit le protocole concret (SOAP, ici) et les formats des données
pour les opérations et les messages définis pour un type de port particulier.
■■ <service> contient une collection d’éléments <port> associés, chacun, à une
extrémité (une adresse réseau ou une URL).
Le fichier WSDL du Listing 14.11 vous aidera à mieux comprendre les informations
décrites dans le service web CardValidator.

Listing 14.11 : Fichier WSDL pour le service web CardValidator


<definitions targetNamespace="http://chapter14.javaee6.org/"
name="CardValidatorService">
<types>
<xsd:schema>
<xsd:import namespace="http://chapter14.javaee6.org/"
schemaLocation=
„ "http://localhost:8080/chapter14/CardValidatorService?xsd=1"/>
</xsd:schema>
</types>
<message name="validate">
<part name="parameters" element="tns:validate"/>
</message>
<message name="validateResponse">
<part name="parameters" element="tns:validateResponse"/>
</message>
<portType name="CardValidator">
<operation name="validate">
<input message="tns:validate"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 483

<output message="tns:validateResponse"/>
</operation>
</portType>
<binding name="CardValidatorPortBinding"
type="tns:CardValidator">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="validate">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="CardValidatorService">
<port name="CardValidatorPort"
binding="tns:CardValidatorPortBinding">
<soap:address location =
„ "http://localhost:8080/chapter14/CardValidatorService"/>
</port>
</service>
</definitions>

L’élément <xsd:import namespace> fait référence à un schéma XML qui doit être
disponible sur le réseau pour les clients du WSDL. Le Listing 14.12 montre que ce
schéma définit les types utilisés par le service web (structure d’un objet CreditCard
avec un numéro, une date d’expiration, etc.).

Listing 14.12 : Schéma importé par le fichier WSDL


<xs:schema version="1.0"
targetNamespace="http://chapter14.javaee6.org/">
<xs:element name="creditCard" type="tns:creditCard"/>
<xs:element name="validate" type="tns:validate"/>
<xs:element name="validateResponse" type="tns:validateResponse"/>
<xs:complexType name="validate">
<xs:sequence>
<xs:element name="arg0" type="tns:creditCard" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="controlNumber" type="xs:int" minOccurs="0"/>
<xs:element name="expiryDate" type="xs:string" minOccurs="0"/>
<xs:element name="number" type="xs:string" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
484 Java EE 6 et GlassFish 3 

<xs:complexType name="validateResponse">
<xs:sequence>
<xs:element name="return" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Ce fichier WSDL et ce schéma sont généralement produits par des outils fournis
avec JAX-WS qui transforment les métadonnées en XML.

SOAP

Alors que WSDL décrit une interface abstraite du service web, SOAP fournit une
implémentation concrète en définissant la structure XML des messages échangés.
Dans le cadre du Web, SOAP est une structure de messages pouvant être délivrés par
HTTP (ou d’autres protocoles de communication) – la liaison HTTP de SOAP contient
quelques en-têtes d’extension HTTP standard. Cette structure de message est décrite
en XML. Au lieu d’utiliser HTTP pour demander une page web à partir d’un navi-
gateur, SOAP envoie un message XML via une requête HTTP et reçoit une réponse
HTTP. Un message SOAP est un document XML contenant les éléments suivants :
■■ <Envelope>. Définit le message et l’espace de noms utilisés dans le document.
Il s’agit de l’élément racine obligatoire.
■■ <Header>. Contient les attributs facultatifs du message ou l’infrastructure spéci-
fique à l’application, comme les informations sur la sécurité ou le routage réseau.
■■ <Body>. Contient le message échangé entre les applications.
■■ <Fault>. Fournit des informations sur les erreurs qui surviennent au cours du
traitement du message. Cet élément est facultatif.
Seuls l’enveloppe et le corps sont obligatoires. Dans notre exemple, une application
cliente appelle le service web pour valider une carte de crédit (une enveloppe SOAP
pour la requête) et reçoit un booléen indiquant si cette carte est valide ou non (une
autre enveloppe SOAP pour la réponse). Les Listings 14.13 et 14.14 montrent les
structures de ces deux messages SOAP.

Listing 14.13 : Enveloppe SOAP de la requête


<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cc="http://chapter14.javaee6.org/">
<soap:Header/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 485

<soap:Body>
<cc:validate>
<arg0>
<controlNumber>1234</controlNumber>
<expiryDate>10/10</expiryDate>
<number>9999</number>
<type>VISA</type>
</arg0>
</cc:validate>
</soap:Body>
</soap:Envelope>

Listing 14.14 : Enveloppe SOAP de la réponse


<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:validateResponse
xmlns:ns2="http://chapter14.javaee6.org/">
<return>true</return>
</ns2:validateResponse>
</soap:Body>
</soap:Envelope>

JAX-WS : Java API for XML-Based Web Services

Nous venons de voir un document WSDL simple, ainsi qu’une requête et une réponse
SOAP. Lorsque les services web proposent plusieurs opérations avec des paramètres
complexes, ces documents XML deviennent un véritable cauchemar pour le déve-
loppeur ; heureusement, JAX-WS leur facilite la vie en masquant cette complexité.
Ceci dit, vous devrez hélas parfois vous plonger dans la structure WSDL.
Le document WSDL étant le contrat qui lie le consommateur et le service, il peut ser-
vir à écrire le code Java pour ces deux parties : c’est ce que l’on appelle la méthode
descendante, également méthode par contrat car elle part du contrat (le WSDL) en
définissant les opérations, les messages, etc. Lorsque le consommateur et le fournis-
seur sont d’accord sur le contrat, on peut implémenter les classes Java en fonction
de celui-ci. Metro fournit quelques outils permettant de produire des classes à partir
d’un document WSDL.
Dans l’autre approche, la méthode ascendante, la classe de l’implémentation existe
déjà et il suffit de créer le WSDL. Là encore, Metro dispose d’outils pour effectuer
cette opération. Dans les deux cas, le code doit parfois être ajusté pour correspondre
au WSDL ou vice versa, et c’est là que JAX-WS vient à votre secours : grâce à un

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
486 Java EE 6 et GlassFish 3 

modèle de développement simple et à quelques annotations, vous pouvez ajuster


l’association Java-WSDL. L’approche ascendante peut produire des applications
très inefficaces car les méthodes et les classes Java n’ont aucune idée de la granula-
rité idéale des messages qui circulent sur le réseau. Si la latence est élevée et/ou la
bande passante, faible, il est préférable d’utiliser moins de messages, qui seront plus
gros : seule l’approche par contrat permet de faire ce choix.
Ce chapitre a présenté les concepts et les spécifications des services web en géné-
ral puis a introduit JAXB et a effleuré la surface des documents WSDL et SOAP.
Cependant, les services web suivent le paradigme de facilité de développement de
Java EE 6 et n’obligent pas à écrire le moindre code WSDL ou SOAP. Un service
web est simplement un POJO annoté qui doit être déployé dans un conteneur de
service web. Toutefois, ce modèle de programmation mérite notre attention.

Le modèle JAX-WS

Comme la plupart des composants de Java EE 6, les services web s’appuient sur le
paradigme de la configuration par exception. Seule l’annotation @WebService est
nécessaire pour transformer un POJO en service web, mais cette classe doit respecter
les règles suivantes :
■■ Elle doit être annotée par @javax.jws.WebService ou son équivalent XML dans
un descripteur de déploiement.
■■ Pour transformer un service web en entité EJB, la classe doit être annotée par
@javax.ejb.Stateless (voir Chapitre 7).

■■ Elle doit être publique et ne doit pas être finale ni abstraite.


■■ Elle doit posséder un constructeur par défaut public.
■■ Elle ne doit pas définir la méthode finalize().
■■ Un service doit être un objet sans état et ne pas mémoriser l’état spécifique d’un
client entre les appels de méthode.
La spécification WS-Metadata (JSR 181) énonce que, tant qu’il respecte ces règles,
un POJO peut être utilisé pour implémenter un service web déployé dans le conte-
neur de servlet – il est alors souvent désigné sous le terme d’extrémité de servlet
(Servlet end point). Un bean de session sans état peut également servir à implé-
menter un service web qui sera déployé dans un conteneur EJB – il est alors appelé
extrémité EJB (EJB end point).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 487

Extrémités d’un service web


JAX-WS 2.2 permet de transformer des classes Java classiques et des EJB sans état
en services web. Les codes d’un service web POJO (voir Listing 14.1) et d’un ser-
vice web EJB (voir Listing 14.18) sont peu différents : la seule différence est que
le second a une annotation @Stateless supplémentaire. En outre, l’assemblage est
différent : un service web POJO est assemblé dans un module web (un fichier war)
et est appelé extrémité de servlet, tandis qu’un service web EJB est assemblé dans
un fichier jar et est appelé extrémité EJB. Le premier est déployé dans un conteneur
de servlet, le second, dans un conteneur EJB.
Ces deux extrémités ont un comportement quasiment identique, mais les extrémi-
tés EJB ont quelques avantages supplémentaires car, le service web étant en ce cas
également un EJB, il bénéficie automatiquement des transactions et de la sécurité
qui sont gérées par le conteneur. En outre, il peut utiliser des intercepteurs, ce qui
n’est pas possible avec les extrémités de servlets. Le code métier peut être exposé
simultanément comme un service web et comme un EJB, ce qui signifie qu’il peut
être exposé à la fois via SOAP et RMI en ajoutant une interface distante.

Annotations
Au niveau du service, les systèmes sont définis en termes de messages XML, d’opé-
rations WSDL et de messages SOAP. Cependant, au niveau Java, les applications
sont décrites en termes d’objets, d’interfaces et de méthodes. Il est donc nécessaire
d’effectuer une traduction des objets Java vers les opérations WSDL. L’environne-
ment d’exécution de JAXB utilise les annotations pour savoir comment sérialiser/
désérialiser une classe vers/à partir de XML. Généralement, ces annotations sont
cachées au développeur du service web. De même, JWS se sert d’annotations pour
déterminer comment sérialiser un appel de méthode vers un message de requête
SOAP et comment désérialiser une réponse SOAP vers une instance du type du
résultat de la méthode.
La spécification WS-Metadata (JSR 181) définit deux sortes d’annotations :
■■ Les annotations de traduction WSDL. Elles appartiennent au paquetage
javax.jws et permettent de modifier les associations entre WSDL et Java. Les
annotations @WebMethod, @WebResult, @WebParam et @OneWay sont utilisées sur le
service web pour adapter la signature des méthodes exposées.
■■ Les annotations de traduction SOAP. Elles appartiennent au paquetage javax.
jws.soap et permettent d’adapter la liaison SOAP (@SOAPBinding et @SOAP­
MessageHandler).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
488 Java EE 6 et GlassFish 3 

Comme toutes les autres spécifications de Java EE 6, ces annotations peuvent être
redéfinies par un descripteur de déploiement XML facultatif (webservices.xml).
Les sections qui suivent décrivent plus précisément chacune d’elles.

@WebService
L’annotation @javax.jws.WebService marque une classe ou une interface Java
comme étant un service web. Lorsqu’elle est utilisée directement sur la classe
(comme dans les exemples que nous avons présentés jusqu’à maintenant), le pro-
cesseur d’annotations du conteneur produira l’interface – les deux extraits de code
qui suivent sont donc équivalents. Voici l’annotation sur une classe :
@WebService
public class CardValidator {

Voici l’annotation sur une interface implémentée par une classe :


@WebService
public interface CCValidator {

public class CardValidator implements CCValidator {

Cette annotation possède un certain nombre d’attributs (voir Listing  14.15) per-
mettant de personnaliser le nom du service web dans le fichier WSDL (éléments
<wsdl:portType> ou <wsdl:service>) et son espace de noms, ainsi que l’empla­
cement du fichier WSDL lui-même (attribut wsdlLocation).

Listing 14.15 : API de @WebService


@Retention(RUNTIME)
@Target(TYPE)
public @interface WebService {
String name() default "";
String targetNamespace() default "";
String serviceName() default "";
String portName() default "";
String wsdlLocation() default "";
String endpointInterface() default "";
}

Lorsque l’on utilise @WebService, toutes les méthodes publiques du service web qui
n’utilisent pas l’annotation @WebMethod sont exposées.

@WebMethod
Par défaut, toutes les méthodes publiques d’un service web sont exposées dans le
WSDL et utilisent toutes les règles d’association par défaut. L’annotation @javax.jws.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 489

WebMethod permet de personnaliser certaines de ces associations de méthodes. Son


API est assez simple et permet de renommer une méthode ou de l’exclure du WSDL.
Le Listing 14.16, par exemple, montre comment le service web CardValidator peut
renommer sa première méthode en ValidateCreditCard et exclure la seconde.

Listing 14.16 : Renommage d’une méthode et exclusion de l’autre


@WebService
public class CardValidator {

@WebMethod(operationName = "ValidateCreditCard")
public boolean validate(CreditCard creditCard) {
// Logique métier
}

@WebMethod(exclude = true)
public void validate(String ccNumber) {
// Logique métier
}
}

@WebResult
L’annotation @javax.jws.WebResult fonctionne en relation avec @WebMethod pour
contrôler le nom de la valeur renvoyée par le message dans le WSDL. Dans le Lis-
ting 14.17, le résultat de la méthode validate() est renommé en IsValid.

Listing 14.17 : Renommage du résultat de la méthode


@WebService
public class CardValidator {

@WebMethod
@WebResult(name = "IsValid")
public boolean validate(CreditCard creditCard) {
// Logique métier
}
}

Cette annotation possède d’autres éléments permettant, par exemple, de person­


naliser l’espace de noms XML pour la valeur renvoyée, un peu comme l’annotation
@WebParam.

@WebParam
@javax.jws.WebParam, dont l’API est présentée dans le Listing  14.18, ressemble
à @WebResult car elle personnalise les paramètres des méthodes du service web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
490 Java EE 6 et GlassFish 3 

Cette API permet de modifier le nom du paramètre dans le WSDL (voir Listing 14.19),


l’espace de noms et le mode de passage des paramètres – IN, OUT ou INOUT.

Listing 14.18 : API de @WebParam


@Retention(RUNTIME) @Target(PARAMETER)
public @interface WebParam {
String name() default "";
public enum Mode {IN, OUT, INOUT};
String targetNamespace() default "";
boolean header() default false;
String partName() default "";
}

Listing 14.19 : Renommage du paramètre de la méthode


@WebService
public class CardValidator {

@WebMethod
public boolean validate(@WebParam(name = "Credit-Card")
„ // Logique métier
}
}

@OneWay
L’annotation @OneWay peut être utilisée avec les méthodes qui ne renvoient aucun
résultat, comme celles de type void. Cette annotation n’a aucun élément et peut être
considérée comme une interface de marquage informant le conteneur que l’appel
de cette méthode peut être optimisé (en utilisant un appel asynchrone, par exemple)
puisqu’il n’y a pas de valeur de retour.

Récapitulatif des annotations


Pour mieux comprendre ces annotations, nous allons les utiliser avec le service web
CardValidator (voir Listing  14.20) afin de montrer leur impact sur le document
WSDL (voir Listing 14.21) et le schéma associé (voir Listing 14.22). Nous avons
souligné les différences du Listing  14.21 par rapport au WSDL original du Lis-
ting 14.14 et celles du Listing 14.22 par rapport au schéma original du Listing 14.15.

Listing 14.20 : Le service web CardValidator avec des annotations


@WebService(name = "CreditCardValidator",
portName = "ValidatorPort")
public class CardValidator {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 491

@WebMethod(operationName = "ValidateCreditCard")
@WebResult(name = "IsValid")
public boolean validate(@WebParam(name = "CreditCard")
„ CreditCard creditCard) {
String lastDigit = creditCard.getNumber().substring(
„ creditCard.getNumber().length() - 1,
„ creditCard.getNumber().length());
if (Integer.parseInt(lastDigit) % 2 != 0) {
return true;
} else {
return false;
}
}

@WebMethod(exclude = true)
public void validate(String ccNumber) {
// Logique métier
}
}

L’annotation @WebService renomme les éléments <portType name> et <port name>


du WSDL. Toutes les autres annotations ont un impact sur la méthode, qui est repré-
sentée par l’élément <message> du WSDL.

Listing 14.21 : Le document WSDL après personnalisation


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions targetNamespace="http://chapter14.javaee6.org/"
name="CardValidatorService"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://chapter14.javaee6.org/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<xsd:schema>
<xsd:import namespace="http://chapter14.javaee6.org/"
schemaLocation="CardValidatorService_schema1.xsd"/>
</xsd:schema>
</types>
<message name="ValidateCreditCard">
<part name="parameters" element="tns:ValidateCreditCard"/>
</message>
<message name="ValidateCreditCardResponse">
<part name="parameters"
element="tns:ValidateCreditCardResponse"/>
</message>
<portType name="CreditCardValidator">
<operation name="ValidateCreditCard">
<input message="tns:ValidateCreditCard"/>
<output message="tns:ValidateCreditCardResponse"/>
</operation>
</portType>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
492 Java EE 6 et GlassFish 3 

<binding name="ValidatorPortBinding"
type="tns:CreditCardValidator">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="ValidateCreditCard">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="CardValidatorService">
<port name="ValidatorPort" binding="tns:ValidatorPortBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>

Le schéma est également personnalisé car deux éléments, la requête et la réponse,


sont définis dans un élément <xs:complexType>. La valeur renvoyée par la méthode
s’appelle IsValid et est de type Boolean.

Listing 14.22 : Le schéma XML après personnalisation


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://chapter14.javaee6.org/"
xmlns:tns="http://chapter14.javaee6.org/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="ValidateCreditCard"
type="tns:ValidateCreditCard"/>

<xs:element name="ValidateCreditCardResponse"
type="tns:ValidateCreditCardResponse"/>

<xs:element name="creditCard" type="tns:creditCard"/>

<xs:complexType name="ValidateCreditCard">
<xs:sequence>
<xs:element name="CreditCard"
type="tns:creditCard" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="controlNumber"
type="xs:int" minOccurs="0"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 493

<xs:element name="expiryDate"
type="xs:string" minOccurs="0"/>
<xs:element name="number" type="xs:string" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="ValidateCreditCardResponse">
<xs:sequence>
<xs:element name="IsValid" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Cycle de vie et méthodes de rappel


Comme vous pouvez le constater avec la Figure 14.5, le cycle de vie des services
web ressemble à celui des beans sans état et des MDB. Comme avec tous les compo-
sants qui ne mémorisent pas l’état, soit ils n’existent pas, soit ils sont prêts à traiter
une requête. Ce cycle de vie est géré par le conteneur.

Figure 14.5 N'existe pas


Cycle de vie
des services web. @PostConstruct @PreDestroy

Prêt

Appel de méthode

Comme elles s’exécutent dans un conteneur, les extrémités de servlet et EJB auto-
risent l’injection des dépendances et les méthodes de rappel du cycle de vie : si elle
existe, le conteneur appellera la méthode de rappel @PostConstruct lorsqu’il crée
une instance d’un service web et la méthode de rappel @PreDestroy lorsqu’il la
détruit.
Une différence entre les extrémités de servlet et les extrémités EJB est que ces
dernières peuvent utiliser des intercepteurs, qui sont l’implémentation Java EE du
concept de programmation orientée aspects (POA) décrit au Chapitre 8.

Contexte d’un service web


Un service a un contexte d’environnement auquel il peut accéder en injectant
une référence à javax.xml.ws.WebServiceContext au moyen d’une annotation @

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
494 Java EE 6 et GlassFish 3 

Resource. Dans ce contexte, le service peut obtenir des informations d’exécution


comme la classe qui implémente l’extrémité, le contexte du message et des informa-
tions concernant la sécurité relative à la requête qui est traitée.
@Resource
private WebServiceContext context;

Le Tableau  14.2 énumère les méthodes définies dans l’interface javax.xml.


ws.WebServiceContext.

Tableau 14.2 : Méthodes de l’interface WebServiceContext

Méthode Description
getMessageContext Renvoie le MessageContext pour la requête en cours de
traitement au moment de l’appel. Permet d’accéder aux en-têtes
du message SOAP, etc.

getUserPrincipal Renvoie le principal qui identifie l’émetteur de la requête en


cours de traitement.
isUserInRole Teste si l’auteur authentifié appartient au rôle logique indiqué.
getEndpointReference Renvoie l’EndpointReference associé à cette extrémité.

Appel d’un service web

Vous pouvez invoquer un service web en utilisant le fichier WSDL et certains outils
de génération de classes Java relais (stubs). L’appel d’un service web ressemble à
l’appel d’un objet distribué avec RMI : comme ce dernier, JAX-WS permet au pro-
grammeur d’utiliser un appel de méthode local pour invoquer un service se trouvant
sur un autre hôte. La différence est qu’un service web sur l’hôte distant peut être
écrit dans un autre langage de programmation (notez cependant que vous pouvez
aussi appeler du code non Java avec RMI-IIOP). Le fichier WSDL établit le contrat
entre le consommateur et le service, et Metro fournit un outil de conversion WSDL
vers Java (wsimport) qui produit des classes et des interfaces Java à partir du code
WSDL – ces interfaces sont appelées SEI (service endpoint interfaces) car ce sont
des représentations Java d’une extrémité de service web (servlet ou EJB). Cette SEI
agit comme un proxy qui route l’appel Java local vers le service web distant via
HTTP ou d’autres protocoles de transport.
Lorsqu’une méthode de ce proxy est appelée (voir Figure 14.6), elle convertit ses
paramètres en message SOAP (la requête) et l’envoie à l’extrémité du web service.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 495

Pour obtenir le résultat, la réponse SOAP est reconvertie en une instance du type ren-
voyé. Pour l’utiliser, il n’est pas nécessaire de connaître le fonctionnement interne
du proxy ni d’étudier son code. Avant de compiler le consommateur client, il faut
produire la SEI afin d’obtenir la classe proxy pour l’appeler dans le code.

JVM du client Conteneur de service web

<<component>> <<component>>
Consommateur Service web
WSDL
Java SOAP / HTTP Java
interface interface

<<component>> <<component>>
validate
Proxy consommateur Extrémité de service web
validateResponse

Figure 14.6
Un consommateur appelle un service web via un proxy.

Le consommateur peut obtenir une instance du proxy par injection ou en la créant


par programme. On injecte un client de service web à l’aide de l’annotation @javax.
xml.ws.WebServiceRef, qui ressemble aux annotations @Resource ou @EJB pré-
sentées aux chapitres précédents. Lorsqu’elle est appliquée à un attribut (ou à une
méthode getter), le conteneur injecte une instance du service web lorsque l’appli-
cation est initialisée. Le code est de la forme suivante :
@WebServiceRef
private CardValidatorService cardValidatorService;
// ...
CardValidator cardValidator =
cardValidatorService.getCardValidatorPort(); cardValidator.
validate(creditCard);

La classe CardValidatorService est la SEI, pas le service web lui-même. Vous


devez ensuite obtenir la classe proxy CardValidator pour invoquer localement les
méthodes métiers. On appelle localement la méthode validate() du proxy, qui, à
son tour, invoquera le service web distant, créera la requête SOAP, sérialisera les
messages, etc.
Pour que cette injection fonctionne, ce code doit s’exécuter dans un conteneur (de
servlet, d’EJB ou de client d’application)  : dans le cas contraire, on ne peut pas
utiliser l’annotation @WebServiceRef et il faut alors passer par la programmation.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
496 Java EE 6 et GlassFish 3 

Le  lasses produites par l’outil wsimport peuvent être directement utilisées de la
façon suivante :
CardValidatorService cardValidatorService =
new CardValidatorService();
CardValidator cardValidator =
cardValidatorService.getCardValidatorPort(); cardValidato
r.validate(creditCard);

Ce code crée une instance de CardValidatorService à l’aide du mot-clé new ; le


reste est identique.

INFO

Les outils wsimport et wsgen sont fournis avec le JDK  1.6. wsimport prend en entrée un
fichier WSDL et produit les artéfacts JAX-WS – une SEI notamment. wsgen lit une classe
extrémité de service web et produit le WSDL. Vous pouvez accéder directement à ces outils
ou passer par l’interface en ligne de commande de GlassFish, par une tâche Ant ou par une
extension Maven.

Récapitulatif

Nous allons rassembler tous ces concepts en écrivant un service web et un consom-
mateur, puis en les déployant et en les testant avec GlassFish. Nous utiliserons pour
cela les annotations JAXB et JAX-WS et produirons une SEI avec le but wsimport
de Maven. Plusieurs étapes sont nécessaires pour écrire un service web : nous les
présenterons en même temps que le service CardValidator.
Le service web CardValidator teste si une carte de crédit est valide. Il n’a qu’une
seule méthode qui prend un objet CreditCard en paramètre, applique un algorithme
et renvoie true si cette carte est valide, false sinon. Une fois que ce service a été
déployé dans GlassFish, on utilise wsimport pour produire les artéfacts nécessaires
au consommateur. Celui-ci peut ensuite invoquer le service web pour valider des
cartes de crédit.
Nous utiliserons deux projets Maven : l’un pour assembler le service web dans un
fichier war (chapter14- service-1.0.war), l’autre pour assembler le consommateur
dans un fichier jar (chapter14-consumer- 1.0.jar).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 497

La classe CreditCard

La classe CreditCard présentée dans le Listing 14.23 est le POJO passé en ­paramètre


à la méthode validate() du service web. Les services web échangeant des messages
XML et non des objets Java, cette classe est annotée par l’annotation @XmlRoot­
Element de JAXB afin d’être sérialisée en XML pour être envoyée dans une requête
SOAP. Un objet CreditCard a quelques attributs de base comme le numéro de la
carte, la date d’expiration (au format MM/AA), le type de la carte (Visa, Master
Card, American Express, etc.) et un code de contrôle.

Listing 14.23 : La classe CreditCard avec une annotation JAXB


@XmlRootElement
public class CreditCard {

private String number;


private String expiryDate;
private Integer controlNumber;
private String type;

// Constructeurs, getters, setters


}

Le service web CardValidator

CardValidator (voir Listing 14.24) est également un POJO, mais annoté par l’an-
notation @WebService de JAX-WS. Ce n’est pas une extrémité EJB car elle n’a
pas d’annotation @Stateless : c’est donc une extrémité servlet qui sera déployée
dans un fichier war (chapter14-service-1.0.war). Elle possède une méthode vali-
date() qui prend un objet CreditCard en paramètre et dont l’algorithme teste si la
carte est valide en fonction de son numéro : les numéros impairs sont considérés
comme valides, les pairs, comme non valides. Cette méthode renvoie un booléen.

Listing 14.24 : Le service web CardValidator


@WebService
public class CardValidator {

public boolean validate(CreditCard creditCard) {


String lastDigit = creditCard.getNumber().substring(
„ creditCard.getNumber().length() - 1,
„ creditCard.getNumber().length());

if (Integer.parseInt(lastDigit) % 2 != 0) {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
498 Java EE 6 et GlassFish 3 

return true;
} else {
return false;
}
}

Pour rester simple, nous n’utilisons pas d’autres associations Java vers WSDL : il
n’y a donc pas d’annotation @WebMethod, @WebResult ou @WebParam, ce qui nous
permet de constater la simplicité d’écriture d’un service web avec les règles d’asso-
ciation par défaut.

Compilation et assemblage avec Maven

Le service web CardValidator doit être compilé et assemblé dans un fichier war
(<packaging>war</packaging>). Le fichier pom.xml du Listing  14.25 déclare la
dépendance jaxws-rt pour utiliser la version 2.2 de JAX-WS. Java SE 6 est fourni
avec une implémentation de JAXB et JAX-WS : préciser la version 1.6 dans l’élé-
ment maven-compiler-plugin de Maven devrait suffire à indiquer explicitement que
vous voulez utiliser Java SE 6 (<source>1.6</source>) mais, si vous voulez contrô-
ler les versions des dépendances et que vous exigez absolument la version 2.2 de
JAX-WS, il est préférable d’ajouter explicitement la dépendance jaxws-rt.

Listing 14.25 : Fichier pom.xml pour compiler et assembler le service web


<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter14-service</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 499

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Les descripteurs de déploiement étant facultatifs avec Java EE 6, nous n’avons pas
besoin de fichier web.xml ou webservices.xml. Cependant, Maven nous obligeant
quand même à ajouter un fichier web.xml dans un war, nous devons modifier l’at-
tribut failOnMissingWebXml de maven-war-plugin en le mettant à false  ; sinon
Maven échouera lors de la compilation.
Pour compiler et assembler le service web, il suffit d’ouvrir une ligne de commande
dans le répertoire contenant le fichier pom.xml et d’entrer la commande Maven
suivante :
mvn package

Le répertoire target devrait maintenant contenir un fichier chapter14-service-


1.0.war. Si nous l’ouvrons, nous pouvons constater que les classes CardValidator.
class et CreditCard.class se trouvent toutes les deux sous le répertoire WEB-INF\
classes et que l’archive ne contient rien d’autre, pas même un fichier WSDL.

Déploiement dans GlassFish


Lorsque le service web a été assemblé dans le fichier war, il faut le déployer dans
GlassFish. Ouvrez une fenêtre de commande, placez-vous dans le répertoire target
où se trouve le fichier chapter14-service-1.0.war, assurez-vous que GlassFish
s’exécute et lancez la commande suivante :
asadmin deploy chapter14-service-1.0.war

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
500 Java EE 6 et GlassFish 3 

Si le déploiement a réussi, la commande qui suit devrait renvoyer le nom des


c­omposants déployés et leurs types :
asadmin list-components
chapter14-service-1.0 <web-module>
chapter14-service-1.0#CardValidator <webservice>

Il est intéressant de noter que GlassFish reconnaît le module web (le fichier war aurait
pu contenir des pages web, des servlets, etc.) comme étant un service web. Si vous
utilisez la console d’administration de GlassFish présentée à la Figure 14.7 (http://
localhost:4848/), vous constaterez que chapter14-service-1.0 est déployé sous
Applications -> Web Applications et que le service web CardValidator est déployé
sous le nœud Web Services.

Figure 14.7
Services web déployés dans la console d’administration de GlassFish.

Si vous cliquez sur le lien View WSDL de cette page, une fenêtre du navigateur
affichera l’URL suivante, qui présente le WSDL du service web (voir Figure 14.8) :
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl

Il est intéressant de noter que vous n’avez pas créé ce WSDL et que vous ne l’avez
pas non plus déployé dans le fichier war : c’est la pile Metro qui le produit automa-
tiquement en se servant des annotations contenues dans le service web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 501

Figure 14.8
Le WSDL produit par Metro.

Le consommateur du service web

Le service web est désormais en ligne et nous savons où trouver son WSDL. Grâce
à lui et à l’aide de l’outil wsimport, le consommateur pourra produire les artéfacts
nécessaires à l’appel du service. Le Listing 14.26 montre le code du consommateur.

Listing 14.26 : La classe Main invoque le service web à l’aide d’une injection


public class Main {

@WebServiceRef
private static CardValidatorService cardValidatorService;

public static void main(String[] args) {


CreditCard creditCard = new CreditCard();
creditCard.setNumber("12341234");
creditCard.setExpiryDate("10/10");
creditCard.setType("VISA");

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
502 Java EE 6 et GlassFish 3 

creditCard.setControlNumber(1234);

CardValidator cardValidator =
cardValidatorService.getCardValidatorPort();
System.out.println(cardValidator.validate(creditCard));
}
}

Cette classe Main crée une instance de CreditCard, initialise quelques données,
obtient une référence au service web, invoque sa méthode validate() et affiche
le résultat (true ou false selon que la carte est valide ou non). Le point important
est que le consommateur n’a aucune de ces classes : CardValidatorService, Card–
Validator et CreditCard lui sont totalement inconnues. Ce code ne pourra donc pas
être compilé tant que ces classes n’ont pas été générées.

Création des artefacts du consommateur et assemblage avec Maven

Avant de compiler la classe Main du consommateur, nous devons créer les artéfacts
avec l’outil wsimport. Heureusement, Maven possède un but wsimport qui est exé-
cuté automatiquement au cours de la phase generate-sources du cycle de vie –
comme on l’a expliqué au Chapitre 1, cette phase sert à produire le code et s’exécute
avant la compilation. La seule chose à faire consiste donc à informer le but wsimport
de l’emplacement du document WSDL. Lorsque nous avons déployé le web service
dans GlassFish et que nous avons demandé l’affichage du WSDL, nous avons pu
constater que ce document se trouvait à l’URL suivante :
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl

Le fichier pom.xml du Listing 14.27 précise également les dépendances nécessaires,


la version de jaxws-rt (2.2) et celle du JDK (1.6). La classe Main est assemblée dans
un fichier jar qui, comme tout fichier jar, contient un fichier META-INF\MANIFEST.MF :
celui-ci peut servir à définir des métadonnées de l’archive – c’est le but de l’extension
maven-jar  : on ajoute un élément mainClass au MANIFEST, pointant vers la classe
Main du consommateur. Cette information permettra l’exécution du fichier jar.

Listing 14.27 : Le fichier pom.xml produit les artéfacts et les paquetages


pour le consommateur
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 503

<groupId>com.apress.javaee6</groupId>
<artifactId>chapter14-consumer</artifactId>
<packaging>jar</packaging>
<version>1.0</version>

<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.apress.javaee6.chapter14.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<wsdlUrls>
<wsdlUrl>
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl
</wsdlUrl>
</wsdlUrls>
<keep>true</keep>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
504 Java EE 6 et GlassFish 3 

<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Pour mieux comprendre ce qui se passe en coulisse, commençons par créer les arté-
facts à l’aide de la commande Maven suivante :
mvn clean generate-sources

Celle-ci exécute la phase generate-sources du cycle de vie de Maven et donc le but


wsimport qui lui est attaché. wsimport se connecte à l’URL du WSDL du service
web, le télécharge et produit les artéfacts. Voici la sortie de cette commande :
[INFO] [clean:clean]
[INFO] [jaxws:wsimport {execution: default}]
[INFO] Processing: http://localhost:8080/chapter14-service-1.0/
„ CardValidatorService?wsdl
[INFO] jaxws:wsimport args: [-s, D:\Chapter14-Consumer\target\
„ jaxws\wsimport\java, -d, D:\Chapter14-Consumer\target\
„ classes, -Xnocompile, http://localhost:8080/chapter14-
„ service-1.0/CardValidatorService?wsdl]

parsing WSDL...
generating code...

[INFO]----------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]----------------------------------------

Si vous êtes curieux, vous pouvez vous rendre dans le répertoire target\
jaxws\wsimport\java et vérifier les classes qui ont été produites. Vous y trouve-
rez bien sûr les classes CardValidator, CardValidatorService et CreditCard mais
également une classe pour la requête SOAP (Validate) et une autre pour la réponse
(ValidateResponse). Ces classes sont remplies d’annotations JAXB et JAXW car
elles sérialisent l’objet CreditCard et se connectent au service web distant. Ne vous
souciez pas du code produit. Le fichier jar peut être compilé et assemblé :
mvn package

Cette commande créera le fichier chapter14-consumer-1.0.jar contenant la classe


Main que nous avons écrite, plus toutes les autres classes que nous venons de pro-
duire. Ce jar est autonome et peut désormais être lancé pour invoquer le service web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 505

Exécution de la classe Main

N’oubliez pas que la classe Main du consommateur utilise l’annotation @WebServi-


ceRef pour obtenir une référence à la SEI du web service par injection. Ceci signi-
fie que le code doit être exécuté dans le conteneur de client d’application (ACC).
­Rappelez-vous également que le fichier chapter14-consumer-1.0.jar est exécu-
table car nous lui avons ajouté un élément mainClass dans le fichier MANIFEST.MF.
La seule chose à faire consiste donc à appeler l’outil appclient de GlassFish en lui
passant le fichier jar :
appclient -client chapter14-consumer-1.0.jar

Cette commande appellera le service web via HTTP et obtiendra une réponse indi-
quant si la carte de crédit est valide ou non.

Résumé

Les services web sont utilisés par l’intégration B2B (business-to-business) et sont
une technologie essentielle pour SOA. Amazon, eBay, Google, Yahoo! fournissent
des services web à leurs utilisateurs et de nombreuses sociétés les utilisent beau-
coup en interne. Dans ce chapitre, nous avons présenté plusieurs standards (UDDI,
WSDL, SOAP, XML, etc.) et nous nous sommes intéressés aux spécifications de
Java EE qui les prennent en charge (JAX-WS, JAXB, WS-Metadata, etc.).
JAXB (Java Architecture for XML Binding) définit un standard pour lier les repré-
sentations Java à XML et vice versa. Elle fournit un haut niveau d’abstraction car
elle repose sur les annotations. Bien que JAXB puisse être utilisée avec n’importe
quel type d’application Java, elle s’intègre particulièrement bien dans l’espace des
services web car les informations échangées sont écrites en XML.
Puis nous avons étudié WSDL et SOAP. Ces spécifications sont vitales pour les
services web car elles décrivent, respectivement, leurs interfaces et les messages
échangés.
JAX-WS suit un modèle de développement simple et n’utilise qu’un petit nombre
d’annotations pour ajuster la traduction de Java en WSDL. L’écriture d’un service
web (extrémité de servlet ou d’EJB) ou d’un consommateur en est donc d’autant plus
simple puisqu’il suffit d’un POJO annoté avec, éventuellement, des descripteurs de
déploiement.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

506 Java EE 6 et GlassFish 3 

Ce chapitre s’est terminé en étudiant l’écriture d’un web service, sa compilation et


son assemblage avec Maven et la production des artéfacts du consommateur avec
l’outil wsimport.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
15
Services web REST

La pile des services web (SOAP, WSDL, WS-*) décrite au chapitre précédent four-
nit à la fois une interopérabilité pour l’injection des messages et le style RPC. Avec
l’avènement du Web  2.0, des frameworks web comme Rails sont apparus et un
nouveau type de services web est devenu à la mode : les services web REST.
De nombreux acteurs essentiels du Web, comme Amazon, Google et Yahoo!, ont
abandonné leurs services SOAP en faveur de services REST orientés ressources.
Lorsqu’il s’agit de choisir entre des services SOAP et REST, de nombreux facteurs
entrent en ligne de compte.
REST (Representational State Transfer) est un type d’architecture reposant sur le
fonctionnement même du Web, qu’il applique aux services web. Pour concevoir un
service REST, il faut connaître le protocole HTTP (Hypertext Transfer Protocol), le
principe des URI (Uniform Resource Identifiers) et respecter quelques règles. Il faut
raisonner en termes de ressources.
REST est intégré à Java EE 6 via JAX-RS (Java API for RESTful Web Services), que
nous utiliserons dans ce chapitre.

Présentation des services web REST


Dans l’architecture REST, toute information est une ressource et chacune d’elles est
désignée par une URI (Uniform Resource Identifier) – généralement un lien sur le
Web. Les ressources sont manipulées par un ensemble d’opérations simples et bien
définies. L’architecture client-serveur de REST est conçue pour utiliser un protocole
de communication sans état – le plus souvent HTTP. Avec REST, les clients et les
serveurs échangent des représentations des ressources en utilisant une interface et
un protocole bien définis. Ces principes encouragent la simplicité, la légèreté et
l’efficacité des applications.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
508 Java EE 6 et GlassFish 3  

Ressources

Les ressources jouent un rôle central dans les architectures REST. Une ressource
est tout ce que peut désigner ou manipuler un client, toute information pouvant être
référencée dans un lien hypertexte. Elle peut être stockée dans une base de données,
un fichier, etc. On évite autant que possible d’exposer des concepts abstraits sous
forme de ressources et l’on privilégie les objets simples.
Certaines ressources de l’application CD-BookStore pourraient donc être :
■■ une liste des livres publiés par Apress et consacrés à Java ;
■■ l’ouvrage Ruby On Rails ;
■■ le résumé d’Ola Bini.
D’autres exemples de ressources sont :
■■ les données météorologiques de Paris en 2008 ;
■■ vos informations de contact ;
■■ le centième nombre de Fibonacci ;
■■ une transaction en cours d’exécution dans un gestionnaire de transactions ;
■■ le nombre d’amis communs de Joe et Bill ;
■■ les photos intéressantes postées sur Flickr au cours des 24 dernières heures ;
■■ les photos intéressantes postées sur Flickr le 01/01/2009.

URI

Une ressource web est identifiée par une URI, qui est un identifiant unique formé
d’un nom et d’une adresse indiquant où trouver la ressource. Il existe différents types
d’URI : les adresses web, les UDI (Universal Document Identifiers), les URI (Uni-
versal Resource Identifiers) et, enfin, les combinaisons d’URL (Uniform Resource
Locators) et d’URN (Uniform Resource Names). Voici quelques exemples d’URI :
■■ http://www.pearson.fr/catalogue/programmation/
■■ http://www.pearson.fr/Resources/titles/27440100325790/Images
/27440100325790L.gif
■■ http://www.pearson.fr/contacts/
■■ http://www.weather.com/weather/2008?location=Paris,France
■■ http://www.flickr.com/explore/interesting/2009/01/01

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 509

■■ http://www.flickr.com/explore/interesting/24hours
■■ http://www.movies.com/catalog/titles/movies/123456
■■ http://www.movies.com/categories/aventure
Les URI devraient être aussi descriptives que possible et ne désigner qu’une seule
ressource, bien que des URI différentes qui identifient des ressources différentes
puissent mener aux mêmes données : à un instant donné, la liste des photos inté-
ressantes publiées sur Flickr le 01/01/2009 était la même que la liste des photos
déposées au cours des 24 dernières heures, bien que l’information convoyée par les
deux URI ne fût pas la même.

Représentations

On peut vouloir obtenir la représentation d’un objet sous forme de texte, de XML,
de PDF ou sous un autre format. Un client traite toujours une ressource au travers
de sa représentation ; la ressource elle-même reste sur le serveur. La représentation
contient toutes les informations utiles à propos de l’état d’une ressource. La liste
des ouvrages sur Java mentionnée précédemment, par exemple, a au moins deux
représentations :
■■ La page HTML telle qu’elle est affichée par un navigateur, http://www.apress.
com/book/catalog?category=32 ;

■■ Le fichier CSV téléchargé pour calculer le nombre de livres, http://www.


apress.com/resource/csv/bookcategory?cat=32.

Deux solutions sont possibles pour choisir entre les différentes représentations d’une
ressource. La première consiste à proposer une URI par représentation : c’est ce que
fait le site d’Apress pour la liste des livres sur Java. Cependant, en ce cas, les deux
URI sont différentes et ne semblent pas directement liées. Voici un ensemble d’URI
mieux organisé :
■■ http://www.apress.com/book/catalog/java
■■ http://www.apress.com/book/catalog/java.csv
■■ http://www.apress.com/book/catalog/java.xml
La première URI est la représentation par défaut de la ressource, les représentations
supplémentaires lui ajoutent simplement l’extension de leur format  : .csv, .xml,
.pdf, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
510 Java EE 6 et GlassFish 3  

L’autre solution consiste à n’exposer qu’une seule URI pour toutes les représenta-
tions (http://www. apress.com/book/catalog/java, par exemple) et à utiliser un
mécanisme appelé négociation du contenu, que nous présenterons un peu plus loin.

WADL
Alors que les services web SOAP utilisent WSDL pour décrire le format des
requêtes possibles, WADL (Web Application Description Language) sert à indiquer
les interactions possibles avec un service web REST. Il facilite le développement des
clients, qui peuvent ainsi charger et interagir directement avec les ressources. Nous
ne présenterons pas WADL dans ce livre car il n’est pas obligatoire pour les services
REST et parce qu’il est peu utilisé.

HTTP

HTTP, un protocole pour les systèmes d’informations distribués, collaboratifs et


hypermédias, a conduit, avec les URI, HTML et les premiers navigateurs à la mise
en place du World Wide Web. Géré par le W3C (World Wide Web Consortium) et
l’IETF (Internet Engineering Task Force), HTTP est le résultat de plusieurs RFC
(Requests For Comment), notamment la RFC 216, qui définit http 1.1. Il repose sur
des requêtes et des réponses échangées entre un client et un serveur.

Requêtes et réponses
Un client envoie une requête à un serveur afin d’obtenir une réponse (voir
Figure 15.1). Les messages utilisés pour ces échanges sont formés d’une enveloppe
et d’un corps également appelé document ou représentation.

Figure 15.1 Client
GET /index.xhtml
Serveur
requête
Requête et réponse http.
200 OK <html><head>...
réponse

Voici, par exemple, un type de requête envoyée à un serveur :


$> curl -v http://www.apress.com/book/catalog?category=32
> GET /book/catalog?category=32 HTTP/1.1
> User-Agent: curl/7.19.3 (i586-pc-mingw32msvc) libcurl/7.19.3
„ zlib/1.2.3
> Host: www.apress.com
> Accept: */*

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 511

Cette requête contient plusieurs informations envoyées par le client :


■■ la méthode HTTP : GET ;
■■ le chemin : /book/catalog?category=32 ;
■■ plusieurs autres en-têtes de requête.
Vous remarquerez que la requête n’a pas de corps (un GET n’a jamais de corps).
En réponse, le serveur renverra le message suivant :
< HTTP/1.1 200 OK
< Date: Mon, 23 Feb 2009 07:28:09 GMT
< Server: Apache/2.0.63 (Unix) PHP/5.2.6
< X-Powered-By: PHP/5.2.6
< Set-Cookie: PHPSESSID=b9ae64b800781d9761670fcf6067a317; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-
check=0 < Pragma: no-cache
< Transfer-Encoding: chunked
< Content-Type: text/html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html>
<head>
<title>
...

Cette réponse est formée des parties suivantes :


■■ Un code de réponse. Ici, ce code est 200 OK.

■■ Plusieurs en-têtes de réponse. Notamment Date, Server, Content-Type. Ici, le


type du contenu est text/html, mais il pourrait s’agir de n’importe quel format
comme du XML (application/xml) ou une image (image/jpeg).
■■ Un corps ou représentation. Ici, il s’agit du contenu de la page web renvoyée
(dont nous n’avons reproduit qu’un fragment).

INFO

Nous utilisons ici cURL (http://curl.haxx.se/) comme client, mais nous aurions pu également
utiliser Firefox ou n’importe quel autre client web. CURL est un outil en ligne de commande
permettant de transférer des fichiers par HTTP, FTP, Gopher, SFTP, FTPS, SCP, TFTP et bien
d’autres protocoles encore en utilisant une URL. Avec lui, vous pouvez envoyer des com-
mandes HTTP, modifier les en-têtes HTTP, etc. : c’est donc un outil parfait pour simuler les
actions d’un utilisateur dans un navigateur web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
512 Java EE 6 et GlassFish 3  

Méthodes de HTTP
Le Web est formé de ressources bien identifiées, reliées ensemble et auxquelles accé-
der au moyen de requêtes HTTP simples. Les requêtes principales de HTTP sont de
type GET, POST, PUT et DELETE. Ces types sont appelés verbes ou méthodes. HTTP
définit quatre autres verbes plus rarement utilisés, HEAD, TRACE, OPTIONS et CONNECT.

GET
GET est une méthode de lecture demandant une représentation d’une ressource. Elle
doit être implémentée de sorte à ne pas modifier l’état de la ressource. En outre, GET
doit être idempotente, ce qui signifie qu’elle doit laisser la ressource dans le même
état, quel que soit le nombre de fois où elle est appelée. Ces deux caractéristiques
garantissent une plus grande stabilité : si un client n’obtient pas de réponse (à cause
d’un problème réseau, par exemple), il peut renouveler sa requête et s’attendre à la
même réponse que celle qu’il aurait obtenue initialement, sans corrompre l’état de
la ressource sur le serveur.

POST
À partir d’une représentation texte, XML, etc., POST crée une nouvelle ressource
subordonnée à une ressource principale identifiée par l’URI demandée. Des
exemples d’utilisation de POST sont l’ajout d’un message à un fichier journal, d’un
commentaire à un blog, d’un livre à une liste d’ouvrages, etc. POST modifie donc
l’état de la ressource et n’est pas idempotente (envoyer deux fois la même requête
produit deux nouvelles ressources subordonnées). Si une ressource a été créée sur
le serveur d’origine, le code de la réponse devrait être 201 (Created). La plupart des
navigateurs modernes ne produisent que des requêtes GET ou POST.

PUT
Une requête PUT est conçue pour modifier l’état de la ressource stockée à une cer-
taine URI. Si l’URI de la requête fait référence à une ressource inexistante, celle-ci
sera créée avec cette URI. PUT modifie donc l’état de la ressource, mais elle est
idempotente : même si l’on envoie plusieurs fois la même requête PUT, l’état de la
ressource finale restera inchangé.

DELETE
Une requête DELETE supprime une ressource. La réponse à DELETE peut être un mes-
sage d’état dans le corps de la réponse ou aucun code du tout. DELETE est idempo-
tente, mais elle modifie évidemment l’état de la ressource.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 513

Autres
Comme on l’a évoqué, il existe d’autres actions HTTP, plus rarement employées :
■■ HEAD ressemble à GET sauf que le serveur ne renvoie pas de corps dans sa réponse.
HEAD permet par exemple de vérifier la validité d’un client ou la taille d’une entité
sans avoir besoin de la transférer.
■■ TRACE retrace la requête reçue.
■■ OPTIONS est une demande d’information sur les options de communication dispo-
nibles pour la chaîne requête/réponse identifiée par l’URI. Cette méthode permet
au client de connaître les options et/ou les exigences associées à une ressource,
ou les possibilités d’un serveur sans demander d’action sur une ressource et sans
récupérer aucune ressource.
■■ CONNECT est utilisé avec un proxy pouvant se transformer dynamiquement en
tunnel (une technique grâce à laquelle le protocole HTTP sert d’enveloppe à
différents protocoles réseau).

Négociation du contenu
La négociation du contenu, décrite dans la section 12 du standard HTTP, est défi-
nie comme "le fait de choisir la meilleure représentation pour une réponse donnée
lorsque plusieurs représentations sont disponibles". Les besoins, les souhaits et les
capacités des clients varient : la meilleure représentation pour l’utilisateur d’un télé-
phone portable au Japon peut, en effet, ne pas être la plus adaptée à un lecteur de
flux RSS en France.
La négociation du contenu utilise entre autres les en-têtes HTTP Accept, Accept-
Charset, Accept-Encoding, Accept-Language et User-Agent. Pour obtenir, par
exemple, la représentation CSV de la liste des livres sur Java publiés par Apress,
l’application cliente (l’agent utilisateur) demandera http://www.apress.com/book/
catalog/java avec un en-tête Accept initialisé à text/csv. Vous pouvez aussi ima-
giner que, selon la valeur de l’en-tête Accept-Language, le contenu de ce document
CSV pourrait être traduit par le serveur dans la langue correspondante.

Types de contenu
HTTP utilise des types de supports Internet (initialement appelés types MIME) dans
les en-têtes Content-Type et Accept afin de permettre un typage des données et une
négociation du contenu ouverts et extensibles. Les types de support Internet sont

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
514 Java EE 6 et GlassFish 3  

divisés en cinq catégories : text, image, audio, video et application. Ces types
sont à leur tour divisés en sous-types (text/plain, text/xml, text/xhtml, etc.).
Voici quelques-uns des plus utilisés :
■■ text/html. HTML est utilisé par l’infrastructure d’information du World Wide
Web depuis 1990 et sa spécification a été décrite dans plusieurs documents infor-
mels. Le type de support text/html a été initialement défini en 1995 par le groupe
de travail IETF HTML. Sa définition complète se trouve dans la RFC 2854.
■■ text/plain. Il s’agit du type de contenu par défaut car il est utilisé pour les
‑messages textuels simples.
■■ image/gif, image/jpeg, image/png. Le type de support image exige la présence
d’un dispositif d’affichage (un écran ou une imprimante graphique, par exemple)
permettant de visualiser l’information.
■■ text/xml, application/xml. XML 1.0 a été publié par le W3C en février 1998.
La définition complète de ce support est décrite dans la RFC 3023.
■■ application/json. JSON (JavaScript Object Notation) est un format textuel léger
pour l’échange de données. Il est indépendant des langages de programmation.

Codes d’état
Un code HTTP est associé à chaque réponse. La spécification définit environ
60 codes d’états ; l’élément Status-Code est un entier de trois chiffres qui décrit le
contexte d’une réponse et qui est intégré dans l’enveloppe de celle-ci. Le premier
chiffre indique l’une des cinq classes de réponses possibles :
■■ 1xx : Information. La requête a été reçue et le traitement se poursuit.
■■ 2xx : Succès. L’action a bien été reçue, comprise et acceptée.
■■ 3xx : Redirection. Une autre action est requise pour que la requête s’effectue.
■■ 4xx : Erreur du client. La requête contient des erreurs de syntaxe ou ne peut
pas être exécutée.
■■ 5xx : Erreur du serveur. Le serveur n’a pas réussi à exécuter une requête pourtant
apparemment valide.
Voici quelques codes d’état que vous avez sûrement déjà dû rencontrer :
■■ 200 OK. La requête a réussi. Le corps de l’entité, si elle en possède un, contient
la représentation de la ressource.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 515

■■ 301 Moved Permanently. La ressource demandée a été affectée à une autre URI


permanente et toute référence future à cette ressource devrait utiliser l’une des
URI renvoyées.
■■ 404 Not Found. Le serveur n’a rien trouvé qui corresponde à l’URL demandée.
■■ 500 Internal Server Error. Le serveur s’est trouvé dans une situation inattendue
qui l’a empêché de répondre à la requête.

Mise en cache et requêtes conditionnelles


La mise en cache est un élément crucial pour la plupart des systèmes distribués. Elle
a pour but d’améliorer les performances en évitant les requêtes inutiles et en rédui-
sant le volume des données des réponses. HTTP dispose de mécanismes permettant
la mise en cache et la vérification de l’exactitude des données du cache. Si le client
décide de ne pas utiliser ce cache, il devra toujours demander les données, même si
elles n’ont pas été modifiées depuis la dernière requête.
La réponse à une requête de type GET peut contenir un en-tête Last-Modified indi-
quant la date de dernière modification de la ressource. La prochaine fois que l’agent
utilisateur demandera cette ressource, il passera cette date dans l’en-tête If-Modi-
fied-Since : le serveur web (ou le proxy) la comparera alors à la date de dernière
modification. Si celle envoyée par l’agent utilisateur est égale ou plus récente, le ser-
veur renverra une réponse sans corps, avec un code d’état 304 Not Modified. Sinon
l’opération demandée sera réalisée ou transférée.
Les dates peuvent être difficiles à manipuler et impliquent que les agents concernés
soient, et restent, synchronisés : c’est le but de l’en-tête de réponse ETag, qui peut
être considéré comme un hachage MD5 ou SHA1 de tous les octets d’une représen-
tation – si un seul octet est modifié, la valeur d’ETag sera différente. La valeur ETag
reçue dans une réponse à une requête GET peut, ensuite, être affectée à un en-tête
If-Match d’une requête.

La Figure  15.2 montre un exemple d’utilisation d’ETag. Pour obtenir une res-
source livre, on utilise une action GET en lui indiquant l’URI de la ressource (GET /
book/12345). Le serveur répondra par une représentation XML du livre, un code 200
OK et un ETag. La seconde fois que l’on demandera la même ressource et que l’on
passera la valeur de cet ETag dans la requête, le serveur ne renverra pas la représen-
tation de la ressource, mais uniquement une réponse avec un code 304 Not Modified
afin d’informer le client que cette ressource n’a pas été modifiée depuis son dernier
accès.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
516 Java EE 6 et GlassFish 3  

Figure 15.2 Client GET /book/12345 Serveur

Utilisation du cache
200 OK <book>...</book>
et du code 304. ETag: 328987

GET/book/12315
If-Ncn-Match: 328987

301 Not modified

Les requêtes qui utilisent les en-têtes HTTP If-Modified-Since, If-Unmodified-


Since, If-Match, If-None-Match et If-Range sont appelées requêtes condition-
nelles. Elles permettent d’économiser la bande passante et le temps de calcul (à la
fois sur le serveur et sur le client) en évitant des allers et retours ou des transferts
de données inutiles. Généralement, ces en-têtes If-* sont surtout utilisés avec les
requêtes GET et PUT.

Spécification des services web REST

Contrairement à SOAP et à la pile WS-*, qui reposent sur les standards du W3C ;
REST n’est pas un standard : c’est uniquement un style d’architecture respectant
certains critères de conception. Les applications REST, cependant, dépendent
­fortement d’autres standards :
■■ HTTP ;
■■ URI, URL ;
■■ XML, JSON, HTML, GIF, JPEG, etc. (représentation des ressources).
Sa prise en compte par Java a été spécifiée par JAX-RS, mais REST est comme un
patron de conception  : c’est une solution réutilisable d’un problème courant, qui
peut être implémentée en différents langages.

Historique rapide de REST

Le terme REST a été employé pour la première fois par Roy Thomas Fielding dans
le Chapitre 5 de sa thèse de doctorat, Architectural Styles and the Design of Network-
based Software Architectures (université de Californie, Irvine, 2000, http://www.
ics.uci.edu/~fielding/pubs/dissertation/top.htm). Sa dissertation est une rétros-
pective des architectures choisies pour développer le Web.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 517

Dans sa thèse, Fielding étudie les parties du Web qui fonctionnent très bien et en
extrait les principes de conception qui pourraient rendre aussi efficaces les autres
systèmes hypermédias distribués – qu’ils soient ou non liés au Web.
La raison initiale qui a poussé au développement de REST était donc de créer un
modèle architectural du Web. Roy T. Fielding étant également l’un des auteurs de la
spécification HTTP, il n’est pas surprenant que ce protocole corresponde particuliè-
rement bien à l’architecture décrite dans sa dissertation.

JAX-RS 1.1
Pour écrire des services web REST, il suffit d’un client et d’un serveur reconnais-
sant le protocole HTTP. N’importe quel navigateur et un conteneur de servlet HTTP
pourraient donc faire l’affaire, au prix d’un peu de configuration XML et d’ajus-
tement du code. Mais, au final, ce code pourrait devenir peu lisible et difficile à
maintenir : c’est là que JAX-RS vole à notre secours. Sa première version, finalisée
en octobre 2008, définit un ensemble d’API mettant en avant une architecture REST.
C’est le standard JCP pour les services web REST (JSR 311). Au moyen d’annota-
tions, il simplifie l’implémentation de ces services et améliore la productivité. La
spécification ne couvre que la partie serveur de REST.

Nouveautés de JAX-RS 1.1

JAX-RS 1.1 est une version de maintenance axée sur l’intégration avec Java EE 6
et ses nouvelles fonctionnalités. Les nouveautés principales de JAX-RS 1.1 sont les
suivantes :
■■ Le support de beans de session sans état comme ressources racine.
■■ Il est désormais possible d’injecter des ressources externes (gestionnaire de
­persistance, sources de données, EJB, etc.) dans une ressource REST.
■■ Les annotations JAX-RS peuvent s’appliquer à l’interface locale d’un bean ou
directement à un bean sans interface.

Implémentation de référence

Jersey est l’implémentation de référence officielle de JAX-RS par Sun. C’est un pro-
jet open-source couvert à la fois par les licences CDDL et GPL. Jersey est extensible
au moyen de son API et fournit également une API cliente (qui n’est pas couverte
par la spécification).

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
518 Java EE 6 et GlassFish 3  

Il existe également d’autres implémentations de JAX-RS, comme CXF (Apache),


RESTEasy (JBoss) et Restlet (un ancien projet qui existait avant que JAX-RS ne
soit finalisée).

L’approche REST

Comme on l’a déjà mentionné, REST est un ensemble de contraintes de conceptions


générales reposant sur HTTP. Ce chapitre s’intéressant aux services web et REST
dérivant du Web, nous commencerons par une navigation réelle passant en revue les
principes du Web. Ce dernier est devenu une source essentielle d’informations et fait
désormais partie de nos outils quotidiens : vous le connaissez donc sûrement très
bien et cette familiarité vous aidera donc à comprendre les concepts et les propriétés
de REST.

Du Web aux services web

Nous savons comment fonctionne le Web : pourquoi les services web devraient-ils se
comporter différemment ? Après tout, ils échangent souvent uniquement des ressources
bien identifiées, liées à d’autres au moyen de liens hypertextes. L’architecture du Web
ayant prouvé sa tenue en charge au cours du temps, pourquoi réinventer la roue ?
Pour créer, modifier et supprimer une ressource livre, pourquoi ne pas utiliser les
verbes classiques de HTTP ? Par exemple :
■■ Utiliser POST sur des données (au format XML, JSON ou texte) afin de créer
une ressource livre avec l’URI http://www.apress.com/books/. Le livre créé,
la réponse renvoie l’URI de la nouvelle ressource  : http://www.apress.com/
books/123456.

■■ Utiliser GET pour lire la ressource (et les éventuels liens vers d’autres ressources
à partir du corps de l’entité) à l’URI http://www.apress.com/books/123456.
■■ Utiliser PUT pour modifier la ressource située à l’URI http://www.apress.com/
books/123456.

■■ Utiliser DELETE pour supprimer la ressource à l’URI http://www.apress.com/


books/123456.

En se servant de verbes HTTP, nous pouvons donc effectuer toutes les actions CRUD
(Create, Read, Update, Delete) sur une ressource.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 519

Pratique de la navigation sur le Web

Comment obtenir la liste des livres sur Java publiés par Apress ? En faisant pointer
son navigateur sur le site web d’Apress : http://www.apress.com. Même si cette
page ne contiendra sûrement pas les informations exactes que l’on recherche, on
s’attend à ce qu’elle donne accès, par un moyen ou un autre, à la liste des livres
consacrés à Java. La page d’accueil offre un moteur de recherche de tous les livres
d’Apress, mais également un répertoire de livres classés par technologies. Si l’on
clique sur le nœud Java, la magie de l’hypertexte opère et l’on obtient la liste com-
plète des livres sur Java publiés par Apress. Elle est assez longue et, bien que le
nombre d’ouvrages ne soit pas indiqué, le lien vers le format CSV et nos compé-
tences en shell permettront de faire ce calcul en quelques cycles d’horloge (il y a ici
144 livres dans la catégorie Java) :
curl http://www.apress.com/resource/csv/bookcategory?cat=32 |
„ sed -n 2~1p | wc -l
(..)
144

Supposons que nous ayons sauvegardé le lien dans notre gestionnaire des favoris
et qu’au cours du parcours de la liste l’ouvrage The Definitive Guide to Grails, de
Graeme Rocher et Jeff Brown, attire notre attention : le lien hypertexte sur le titre
de ce livre nous mènera à la page contenant le résumé, la biographie des auteurs,
etc. et nous pourrons noter qu’un des livres mentionnés dans la section Related titles
est également susceptible de nous intéresser. Nous voudrons comparer l’ouvrage de
Graeme Rocher avec Practical JRuby on Rails Web 2.0 Projects: Bringing Ruby on
Rails to Java, d’Ola Bini. Les pages des livres d’Apress nous donnent accès à une
représentation plus concrète sous la forme de prévisualisations : nous pouvons alors
ouvrir une prévisualisation, lire la table des matières et faire notre choix.
Voici ce que l’on fait quotidiennement avec nos navigateurs. REST applique les
mêmes principes à vos services où les livres, les résultats des recherches, une table
des matières ou la couverture d’un livre peuvent être définis comme des ressources.

Interface uniforme

L’une des principales contraintes constituant une architecture REST est l’utilisation
d’une interface uniforme pour la gestion des ressources. Nous pouvons choisir l’in-
terface que nous voulons, du moment que nous l’utilisons de la même façon partout,
d’une ressource à une autre, d’un service à un autre. Il ne faut jamais s’en écarter ou

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
520 Java EE 6 et GlassFish 3  

modifier sa signification initiale. En utilisant une interface uniforme, "l’architecture


globale du système est simplifiée et la visibilité des interactions, améliorée" (Roy
Thomas Fielding, Architectural Styles and the Design of Network-based Software
Architectures). Nos services font alors partie d’une communauté de services ­utilisant
exactement la même sémantique.
Le protocole de facto du Web est HTTP – un protocole standardisé de requête/
réponse entre un client et un serveur –, et c’est l’interface uniforme des services
web REST. Les services reposant sur SOAP, WSDL et les autres standards WS-*
l’utilisent également au niveau de la couche transport, mais ils ne se servent que
d’une infime partie de ses possibilités. S’il faut analyser le WSDL pour découvrir la
sémantique d’un service SOAP puis appeler les bonnes méthodes, les services web
REST, en revanche, ont une interface uniforme (les verbes HTTP et les URI) : dès
que l’on connaît l’emplacement d’une ressource (son URI), il suffit d’invoquer les
verbes HTTP (GET, POST, etc.).
Outre l’avantage de sa familiarité, une interface web met en avant l’interopérabilité
entre les applications : HTTP est largement répandu et le nombre de bibliothèques
HTTP clientes garantit que nous n’aurons pas besoin de nous soucier des problèmes
de communication.

Accessibilité

Le second principe de la conception des services REST est l’accessibilité. Un ser-


vice web doit rendre une application aussi accessible que possible, ce qui signifie
que chaque fragment d’information intéressante de cette application doit être une
ressource et posséder une URI afin de pouvoir être aisément atteinte. C’est la seule
information qu’il faut publier pour rendre la ressource accessible, afin que le parte-
naire n’ait pas besoin de jouer aux devinettes pour l’atteindre.
Supposons, par exemple, qu’un bogue se soit glissé dans notre application et que nos
investigations nous amènent à la ligne 42 de la classe CreditCardValidator.java.
Comme nous ne sommes pas responsables de cette partie de l’application, nous
remplissons un rapport de bogue que nous envoyons à la personne qualifiée. Com-
ment lui montrer la ligne incriminée ? Nous pourrions écrire "Allez à la ligne 42
de la classe CreditCardValidator" ou, si notre code source est accessible via un
navigateur, placer l’URI de cette ligne dans le rapport de bogue. Ceci pose donc le
problème de la granularité des ressources REST d’une application : elle peut inter-
venir au niveau d’une ligne, d’une méthode, d’une classe, etc.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 521

Les URI uniques permettent de créer des liens vers les ressources et, comme elles
sont exposées via une interface uniforme, chacun sait exactement comment interagir
avec elles : ceci permet d’utiliser une application de façon insoupçonnée.

Connectivité

En théorie des graphes, on dit qu’un graphe est connexe lorsque tous les sommets
peuvent se connecter les uns aux autres via un chemin quelconque. Un graphe est dit
fortement connexe s’il existe un chemin direct de u à v et un chemin direct de v à u
pour toute paire de sommets (u, v). REST demande à ce que les ressources soient
les plus connectées possible. C’est le fameux principe "L’hypermédia est le moteur
de l’état de l’application".
Ce principe provient, là encore, d’un examen du succès du Web. Les pages contien-
nent des liens pour passer de page en page de façon logique et simple, et l’on peut
donc considérer que le Web est très bien connecté. S’il existe une relation forte entre
deux ressources, celles-ci doivent être connectées. REST précise que les services
web devraient également tirer parti de l’hypermédia pour informer le client de ce qui
est disponible et de la façon de s’y rendre. Il met en avant la découverte des services.
À partir d’une seule URI, un agent utilisateur accédant à un service bien connecté
pourra découvrir toutes les actions et ressources disponibles, leurs différentes repré-
sentations, etc.
Lorsqu’un agent utilisateur recherche, par exemple, la représentation d’un CD (voir
Listing 15.1), celle-ci peut contenir non seulement le nom de l’artiste, mais égale-
ment un lien – une URI – vers sa biographie. C’est ensuite à l’agent utilisateur de
la récupérer ou non. La représentation pourrait également contenir des liens vers
d’autres représentations de la ressource ou vers les actions possibles.

Listing 15.1 : Représentation d’un CD connectée à d’autres services


<cd>
<title>Ella and Louis</title>
<year ref=’http://music.com/year/1956’>1956</year>
<artist ref=’http://music.com/artists/123’>
Ella Fitzgerald
</artist>
<artist ref=’http://music.com/artists/456’>
Louis Armstrong
</artist>
<link rel="self" type="text/json"
href="http://music.com/album/789"/>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
522 Java EE 6 et GlassFish 3  

<link rel="self" type="text/xml"


href="http://music.com/album/789"/>
<link rel="http://music.com/album/comments" type="text/xml"
href="http://music.com/album/789/comments"/>
</cd>

Un autre aspect essentiel du principe de l’hypermédia est qu’il doit piloter l’état de
l’application. Pour résumer, le fait que le service web fournisse un ensemble de liens
permet au client de faire passer l’application d’un état à l’autre en suivant simplement
un lien.
Dans l’extrait XML précédent, le client pouvait ainsi changer l’état de l’application
en apportant un commentaire sur l’album. La liste des commentaires de cet album
est une ressource accessible par l’URI http://music.com/album/789/comments. Ce
service web utilisant une interface uniforme, tout client connaissant le format de
l’URI, les types de contenus disponibles et le format des données savent exacte-
ment comment interagir avec lui : un GET récupérera la liste des commentaires exis-
tants, un POST créera un nouveau commentaire, etc. À partir de cette requête initiale
simple, le client peut réaliser de nombreuses actions, et c’est la raison pour laquelle
l’hypermédia pilote l’état de l’application.

Sans état

La dernière fonctionnalité de REST est l’absence d’état, ce qui signifie que toute
requête HTTP est totalement indépendante puisque le serveur ne mémorisera jamais
les requêtes qui ont été effectuées. Pour plus de clarté, l’état d’une ressource et celui
de l’application sont généralement différenciés : l’état de la ressource doit se trouver
sur le serveur et être partagé par tous, tandis que celui de l’application doit rester
chez le client et être sa seule propriété. Si l’on revient à l’exemple du Listing 15.1,
l’état de l’application est que le client a récupéré une représentation de l’album Ella
and Louis, mais le serveur ne mémorise pas cette information. L’état de la ressource,
quant à lui, est l’information sur l’album : le serveur doit évidemment la mémoriser
et le client peut le modifier. Si le panier virtuel est une ressource dont l’accès est
réservé à un seul client, l’application doit stocker l’identifiant de ce panier dans la
session du client.
L’absence d’état a de nombreux avantages, notamment une meilleure adaptation à
la charge : aucune information de session à gérer, pas besoin de router les requêtes
suivantes vers le même serveur, gestion des erreurs, etc. Si vous devez mémoriser
l’état, le client devra faire un travail supplémentaire pour le stocker.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 523

JAX-RS : Java API for RESTful Web Services


Vous pouvez vous demander à quoi ressemblera le code qui s’appuie sur des concepts
d’aussi bas niveau que le protocole HTTP. En réalité, vous n’avez pas besoin d’écrire
des requêtes HTTP ni de créer manuellement des réponses car JAX-RS est une
API très élégante qui permet de décrire une ressource à l’aide de quelques annota-
tions seulement. Comme le montre le Listing 15.2, une ressource est alors un POJO
­possédant au moins une méthode annotée par @javax.ws.rs.Path.

Listing 15.2 : Une ressource livre simple


@Path("/book")
public class BookResource {

@GET
@Produces("text/plain")
public String getBookTitle() {
return "H2G2";
}
}

BookResource étant une classe Java annotée par @Path, la ressource sera hébergée
à l’URI /book. La méthode getBookTitle() est annotée par @GET afin d’indiquer
qu’elle traitera les requêtes HTTP GET et qu’elle produit du texte (le contenu est
identifié par le type MIME text/plain). Pour accéder à la ressource, il suffit d’un
client HTTP, un navigateur, par exemple, pouvant envoyer une requête GET vers
l’URL http://www.myserver.com/book.

Le modèle JAX-RS

Le Listing 15.2 montre que le service REST n’implémente aucune interface et n’étend


aucune classe : @Path est la seule annotation obligatoire pour transformer un POJO en
service REST. JAX-RS utilisant la configuration par exception, un ensemble d’anno-
tations permettent de modifier son comportement par défaut. Les exigences que doit
satisfaire une classe pour devenir un service REST sont les suivantes :
■■ Elle doit être annotée par @javax.ws.rs.Path.
■■ Pour ajouter les fonctionnalités des EJB au service REST, la classe doit être
annotée par @javax.ejb.Stateless.
■■ Elle doit être publique et ne pas être finale ni abstraite.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
524 Java EE 6 et GlassFish 3  

■■ Les classes ressources racine (celles ayant une annotation @Path) doivent avoir
un constructeur par défaut public. Les classes ressources non racine n’exigent
pas ce constructeur.
■■ La classe ne doit pas définir la méthode finalize().
Par nature, JAX-RS repose sur HTTP et dispose d’un ensemble de classes et d’an-
notations clairement définies pour gérer HTTP et les URI. Une ressource pouvant
avoir plusieurs représentations, l’API permet de gérer un certain nombre de types
de contenu et utilise JAXB pour sérialiser et désérialiser les représentations XML
et JSON en objets. JAX-RS étant indépendant du conteneur, les ressources peuvent
être évidemment déployées dans GlassFish, mais également dans un grand nombre
d’autres conteneurs de servlets.

Écriture d’un service REST


Les services REST peuvent être des beans de session sans état, ce qui permet d’utili-
ser des transactions pour accéder à une couche de persistance (entités JPA), comme
le montre le Listing 15.3.

Listing 15.3 : Une ressource livre qui crée et récupère des livres dans une base de données
@Path("books")
@Stateless
@Produces({"application/xml", "application/json"})
@Consumes({"application/xml", "application/json"})
public class BookResource {

@Context
private UriInfo uriInfo;
@PersistenceContext(unitName = "chapter15PU")
private EntityManager em;

@GET
public List<Book> getAllBooks() {
Query query = em.createNamedQuery("findAllBooks");
List<Book> books = query.getResultList();
return books;
}

@POST
public Response createNewBook(JAXBElement<Book> bookJaxb) {
Book book = bookJaxb.getValue();
em.persist(book);
URI bookUri = uriInfo.getAbsolutePathBuilder().path(
„ book.getId().toString()).build();
return Response.created(bookUri).build();
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 525

Le code du Listing 15.3 représente un service REST pouvant consommer et produire


les représentations XML et JSON d’un livre. La méthode getAllBooks() récupère
la liste des livres à partir d’une base de données et renvoie sa représentation XML ou
JSON (en utilisant la négociation du contenu) ; elle est appelée par une requête GET.
La méthode createNewBook() prend une représentation XML ou JSON d’un livre et
le stocke dans la base de données. Cette méthode est invoquée par une requête POST
et renvoie l’URI du nouveau livre.

Définition des URI

La valeur de l’annotation @Path est relative à un chemin d’URI. Lorsqu’elle est utili-
sée sur des classes, celles-ci sont considérées comme des ressources racine, car elles
fournissent la racine de l’arborescence des ressources et l’accès aux sous-ressources.
@Path("/customers")
public class CustomersResource {

@GET
public List getCustomers() {
// ...
}
}

Vous pouvez également intégrer dans la syntaxe de l’URI des templates de chemins
d’URI : ces variables seront évaluées à l’exécution. La documentation Javadoc de
l’annotation @Path décrit la syntaxe des templates :
@Path("/customers/{customername}")
@Path peut également s’appliquer aux méthodes des ressources racine, ce qui per-
met de regrouper les fonctionnalités communes à plusieurs ressources, comme le
montre la Figure 15.4 (ignorez pour l’instant les annotations @GET, @POST et @DELETE
car nous les décrirons plus loin, dans la section "Méthodes ou interface uniforme").

Listing 15.4 : Une ressource article avec plusieurs annotations @Path


@Path("/items")
public class ItemsResource {

@GET
public List<Item> getListOfItems() {
// ...
}

@GET
@Path("{itemid}")

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

526 Java EE 6 et GlassFish 3  

public Item getItem(@PathParam("itemid") String itemid) {


// ...
}

@PUT
@Path("{itemid}")
public void putItem(@PathParam("itemid") String itemid,
Item item) {
// ...
}

@DELETE
@Path("{itemid}")
public void deleteItem(@PathParam("itemid") String itemid) {
// ...
}

@GET
@Path("/books/")
public List<Book> getListOfBooks() {
// ...
}

@GET
@Path("/books/{bookid}")
public Book getBook(@PathParam("bookid") String bookid) {
// ...
}
}

Si @Path est appliquée à la fois sur la classe et une méthode, le chemin relatif de
la ressource produite par cette méthode est la concaténation de la classe et de la
méthode. Pour obtenir un livre par son identifiant, par exemple, le chemin sera
/items/books/1234. Si l’on demande la ressource racine /items, seule la méthode
sans annotation @Path sera sélectionnée (getListOfItems(), ici). Si l’on demande
/items/books, c’est la méthode getListOfBooks() qui sera invoquée. Si @Path
("/items") n’annotait que la classe et aucune méthode, le chemin d’accès de toutes
les méthodes serait le même et il faudrait utiliser le verbe HTTP (GET, PUT) et la
négociation du contenu (texte, XML, etc.) pour les différencier.

Extraction des paramètres

Nous avons besoin d’extraire des informations sur les URI et les requêtes lorsqu’on
les manipule. Le Listing  15.4 a déjà montré comment extraire un paramètre du
­chemin à l’aide de @javax.ws.rs.PathParam et JAX-RS fournit tout un ensemble

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 527

d’annotations pour extraire les différents paramètres qu’une requête peut envoyer
(@QueryParam, @MatrixParam, @CookieParam, @HeaderParam et @FormParam).
L’annotation @PathParam permet d’extraire la valeur d’un paramètre template d’une
URI. Le code suivant, par exemple, extraira l’identifiant du client (98342) de l’URI
http://www.myserver. com/customers/98342 :

@Path("/customers")
public class CustomersResource {

@GET
public Customer getCustomer(@PathParam("customerid")
customerid) {
// ...
}
}

L’annotation @QueryParam extrait la valeur du paramètre d’une requête. Le code


suivant extraira le code postal (19870) de l’URI http://www.myserver.com/
customers?zip=19870 :
@Path("/customers")
public class CustomersResource {

@GET public Customer getCustomerByZipCode(@QueryParam("zip")


Long zip) {
// ...
}
}

L’annotation @MatrixParam agit comme @QueryParam, sauf qu’elle extrait la valeur


d’un paramètre matrice d’une URI (le délimiteur est ; au lieu de ?). Elle permet,
par exemple, d’extraire le nom de l’auteur (Goncalves) de cette URI : http://www.
myserver.com/products/ books;author=Goncalves.

Deux autres annotations sont liées aux détails internes de HTTP, ce que l’on ne voit
pas directement dans les URI : les cookies et les en-têtes. @CookieParam extrait la
valeur d’un cookie, tandis que @HeaderParam permet d’obtenir la valeur d’un en-
tête. Le code suivant, par exemple, extrait l’identifiant de session du cookie :
@Path("/products")
public class ItemsResource {

@GET
public Book getBook(@CookieParam("sessionid") int sessionid) {
// ...
}
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
528 Java EE 6 et GlassFish 3  

L’annotation @FormParam précise que la valeur d’un paramètre doit être extraite d’un
formulaire situé dans le corps de la requête.
On peut ajouter @DefaultValue à toutes ces annotations pour définir une valeur par
défaut pour le paramètre que l’on attend. Cette valeur sera utilisée si les métadon-
nées correspondantes sont absentes de la requête. Si le paramètre age ne se trouve
pas dans la requête, par exemple, le code suivant utilisera la valeur 50 par défaut :
@Path("/customers")
public class CustomersResource {

@GET
public Response getCustomers(@DefaultValue("50")
@QueryParam("age") int age) {
// ...
}
}

Consommation et production des types de contenus

Avec REST, une ressource peut avoir plusieurs représentations : un livre peut être
représenté comme une page web, un document XML ou une image affichant sa cou-
verture. Les annotations @javax.ws.rs.Consumes et @javax.ws.rs.Produces peu-
vent s’appliquer à une ressource qui propose plusieurs représentations : elle définit
les types de médias échangés entre le client et le serveur. L’utilisation de l’une de ces
annotations sur une méthode ressource redéfinit celle qui s’appliquait sur la classe
de la ressource pour un paramètre d’une méthode ou une valeur de retour. En leur
absence, on suppose que la ressource supporte tous les types de médias (*/*). Dans
le Listing  15.5, CustomerResource produit par défaut une représentation en texte
brut, sauf pour certaines méthodes.

Listing 15.5 : Une ressource customer avec plusieurs représentations


@Path("/customers")
@Produces("text/plain")
public class CustomersResource {

@GET
public String getAsPlainText() {
// ...
}

@GET
@Produces("text/html")

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 529

public String getAsHtml() {


// ...
}

@GET
@Produces("application/json")
public List<Customer> getAsJson() {
// ...
}

@PUT
@Consumes("text/plain")
public void putBasic(String customer) {
// ...
}

@POST
@Consumes("application/xml")
public Response createCustomer(InputStream is) {
// ...
}
}

Si une ressource peut produire plusieurs types de médias Internet, la méthode choi-
sie correspondra au type qui convient le mieux à l’en-tête Accept de la requête
HTTP du client. Si, par exemple, cet en-tête est :
Accept: text/plain

et que l’URI est /customers, c’est la méthode getAsPlainText() qui sera invoquée.
Le client aurait également pu utiliser l’en-tête suivant :
Accept: text/plain; q=0.8, text/html

Cet en-tête annonce que le client peut accepter les types text/plain et text/html
mais qu’il préfère le dernier avec un facteur de qualité de 0.8 ("je préfère huit fois
plus le text/html que le text/plain"). En incluant cet en-tête dans une requête
adressée à /customers, c’est la méthode getAsHtml() qui sera invoquée.
Dans le Listing 15.5, la méthode getAsJson() renvoie une représentation de type
application/json. JSON est un format léger pour l’échange de données : comme
vous pourrez le constater en étudiant le Listing 15.6, il est moins verbeux et plus
lisible que XML.

Listing 15.6 : Représentation JSON d’une liste de clients


{"customers": {
"id": "0",

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
530 Java EE 6 et GlassFish 3  

"version": "1",
"customer": [
{
"firstName": "Alexis",
"lastName": "Midon",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"postalCode": 10021
},
"phoneNumbers": [
"212 555-1234",
"646 555-4567"
]
},
{
"firstName": "Sebastien",
"lastName": "Auvray",
"address": {
"streetAddress": "2 Rue des Acacias",
"city": "Paris",
"postalCode": 75015
},
"phoneNumbers": [
"+33 555-1234",
]
}
]
})

Jersey convertira pour vous la liste des clients au format JSON en utilisant les four-
nisseurs d’entités prédéfinis MessageBodyReader et MessageBodyWriter.

INFO

Jersey ajoute une association BadgerFish de JAXB XML vers JSON. BadgerFish (http:// bad-
gerfish.ning.com/) est une convention de traduction d’un document XML en objet JSON afin
qu’il soit plus facile à manipuler avec JavaScript.

Fournisseurs d’entités

Lorsque les entités sont reçues dans des requêtes ou envoyées dans des réponses,
l’implémentation JAX-RS doit pouvoir convertir les représentations en Java et vice
versa : c’est le rôle des fournisseurs d’entités. JAXB, par exemple, traduit un objet
en représentation Java et réciproquement. Il existe deux variantes de fournisseurs
d’entités : MessageBodyReader et MessageBodyWriter.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 531

Pour traduire le corps d’une requête en Java, une classe doit implémenter l’interface
javax.ws.rs.ext.MessageBodyReader et être annotée par @Provider. Par défaut,
l’implémentation est censée consommer tous les types de médias (*/*), mais l’anno-
tation @Consumes permet de restreindre les types supportés. Le code du Listing 15.7,
par exemple, prend un flux XML en entrée (représenté par un objet javax.xml.
bind.Element) et le convertit en objet Customer.

Listing 15.7 : Fournisseur lisant un flux XML et créant une entité Customer


@Provider
@Consumes("application/xml")
public class CustomerReader
implements MessageBodyReader<Element> {

public boolean isReadable(Class<?> type) {


return Element.class.isAssignableFrom(type);
}

public Element readFrom(Class<Element> customer,


MediaType mediaType,
MultivaluedMap<String, String> headers,
InputStream inputStream)
throws IOException {
Document doc = getParser().parse(inputStream);
Element node = doc.getDocumentElement();
if (node.getLocalName().equals("customer")) {
return el;
} else {
throw new IOException("Unexpected payload!");
}
}
}

De la même façon, un type Java peut être traduit en corps de réponse. Une classe
Java voulant effectuer ce traitement doit implémenter l’interface javax.ws.rs.
ext.MessageBodyWriter et être annotée par l’interface @Provider. L’annotation
@Produces indique les types de médias supportés. Le code du Listing 15.8 montre
comment convertir une entité Customer en un corps HTTP en texte brut.

Listing 15.8 : Fournisseur produisant une représentation en texte brut d’une entité Customer
@Provider
@Produces("text/plain")
public class CustomerWriter
implements MessageBodyWriter<Customer> {

public boolean isWriteable(Class<?> type) {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
532 Java EE 6 et GlassFish 3  

return Customer.class.isAssignableFrom(type);
}

public void writeTo(Customer customer, MediaType mediaType,


MultivaluedMap<String, Object> headers,
OutputStream outputStream)
throws IOException {
outputStream.write(customer.get(id).getBytes());
// ...
}
}

Les implémentations de MessageBodyReader et MessageBodyWriter peuvent lever


une exception WebApplicationException lorsqu’elles ne peuvent produire aucune
représentation. L’implémentation de JAX-RS offre un ensemble de fournisseurs
d’entités par défaut convenant aux situations courantes (voir Tableau 15.1).

Tableau 15.1 : Fournisseurs par défaut de l’implémentation de JAX-RS

Type Description
byte[] Tous types de médias (*/*)
java.lang.String Tous types de médias (*/*)
java.io.InputStream Tous types de médias (*/*)
java.io.Reader Tous types de médias (*/*)
java.io.File Tous types de médias (*/*)
javax.activation.DataSource Tous types de médias (*/*)
javax.xml.transform.Source Types XML (text/xml, application/xml et
application/-*+xml)

javax.xml.bind.JAXBElement Types de médias XML de JAXB (text/-xml,


application/ xml et application/*+xml)

MultivaluedMap<String,String> Contenu de formulaire (application/x-www-


form-urlencoded)

javax.ws.rs.core StreamingOutput Tous types de médias (*/*), uniquement


MessageBodyWriter

Méthodes ou interface uniforme

Nous avons vu comment le protocole HTTP gérait ses requêtes, ses réponses et
ses méthodes d’actions (GET, POST, PUT, etc.). JAX-RS définit ces méthodes HTTP

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 533

à l’aide d’annotations : @GET, @POST, @PUT, @DELETE, @HEAD et @OPTIONS. Seules les
méthodes publiques peuvent être exposées comme des méthodes de ressources. Le
Listing 15.9, par exemple, montre une ressource customer exposant les méthodes
CRUD.

Listing 15.9 : Ressource customer exposant les opérations CRUD


@Path("/customers")
public class CustomersResource {

@GET
public List<Customer> getListOfCustomers() {
// ...
}

@POST
@Consumes("application/xml")
public Response createCustomer(InputStream is) {
// ...
}

@PUT
@Path("{customerid}")
@Consumes("application/xml")
public Response updateCustomer(@PathParam("customerid")
String customerId,
InputStream is) {
// ...
}

@DELETE
@Path("{customerid}")
public void deleteCustomer(@PathParam("customerid")
String customerId) {
// ...
}
}

Lorsqu’une méthode ressource est invoquée, les paramètres annotés par l’une des
annotations d’extraction vues précédemment sont initialisés. La valeur d’un para-
mètre non annoté (appelé paramètre entité) est obtenue à partir du corps de la requête
et convertie par un fournisseur d’entités.
Les méthodes peuvent renvoyer void, Response ou un autre type Java. Response
indique qu’il faudra fournir d’autres métadonnées – lorsque l’on crée un nouveau
client, par exemple, il faudra renvoyer son URI.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
534 Java EE 6 et GlassFish 3  

Informations contextuelles

Le fournisseur de ressources a besoin d’informations contextuelles pour traiter cor-


rectement une requête. L’annotation @javax.ws.rs.core.Context sert à injecter les
classes suivantes dans un attribut ou dans le paramètre d’une méthode : HttpHea-
ders, UriInfo, Request, SecurityContext et Providers. Le code du Listing 15.10,
par exemple, reçoit par injection une UriInfo afin de pouvoir construire des URI.

Listing 15.10 : Ressource customer obtenant une UriInfo


@Path("/customers")
public class CustomersResource {

@Context
UriInfo uriInfo;

@GET
@Produces("application/json")
public JSONArray getListOfCustomers() {
JSONArray uriArray = new JSONArray();
for (Customer customer : customerDAO.findAll()) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI userUri = ub.path(userEntity.getCustomerId()).build();
uriArray.put(userUri.toASCIIString());
}
return uriArray;
}
}

En-têtes
Comme on l’a vu précédemment, les informations transportées entre le client et
le serveur sont formées non pas uniquement du corps d’une entité, mais égale-
ment d’en-têtes (Date, Server, Content-Type, etc.). Les en-têtes HTTP font partie
de l’interface uniforme et les services web REST les utilisent. La classe javax.
ws.rs.core.HttpHeaders permet aux développeurs de ressources d’y accéder : une
instance de HttpHeaders peut être injectée dans un attribut ou dans un paramètre
de méthode au moyen de l’annotation @Context afin de permettre d’accéder aux
valeurs des en-têtes sans tenir compte de leur casse.
Si le service fournit des ressources localisées, par exemple, le code suivant permettra
d’extraire la valeur de l’en-tête Accept-Language :
@GET
@Produces{"text/plain"}
public String get(@Context HttpHeaders headers) {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 535

List<java.util.Locale> locales =
headers.getAcceptableLanguages()
// ou headers.getRequestHeader("accept-language")
...
}

Construction d’URI

Les liens hypertextes sont un élément central des applications REST. Pour évoluer à
travers les états de l’application, les services web REST doivent gérer les transitions
et la construction des URI. Pour ce faire, JAX-RS fournit une classe javax.ws.rs.
core.UriBuilder destinée à remplacer java.net.URI et à faciliter la construction
d’URI. UriBuilder dispose d’un ensemble de méthodes permettant de construire de
nouvelles URI ou d’en fabriquer à partir d’URI existantes. Le code du Listing 15.11
s’en sert pour renvoyer un tableau d’URI.

Listing 15.11 : Méthode renvoyant un tableau d’URI


@Path("/customers/")
public class CustomersResource {

@Context UriInfo uriInfo;

@GET
@Produces("application/json")
public JSONArray getCustomersAsJsonArray() {
JSONArray uriArray = new JSONArray();
for (UserEntity userEntity : getUsers()) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI userUri = ub.path(userEntity.getUserid()).build();
uriArray.put(userUri.toASCIIString());
}
return uriArray;
}
}

UriBuilder peut également servir à traduire des templates d’URI de façon simple.
L’URI http://www.myserver.com/products/books?author=Goncalves peut, par
exemple, être obtenue de la façon suivante :
UriBuilder.fromUri("http://www.myserver.com/")
.path("{a}/{b}").queryParam("author", "{value}")
.build("products", "books", "Goncalves");

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
536 Java EE 6 et GlassFish 3  

Gestion des exceptions

Les codes que nous avons présentés jusqu’à maintenant s’exécutaient dans un monde
parfait, où tout se passe bien et où il n’y a pas besoin de traiter les exceptions. Mal-
heureusement, ce monde n’existe pas et, tôt ou tard, une ressource nous explosera
en plein visage parce que les données reçues ne sont pas valides ou que des parties
du réseau ne sont pas fiables.
Comme le montre le Listing 15.12, nous pouvons lever à tout instant une exception
WebApplicationException ou l’une de ses sous-classes dans un fournissseur de res-
sources. Cette exception sera capturée par l’implémentation de JAX-RS et convertie
en réponse HTTP. L’erreur par défaut est un code 500 avec un message vide, mais
la classe javax.ws.rs.WebApplicationException offre différents constructeurs
permettant de choisir un code d’état spécifique (défini dans l’énumération javax.
ws.rs.core.Response.Status) ou une entité.

Listing 15.12 : Méthode levant des exceptions


@Path("customers/{id}")
public Customer getCustomer(@PathParam("id") int id) {
if(id < 1)
throw new WebApplicationException(Response.status(400)
„ .entity("Id must be a positive integer!"));
Item i = em.find(Item.class, id);
if (i == null)
throw new WebApplicationException(Response.Status.NOT_FOUND);
return i;
}

Pour écrire un code DRY (acronyme de Don’t Repeat Yourself), nous pouvons four-
nir des fournisseurs de traduction d’exception, qui traduisent une exception en un
objet Response. Si cette exception est lancée, l’implémentation JAX-RS la capturera
et appellera le fournisseur de traduction d’exception approprié. Comme le montre
le Listing 15.13, ces fournisseurs sont des implémentations d’ExceptionMapper<E
extends java.lang.Throwable> annotées par @Provider.

Listing 15.13 : Traduction d’exception


@Provider
public class EntityNotFoundMapper implements
ExceptionMapper<javax.persistence.EntityNotFoundException> {

public Response toResponse(


javax.persistence.EntityNotFoundException ex) {

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 537

return Response.status(404).entity(ex.getMessage()).
„ type("text/plain").build();
}
}

Les exceptions non contrôlées et les erreurs qui n’entrent pas dans les deux cas
­précédents seront relancées comme toute exception Java non contrôlée.

Cycle de vie

Lorsqu’une requête arrive, la ressource cible est résolue et une nouvelle instance
de la classe ressource racine correspondante est créée. Le cycle de vie d’une classe
ressource racine dure donc le temps d’une requête, ce qui implique que la classe
ressource n’a pas à s’occuper des problèmes de concurrence et qu’elle peut donc
utiliser les variables d’instance en toute sécurité.
S’ils sont déployés dans un conteneur Java EE (servlet ou EJB), les classes res-
sources et les fournisseurs JAX-RS peuvent également utiliser les annotations de
gestion du cycle de vie et de la sécurité définies dans la JSR 250 : @PostConstruct,
@PreDestroy, @RunAs, @RolesAllowed, @PermitAll, @DenyAll et @DeclareRoles. Le
cycle de vie d’une ressource peut se servir de @PostConstruct et de @PreDestroy
pour ajouter de la logique métier respectivement après la création d’une ressource
et avant sa suppression. La Figure 15.3 montre que ce cycle de vie est équivalent à
celui de tous les composants sans état de Java EE.

Figure 15.3 N'existe pas


Cycle de vie
d’une ressource.
@PostConstruct @PreDestroy

Prêt

Appel de méthode

Récapitulatif

Rassemblons tous ces concepts pour écrire une ressource livre, l’assembler et la
déployer dans GlassFish puis la tester avec cURL. L’idée consiste à avoir une entité

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
538 Java EE 6 et GlassFish 3  

Book associée à une base de données et une classe BookResource produisant une
représentation REST du livre. BookResource est également un bean de session sans
état, ce qui autorise une démarcation des transactions avec le gestionnaire d’entités
pour les opérations CRUD. Ces classes déployées, nous pourrons créer, récupérer
ou supprimer des livres à l’aide des méthodes HTTP avec cURL et obtenir à la fois
des représentations XML et JSON.
Cet exemple respecte la structure de répertoires de Maven et assemble toutes les
classes dans un fichier war (chapter15-resource-1.0.war). Les classes doivent se
trouver dans les répertoires suivants :
■■ src/main/java. Répertoire pour l’entité Book et le service web REST
BookResource.
■■ src/main/resources. Contient le fichier persistence.xml utilisé par la res-
source pour faire correspondre l’entité Book à la base de données Derby.
■■ src/webapp/WEB-INF. Contient le fichier web.xml qui déclare la servlet Jersey.
■■ pom.xml. Le fichier POM (Project Object Model) de Maven, qui décrit le projet
et ses dépendances.

L’entité Book

Vous connaissez déjà le code de l’entité Book mais, comme le montre la Figure 15.14,
un élément très important a été ajouté ici : l’annotation @XmlRootElement de JAXB,
qui permet d’obtenir une représentation XML d’un livre.

Listing 15.14 : Entité Book avec une annotation JAXB


@Entity
@XmlRootElement
@NamedQuery(name = "findAllBooks", query = "SELECT b FROM Book b") public class
Book {

@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;

// Constructeurs, getters, setters


}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 539

Cette entité doit également être assemblée avec un fichier persistence.xml (que
nous ne présentons pas ici).

Le service REST BookResource

BookResource est un service web REST implémenté comme un bean de session sans
état et qui utilise un gestionnaire d’entités pour créer, supprimer et obtenir des livres.

En-tête
L’en-tête de BookResource est important car il utilise plusieurs annotations. Avec
JAX-RS, les utilisateurs peuvent accéder aux ressources en publiant une URI. L’an-
notation @Path("books") indique le chemin de la ressource (c’est-à-dire l’URL pour
l’atteindre) – ici, elle est de la forme http:// localhost:8080/rs/books.
Les annotations @Produces et @Consumes définissent les types de contenus par défaut
que produit ou consomme cette ressource – XML et JSON, ici. Vous pouvez redé-
finir ce type de contenu méthode par méthode. Enfin, l’annotation @Stateless que
nous avons étudiée au Chapitre 7 informe le conteneur que cette ressource doit éga-
lement être considérée comme un EJB et qu’elle autorise la démarcation des tran-
sactions lors des accès à la base de données. Cette ressource reçoit par injection une
référence à un gestionnaire d’entités et une UriInfo :
@Path("books")
@Produces({"application/xml", "application/json"})
@Consumes({"application/xml", "application/json"})
@Stateless
public class BookResource {

@PersistenceContext(unitName = "chapter15PU")
private EntityManager em;
@Context
private UriInfo uriInfo;

Création d’un livre


Pour respecter la sémantique de REST, nous utilisons une méthode POST pour créer
une nouvelle ressource en XML ou JSON, comme l’indique l’annotation @Consumes
de l’en-tête. Par défaut, chaque méthode (et notamment createNewBook())
consomme du XML et du JSON. Comme nous l’avons vu avec le Listing 15.3, cette
méthode prend un JAXBElement en paramètre – n’oubliez pas que l’entité Book est

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
540 Java EE 6 et GlassFish 3  

également un objet JAXB. Lorsque le XML a été sérialisé en objet Book (bookJaxb.
getValue()), on utilise le gestionnaire d’entités pour rendre le livre persistant.

Cette méthode renvoie un objet Response représentant l’URI du nouveau livre. Nous
pourrions renvoyer le code d’état 200 OK pour indiquer que la création a réussi mais,
selon les principes de REST, la méthode devrait renvoyer un code 201 (ou 204)
pour indiquer que la requête a été traitée et qu’elle a produit une nouvelle ressource,
­désignée par l’URI renvoyée dans la réponse.
@POST
public Response createNewBook(JAXBElement<Book> bookJaxb) {
Book book = bookJaxb.getValue();
em.persist(book);
URI bookUri = uriInfo.getAbsolutePathBuilder().
„ path(book.getId().toString()).build();
return Response.created(bookUri).build();
}

Pour créer une ressource avec ce code, nous avons le choix entre envoyer un docu-
ment XML ou un document JSON – ce dernier est plus léger. La commande cURL
utilise une méthode POST en lui passant les données dans un format JSON qui doit
respecter l’association JSON/XML utilisée par Jersey (n’oubliez pas que le XML
est à son tour obtenu à partir de l’objet Book à l’aide des règles JAXB) :
curl -X POST --data-binary
„ "{ \"title\":\"H2G2\", \"description\":\"Scifi IT book\",
„ \"illustrations\":\"false\",\"isbn\":\"134-234\",
„ \"nbOfPage\":\"241\", ➥ \"price\":\"24.0\" }
„ " -H "Content-Type: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books –v
Le mode verbeux de cURL (activé par l’option -v) affiche la requête et la réponse
HTTP. Vous pouvez voir dans la réponse l’URI de la ressource livre qui a été créée
(son identifiant est 601) :
> POST /chapter15-resource-1.0/rs/books HTTP/1.1
> User-Agent: curl/7.19.0 (i586-pc-mingw32msvc) libcurl/7.19.0 zlib/1.2.3
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 127
>
< HTTP/1.1 201 Created
< X-Powered-By: Servlet/3.0
< Server: GlassFish/v3
< Location: http://localhost:8080/chapter15-resource-1.0/rs/books/601
< Content-Type: application/xml
< Content-Length: 0

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 541

Obtention d’un livre par son identifiant


Pour récupérer une ressource livre par son identifiant, la requête doit porter sur une
URI de la forme /books/{identifiant du livre}. L’identifiant sert de paramètre
pour retrouver le livre dans la base de données. Avec une requête GET adressée à
/books/601, la méthode getBookById() renverra une représentation XML ou JSON
du livre qui a l’identifiant 601 :
@GET
@Path("{id}/")
public Book getBookById(@PathParam("id") Long id) {
Book book = em.find(Book.class, id);
return book;
}

L’annotation @Path indique le sous-chemin dans le chemin déjà indiqué au niveau


de la classe. La syntaxe {id} lie l’élément de l’URL au paramètre de la méthode.
La commande cURL suivante permet d’accéder à la ressource 601 et d’obtenir sa
représentation JSON :
curl -X GET -H "Accept: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books/601

{"description":"Scifi IT book","illustrations":"false",
„ "isbn":"1234-234","nbOfPage":"241","price":"24.0","title":"H2G2"}

En changeant simplement la valeur de l’en-tête Accept, le même code permet d’obtenir


la représentation XML du livre 601 :
curl -X GET -H "Accept: application/xml"
„ http://localhost:8080/chapter15-resource-1.0/rs/books/601

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<book><description>Scifi IT book</description>
<illustrations>false</illustrations><isbn>1234-234</isbn>
<nbOfPage>241</nbOfPage><price>24.0</price><title>H2G2</title></book>

Suppression d’un livre


La méthode deleteBook() a le même format que celui de getBookById() car elle
utilise un sous-chemin et un identifiant de livre en paramètre – la seule différence est
la requête HTTP utilisée. Ici, nous nous servons de la méthode DELETE, qui supprime
le contenu associé à l’URL indiquée :
@DELETE
@Path("{id}/")
public void deleteBook(@PathParam("id") Long id) {
Book book = em.find(Book.class, id);
em.remove(book);
}

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
542 Java EE 6 et GlassFish 3  

Avec le mode verbeux de cURL, on voit la requête DELETE qui est envoyée et le code
d’état 204 No Content qui apparaît dans la réponse pour indiquer que la ressource
n’existe plus :
curl -X DELETE http://localhost:8080/chapter15-resource-
„ 1.0/rs/books/61 -v
> DELETE /chapter15-resource-1.0/rs/books/61 HTTP/1.1
> User-Agent: curl/7.19.0 (i586-pc-mingw32msvc)
„ libcurl/7.19.0 zlib/1.2.3
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 204 No Content
< X-Powered-By: Servlet/3.0
< Server: GlassFish/v3

Configuration avec web.xml

Avant de déployer la classe BookResource et l’entité Book, nous devons enregistrer


Jersey dans le fichier web.xml (voir Listing 15.15) afin que les requêtes envoyées
au chemin /rs soient interceptées par Jersey. Le paramètre com.sun.jersey.config.
property.packages indiquera à ce dernier où rechercher les classes Java annotées
par JAX-RS.

Listing 15.15 : Fichier web.xml déclarant Jersey


<?xml version=’1.0’ encoding=’UTF-8’?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>com.apress.javaee6.chapter15</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 543

<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
</web-app>

Le descripteur de déploiement associe les requêtes au motif d’URL /rs/*, ce qui


signifie que toutes les URL commençant par /rs/ seront traitées par Jersey. Nos
exemples avec cURL ont, bien sûr, manipulé des URL commençant par /rs :
curl -X GET -H "Accept: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books
curl -X DELETE http://localhost:8080/chapter15-resource-
„ 1.0/rs/books/61

Compilation et assemblage avec Maven

L’application web doit être compilée et déployée dans un fichier war


(<packaging>war</packaging>). Le fichier pom.xml du Listing 15.16 déclare toutes
les dépendances nécessaires à la compilation du code (jsr311-api, javax.ejb et
javax.persistence). N’oubliez pas que JAXB fait partie de Java SE et qu’il n’y a
donc pas besoin d’ajouter cette dépendance.

Listing 15.16 : Le fichier pom.xml de Maven pour compiler et assembler l’application web


<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter15-resource</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>Chapter 15 - REST Resource</name>

<parent>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapters</artifactId>
<version>1.0</version>
</parent>

</dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

544 Java EE 6 et GlassFish 3  

<artifactId>jsr311-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

Pour compiler et assembler les classes, ouvrez une fenêtre de commandes et pla-
cez-vous dans le répertoire contenant le fichier pom.xml, puis entrez la commande
suivante :
mvn package

Le répertoire target devrait désormais contenir un fichier chapter15-resource-


1.0.war. Ouvrez-le et vous constaterez que les fichiers Book.class et BookResource.
class se trouvent dans le répertoire WEB-INF\classes. Ce fichier war contient éga­
lement les fichiers web.xml et persistence.xml.

Déploiement dans GlassFish

Une fois le code assemblé et lorsque GlassFish et Derby s’exécutent, nous pouvons
déployer le fichier war à l’aide de la commande asadmin. Ouvrez une fenêtre de

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 545

commande, placez-vous dans le répertoire target où se trouve le fichier chapter15-


resource-1.0.war et lancez la commande suivante :

asadmin deploy chapter15-resource-1.0.war

Si le déploiement réussit, la commande suivante devrait afficher le nom et le type du


composant déployé :
asadmin list-components
chapter15-resource-1.0 <ejb, web>

Exécution de l’exemple

Lorsque l’application a été déployée, vous pouvez utiliser cURL en ligne de com-
mande pour créer des ressources livres à l’aide de requêtes POST ou obtenir des
ressources spécifiques avec des requêtes GET. Pour supprimer des livres, utilisez la
requête DELETE :
// Création d’un livre
curl -X POST --data-binary
„ "{ \"title\":\"H2G2\", \"description\":\"Scifi IT book\",
„ \"illustrations\":\"false\",\"isbn\":\"134-234\",
„ \"nbOfPage\":\"241\", \"price\":\"24.0\" }"
„ -H "Content-Type: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books

// Récupération de tous les livres


curl -X GET -H "Accept: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books

// Récupération du livre 601 au format JSON


curl -X GET -H "Accept: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books/601

{"description":"Scifi IT book","illustrations":"false",
„ "isbn":"1234-234","nbOfPage":"241","price":"24.0","title":"H2G2"}

// Récupération du livre 601 au format XML


curl -X GET -H "Accept: application/xml"
„ http://localhost:8080/chapter15-resource-1.0/rs/books/601

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<book><description>Scifi IT book</description>
<illustrations>false</illustrations><isbn>1234-234</isbn>
<nbOfPage>241</nbOfPage><price>24.0</price><title>H2G2</title>
</book>

// Suppression du livre 601


curl -X DELETE http://localhost:8080/chapter15-resource-
„ 1.0/rs/books/601

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
546 Java EE 6 et GlassFish 3  

Résumé

Le chapitre précédent a présenté les services web SOAP. Ceux-ci utilisent une enve-
loppe spéciale, sont décrits avec WSDL et peuvent être invoqués par différents pro-
tocoles, dont HTTP. Ce chapitre a décrit les services web REST, et vous connaissez
maintenant la différence entre une implémentation de JAX-WS et JAX-RS.
Nous avons commencé par présenter REST. À l’aide de verbes simples (GET, POST,
PUT, etc.) et d’une interface uniforme, nous avons vu comment accéder à n’importe
quelle ressource déployée sur le Web. Puis nous nous sommes intéressés à HTTP, un
protocole reposant sur un mécanisme de requêtes/réponses utilisant une négociation
pour choisir le type de contenu approprié en réponse à une requête.
La mise en cache permet d’optimiser le trafic réseau grâce à des requêtes condition-
nelles et à des en-têtes ETag. Nous avons vu que REST pouvait tirer parti de cette
optimisation car, comme lui, elle repose sur HTTP.
JAX-RS est une API Java fournie avec Java EE 6 qui simplifie le développement
des services web REST. À l’aide d’annotations, nous pouvons définir le chemin et
les sous-chemins permettant d’accéder à une ressource, extraire différents types de
paramètres ou traduire du code Java en méthodes HTTP (@GET, @POST, etc.). Lorsque
l’on développe des services web REST, il est important de raisonner en termes de
ressources, d’étudier la façon dont elles sont reliées et de savoir gérer leur état avec
HTTP.

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Index

Symboles <entity-listener>, marqueur 197


@EntityListeners, annotation 193
@Access, annotation 94 @Enumerated, annotation 90
@AccessTimeout, annotation 238 @ExcludeClassInterceptors, annotation 278, 279,
@ActivationConfigProperty, annotation 448, 449, 281, 282
453, 454, 459 @ExcludeDefaultInterceptors, annotation 281, 282
@AfterClass, annotation 69 @ExcludeDefaultListeners, annotation 197
@ApplicationException, annotation 295, 296 @ExcludeSuperclassListeners, annotation 195
@ApplicationScoped, annotation 393, 394, 395 @FacesConverter, annotation 406, 407
@AroundInvoke, annotation 275, 277, 279, 280, 281 @FacesValidator, annotation 409
@Asynchronous, annotation 252 @FormParam, annotation 527, 528
@AttributeOverride, annotation 133 @GeneratedValue, annotation 57, 61, 81
@Basic, annotation 86 @GET, annotation 523, 524, 525, 526, 527, 528,
@Before, annotation 69 533, 534, 535, 541, 546
@BeforeClass, annotation 69 @HEAD, annotation 533
@CollectionTable, annotation 96 @HeaderParam, annotation 527, 528
@Column, annotation 57, 61, 87 @Id, annotation 57, 61, 74, 81
@ConcurrencyManagement, annotation 236 @IdClass, annotation 84
@Consumes, annotation 524, 528, 529, 531, 533, @Inheritance, annotation 127
534, 539 @Interceptors, annotation 277, 278, 279, 280, 281,
@Context, annotation 524, 534, 535, 539 282
@CookieParam, annotation 527, 528 @JoinColumn, annotation 114
@DeclareRoles, annotation 301, 302, 303, 304, 537 @JoinTable, annotation 116
@DefaultValue, annotation 528, 529 @Lob, annotation 87
@DELETE, annotation 525, 526, 533, 541 @Local, annotation 241
@DenyAll, annotation 301, 302, 303, 304, 537 @LocalBean, annotation 245
@DependsOn, annotation 235, 267 @Lock, annotation 237
@DiscriminatorColumn, annotation 129 @ManagedBean, annotation 314, 315, 321, 322,
@DiscriminatorValue, annotation 130 391, 392, 393, 394, 395, 397, 398, 399
@EJB, annotation 203, 249, 250, 266, 268, 315, @ManagedProperty, annotation 392, 395, 396
321, 322, 452 @ManytoMany, annotation 119
@ElementCollection, annotation 96 @MapKeyColumn, annotation 98
@Embeddable, annotation 102, 103 @MappedSuperclass, annotation 137
@Embedded, annotation 102 @MatrixParam, annotation 527, 528
@EmbeddedId, annotation 83 @MessageDriven, annotation 447, 448, 449, 453,
@Entity, annotation 57, 61, 74 454, 459

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
548 Java EE 6 et GlassFish 3 

@NamedNativeQuery, annotation 179 @RolesAllowed, annotation 301, 302, 303, 304,


@NamedQuery, annotation 59, 61, 176 305, 537
@NoneScoped, annotation 394 @RunAs, annotation 301, 304, 305, 537
@OnetoMany, annotation 115 @Schedule, annotation 257, 260
@OneToOne, annotation 112, 156 @SecondaryTable, annotation 79
@OneWay, annotation 487, 490 @SessionScoped, annotation 393
@OPTIONS, annotation 533 @Singleton, annotation 234, 267
@OrderBy, annotation 123 @SOAPBinding, annotation 487
@OrderColumn, annotation 124 @SOAPMessageHandler, annotation 487
@Path, annotation 523, 524, 525, 526, 527, 528, @Startup, annotation 235, 267
533, 534, 535, 536, 539, 540, 541, 543 @Stateful, annotation 232, 267
@PathParam, annotation 526, 527, 533, 536, 537, @StatefulTimeout, annotation 232
541, 542, 543 @Stateless, annotation 203, 228, 229, 267, 273, 274,
@PermitAll, annotation 301, 303, 537 277, 278, 279, 280, 281, 282, 497, 498, 524, 539
@PersistenceContext, annotation 148, 244, 249, @Table, annotation 78
250, 266, 268 @Temporal, annotation 89
@PersistenceUnit, annotation 249 @Test, annotation 69
<persistence-unit-defaults>, marqueur 197 @Timeout, annotation 257
<persistence-unit-metadata>, marqueur 197 @TransactionAttribute, annotation 292, 293, 302,
@PostActivate, annotation 270, 271, 272, 278, 279 456
@POST, annotation 524, 525, 529, 530, 533, 540, @TransactionManagement, annotation 297, 302
546 @Transient, annotation 90, 480
@PostConstruct, annotation 266, 268, 269, 270, @Version, annotation 182
271, 272, 278, 279, 280, 282, 394, 395, 452, 454, @ViewScoped, annotation 393
493, 537 @WebMethod, annotation 487, 488, 489, 490, 491,
@PostLoad, annotation 190 498
@PostPersist, annotation 189 @WebParam, annotation 487, 489, 490, 491, 498
@PostRemove, annotation 189 @WebResult, annotation 487, 489, 490, 498
@PostUpdate, annotation 189 @WebService, annotation 486, 488, 489, 490, 491,
@PreDestroy, annotation 269, 270, 271, 272, 278, 497, 498
279, 280, 282, 396, 397, 452, 454, 493, 537 @WebServiceRef, annotation 249, 250, 495, 501,
@PrePassivate, annotation 270, 271, 272, 278, 279 505
@PrePersist, annotation 189 @XmlAccessorType, annotation 478, 479
@PreRemove, annotation 189 @XmlAttribute, annotation 478, 479, 480
@PreUpdate, annotation 189 @XmlElement, annotation 478, 479, 480
@Produces, annotation 523, 524, 528, 531, 534, @XmlEnum, annotation 480
535, 536, 539 @XmlEnumValue, annotation 480
@Provider, annotation 531, 536, 537 @XmlID, annotation 480
@QueryParam, annotation 527, 528, 529 @XmlIDREF, annotation 480
@Remote, annotation 216, 241 @XmlList, annotation 480
@Remove, annotation 232, 269, 270, 271, 272 @XmlMimeType, annotation 480
@RequestScoped, annotation 321, 322, 392, 394 @XmlNs, annotation 480
@Resource, annotation 249, 250, 266, 268, 293, @XmlRootElement, annotation 472, 478, 480, 481,
297, 298, 306, 431, 432, 438, 440, 441, 451, 452, 497, 498, 538
453, 454, 458, 493, 494, 495 @XmlSchema, annotation 479, 480

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Index 549

@XmlTransient, annotation 480 column (JSF) 369


@XmlType, annotation 480 commandButton
@XmlValue, annotation 480 composant JSF 396, 397, 398, 413, 416
(JSF) 363
commandLink
A
composant JSF 393, 396, 397
ABS(), fonction JPQL 167 (JSF) 363
Accept, en-tête HTTP 510, 513, 529, 534, 540, 541, component (Facelets) 359
542, 543, 545 objet implicite 381
actionSource (JSF) 377 Composants graphiques de JSF 324
Activation 230, 268 compositeComponent, objet implicite 381
AND, opérateur JPQL 168 composition
appclient, programme 221, 462, 505 (Facelets) 359
application, objet implicite 381 (JSF) 372
applicationScope, objet implicite 381 CONCAT(), fonction JPQL 167
asadmin ConnectionFactory, classe 430, 432, 433, 438, 439,
commande 48, 329 440, 441, 451, 452, 454, 455, 458, 459, 461, 462
programme 221, 430, 461, 499, 500 Connection, interface 431, 432, 433, 434, 438, 439,
assert, mot-clé 38 440, 441, 442, 454, 455, 458, 459
AsyncResult, classe 253 containsIgnoreCase (JSTL) 358
attribute contains (JSTL) 358
action JSP 345 contains(), méthode d'EntityManager 151, 158, 188
(JSF) 376 Content-Type, en-tête HTTP 513, 534, 540, 542,
AVG(), fonction JPQL 167 545, 546
convertDateTime, composant JSF 405
B Converter
composant JSF 407
Backing bean 386, 391, 392, 404 interface 406
BETWEEN, opérateur JPQL 168 ConverterException, exception 406
body convertNumber, composant JSF 405
action JSP 345 cookie, objet implicite 381
(JSF) 371 CORBA (Common Object Request Broker
bundle (JSTL) 352 Architecture) 469
COUNT(), fonction JPQL 167
C createNamedQuery(), méthode d'EntityManager
172, 177
Cache, interface 163 createNativeQuery(), méthode d'EntityManager 178,
Cardinalité d'une relation 107 179
cascade, attribut d'annotation 161 createQuery(), méthode d'EntityManager 172, 174
catch (JSTL) 350 cURL, programme 511, 537, 538, 540, 541, 542,
CDATA, section XML 344 543, 545
choose (JSTL) 350, 355 CURRENT_DATE(), fonction JPQL 167
Classe de clé primaire 82 CURRENT_ TIME(), fonction JPQL 167
clear(), méthode d'EntityManager 151, 158, 189 CURRENT_TIMESTAMP(), fonction JPQL 167

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
550 Java EE 6 et GlassFish 3 

D exception
d'application 294
dataTable (JSF) 369 système 294
dateParam (JSTL) 354 executeUpdate(), méthode de Query 173
dblook, programme 40 expression (JSP) 344
DCOM (Distributed Component Object Model) 469
debug (Facelets) 359
déclaration (JSP) 343 F
decorate
(Facelets) 359 faces-config.xml, fichier 311, 318, 328, 390, 391,
(JSF) 372 392, 395, 396, 398, 399, 400, 401, 406, 407, 409
define FacesContext
(Facelets) 359 classe 387, 389, 401, 406, 408, 409
(JSF) 372 objet implicite 381, 389
DELETE, instruction JPQL 170 FacesMessage, classe 389, 401, 402, 408, 409
Destination, interface 431, 435, 453, 454 FacesServlet, classe 386, 387, 388, 390, 391, 392,
detach(), méthode d'EntityManager 158 398, 399
Direction d'une relation 106 facet (JSF) 369, 376
DISTINCT, clause JPQL 166 find(), méthode d'EntityManager 151, 154, 188
DOM, API 13 flush(), méthode d'EntityManager 151, 156
forEach (JSTL) 350, 355
formatDate (JSTL) 352
E
formatNumber (JSTL) 352
EAI (Enterprise Application Integration) 465 form (JSF) 371
ear forTokens (JSTL) 350
archive 15 forward, action JSP 345
fichier archive 208 fragment
EclipseLink, framework 54 (Facelets) 359
ECMA (European Computer Manufacturers (JSF) 372
Association) 339 FROM, clause JPQL 167
editableValueHolder (JSF) 377 Future, interface 253
EJBContext, interface 247
EJBException, exception 291, 294
ejb-jar.xml, fichier 208, 249, 281, 292, 297 G
EJBTransactionRequiredException, exception 291
getProperty, action JSP 345
element, action JSP 345
getReference(), méthode d'EntityManager 151, 154
endsWith (JSTL) 358
getResultList(), méthode de Query 173
EntityManager
getSingleResult(), méthode de Query 173
classe 62
gestionnaire d'entités 59 graphicImage (JSF) 368
interface 142, 146 GROUP BY, clause JPQL 169
EntityManagerFactory, classe 62, 144
EntityNotFoundException, exception 154 H
EntityTransaction, classe 152
escapeXml (JSTL) 358 HAVING, clause JPQL 170
ETag, en-tête HTTP 515, 546 headerValues, objet implicite 381

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Index 551

head (JSF) 371 JACC, API 14


Hibernate, ORM 53 JAF, API 13
HttpHeaders, classe 534 jar, archive 15
HTTP, protocole 14 JavaMail, API 13
HTTPS, protocole 14 javax.faces.component.UIComponent (JSF) 363
javax.faces.convert
I convertisseurs JSF 314
paquetage 404
Idempotence 512, 513 javax.faces.validator, paquetage 407
IDL, protocole 14 javax.persistence, paquetage 57
if (JSTL) 350, 355 javax.servlet.Servlet, interface 387
If-Match, en-tête HTTP 515, 516 JAXBContext, classe 476, 477, 478
If-Modified-Since, en-tête HTTP 515, 516 JAXP, API 13
If-None-Match, en-tête HTTP 516 JAX-RPC, API 14
If-Range, en-tête HTTP 516 JAX-RS, API 14
If-Unmodified-Since, en-tête HTTP 516 JAX-WS, API 14
ij, programme 40, 66 JCA, API 13
implementation (JSF) 376 JDBC, API 52
import (JSTL) 350 Jersey, implémentation 46
include JMS, API 13
action JSP 345 JMSException, exception 454, 456, 458, 459, 460
directive JSP 342 JMX, API 14, 16
(Facelets) 359 JNDI, API 13
INDEX(), fonction JPQL 167 join (JSTL) 358
indexOf (JSTL) 358 JPA, API 13
InitialContext, classe 423, 430, 431
JRMP, protocole 14
initParam, objet implicite 382
jsf.js, fichier 412
IN, opérateur JPQL 168
JTA, API 13, 53
inputHidden (JSF) 365
inputSecret (JSF) 365
inputTextarea (JSF) 365 L
inputText (JSF) 365
insert Last-Modified, en-tête HTTP 515
(Facelets) 359 LENGTH(), fonction JPQL 167
(JSF) 372 length (JSTL) 358
insertChildren (JSF) 376 LengthValidator, validateur JSF 314
insertFacet (JSF) 376 Lifecycle, classe 387
interface (JSF) 376 LIKE, opérateur JPQL 168
InvocationContext, interface 275, 276, 277, 279, 280 lock(), méthode d'EntityManager 181
IS EMPTY, opérateur JPQL 168 LOWER(), fonction JPQL 167
IS NULL, opérateur JPQL 168
M
J
MANIFEST.MF, fichier 502, 505
JAAS, API 13 Marshaller, classe 476, 477, 478, 479

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
552 Java EE 6 et GlassFish 3 

MAX(), fonction JPQL 167 out (JSTL) 350, 355


MEMBER, opérateur JPQL 168 outputLabel (JSF) 366
merge(), méthode d'EntityManager 151, 160, 189 outputLink (JSF) 366
message outputScript
composant JSF 401, 402 composant JSF 412, 414, 415
(JSF) 370 (JSF) 371
(JSTL) 352 outputStylesheet (JSF) 371
MessageBodyReader, interface 530, 531, 532, 533 outputText (JSF) 366
MessageBodyWriter, interface 530, 531, 532
MessageConsumer, interface 424, 425, 432, 433,
438, 440, 441, 442 P
MessageDrivenContext, interface 451, 455
page, directive JSP 342
MessageListener, interface 439, 441, 447, 448, 449,
panelGrid (JSF) 369
452, 453, 454, 455, 459
panelGroup (JSF) 369
MessageProducer, interface 424, 437, 438, 443, 444,
param
445, 446, 454, 455, 458, 459
action JSP 345
Metro
(Facelets) 359
implémentation 45
(JSTL) 350, 352, 354, 355
de référence 471
objet implicite 382
MIN(), fonction JPQL 167
paramValues, objet implicite 382
MOD(), fonction JPQL 167
parseDate (JSTL) 352
Mojarra
parse (JSTL) 355
implémentation 46
parseNumber (JSTL) 352
de JSF 2.0 318
PartialViewContext, classe 413
MOM (Middleware Orienté Messages) 419
Passivation 230, 268
MVC (Modèle-Vue-Contrôleur) 311
Persistence, classe 62, 148
mvn persistence.xml, fichier 63, 145, 197, 538, 539, 544
commande Maven 329 persist(), méthode d'EntityManager 151, 188
programme 65, 499, 504 PessimisticLockException, exception 185
plugin, action JSP 345
N pom.xml, fichier 64, 328, 498, 499, 502, 538, 543,
544
NOT, opérateur JPQL 168 Principal 305, 306
Programmation orientée aspect 272
Propriétaire d'une relation 107
O
Providers, classe 534
OASIS (Organization for the Advancement of
Structured Information Standards) 469 Q
OpenMQ (Open Message Queue) 423
implémentation 46 Query
OptimisticLockException, exception 183 interface 172
ORDER BY, clause JPQL 169 (JSTL) 353
ORM, Object-Relational Mapping 52 Queue
OR, opérateur JPQL 168 classe 424
otherwise (JSTL) 350, 355 interface 427, 431

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
 Index 553

QueueConnectionFactory, interface 430, 431 selectOneMenu (JSF) 367


QueueConnection, interface 433, 434 selectOneRadio (JSF) 367
QueueReceiver, interface 427, 439 Serializable, interface 189, 268
QueueSender, interface 437, 438 session
QueueSession, interface 426, 434, 435 interface 424, 425, 426, 427, 432, 433, 434, 438,
440, 441, 445, 450, 453, 454, 458
objet implicite 382
R SessionContext
redirect (JSTL) 350 classe 293, 294, 297, 305, 306
refresh(), méthode d'EntityManager 151, 156, 157, interface 247
188 sessionScope, objet implicite 382
RegexValidator, validateur JSF 314 setDataSource (JSTL) 353
remove setFirstResult(), méthode de Query 174
set (JSTL) 350, 355
(Facelets) 359
setLocale (JSTL) 352
(JSTL) 350
setLockMode(), méthode de Query 174
remove(), méthode d'EntityManager 154, 155, 188
setMaxResults(), méthode de Query 174
repeat (Facelets) 359
setParameter(), méthode de Query 173, 174
replace (JSTL) 358
setProperty, action JSP 345
request
setTimeZone (JSTL) 352
classe 534
SGML (Standard Generalized Markup Language)
objet implicite 382
332
requestEncoding (JSTL) 352
SIZE(), fonction JPQL 167
requestScope, objet implicite 382 SOAP, protocole 14
Requêtes nommées 58 SOA (Service-Oriented Architecture) 465
resource, objet implicite 382 split (JSTL) 358
REST, protocole 14 SQRT(), fonction JPQL 167
RMI-IIOP, protocole 14 startsWith (JSTL) 358
RMI (Remote Method Invocation) 469 StAX, API 13
RPC (Remote Procedure Call) 469 Struts, framework web 317
substringAfter (JSTL) 358
S substringBefore (JSTL) 358
SUBSTRING(), fonction JPQL 167
SAX, API 13 substring (JSTL) 358
ScheduleExpression, classe 257 SUM(), fonction JPQL 167
schemaGen, programme 477 SVG (Scalable Vector Graphics) 313
scriptlet (JSP) 344 synchronized, mot-clé 238, 393
SecurityContext, classe 534 sysinfo, programme 40
SecurityException, exception 306, 307
selectBooleanCheckbox (JSF) 366 T
SELECT, clause JPQL 165
selectManyCheckbox (JSF) 366 Table
selectManyListbox (JSF) 367 primaire 79
selectManyMenu (JSF) 367 secondaire 79
selectOneListbox (JSF) 367 taglib, directive JSP 342

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
554 Java EE 6 et GlassFish 3 

Timer, classe 262 V


TimerService, interface 261
timeZone (JSTL) 352 validate, message SOAP 472, 473, 481, 482, 483,
toLowerCase (JSTL) 358 485, 489, 490, 491, 495, 496, 502, 504
TopicConnectionFactory, interface 430, 431 validateResponse, message SOAP 482, 483, 484,
TopicConnection, interface 433, 434 485, 504
Topic, interface 431, 438, 440, 441, 447, 449, 453, validateResponse, réponse SOAP 481
454, 455 validator
TopicPublisher, interface 437, 438, 439 composant JSF 409
TopicSession, interface 426, 434, 435 interface 408, 409
TopicSubscriber, interface 427, 439, 440 ValidatorException, exception 408
TopLink, ORM 53 valueHolder (JSF) 377
toUpperCase (JSTL) 358 view, objet implicite 382
Transaction (JSTL) 353 viewScope, objet implicite 382
transform (JSTL) 355 volatile, mot-clé 238
TRIM(), fonction JPQL 167
trim (JSTL) 358 W
Types MIME 513
WebApplicationException, exception 532, 536
U WebServiceContex, interface 493
webservices.xml, fichier 499, 500
UIViewRoot (JSF) 363 web.xml, fichier 327, 387, 538, 542, 544
Unmarshaller, classe 476, 477 when (JSTL) 350, 355
UPDATE WHERE, clause JPQL 167
instruction JPQL 171 WML (Wireless Markup Language) 313
(JSTL) 353 wsgen, programme 496
UPPER(), fonction JPQL 167 wsimport, programme 494, 496, 501, 502, 503, 504,
UriBuilder, classe 534, 535 506
UriInfo, classe 524, 534, 535, 539
url (JSTL) 350 X
useBean, action JSP 345
User-Agent, en-tête HTTP 510, 513, 540, 542 xjc, programme 477
UserTransaction, interface 297, 298 XMLHttpRequest, objet 410, 411, 412

customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Référence
Java EE 6 ™

et GlassFish 3 ™

Aujourd’hui, les applications doivent accéder à des TABLE DES MATIÈRES


données, appliquer une logique métier, ajouter des • Tour d’horizon de Java EE 6
couches de présentation et communiquer avec des • Persistance en Java
systèmes externes. Les entreprises tentent de réaliser • ORM : Object-Relational Mapping
• Gestion des objets persistants
toutes ces opérations à moindre coût, en se servant • Méthodes de rappel et écouteurs
de technologies standard et robustes supportant des • Enterprise Java Beans
charges importantes. Si vous êtes dans cette situation, • Beans de session et service timer
• Méthodes de rappel et intercepteurs
ce livre est fait pour vous. • Transactions et sécurité
• JavaServer Faces
Il explore les innovations de Java EE 6, la dernière • Pages et composants
version de la plate-forme Java Enterprise, et examine les • Traitement et navigation
différentes spécifications et la façon de les assembler • Envoi de messages
• Services web SOAP
pour développer des applications. Conçu un peu • Services web REST
comme un tutoriel approfondi (construction progressive
d’une application), tous les aspects de la plate-forme
sont présentés et illustrés par des exemples que le
lecteur peut tester.
Vous apprendrez comment associer des objets à des
bases de données avec JPA 2.0, à écrire une couche À propos de l’auteur :
métier transactionnelle avec EJBTM 3.1, à ajouter une
Antonio Goncalves est un architecte
couche présentation avec JSFTM 2.0 et à interagir avec logiciel. Membre expert de plusieurs
d’autres systèmes tels que JMSTM et les services web JSR (Java Specification Requests) et
SOAP et REST. En outre, tous les exemples de ce livre co-fondateur du JUG (Java User Group)
ont été écrits spécifiquement pour GlassFish 3, la toute de Paris, il participe aux grandes
conférences internationales consacrées
dernière version de l’implémentation de référence pour à Java EE, dont JavaOne, le Server
la plate-forme Java EE. Side Symposium, Devoxx et Jazoon.
Son premier livre Java EE 5 est paru en
Code source téléchargeable sur www.pearson.fr 2007 aux éditions Eyrolles.

Développement
web Niveau : Tous niveaux
Configuration : Multiplate-forme

Pearson Education France ISBN : 978-2-7440-4157-0


47 bis, rue des Vinaigriers
75010 Paris
Tél. : 01 72 74 90 00
Fax : 01 42 05 22 17
www.pearson.fr
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>

Vous aimerez peut-être aussi