java ee 6 et glassfish 2 · 2013-03-15 · customer 27921 at fri mar 11 19:07:20 +0100 2011...

572
Java EE 6 et GlassFish 3 Référence Réseaux et télécom Sécurité Système d’exploitation Antonio Goncalves Programmation Développement web

Upload: others

Post on 30-May-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Java EE 6 et GlassFish 3

Référ

ence

Réseauxet télécom

Sécurité

Système d’exploitation

Antonio Goncalves

Programmation

Développementweb

Page 2: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Java EE 6 et GlassFish 3Antonio Goncalves

Traduit par Éric Jacoboni,

avec la contribution technique de Éric Hébert

Page 3: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 France47 bis, rue des Vinaigriers75010 PARISTél. : 01 72 74 90 00www.pearson.fr

Mise en pages : TyPAO

ISBN : 978-2-7440-4157-0Copyright © 2010 Pearson Education FranceTous droits réservés

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.

Titre original : Beginning Java™ EE6 Platform with GlassFish™ 3

Traduction : Éric Jacoboni, avec la contribution de Éric Hébert

ISBN original : 978-1-4302-1954-5Copyright © 2009 by Antonio Goncalves Tous droits réservés

Édition originale publiée par Apress2855 Telegraph AvenueBerkeley, CA 94705www.apress.com

Page 4: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 5: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 6: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 7: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 8: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 9: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 10: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 11: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 12: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 13: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 14: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 15: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

À 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.

Page 16: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 17: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 !

Page 18: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 19: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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ésentationChapitre 10 : JavaServer FacesChapitre 11 : Pages et composantsChapitre 12 : Traitements et navigation

InteropérabilitéChapitre 13 : Envoi de messagesChapitre 14 : Services web SOAPChapitre 15 : Services web REST

Logique métierChapitre 6 : Enterprise Java BeansChapitre 7 : Beans de session et service timerChapitre 8 : Méthodes de rappel et intercepteursChapitre 9 : Transactions et sécurité

PersistanceChapitre 2 : Persistance en JavaChapitre 3 : ORM : ObjectRelational MappingChapitre 4 : Gestion des objets persistantsChapitre 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.

Page 20: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 [email protected]. Vous pouvez également visiter mon site web, http://www. antoniogoncalves.org.

Page 21: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 22: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 23: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 24: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

Project JPE

J2EE 1.2

J2EE 1.3

J2EE 1.4

Java EE 5

Java EE 6

Applicationd'entreprise

Mai 1998 Dec 199910 specs

Sept 200113 specs

Nov 200320 specs

Mai 200623 specs

Q3 200928 specs

Robuste,évolutif

Servicesweb

Facilité dedéveloppement

Profil WebEoD

ÉlagageConteneurintégrableJAX-RSValidationdes beans

AnnotationsInjectionJPAWS-*JSF

Services webGestionDéploiement

EJB CMPJCA

ServletJSPEJBJMSRMI/IIOP

Profil web

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

Figure 1.1

Historique de J2EE/Java EE.

Page 25: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 26: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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-

Page 27: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Conteneurs standard de Java EE.

RMI / IIOP

RMI / IIOP

RMI / IIOPHTTP SSL

HTTP

<<component>>Servlet

<<component>>JSF

<<executionEnvironment>>Conteneur web

<<component>>Applet

<<executionEnvironment>>Conteneur Applets

<<component>>EJB

<<executionEnvironment>>Conteneur EJB

<<component>>Application

Conteneurd'applications client

SSL

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.

Openmirrors.com

Page 28: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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).

Page 29: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

SAAJJA

X-R

SJA

XR

JAS

PC

JAC

CS

ervices web

Métadonnées W

S

JAX

-WS

JAX

-RP

C

Connecteurs

JTAJPAJS

FJS

TLJavaM

ail

JMS

Gestion

Java SE

Conteneur webJSFJSP Servlet

SAAJ

JAX

-RS

JAX

RJA

SP

CJA

CC

Services w

ebM

étadonnées WS

JAX

-WS

JAX

-RP

C

Connecteurs

JTAJPA

JavaMail

JMS

Gestion

Java SE

Conteneur EJBEJB

Base dedonnéesJava SE

JDBC

JDBC

RMI/IIOPHTTP SSLHTTPSSL

Applet

ConteneurApplets

Java SESAAJ

JPAG

estionM

étadonnées WS

Services w

ebJM

SJA

XR

JAX

-WS

JAX

-RP

C

Application cliente

Conteneurd'applications client

RMI/IIOP

Figure 1.3

Services fournis par les conteneurs.

Openmirrors.com

Page 30: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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

Page 31: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 32: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

Page 33: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 34: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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 services web

1.1 181 http://jcp.org/en/jsr/detail?id=181

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 Pages Standard Tag Library)

1.2 52 http://jcp.org/en/jsr/detail?id=52

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

Page 35: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 communes

1.0 250 http://jcp.org/en/jsr/detail?id=250

Déploiement d’applications Java EE

1.2 88 http://jcp.org/en/jsr/detail?id=88 X

Gestion Java EE 1.1 77 http://jcp.org/en/jsr/detail?id=77 X

Interface de fournisseur de service d’authentification pour les conteneurs

1.0 196 http://jcp.org/en/jsr/detail?id=196

Support du débogage pour les autres langages

1.0 45 http://jcp.org/en/jsr/detail?id=45

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é.

Openmirrors.com

Page 36: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

Page 37: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Java EE 6 complet

Profil web

Profil X

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.

Openmirrors.com

Page 38: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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 Language

1.2

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 communes

1.0 250 http://jcp.org/en/jsr/detail?id=250

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).

Page 39: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

22 Java EE 6 et GlassFish 3

Listing 1.1 : Un bean géré par JSF

@ManagedBeanpublic 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

@Statelesspublic 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

Openmirrors.com

Page 40: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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

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

public void addToCache(Long id, Object object) { if (!cache.containsKey(id))

Page 41: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 42: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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

Diagramme des cas d’utilisation de l’application CD-Bookstore.

Gestion du catalogue des articles

CD BookStore

Gestiondes clients

Parcoursdes commandes

Créationde commande

Validationde carte bancaire

Créationd'un compte

Parcoursdu catalogue

Recherched'un article

Connexion etdéconnexion

Modificationdu compte

Achatd'articles

Employé

Banque

Utilisateur

Client

<<Extend>>

<<Include>>

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.

Page 43: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Openmirrors.com

Page 44: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

Page 45: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 46: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 Tour d’horizon de Java EE 6 29

Descripteurdeprojet

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.

Gestiondesarté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

Page 47: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 48: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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édesprojets

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.

Page 49: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

32 Java EE 6 et GlassFish 3

Extensionsetcycledevie

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.

Openmirrors.com

Page 50: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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).

Page 51: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 52: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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 calculateAge() permettant de calculer l’âge d’un client donné.

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}

Page 53: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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();

Openmirrors.com

Page 54: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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éthodesdetest

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.

Page 55: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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éthodesd’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.

LancementdeJUnit

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.ITime: 0.016 There was 1 failure:

Openmirrors.com

Page 56: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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égrationdeJUnit

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.

Page 57: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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’;

Openmirrors.com

Page 58: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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éeij> select * from customer where custid=3;CUSTID |FIRSTNAME |LASTNAME ---------------------------------------------------3 |Robin |Riou

1 ligne sélectionnéeij> 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

Page 59: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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/uniqueALTER 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-

Openmirrors.com

Page 60: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

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.

ArchitecturedeGlassFishv3

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.

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.

INFO

INFO

Page 61: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture de GlassFish v3.

Conteneurweb JSF JPA Metro Conteneur

EJB JMS

OSGI

Java SE

Centre de mises à jour

Grizzly

Console de gestion

Service denommage Injection

Transaction Sécurité

CLI gestion

Configuration

Clustering

Surveillance

DéploiementCœur de Glassfish V(Module Subsystem)

HK2

Centredemiseà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

Openmirrors.com

Page 62: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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-projetsGlassFish

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

Page 63: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Extensions duserveur web

Clients web

ClientsJava/C++/IIOP

Conteneurd'applications

client

Autres serveursd'applications

compatibles Java EE

Serveurd'administration

Applicationd'administration

Instance d'un serveur d'applications

ServeurHTTP

ÉcouteursHTTP

ÉcouteursIIOP

ORB

Conteneurweb

ConteneurEJB

Servicesweb

Gestiondu cyclede vie

Processus, gestion des threads, contrôle de l'exécution

ConnecteurJava EE

JavaMessageService

Gestionnairede persistance

JDBC

Gestionnairede transactions

Fournisseurde messages

EIS

ServeurHADB

Base dedonnées

Consoled'administration

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.

Openmirrors.com

Page 64: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

Page 65: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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, à

Openmirrors.com

Page 66: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre1 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.

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

INFO

Page 67: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 68: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 69: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 70: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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).

Page 71: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 72: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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.

Page 73: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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}

Openmirrors.com

Page 74: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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

L’entité Book est associée à la table BOOK.

BOOK+IDTITLEPRICEDESCRIPTIONISBNNBOFPAGEILLUSTRATIONS

bigintvarchar(255)doublevarchar(2000)varchar(255)integersmallint

Nullable = falseNullable = falseNullable = trueNullable = trueNullable = trueNullable = trueNullable = true

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

<<entity>>Book

Association

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

Page 75: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

SQL / JDBCMain

Base dedonnées

«Interface»EntityManager

+persist(entity : Object) : void+find(entityClass, : Class<T>, primaryKey : Object) : <T>

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.

Openmirrors.com

Page 76: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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

Page 77: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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é.

Base dedonnées

Existe en mémoire

Détaché Géré Supprimé

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 ;

Openmirrors.com

Page 78: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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é Book présentée dans le Listing 2.3 doit être développée sous le répertoire 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}

Page 79: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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);

Openmirrors.com

Page 80: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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"/>

Page 81: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 82: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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.

Page 83: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 84: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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

Page 85: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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);

Openmirrors.com

Page 86: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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

Page 87: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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]----------------------------------------------------------------

Openmirrors.com

Page 88: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre2 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.

Page 89: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 90: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 91: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

74 Java EE 6 et GlassFish 3

Listing 3.1 : Exemple d’entité Book

@Entitypublic 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é primaire simple.

Openmirrors.com

Page 92: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Synchronisation des données entre l’entité et la table.

BOOK+IDTITLEPRICEDESCRIPTIONISBNNBOFPAGEILLUSTRATIONS

bigintvarchar(255)doublevarchar(2000)varchar(255)integersmallint

Nullable = falseNullable = falseNullable = trueNullable = trueNullable = trueNullable = trueNullable = true

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

<<entity>>Book

Couche de persistanceCouche base de données

Association

Page 93: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 94: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Page 95: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 96: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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é

Page 97: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

L’entité Address est associée à trois tables.

CITY+#IDCITYSTATEZIPCODE

bigintvarchar(255)varchar(255)varchar(255)

Nullable = falseNullable = trueNullable = trueNullable = true

ADDRESS+IDSTREET1STREET2

bigintvarchar(255)varchar(255)

Nullable = falseNullable = trueNullable = true

COUNTRY+#IDCOUNTRY

bigintvarchar(255)

Nullable = falseNullable = true

-id : Long-street1 : String-street2 : String-city : String-zipcode : String-country : String+Address()

<<entity>>Address

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

Openmirrors.com

Page 98: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

@Idet@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

Page 99: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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ésprimairescomposé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

Openmirrors.com

Page 100: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Embeddablepublic 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

@Entitypublic class News {

@EmbeddedId private NewsId id; private String content;

// Constructeurs, getters, setters}

Page 101: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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;

Openmirrors.com

Page 102: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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[]) ;

Page 103: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 =

Openmirrors.com

Page 104: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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;

Page 105: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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));

Openmirrors.com

Page 106: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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.

Page 107: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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

Openmirrors.com

Page 108: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 91

auxquelles 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).

Page 109: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 110: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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

@Entitypublic 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; }

Page 111: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 112: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Page 113: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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.

Openmirrors.com

Page 114: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 97

#BOCK IDVALUE

bigintvarchar(255)

Nullable = falseNullable = true

TAG

BOOK+IDTITLEPRICEDESCRIPTIONISBNNBOFPAGEILLUSTRATIONS

bigintvarchar(255)doublevarchar(2000)varchar(255)integersmallint

Nullable = falseNullable = falseNullable = trueNullable = trueNullable = trueNullable = trueNullable = 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).

Page 115: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

98 Java EE 6 et GlassFish 3

Listing 3.28 : Album CD avec un hachage de pistes

@Entitypublic 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 IDPOSITIONTITLE

bigintintegervarchar(255)

Nullable = falseNullable = trueNullable = true

TRACK

CD+IDTITLEPRICEDESCRIPTIONCOVER

bigintvarchar(255)doublevarchar(255)blob(64000)

Nullable = falseNullable = trueNullable = trueNullable = trueNullable = true

Figure 3.4

Relation entre les tables CD et TRACK.

Openmirrors.com

Page 116: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

Page 117: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

100 Java EE 6 et GlassFish 3

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

@Entitypublic 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>

Openmirrors.com

Page 118: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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>

Page 119: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Embeddablepublic 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

Openmirrors.com

Page 120: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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));

Page 121: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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() {

Openmirrors.com

Page 122: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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).

Page 123: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

106 Java EE 6 et GlassFish 3

.

<<embeddable>>Address

<<entity>><<field access>>

Customer

<<entity>><<property access>>

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

Relation unidirectionnelle entre deux classes.

Class2Class1

Openmirrors.com

Page 124: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Relation bidirectionnelle entre deux classes.

Class2Class1

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

Cardinalité des relations entre classes.

Class2Class1 1 0..*

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

Page 125: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

CustomerClé primaire

1

2

3

Firstname

James

Dominic

Maca

Lastname

Rorisson

Johnson

Macaron

Clé étrangère

11

12

13

AddressClé primaire

11

12

13

Street

Aligre

Balham

Alfama

City

Paris

London

Lisbon

Country

France

UK

Portugal

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.

CustomerClé primaire

1

2

3

Firstname

James

Dominic

Maca

Lastname

Rorisson

Johnson

Macaron

Clé étrangère

11

12

13

AddressClé primaire

11

12

13

Street

Aligre

Balham

Alfama

City

Paris

London

Lisbon

Country

France

UK

Portugal

Table de jointureCustomer Pk

1

2

3

Address Pk

11

12

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,

Openmirrors.com

Page 126: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

Page 127: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

110 Java EE 6 et GlassFish 3

Unidirectionnelleetbidirectionnelle

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

Relation unidirectionnelle entre Customer et Address.

-id : Long-firstname : String-lastname : String-email : String-phoneNumber : String

Customer -id : Long-sreet1 : String-street2 : String-city : String-state : String-zipcode : String-country : String

Address

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

Relation bidirectionnelle entre Customer et Address.

-id : Long-firstName : String-lastName : String-email : String-phoneNumber : String

Customer -id : Long-street1 : String-street2 : String-city : String-state : String-zipcode : String-country : String

Address

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).

Openmirrors.com

Page 128: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 111

Figure 3.13

Relation bidirectionnelle représentée par deux relations unidirectionnelles.

-id : Long-firstName : String-lastName : String-email : String-phoneNumber : String

Customer -id : Long-street1 : String-street2 : String-city : String-state : String-zipcode : String-country : String

Address

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 CUSTOMER et ADDRESS. CUSTOMER est la table propriétaire de la relation car elle contient la clé étrangère vers ADDRESS.

CUSTOMER+IDLASTNAMEPHONENUMBEREMAILFIRSTNAME#ADDRESS FK

bigintvarchar(255)varchar(255)varchar(255)varchar(255)bigint

@Entitypublic class C {

@Id @GeneratedValueprivate Long i ;private String firstName;private String lastName;private String email;private String phoneNumber;@On T O@JoinColumn(name = " s k")private Address address;

}

@Entitypublic class Address {

@Id @GeneratedValueprivate Long id;private String street1;private String street2;private String city;private String state;private String zipcode;private String country;@ n ( p d = " d s")private Customer customer;

}

Nullable falseNu lable trueNu lable trueNu lable trueNu lable trueNu lable true

ADDRESS+IDSTREET2STREET1ZIPCODESTATECOUNTRYCITY

bigintvarchar(255)varchar(255)varchar(255)varchar(255)varchar(255)varchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable trueNullable trueNullable true

Figure 3.14

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

Page 129: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

@OnetoOneunidirectionnelle

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

Un client a une seule adresse.

-id : Long-firstName : String-lastName : String-email : String-phoneNumber : String

1

Customer -id : Long-street1 : String-street2 : String-city : String-state : String-zipcode : String-country : String

Address

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

Openmirrors.com

Page 130: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 113

Listing 3.38 : Une entité Customer avec une seule adresse

@Entitypublic 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

@Entitypublic 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),

Page 131: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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é

Openmirrors.com

Page 132: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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.

@OnetoManyunidirectionnelle

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

Une commande compte plusieurs lignes.

-id : Long-creationDate : Date

Order -id : Long-item : String-unitPrice : Double-quantity : Integer

OrderLine

*

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).

Page 133: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

116 Java EE 6 et GlassFish 3

Listing 3.44 : L’entité Order contient des OrderLine

@Entitypublic 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.).

Openmirrors.com

Page 134: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 117

+IDITEMUNITPRICEQUANTITY

bigintvarchar(255)doubleinteger

Nullable = falseNullable = trueNullable = trueNullable = true

ORDER LINE

+IDCREATIONDATE

biginttimestamp

Nullable = falseNullable = true

ORDER

+#ORDER ID+#ORDERLINES ID

bigintbigint

Nullable = falseNullable = false

ORDER ORDER LINE

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

@Entitypublic class Order {

@Id @GeneratedValue

Page 135: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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

Openmirrors.com

Page 136: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

+IDITEMQUANTITYUNITPRICE#ORDER FK

bigintvarchar(255)integerdoublebigint

Nullable = falseNullable = trueNullable = trueNullable = trueNullable = true

ORDER LINEORDER+IDCREATIONDATE

biginttimestamp

Nullable = falseNullable = true

Figure 3.18

Colonne de jointure entre Order et OrderLine.

@ManytoManybidirectionnelle

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

Page 137: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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.

+IDTITLEPRICEDESCRIPTION

bigintvarchar(255)doublevarchar(255)

Nullable = falseNullable = trueNullable = trueNullable = true

CD

+IDLASTNAMEFIRSTNAME

bigintvarchar(255)varchar(255)

Nullable = falseNullable = trueNullable = true

ARTIST

+#CD FK+#ARTIST FK

bigintbigint

Nullable = falseNullable = false

JND ART CD

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.

Openmirrors.com

Page 138: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Quatre entités avec des relations gloutonnes.

Class2Class1 1gloutonne gloutonne gloutonne

Class31..* Class41..*

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

Quatre entités avec des relations paresseuses.

Class2Class1 1paresseuse paresseuse paresseuse

Class31..* Class41..*

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.

Page 139: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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.

Openmirrors.com

Page 140: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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

@Entitypublic 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}

Page 141: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic class Comment {

@Id @GeneratedValue

Openmirrors.com

Page 142: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

@Entitypublic 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.

Page 143: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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é.

Openmirrors.com

Page 144: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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

Hiérarchie d’héritage entre CD, Book et Item.

-isbn : String-publisher : String-nbOfPage : Integer-illustrations : Boolean

<<entity>>Book

-musicCompany : String-numberOfCDs : Integer-totalDuration : Float-gender : String

<<entity>>CD

-id : Long-title : String-price : Float-description : String

<<entity>>Item

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égieutilisantuneseuletable

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)

Page 145: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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

@Entitypublic 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.

Openmirrors.com

Page 146: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 129

Figure 3.23

Structure de la table ITEM.

ITEM+IDDTYPETITLEPRICEDESCRIPTIONILLUSTRATIONSISBNNBOFPAGEPUBLISHERMUSICCOMPANYNUMBEROFCDSTOTALDURATIONGENDER

bigintvarchar(31)varchar(255)doublevarchar(255)smallintvarchar(255)integervarchar(255)varchar(255)integerdoublevarchar(255)

Nullable = falseNullable = trueNullable = falseNullable = falseNullable = trueNullable = trueNullable = trueNullable = trueNullable = trueNullable = trueNullable = trueNullable = trueNullable = 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

Fragment de contenu de la table ITEM.

ID

12345

DTYPE

ItemCDCDBookBook

TITLE

PenSoulTraneZootAlluresThe robots of dawnH2G2

PRICE

2,1023,501822,3017,50

DESCRIPTION

Beautiful black penFantastic jazz albumOne of the best of ZappaRobots everywhereFunny IT book ;0)

MUSICCOMPANY

PrestigeWarner

ISBN

0-554-4561-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")

Page 147: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 148: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 ORM : Object-Relational Mapping 131

Figure 3.25

La table ITEM avec un nom et des valeurs différentes pour la colonne discriminante.

ID

12345

DISC

ICCBB

TITLE

PenSoulTraneZootAlluresThe robots of dawnH2G2

PRICE

2,1023,501822,3017,50

DESCRIPTION

Beautiful black penFantastic jazz albumOne of the best of ZappaRobots everywhereFunny IT book ;0)

MUSICCOMPANY

PrestigeWarner

ISBN

0-554-4561-278-983

...

...

...

...

...

...

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égieparjointure

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}

Page 149: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

+#IDILLUSTRATIONSISBNNBOFPAGEPUBLISHER

bigintsmallintvarchar(255)integervarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable true

BOOK+IDDTYPETITLEPRICEDESCRIPTION

bigintvarchar(31)varchar(255)doublevarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable true

ITEM+#IDMUSICCOMPANYNUMBEROFCDSTOTALDURATIONGENDER

bigintvarchar(255)integerdoublevarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable true

CD

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égieunetableparclasse

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;

Openmirrors.com

Page 150: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

+IDTITLEPRICEDESCRIPTION

bigintvarchar(255)doublevarchar(255)

Nullable falseNullable trueNullable trueNullable true

ITEM+IDTITLEPRICEILLUSTRATIONSDESCRIPTIONISBNNBOFPAGEPUBLISHER

bigintvarchar(255)doublesmallintvarchar(255)varchar(255)integervarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable trueNullable trueNullable trueNullable true

BOOK+IDMUSICCOMPANYNUMBEROFCDSTITLETOTALDURATIONPRICEDESCRIPTIONGENDER

bigintvarchar(255)integervarchar(255)doubledoublevarchar(255)varchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable trueNullable trueNullable trueNullable true

CD

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.

Page 151: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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-

Openmirrors.com

Page 152: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

+IDTITLEPRICEDESCRIPTION

bigintvarchar(255)doublevarchar(255)

Nullable falseNullable trueNullable trueNullable true

ITEM+BOOK_IDBOOK TITLEBOOK DESCRIPTIONPRICEILLUSTRATIONSISBNNBOFPAGEPUBLISHER

bigintvarchar(255)varchar(255)doublesmall ntvarchar(255)ntegervarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable trueNullable trueNullable trueNullable true

BOOK+CD_IDCD TITLECD DESCRIPTIONPRICEMUSICCOMPANYNUMBEROFCDSTOTALDURATIONGENDER

bigintvarchar(255)varchar(255)doublevarchar(255)integerdoublevarchar(255)

Nullable falseNullable trueNullable trueNullable trueNullable trueNullable trueNullable trueNullable true

CD

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ésabstraites

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.

Page 153: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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}

Openmirrors.com

Page 154: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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;

Page 155: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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"

@Entitypublic 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));

Openmirrors.com

Page 156: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre3 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.

Page 157: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 158: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 159: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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.

Openmirrors.com

Page 160: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 161: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

SQL / JDBCMain

Base dedonnées

<<Interface>>EntityManager

+persist(entity : Object) : void+find(entityClass, : Class<T>, primaryKey : Object) : <T>

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 :

Openmirrors.com

Page 162: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 163: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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();

Openmirrors.com

Page 164: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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.

Page 165: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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);

Openmirrors.com

Page 166: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 167: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Openmirrors.com

Page 168: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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, Object primaryKey)

Recherche une entité de la classe et de la clé indiquées.

<T> T getReference(Class<T> entityClass, Object primaryKey)

Obtient une instance dont l’état peut être 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

@Entitypublic class Customer {

@Id @GeneratedValue private Long id; private String firstName; private String lastName;

Page 169: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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.

+IDSTREET1ZIPCODECOUNTRYCITY

bigintvarchar(255)varchar(255)varchar(255)varchar(255)

Nullable = falseNullable = trueNullable = trueNullable = trueNullable = true

ADDRESSCUSTOMER+IDLASTNAMEEMAILFIRSTNAME#ADDRESS FK

bigintvarchar(255)varchar(255)varchar(255)bigint

Nullable = falseNullable = falseNullable = trueNullable = trueNullable = 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.

Rendreuneentité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é

Openmirrors.com

Page 170: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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", "[email protected]"); 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.

Page 171: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Rechercheparidentifiant

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 }

Openmirrors.com

Page 172: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 Gestion des objets persistants 155

Suppressiond’uneentité

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", "[email protected]"); 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 accessibleassertNotNull(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.

Suppressiondesorphelins

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.

Page 173: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Entitypublic 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).

Synchronisationaveclabasededonné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.

Openmirrors.com

Page 174: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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");

Page 175: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

158 Java EE 6 et GlassFish 3

Contenuducontextedepersistance

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", "[email protected]");

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.

Openmirrors.com

Page 176: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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", "[email protected]");

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)).

Fusiond’uneentité

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", "[email protected]");

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();

Page 177: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Modificationd’uneentité

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", "[email protected]");

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

customer.setFirstName("William");

tx.commit();

Répercussiond’é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,

Openmirrors.com

Page 178: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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", "[email protected]"); 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", "[email protected]"); 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 {

Page 179: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 180: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 181: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 :

Openmirrors.com

Page 182: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 183: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 184: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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’

Page 185: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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’

Liaisondeparamè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

Openmirrors.com

Page 186: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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)

OrderBy

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

GroupBy 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

Page 187: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>]

Openmirrors.com

Page 188: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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.

Page 189: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 sqlString)

Crée une instance de Query permettant d’exécuter une instruction SQL native.

Query createNativeQuery(String sqlString, Class resultClass)

Crée une instance de Query permettant 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);

Openmirrors.com

Page 190: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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.

Page 191: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 set LockMode(LockModeType).

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)

Openmirrors.com

Page 192: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 193: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 {...}

Openmirrors.com

Page 194: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 195: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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();

Openmirrors.com

Page 196: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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

Page 197: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 €.

temps

tx1.begin()

// Le prix du livre est de 10€Book book = em.find(Book.class, 12);

book.raisePriceByT Euros();

tx1.comit();// Le prix est maintenant de 12€

tx2.begin()

// Le prix du livre est de 10€Book book = em.find(Book.class, 12);

book.raisePriceByFi Euros();

tx2.comit();// Le prix est maintenant de 15€

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.

Openmirrors.com

Page 198: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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, Object primaryKey, LockModeType lockMode)

Recherche une entité de la classe avec la clé indiquée et la verrouille selon le type du verrou.

void lock(Object entity, LockModeType lockMode)

Verrouille une instance d’entité contenue dans le contexte de persistance avec le type de verrou indiqué.

void refresh(Object entity, LockModeType lockMode)

Rafraîchit l’état de l’instance à partir de la base de 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 lockMode)

Fixe le type de verrou utilisé pour l’exécution de 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 :

Page 199: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

182 Java EE 6 et GlassFish 3

Book book = em.find(Book.class, 12); // Verrouille pour augmenter le prixem.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 prixbook.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

@Entitypublic 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}

Openmirrors.com

Page 200: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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.

Page 201: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

temps

tx1.begin();

// Le prix du livre est de 10€Book book = em.find(Book.class, 12);

b t sio ) == 1

book.raisePriceByTwoEuros();

tx1.comit();// Le prix est maintenant de 12€

b sio ( == 2

tx2.begin();

// Le prix du livre est de 10€Book book = em.find(Book.class, 12);

k t s == 1

book.raisePriceByFiveEuros();

tx2.comit(); d t êt 1 ll ut 2 p k p

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 prixem.lock(book, LockModeType.OPTIMISTIC); book.raisePriceByTwoDollars();

Openmirrors.com

Page 202: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre4 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.

Page 203: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 204: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 205: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Cycle de vie d’une entité.

Base dedonnées

Existe en mémoire

Détachée Gérée Supprimée

Supprimée par le ramasse-miettes

Supprimée dela base de données,

mais toujours en mémoire

Customer cust = em.find()Requête JPQL

em.refresh(cust)Modifiée avec les accesseurs

Customer cust = new Customer()

em.persist(cust)

em.remove(cust)

em.merge(cust)

em.clear()em.merge(cust)Sérialisée vers une autre couche

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.

Openmirrors.com

Page 206: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre5 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é.

Page 207: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 d’une entité avec les annotations des méthodes de rappel.

Existe en mémoire

Détachée Gérée Supprimée

Supprimée par le ramasse-miettes

findById ou JPQL

@PostLoad

@PostRemove

@PreRemove

@PrePersist

@PostPersist

@PreUpdate and @PostUpdate lorsque les accesseurssont appelés

@PostLoad après le rafraîchissement

@PostLoad après fusion@PreUpdate et @PostUpdatesi l'entité a été modifiée

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é.

Openmirrors.com

Page 208: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre5 Méthodes de rappel et écouteurs 191

Listing 5.1 : Entité Customer avec méthodes de rappel

@Entitypublic 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

Page 209: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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)

Openmirrors.com

Page 210: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre5 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"); }}

Page 211: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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)

Openmirrors.com

Page 212: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre5 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"); }

Page 213: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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">

Openmirrors.com

Page 214: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre5 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

Page 215: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 216: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 217: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture en couches. <<layer>>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

Openmirrors.com

Page 218: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Page 219: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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; }}

Openmirrors.com

Page 220: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Page 221: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 222: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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.

Page 223: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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;

Openmirrors.com

Page 224: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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.

Page 225: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Assemblage des EJB. <<artifact>>

BookEJB.jarsession/BookEJB.ClassMETA-INF/ejb-jar.xml

<<artifact>>BookApplication.ear

<<artifact>>BookEntity.jar

entity/Book.ClassMETA-INF/persitence.xml

<<artifact>>BookServlet.war

servlet/BookServlet.ClassWEB-INF/web.xml

<<artifact>>BookApplication.war

session/BookEJB.classentity/Book.class

servlet.BookServletMETA INF/ejb jar.xml

META INF/persistence.xmlWEB 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

Openmirrors.com

Page 226: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Page 227: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 ;

Openmirrors.com

Page 228: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Page 229: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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, singleton)

Oui Oui

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érenceGlassFish 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.).

Openmirrors.com

Page 230: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Récapitulatif.

chapter06DBjdbc/chapter06DS

+findBooks() : List<Book>+findBookByld(id : Long) : Book+createBook(book : Book) : Book+deleteBook(book : Book) : void+updateBook(book : Book) : Book

<<Interface>>BookEJBRemote+main(args : String []) : void

Main

+findBooks() : List<Book>+findBookByld(id : Long) : Book+createBook(book : Book) : Book+deleteBook(book : Book) : void+updateBook(book : Book) : Book

em : EntityManager

<<stateless ejb>>BookEJB

id : Longtitle : Stringprice : Floatdescription : Stringisbn : StringnbOfPage : Integerillustrations : Boolean

<<entity>>Book

Page 231: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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}

Openmirrors.com

Page 232: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

@Statelesspublic 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));

Page 233: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Remotepublic 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).

Openmirrors.com

Page 234: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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();

Page 235: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 236: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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

Page 237: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 238: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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.

Page 239: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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); }}

Openmirrors.com

Page 240: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre6 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é.

Page 241: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 242: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 243: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Beans de session et service timer dans un conteneur EJB.

<<component>>Bean de session

<<component>>Service timer

<<executionEnvironment>>Conteneur EJB

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.

Openmirrors.com

Page 244: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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é).

Page 245: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

228 Java EE 6 et GlassFish 3

Figure 7.2

Clients accédant à des beans sans état placés dans un pool.

<<executionEnvironment>>Conteneur EJB

<<component>>Instance 1

<<component>>Instance 2

<<component>>Client 1

<<component>>Client 2

<<component>>Client n

<<executionEnvironment>>Pool

...

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

@Statelesspublic 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; }}

Openmirrors.com

Page 246: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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

Page 247: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Clients accédant à des beans avec état. <<component>>

Instance 1

<<component>>Instance 2

<<component>>Client 1

<<component>>Client 2

<<executionEnvironment>>Conteneur EJB

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

Openmirrors.com

Page 248: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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; }

Page 249: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 250: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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.

Page 251: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

234 Java EE 6 et GlassFish 3

Figure 7.4

Clients accédant à un bean singleton.

<<component>>Instance unique

<<component>>Client 1

<<component>>Client 2

<<executionEnvironment>>Conteneur EJB

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

@Singletonpublic 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; }}

Openmirrors.com

Page 252: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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@Startuppublic class CacheEJB { // ...}

Chaînagedesingletons

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 { ...}

Page 253: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 254: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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; }}

Page 255: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 256: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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.

Page 257: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

240 Java EE 6 et GlassFish 3

Interfacesetclassebean

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

Les beans de session peuvent avoir différents types d’interfaces.

<<component>>Client

Local

Distant

pas d'interface

<<component>>Bean de session

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.

Openmirrors.com

Page 258: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 Beans de session et service timer 241

<<executionEnvironment>>Serveur d'applications

<<component>>Servlet

<<component>>JSF

<<executionEnvironment>>Conteneur web

<<component>>Bean de session

<<component>>Bean de session

<<executionEnvironment>>Conteneur EJB

<<component>>Application cliente

<<component>>Application cliente

<<executionEnvironment>>Conteneur client d'applications

<<component>>Composant quelconque

<<executionEnvironment>>Serveur d'applications

Appellocal

Appel local

*

*

*

* : 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.

Page 259: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 260: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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().

Page 261: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Vuecliente

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

Openmirrors.com

Page 262: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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

Page 263: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 JNDIjava:global/cdbookstore/ItemEJB!com.apress.javaee6.ItemEJBjava: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.

Openmirrors.com

Page 264: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 Beans de session et service timer 247

package com.apress.javaee6; @Stateless public class ItemEJB { ... }// Nom JNDI } java:global/cdbookstore/ItemEJB

Contextedesession

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.

Page 265: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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(); }}

Descripteurdedé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".

Openmirrors.com

Page 266: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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.

Injectiondedé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.

Page 267: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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; } ...}

Contextedenommagedel’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; }}

Openmirrors.com

Page 268: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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.

Page 269: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 270: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 Beans de session et service timer 253

Listing 7.17 : Le bean OrderEJB déclare deux méthodes asynchrones

@Statelesspublic 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@Asynchronouspublic class OrderEJB {

Page 271: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 272: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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); }}

Page 273: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 274: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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>>Bean de session

<<component>>Service timer

Enregistrement automatique ou explicite

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.

Page 275: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

258 Java EE 6 et GlassFish 3

Tableau 7.2 : Attributs d’une expression calendaire

Attribut Description Valeurs possiblesValeur par défaut

second Une ou plusieurs secondes dans une minute

[0,59] 0

minute Une ou plusieurs minutes dans une heure

[0,59] 0

hour Une ou plusieurs heures dans une journée

[0,23] 0

dayOfMonth Un ou plusieurs jours dans un mois

[1,31] ou {"1st", "2nd", "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 une année

[1,12] ou {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}

*

dayOfWeek Un ou plusieurs jours dans une semaine

[0,7] ou {"Sun", "Mon", "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.

Openmirrors.com

Page 276: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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 par une virgule

year = "2008,2012,2016"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 séparés par une barre de fraction

minute = "*/15" 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 à 6:55 heure de Paris

minute="55", hour="6",dayOfWeek="Mon- Fri", timezone="Europe/Paris"

Toutes les minutes minute="*", hour="*"

Toutes les secondes second="*", minute="*", hour="*"

Tous les lundis et vendredis, 30 secondes après midi

second="30", hour="12",dayOfWeek="Mon, Fri"

Toutes les cinq minutes minute="*/5", hour="*"

Page 277: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 à 15 h 00

hour="15", dayOfMonth="Last Mon",month="Dec"

Trois jours avant le dernier jour de chaque mois à 13 h 00

hour="13", dayOfMonth="-3"

Toutes les deux heures à partir de midi le second mardi de chaque mois

hour="12/2", dayOfMonth="2nd Tue"

Toutes les 14 minutes de 1 h 00 et 2 h 00

minute = "*/14", hour="1,2"

Toutes les 14 minutes de 1 h 00 et 2 h 00

minute = "0,14,28,42,56",hour = "1,2"

Toutes les 10 secondes à partir de la 30e seconde

second = "30/10"

Toutes les 10 secondes à partir de la 30e seconde

second = "30,40,50"

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")

Openmirrors.com

Page 278: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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");

Page 279: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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(); // ... }}

Openmirrors.com

Page 280: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre7 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.

Page 281: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 282: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 283: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 284: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 Méthodes de rappel et  intercepteurs 267

Figure 8.1

Cycle de vie des beans sans état et singletons.

N'existe pas

Prêt

4. Appel de méthode

1. Nouvelle instance2. Injection de dépendances3. @PostConstruct

5. @PreDestroy

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

Page 285: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 286: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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

Cycle de vie d’un bean avec état.

N'existe pas

Prêt Passivé

4. Appel de méthode

6. PostActivate

7. Expiration du délai

5. @PrePassivate

1. Nouvelle instance2. Injection de dépendances3. @PostConstruct

8. @Remove et @Predestroy

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.

Page 287: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Openmirrors.com

Page 288: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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

@Singletonpublic 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).

Page 289: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

272 Java EE 6 et GlassFish 3

Listing 8.2 : Bean avec état, initialisant et libérant des ressources

@Statefulpublic 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.

Openmirrors.com

Page 290: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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>>Client

<<component>>Bean de session

Intercepteur nIntercepteur 1

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.

Page 291: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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 {

Openmirrors.com

Page 292: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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.

Page 293: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Chaînage de différents intercepteurs.

Client Conteneur Logger InvocationContextCustomerEJB

1 : createCustomer2 : logMethod

3 : entering

4 : proceed

6 : exiting5 : createCustomer

Openmirrors.com

Page 294: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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

@Statelesspublic class CustomerEJB { @PersistenceContext(unitName = "chapter08PU")

Page 295: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 296: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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.

Page 297: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 298: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre8 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.

Page 299: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

282 Java EE 6 et GlassFish 3

<<executionEnvironment>>Conteneur EJB

<<component>>Client

<<component>>Bean de session

Intercepteurde classe

Intercepteurde méthode

Intercepteurpar défaut

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.

Openmirrors.com

Page 300: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 301: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 302: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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

Transaction n’impliquant qu’une seule ressource.

Ressource

Gestionnairede ressources

Gestionnairede transactions

Application

JTA

JTA

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

Page 303: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 304: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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

Transaction XA impliquant deux ressources.

Ressource

Gestionnairede ressources

Ressource JTA / XA

Ressource

Gestionnairede ressources

Gestionnairede transactions

Application

JTA

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.

Page 305: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

288 Java EE 6 et GlassFish 3

Figure 9.3

Validation en deux phases.

Gestionnairede ressources

Gestionnairede ressources

Gestionnairede transactions

prêt

prêt

prépare

prépare

Phase 1 Préparation

Gestionnairede ressources

Gestionnairede ressources

Gestionnairede transactions

validée

validée

valide

valide

Phase 2 Validation

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

Une transaction distribuée XA.

Ressource

Gestionnairede ressources

Gestionnairede transactions

Application

JTA

JTA / XA

Ressource

Gestionnairede ressources

Gestionnairede transactions

JTA / XA

JTS / OTSJTS / OTS

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

Openmirrors.com

Page 306: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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

@Statelesspublic class ItemEJB {

@PersistenceContext(unitName = "chapter09PU") private EntityManager em; @EJB

Page 307: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 InventoryEJB TransactionItemEJB

1 : createBook

2 : begin

5 : validation ou annulation

4 : additem

3 : createBook

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é à

Openmirrors.com

Page 308: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 309: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

InventoryEJBItemEJB Attribut CMT

REQUIRED

REQUIRES NEW

SUPPORTS

MANDATORY

NOT SUPPORTED

NEVER

Résultat

Nouvelle transaction

Nouvelle transaction

Pas de transaction

Exception

Pas de transaction

Pas de transaction

Attribut CMT

REQUIRED

REQUIRES NEW

SUPPORTS

MANDATORY

NOT SUPPORTED

NEVER

Résultat

Transaction du client

Nouvelle transaction

Transaction du client

Transaction du client

Pas de transaction

Exception

additem

additem1. createBook() La méthode X n'est pas appelée dans une transaction.

1. createBook() La méthode X est dans une transaction.

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(); }

Openmirrors.com

Page 310: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Marquaged’unCMTpourannulation

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

@Statelesspublic class InventoryEJB {

@PersistenceContext(unitName = "chapter09PU") private EntityManager em; @Resource

Page 311: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Traitementdesexceptions

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.

Openmirrors.com

Page 312: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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

@Statelesspublic 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

Page 313: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 314: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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é).

Page 315: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

298 Java EE 6 et GlassFish 3

Listing 9.6 : Bean sans état avec BMT

@Statelesspublic 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.

Openmirrors.com

Page 316: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 317: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Scénario de sécurité classique avec JAAS.

Principal authentifiépar mot de passe

Conteneur webou

AACConteneur EJB

Authentifie Autorise

Authentifie

Systèmed'authentification

JAAS

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.

Openmirrors.com

Page 318: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 319: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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)); }}

Openmirrors.com

Page 320: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 321: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 322: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 323: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic 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; }}

Openmirrors.com

Page 324: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre9 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.

Page 325: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 326: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 327: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture de JSF.

XULJSP

FacesServlet

(Ajax)

Mot

eur d

e re

nduRequête

HTTP

RéponseHTTP

Navigation

Validateur

Convertisseur

XHTML

ComposantComposantComposant

Bean géréfaces-config.xml

(facultatif)

Openmirrors.com

Page 328: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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.

Page 329: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 330: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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

Page 331: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 332: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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

@ManagedBeanpublic 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}" />

Page 333: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 334: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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.

Page 335: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 336: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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

Pages et classes impliquées dans l’application web.

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

<<entity>>Book

-cm : EntityManager+findBooks() : List<Book>+createBook(book : Book) : Book

<<stateless ejb>>BookEJB

+doList() : String+doCreateBook() : String

<<managed bean>>BookController

newBook.xhtml

listBook.xhtml

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.

Page 337: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@Statelesspublic class BookEJB {

Openmirrors.com

Page 338: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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.

Page 339: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 340: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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>

Page 341: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

La page newBook.xhtml.

Create a book

Create a new book

ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

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é.

Openmirrors.com

Page 342: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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

La page listBooks.xhtml.

List of the books

APress Beginning Java EE 6

Create a new book

Description

Scifi IT book

Asimov Best seller

The trilogy

Price

12.0

18.5

23.25

Title

H2G2

Robots

Dune

ISBN

1234 234

564 694

256 6 56

Number Of Pages

241

317

529

Illustrations

false

true

false

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).

Page 343: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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"/>

Openmirrors.com

Page 344: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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>

Page 345: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 346: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre10 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-componentschapter10-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

Page 347: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 348: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 349: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 350: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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

Représentation graphique de la page newBook.html.

Create

Create a new book

ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

APress Beginning Java EE 6

Page 351: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 352: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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

Page 353: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 354: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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

Représentation graphique de la page newBook.xhtml.

Create a new book

ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

APress Beginning Java EE 6

Create

Page 355: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 356: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>

Page 357: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

La page newBook.html affiche un message d’erreur.

Create a new book

ISBN :

Tiltle :

Price : Please, fill the price !

Description :

Number of pages :

Illustrations :

APress Beginning Java EE 6

Create

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

Openmirrors.com

Page 358: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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 ;

Page 359: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 />

Openmirrors.com

Page 360: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>

Page 361: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 362: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 363: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

346 Java EE 6 et GlassFish 3

Figure 11.5

La page listBooks.jsp affiche une liste de livres.

List of the books

APress Beginning Java EE 6

Description

Scifi IT book

Best seller

The trilogy

Price

12.0

18.5

23.25

Title

H2G2

Robots

Dune

ISBN

1234 234

56 694

256 6 56

Number Of Pages

241

317

529

Illustrations

true

true

true

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>

Openmirrors.com

Page 364: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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

Page 365: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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]}

Openmirrors.com

Page 366: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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 :

Page 367: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 368: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 369: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 370: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 371: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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/>

Openmirrors.com

Page 372: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>.

Page 373: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 374: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>

Page 375: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 376: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 377: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 378: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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/functions

fn Les pages Facelets peuvent utiliser tous 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.

Page 379: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

362 Java EE 6 et GlassFish 3

Figure 11.6

Cycle de vie de JSF. Restauration

de la vueRequête

Fin dela réponse

Fin dela réponse

Fin dela réponse

Fin dela réponse

Fin dela réponse

Erreurs de conversion ou de validation

Applicationdes valeurs

de la requête

Traitementdes

événementsValidations

Traitementdes

événements

RéponseTraitement

desévénements

Appel del'application

Traitementdes

événements

Modificationdes valeursdu modèle

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.

Openmirrors.com

Page 380: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 381: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 382: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 383: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 384: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 385: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

368 Java EE 6 et GlassFish 3

Marqueur Représentation graphique

h:selectBooleanCheckbox

h:selectManyCheckbox

h:selectManyListbox

h:selectManyMenu

h:selectOneListbox

h:selectOneMenu

h:selectOneRadio

HistoryBiographyLiteratureComicsChildScifi

History

HistoryBiographyLiteratureComicsChildScifi

History

History Biography Literature Comics Child Scifi

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"/>

Grillesettableaux

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).

Openmirrors.com

Page 386: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>

Page 387: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 :

Three

Six

Two

Five

One

Four

Three

Six

Two

Five

One

Four

Header

Footer

Messagesd’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>

Openmirrors.com

Page 388: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

SaveEnter a tiltle :

Validation Error : Value is required.

Informationscomplé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.

Page 389: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 390: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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>

Page 391: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

La page newBook.html avec le template layout.xhtml.

Create a book

Create a new book

ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

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),

Openmirrors.com

Page 392: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 393: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 394: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 395: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

378 Java EE 6 et GlassFish 3

Create a cd

Create a new CD

Tiltle :

Price :

Description :

Music company :

Number of CDs :

Total duration :

Gender :

APress Beginning Java EE 6

Create a book

Create a new book

Tiltle :

Price :

Description :

ISBN :

Number of pages :

Illustrations :

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}">

Openmirrors.com

Page 396: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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

Page 397: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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}"/>

Openmirrors.com

Page 398: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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 web. Sert à obtenir les paramètres de configuration de cette application.

Object

applicationScope Associe les noms d’attributs de l’application à leurs valeurs.

Map

component Désigne le composant courant. UIComponent

compositeComponent Désigne le composant composite courant. UIComponent

cookie Désigne un Map contenant les noms des cookies (clés) et des objets Cookie.

Map

facesContext Désigne l’instance FacesContext de cette requête.

FacesContext

header Fait correspondre chaque nom d’en-tête HTTP à une seule valeur de type String.

Map

headerValue Fait correspondre chaque nom d’en-tête HTTP à un tableau String[] contenant toutes les valeurs de cet en-tête.

Map

Page 399: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 d’initialisation du contexte à leurs valeurs de type String.

Map

param Fait correspondre chaque nom de paramètre à une seule valeur de type String.

Map

paramValues Fait correspondre chaque nom de paramètre à un tableau String[] contenant toutes les valeurs de ce paramètre.

Map

request Représente l’objet requête HTTP. Object

requestScope Fait correspondre les noms des attributs de la requête à leurs valeurs.

Map

resource Indique l’objet ressource. Object

session Représente l’objet session http. Object

sessionScope Fait correspondre les noms des attributs de la session à leurs valeurs.

Map

view Représente la vue courante. UIViewRoot

viewScope Fait correspondre les noms des attributs de la vue à leurs valeurs.

Map

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 :

Openmirrors.com

Page 400: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre11 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.

Page 401: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 402: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page 403: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Le modèle de conception MVC.

ClientServer

Contrôleur(FacesServlet)

Modèle(backing bean)

Vue(pages XHTML)

Navigateur

Requête HTTP

Réponse HTTP

manipule et redirige

crée et gère

accède

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.

Openmirrors.com

Page 404: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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

Interactions de FacesServlet.

Cycle de vie

FacesContext

FacesServletBouton Événement

2. Passe le contrôle au cycle de vie

3. Traitement en 6 étapes

1. Crée un FaceContext

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>

Page 405: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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_METHOD

Définit l’emplacement de sauvegarde de l’état. Les 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_JSF_VIEWHANDLER

Désactive Facelets comme langage de déclaration de page (PDL).

javax.faces.LIBRARIES Liste des chemins qui seront considérés comme une bibliothèque de marqueurs Facelets.

Openmirrors.com

Page 406: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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.

Page 407: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 408: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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

@ManagedBeanpublic 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

Page 409: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 410: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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,

Page 411: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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}

Openmirrors.com

Page 412: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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).

Annotationducycledevieetdesméthodesderappel

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

Page 413: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Cycle de vie d’un bean géré.

N'existe pas

@PostConstruct @PreDestroy

Appel de méthode

Prêt

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).

Openmirrors.com

Page 414: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 Traitement et navigation 397

Create

Create a new book

ISBN

Tiltle

Price

Description

Number of pages

Illustrations

APress Beginning Java EE 6

List of the books

APress Beginning Java EE 6

Create a new book

Description

Scifi IT book

Asimov Best seller

The trilogy

Price

12 0

18 5

23 25

Title

H2G2

Robots

Dune

ISBN

1234 234

564 694

256 6 56

Number Of Pages

241

317

529

Illustrations

false

true

false

<h:commandButton>

newBook.xhtml listBooks.xhtml

<h:commandLink>

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

@ManagedBeanpublic class BookController {

@EJB private BookEJB bookEJB;

Page 415: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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();

Openmirrors.com

Page 416: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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.

Page 417: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 418: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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

Page 419: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Page affichant un message pour un composant d’interface précis.

Create

Create a new book

ISBN :

Tiltle :

Price : Please, fill the price !

Description :

Number of pages :

Illustrations :

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

Openmirrors.com

Page 420: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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 composantsreconverties pour l'affichage• appel de la méthode getAsString()

Valeurs des composantsconverties en objets• conversion par défaut• conversion personnalisée• appel de la méthode getAsObject()

Valeurs des composants validées• validation standard• validation personnalisée

Restaurationde la vue

Erreurs de conversion ou de validation

Applicationdes valeurs

de la requête

Traitementdes

événementsvalidations

Traitementdes

événements

Affichagede la

réponse

Traitementdes

événementsAppel de

l'applicationTraitement

desévénements

Mise à jourdes valeursdu modèle

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

Page 421: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 422: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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>

Page 423: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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) {

Openmirrors.com

Page 424: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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.

Page 425: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 426: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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>

Page 427: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 428: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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

Appels HTTP classiques vs. appels HTTP Ajax.

Appel HTTP Appel Ajax

Appel JavaScript

Bibliothèque Ajax

NavigateurNavigateur

Page web Page web

Serveur

XMLRequête XMLHttp

XML

Page XHTMLRequête HTTP

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.

Page 429: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 430: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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.

Page 431: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

414 Java EE 6 et GlassFish 3

Figure 12.8

Une seule page pour créer et afficher tous les livres.

List of the books

APress Beginning Java EE 6

Description

Scifi IT book

Best seller

Price dollar

12.0

18.5

Title

H2G2

Robots

ISBN

1234 234

564 694

Number Of Pages

241

317

Illustrations

false

true

Create a book

Create a new book

ISBN :

Tiltle :

Price :

Description :

Number of pages :

Illustrations :

256 6 56

Dune

23.25

The trilogy

529

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">

Openmirrors.com

Page 432: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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>

Page 433: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 434: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre12 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>

Page 435: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 436: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 437: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture du MOM.

DestinationProducteur Consommateurenvoie reçoit

Fournisseurde messages

Mess

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 :

Openmirrors.com

Page 438: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Page 439: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 440: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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();

Page 441: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 442: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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

Page 443: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture de JMS.

produit un messageFournisseur JMS

consomme un message

recherche les objets administrés recherche les objets administrésAnnuaire JNDI

ConsommateurProducteur

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.

Openmirrors.com

Page 444: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Émetteur Récepteurenvoie reçoit

Mess Mess

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

Page 445: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Émetteur

Récepteur 1

Récepteur 2

Mess1

Mess2

Mess3

Mess1

Mess2

Mess3

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-abonnement. Éditeur

publie

s'abonne

reçoit

Abonné

Mess

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

Openmirrors.com

Page 446: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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és.

Éditeur

Abonné 1

Abonné 2

Mess3

Mess2

Mess3

Mess2

Mess2

Mess1

Mess1

Mess1

Mess3

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

API JMS (inspirée de la Figure 2.1 de la spécification JMS 1.1).

reçoit de

créecrée

crée

crée

envoie à

<<Interface>>MessageProducer

<<Interface>>ConnectionFactory

<<Interface>>Connection

<<Interface>>Session

<<Interface>>Message

<<Interface>>Destination

<<Interface>>MessageConsumer

Page 447: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

430 Java EE 6 et GlassFish 3

Objetsadministré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");

Openmirrors.com

Page 448: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Page 449: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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();

Openmirrors.com

Page 450: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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

Page 451: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

434 Java EE 6 et GlassFish 3

messages dans une unité de traitement atomique : si vous envoyez plusieurs mes-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 d’un message JMS.

En-têteJMSMessageID

JMSCorrelationIDJMSDeliveryModeJMSDestinationJMSExpiration

JMSPriorityJMSRedelivered

JMSReplyToJMSTimestamp

CorpsBytesMessageTextMessage

ObjectMessageMapMessage

StreamMessage

Propriétés<name><value><name><value><name><value>

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.

Openmirrors.com

Page 452: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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 message.

send() ou publish()

JMSDeliveryMode JMS reconnaît deux modes de délivrance 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.

send() ou publish()

JMSMessageID Valeur identifiant de manière unique chaque message envoyé par un fournisseur.

send() ou publish()

JMSTimestamp Horodatage indiquant l’instant où le message a été passé à un fournisseur pour être envoyé.

send() ou publish()

1 Spécification de JMS 1.1, "3.4: Message Header Fields" (http://jcp.org/en/jsr/detail?id=914).

Page 453: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 lier un message à un autre : pour lier un message de réponse à son message de requête, par exemple.

Le client

JMSReplyTo Contient la destination à laquelle renvoyer la réponse au message.

Le client

JMSRedelivered Valeur booléenne initialisée par le fournisseur pour indiquer si un message a été à nouveau délivré.

Le fournisseur

JMSType Identifiant du type du message. Le client

JMSExpiration Date d’expiration d’un message envoyé, calculée et initialisée en fonction de la valeur time-to-live passée à la méthode send().

send() ou publish()

JMSPriority JMS définit dix niveaux de priorités, 0 étant la plus faible et 9, la plus forte.

send() ou publish()

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("orderAmount");

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.

Openmirrors.com

Page 454: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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) :

Page 455: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 456: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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 asynchrones. Émetteur Récepteur

synchrone

Mess Mess

Mess

envoie collecte

envoie

Mess

collecte

prévient

enregistreÉmetteur Récepteur

asynchrone

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) :

Page 457: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 458: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 Envoi de messages 441

MessageListener. Un écouteur de messages est un objet qui se comporte comme un 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);

Page 459: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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);

Openmirrors.com

Page 460: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Utilisationd’undélaid’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);

Page 461: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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);

Persistancedesmessages

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); // ouproducer.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ôledesaccusésderé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.

Openmirrors.com

Page 462: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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 accuse réception d’un message. Émetteur

envoiereçoit

accuse réception

Abonné 1

MessMess

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();

Abonnementsdurables

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  :

Page 463: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 setPriority() ou en la passant en paramètre à la méthode send() :

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

Openmirrors.com

Page 464: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Page 465: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 466: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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()); }}

Page 467: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Injectiondesdé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 :

@PersistenceContextprivate EntityManager em;

Openmirrors.com

Page 468: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 Envoi de messages 451

@EJBprivate 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;

ContexteMDB

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.

Page 469: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

452 Java EE 6 et GlassFish 3

Cycledevieetannotationsderappels

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

Cycle de vie d’un MDB.

Prêt

N'existe pas

onMessage()

@PreDestroy@PostConstruct

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

Openmirrors.com

Page 470: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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;

Page 471: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 472: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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

Page 473: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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(); }}

Openmirrors.com

Page 474: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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}

Page 475: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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(); } }}

Openmirrors.com

Page 476: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Page 477: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 478: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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/ConnectionFactoryasadmin 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/Queuejms/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>

Page 479: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 480: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre13 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.

Page 481: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Openmirrors.com

Page 482: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 483: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Le consommateur découvre le service via un registre.

<<registry>>UDDI

XML/HTTPConsommateur 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.

Openmirrors.com

Page 484: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

Interface WSDL entre le consommateur et le service web.

Protocole de transport

Message SOAP<<component>>Consommateur

JVM Cliente

<<component>>Service web

Conteneur de service web

WSDL

Page 485: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 486: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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.

Page 487: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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-WS2.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).

WebServices1.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.

JAXB2.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

Openmirrors.com

Page 488: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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-Metadata2.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.

JAXR1.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.

Page 489: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

472 Java EE 6 et GlassFish 3

Listing 14.1 : Le service web CardValidator

@WebServicepublic 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

@XmlRootElementpublic class CreditCard {

private String number; private String expiryDate; private Integer controlNumber; private String type;

// Constructeurs, getters, setters}

Openmirrors.com

Page 490: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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,

Page 491: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@XmlRootElementpublic 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

Openmirrors.com

Page 492: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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.

Page 493: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Architecture JAXB. Compilateur de schémasxjc

Générateur de schémasschemagen

Désérialisation( +validation)

Sérialisation(+validation)

instancie

Framew

orkd'exécution

Classes

Objets

Schéma XML

Document XML

respecte

<creditCard> <controlNumber> <expiryDate> <number> <type>VISA</type></creditCard>

<xs:schema> <xs:element/> <xs:complexType> <xs:element> ... </xs:sequence> </xs:complexType</xs:schema>

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

Openmirrors.com

Page 494: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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.

Page 495: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 496: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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).

Page 497: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 498: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

Consommateur invoquant un service web.

<<component>>Consommateur

JVM Cliente

<<component>>Service web

Conteneur de service webWSDL

validate

validateResponse

SOAP / HTTP

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

Page 499: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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"/>

Openmirrors.com

Page 500: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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>

Page 501: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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/>

Openmirrors.com

Page 502: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

Page 503: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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).

Openmirrors.com

Page 504: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 Services web SOAP 487

Extrémitésd’unserviceweb

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).

Page 505: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 :

@WebServicepublic 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.

Openmirrors.com

Page 506: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

@WebServicepublic 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

@WebServicepublic 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.

Page 507: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

@WebServicepublic 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 {

Openmirrors.com

Page 508: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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>

Page 509: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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"/>

Openmirrors.com

Page 510: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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>

Cycledevieetméthodesderappel

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

Cycle de vie des services web.

Appel de méthode

Prêt

N'existe pas

@PreDestroy@PostConstruct

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.

Contexted’unserviceweb

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 @

Page 511: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 512: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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.

validate

validateResponse

<<component>>Consommateur

JVM du client

<<component>>Service web

<<component>>Proxy consommateur

<<component>>Extrémité de service web

Conteneur de service web

WSDL

Javainterface

Javainterface

SOAP / HTTP

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.

Page 513: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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(); cardValidator.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).

Openmirrors.com

Page 514: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

@XmlRootElementpublic 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

@WebServicepublic 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) {

Page 515: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 516: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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

Page 517: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 518: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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");

Page 519: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 520: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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>

Page 521: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 522: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre14 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.

Page 523: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 524: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Page 525: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 526: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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.

Page 527: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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êtesetré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

Requête et réponse http.requête

réponse

GET /index.xhtmlClient Serveur

200 OK <html><head>...

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: */*

Openmirrors.com

Page 528: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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.

Page 529: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

512 Java EE 6 et GlassFish 3

MéthodesdeHTTP

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.

Openmirrors.com

Page 530: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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égociationducontenu

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.

Typesdecontenu

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

Page 531: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Codesd’é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.

Openmirrors.com

Page 532: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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.

Miseencacheetrequêtesconditionnelles

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.

Page 533: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

516 Java EE 6 et GlassFish 3

Figure 15.2

Utilisation du cache et du code 304.

301 Not modified

GET /book/12345

GET/book/12315If-Ncn-Match: 328987

200 OK <book>...</book>ETag: 328987

Client Serveur

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.

Openmirrors.com

Page 534: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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).

Page 535: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 536: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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

Page 537: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 538: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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"/>

Page 539: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 540: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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.

Page 541: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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(); }}

Openmirrors.com

Page 542: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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}")

Page 543: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 544: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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) { // ... }}

Page 545: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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")

Openmirrors.com

Page 546: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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",

Page 547: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 548: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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) {

Page 549: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 550: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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.

Page 551: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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) {

Openmirrors.com

Page 552: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 Services web REST 535

List<java.util.Locale> locales = headers.getAcceptableLanguages() // ou headers.getRequestHeader("accept-language") ...}

Constructiond’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");

Page 553: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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) {

Openmirrors.com

Page 554: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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

Cycle de vie d’une ressource.

N'existe pas

Prêt

@PreDestroy@PostConstruct

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é

Page 555: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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 }

Openmirrors.com

Page 556: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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éationd’unlivre

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

Page 557: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 558: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 Services web REST 541

Obtentiond’unlivreparsonidentifiant

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>

Suppressiond’unlivre

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);}

Page 559: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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>

Openmirrors.com

Page 560: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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/bookscurl -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>

Page 561: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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

Openmirrors.com

Page 562: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Chapitre15 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 livrescurl -X GET -H "Accept: application/json" „ http://localhost:8080/chapter15-resource-1.0/rs/books

// Récupération du livre 601 au format JSONcurl -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

Page 563: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Openmirrors.com

Page 564: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Index

Symboles

@Access, annotation 94@AccessTimeout, annotation 238@ActivationConfigProperty, annotation 448, 449,

453, 454, 459@AfterClass, annotation 69@ApplicationException, annotation 295, 296@ApplicationScoped, annotation 393, 394, 395@AroundInvoke, annotation 275, 277, 279, 280, 281@Asynchronous, annotation 252@AttributeOverride, annotation 133@Basic, annotation 86@Before, annotation 69@BeforeClass, annotation 69@CollectionTable, annotation 96@Column, annotation 57, 61, 87@ConcurrencyManagement, annotation 236@Consumes, annotation 524, 528, 529, 531, 533,

534, 539@Context, annotation 524, 534, 535, 539@CookieParam, annotation 527, 528@DeclareRoles, annotation 301, 302, 303, 304, 537@DefaultValue, annotation 528, 529@DELETE, annotation 525, 526, 533, 541@DenyAll, annotation 301, 302, 303, 304, 537@DependsOn, annotation 235, 267@DiscriminatorColumn, annotation 129@DiscriminatorValue, annotation 130@EJB, annotation 203, 249, 250, 266, 268, 315,

321, 322, 452@ElementCollection, annotation 96@Embeddable, annotation 102, 103@Embedded, annotation 102@EmbeddedId, annotation 83@Entity, annotation 57, 61, 74

<entity-listener>, marqueur 197@EntityListeners, annotation 193@Enumerated, annotation 90@ExcludeClassInterceptors, annotation 278, 279,

281, 282@ExcludeDefaultInterceptors, annotation 281, 282@ExcludeDefaultListeners, annotation 197@ExcludeSuperclassListeners, annotation 195@FacesConverter, annotation 406, 407@FacesValidator, annotation 409@FormParam, annotation 527, 528@GeneratedValue, annotation 57, 61, 81@GET, annotation 523, 524, 525, 526, 527, 528,

533, 534, 535, 541, 546@HEAD, annotation 533@HeaderParam, annotation 527, 528@Id, annotation 57, 61, 74, 81@IdClass, annotation 84@Inheritance, annotation 127@Interceptors, annotation 277, 278, 279, 280, 281,

282@JoinColumn, annotation 114@JoinTable, annotation 116@Lob, annotation 87@Local, annotation 241@LocalBean, annotation 245@Lock, annotation 237@ManagedBean, annotation 314, 315, 321, 322,

391, 392, 393, 394, 395, 397, 398, 399@ManagedProperty, annotation 392, 395, 396@ManytoMany, annotation 119@MapKeyColumn, annotation 98@MappedSuperclass, annotation 137@MatrixParam, annotation 527, 528@MessageDriven, annotation 447, 448, 449, 453,

454, 459

Page 565: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

548 Java EE 6 et GlassFish 3

@NamedNativeQuery, annotation 179@NamedQuery, annotation 59, 61, 176@NoneScoped, annotation 394@OnetoMany, annotation 115@OneToOne, annotation 112, 156@OneWay, annotation 487, 490@OPTIONS, annotation 533@OrderBy, annotation 123@OrderColumn, annotation 124@Path, annotation 523, 524, 525, 526, 527, 528,

533, 534, 535, 536, 539, 540, 541, 543@PathParam, annotation 526, 527, 533, 536, 537,

541, 542, 543@PermitAll, annotation 301, 303, 537@PersistenceContext, annotation 148, 244, 249,

250, 266, 268@PersistenceUnit, annotation 249<persistence-unit-defaults>, marqueur 197<persistence-unit-metadata>, marqueur 197@PostActivate, annotation 270, 271, 272, 278, 279@POST, annotation 524, 525, 529, 530, 533, 540,

546@PostConstruct, annotation 266, 268, 269, 270,

271, 272, 278, 279, 280, 282, 394, 395, 452, 454, 493, 537

@PostLoad, annotation 190@PostPersist, annotation 189@PostRemove, annotation 189@PostUpdate, annotation 189@PreDestroy, annotation 269, 270, 271, 272, 278,

279, 280, 282, 396, 397, 452, 454, 493, 537@PrePassivate, annotation 270, 271, 272, 278, 279@PrePersist, annotation 189@PreRemove, annotation 189@PreUpdate, annotation 189@Produces, annotation 523, 524, 528, 531, 534,

535, 536, 539@Provider, annotation 531, 536, 537@QueryParam, annotation 527, 528, 529@Remote, annotation 216, 241@Remove, annotation 232, 269, 270, 271, 272@RequestScoped, annotation 321, 322, 392, 394@Resource, annotation 249, 250, 266, 268, 293,

297, 298, 306, 431, 432, 438, 440, 441, 451, 452, 453, 454, 458, 493, 494, 495

@RolesAllowed, annotation 301, 302, 303, 304, 305, 537

@RunAs, annotation 301, 304, 305, 537@Schedule, annotation 257, 260@SecondaryTable, annotation 79@SessionScoped, annotation 393@Singleton, annotation 234, 267@SOAPBinding, annotation 487@SOAPMessageHandler, annotation 487@Startup, annotation 235, 267@Stateful, annotation 232, 267@StatefulTimeout, annotation 232@Stateless, annotation 203, 228, 229, 267, 273, 274,

277, 278, 279, 280, 281, 282, 497, 498, 524, 539@Table, annotation 78@Temporal, annotation 89@Test, annotation 69@Timeout, annotation 257@TransactionAttribute, annotation 292, 293, 302,

456@TransactionManagement, annotation 297, 302@Transient, annotation 90, 480@Version, annotation 182@ViewScoped, annotation 393@WebMethod, annotation 487, 488, 489, 490, 491,

498@WebParam, annotation 487, 489, 490, 491, 498@WebResult, annotation 487, 489, 490, 498@WebService, annotation 486, 488, 489, 490, 491,

497, 498@WebServiceRef, annotation 249, 250, 495, 501,

505@XmlAccessorType, annotation 478, 479@XmlAttribute, annotation 478, 479, 480@XmlElement, annotation 478, 479, 480@XmlEnum, annotation 480@XmlEnumValue, annotation 480@XmlID, annotation 480@XmlIDREF, annotation 480@XmlList, annotation 480@XmlMimeType, annotation 480@XmlNs, annotation 480@XmlRootElement, annotation 472, 478, 480, 481,

497, 498, 538@XmlSchema, annotation 479, 480

Openmirrors.com

Page 566: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Index 549

@XmlTransient, annotation 480@XmlType, annotation 480@XmlValue, annotation 480

A

ABS(), fonction JPQL 167Accept, en-tête HTTP 510, 513, 529, 534, 540, 541,

542, 543, 545actionSource (JSF) 377Activation 230, 268AND, opérateur JPQL 168appclient, programme 221, 462, 505application, objet implicite 381applicationScope, objet implicite 381asadmin

commande 48, 329programme 221, 430, 461, 499, 500

assert, mot-clé 38AsyncResult, classe 253attribute

action JSP 345(JSF) 376

AVG(), fonction JPQL 167

B

Backing bean 386, 391, 392, 404BETWEEN, opérateur JPQL 168body

action JSP 345(JSF) 371

bundle (JSTL) 352

C

Cache, interface 163Cardinalité d'une relation 107cascade, attribut d'annotation 161catch (JSTL) 350CDATA, section XML 344choose (JSTL) 350, 355Classe de clé primaire 82clear(), méthode d'EntityManager 151, 158, 189

column (JSF) 369commandButton

composant JSF 396, 397, 398, 413, 416(JSF) 363

commandLinkcomposant JSF 393, 396, 397(JSF) 363

component (Facelets) 359objet implicite 381

Composants graphiques de JSF 324compositeComponent, objet implicite 381composition

(Facelets) 359(JSF) 372

CONCAT(), fonction JPQL 167ConnectionFactory, classe 430, 432, 433, 438, 439,

440, 441, 451, 452, 454, 455, 458, 459, 461, 462Connection, interface 431, 432, 433, 434, 438, 439,

440, 441, 442, 454, 455, 458, 459containsIgnoreCase (JSTL) 358contains (JSTL) 358contains(), méthode d'EntityManager 151, 158, 188Content-Type, en-tête HTTP 513, 534, 540, 542,

545, 546convertDateTime, composant JSF 405Converter

composant JSF 407interface 406

ConverterException, exception 406convertNumber, composant JSF 405cookie, objet implicite 381CORBA (Common Object Request Broker

Architecture) 469COUNT(), fonction JPQL 167createNamedQuery(), méthode d'EntityManager

172, 177createNativeQuery(), méthode d'EntityManager 178,

179createQuery(), méthode d'EntityManager 172, 174cURL, programme 511, 537, 538, 540, 541, 542,

543, 545CURRENT_DATE(), fonction JPQL 167CURRENT_ TIME(), fonction JPQL 167CURRENT_TIMESTAMP(), fonction JPQL 167

Page 567: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

550 Java EE 6 et GlassFish 3

D

dataTable (JSF) 369dateParam (JSTL) 354dblook, programme 40DCOM (Distributed Component Object Model) 469debug (Facelets) 359déclaration (JSP) 343decorate

(Facelets) 359(JSF) 372

define(Facelets) 359(JSF) 372

DELETE, instruction JPQL 170Destination, interface 431, 435, 453, 454detach(), méthode d'EntityManager 158Direction d'une relation 106DISTINCT, clause JPQL 166DOM, API 13

E

EAI (Enterprise Application Integration) 465ear

archive 15fichier archive 208

EclipseLink, framework 54ECMA (European Computer Manufacturers

Association) 339editableValueHolder (JSF) 377EJBContext, interface 247EJBException, exception 291, 294ejb-jar.xml, fichier 208, 249, 281, 292, 297EJBTransactionRequiredException, exception 291element, action JSP 345endsWith (JSTL) 358EntityManager

classe 62gestionnaire d'entités 59interface 142, 146

EntityManagerFactory, classe 62, 144EntityNotFoundException, exception 154EntityTransaction, classe 152escapeXml (JSTL) 358ETag, en-tête HTTP 515, 546

exceptiond'application 294système 294

executeUpdate(), méthode de Query 173expression (JSP) 344

F

faces-config.xml, fichier 311, 318, 328, 390, 391, 392, 395, 396, 398, 399, 400, 401, 406, 407, 409

FacesContextclasse 387, 389, 401, 406, 408, 409objet implicite 381, 389

FacesMessage, classe 389, 401, 402, 408, 409FacesServlet, classe 386, 387, 388, 390, 391, 392,

398, 399facet (JSF) 369, 376find(), méthode d'EntityManager 151, 154, 188flush(), méthode d'EntityManager 151, 156forEach (JSTL) 350, 355formatDate (JSTL) 352formatNumber (JSTL) 352form (JSF) 371forTokens (JSTL) 350forward, action JSP 345fragment

(Facelets) 359(JSF) 372

FROM, clause JPQL 167Future, interface 253

G

getProperty, action JSP 345getReference(), méthode d'EntityManager 151, 154getResultList(), méthode de Query 173getSingleResult(), méthode de Query 173graphicImage (JSF) 368GROUP BY, clause JPQL 169

H

HAVING, clause JPQL 170headerValues, objet implicite 381

Openmirrors.com

Page 568: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Index 551

head (JSF) 371Hibernate, ORM 53HttpHeaders, classe 534HTTP, protocole 14HTTPS, protocole 14

I

Idempotence 512, 513IDL, protocole 14if (JSTL) 350, 355If-Match, en-tête HTTP 515, 516If-Modified-Since, en-tête HTTP 515, 516If-None-Match, en-tête HTTP 516If-Range, en-tête HTTP 516If-Unmodified-Since, en-tête HTTP 516ij, programme 40, 66implementation (JSF) 376import (JSTL) 350include

action JSP 345directive JSP 342(Facelets) 359

INDEX(), fonction JPQL 167indexOf (JSTL) 358InitialContext, classe 423, 430, 431initParam, objet implicite 382IN, opérateur JPQL 168inputHidden (JSF) 365inputSecret (JSF) 365inputTextarea (JSF) 365inputText (JSF) 365insert

(Facelets) 359(JSF) 372

insertChildren (JSF) 376insertFacet (JSF) 376interface (JSF) 376InvocationContext, interface 275, 276, 277, 279, 280IS EMPTY, opérateur JPQL 168IS NULL, opérateur JPQL 168

J

JAAS, API 13

JACC, API 14JAF, API 13jar, archive 15JavaMail, API 13javax.faces.component.UIComponent (JSF) 363javax.faces.convert

convertisseurs JSF 314paquetage 404

javax.faces.validator, paquetage 407javax.persistence, paquetage 57javax.servlet.Servlet, interface 387JAXBContext, classe 476, 477, 478JAXP, API 13JAX-RPC, API 14JAX-RS, API 14JAX-WS, API 14JCA, API 13JDBC, API 52Jersey, implémentation 46JMS, API 13JMSException, exception 454, 456, 458, 459, 460JMX, API 14, 16JNDI, API 13join (JSTL) 358JPA, API 13JRMP, protocole 14jsf.js, fichier 412JTA, API 13, 53

L

Last-Modified, en-tête HTTP 515LENGTH(), fonction JPQL 167length (JSTL) 358LengthValidator, validateur JSF 314Lifecycle, classe 387LIKE, opérateur JPQL 168lock(), méthode d'EntityManager 181LOWER(), fonction JPQL 167

M

MANIFEST.MF, fichier 502, 505Marshaller, classe 476, 477, 478, 479

Page 569: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

552 Java EE 6 et GlassFish 3

MAX(), fonction JPQL 167MEMBER, opérateur JPQL 168merge(), méthode d'EntityManager 151, 160, 189message

composant JSF 401, 402(JSF) 370(JSTL) 352

MessageBodyReader, interface 530, 531, 532, 533MessageBodyWriter, interface 530, 531, 532MessageConsumer, interface 424, 425, 432, 433,

438, 440, 441, 442MessageDrivenContext, interface 451, 455MessageListener, interface 439, 441, 447, 448, 449,

452, 453, 454, 455, 459MessageProducer, interface 424, 437, 438, 443, 444,

445, 446, 454, 455, 458, 459Metro

implémentation 45de référence 471

MIN(), fonction JPQL 167MOD(), fonction JPQL 167Mojarra

implémentation 46de JSF 2.0 318

MOM (Middleware Orienté Messages) 419MVC (Modèle-Vue-Contrôleur) 311mvn

commande Maven 329programme 65, 499, 504

N

NOT, opérateur JPQL 168

O

OASIS (Organization for the Advancement of Structured Information Standards) 469

OpenMQ (Open Message Queue) 423implémentation 46

OptimisticLockException, exception 183ORDER BY, clause JPQL 169ORM, Object-Relational Mapping 52OR, opérateur JPQL 168otherwise (JSTL) 350, 355

out (JSTL) 350, 355outputLabel (JSF) 366outputLink (JSF) 366outputScript

composant JSF 412, 414, 415(JSF) 371

outputStylesheet (JSF) 371outputText (JSF) 366

P

page, directive JSP 342panelGrid (JSF) 369panelGroup (JSF) 369param

action JSP 345(Facelets) 359(JSTL) 350, 352, 354, 355objet implicite 382

paramValues, objet implicite 382parseDate (JSTL) 352parse (JSTL) 355parseNumber (JSTL) 352PartialViewContext, classe 413Passivation 230, 268Persistence, classe 62, 148persistence.xml, fichier 63, 145, 197, 538, 539, 544persist(), méthode d'EntityManager 151, 188PessimisticLockException, exception 185plugin, action JSP 345pom.xml, fichier 64, 328, 498, 499, 502, 538, 543,

544Principal 305, 306Programmation orientée aspect 272Propriétaire d'une relation 107Providers, classe 534

Q

Queryinterface 172(JSTL) 353

Queueclasse 424interface 427, 431

Openmirrors.com

Page 570: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

Index 553

QueueConnectionFactory, interface 430, 431QueueConnection, interface 433, 434QueueReceiver, interface 427, 439QueueSender, interface 437, 438QueueSession, interface 426, 434, 435

R

redirect (JSTL) 350refresh(), méthode d'EntityManager 151, 156, 157,

188RegexValidator, validateur JSF 314remove

(Facelets) 359(JSTL) 350

remove(), méthode d'EntityManager 154, 155, 188repeat (Facelets) 359replace (JSTL) 358request

classe 534objet implicite 382

requestEncoding (JSTL) 352requestScope, objet implicite 382Requêtes nommées 58resource, objet implicite 382REST, protocole 14RMI-IIOP, protocole 14RMI (Remote Method Invocation) 469RPC (Remote Procedure Call) 469

S

SAX, API 13ScheduleExpression, classe 257schemaGen, programme 477scriptlet (JSP) 344SecurityContext, classe 534SecurityException, exception 306, 307selectBooleanCheckbox (JSF) 366SELECT, clause JPQL 165selectManyCheckbox (JSF) 366selectManyListbox (JSF) 367selectManyMenu (JSF) 367selectOneListbox (JSF) 367

selectOneMenu (JSF) 367selectOneRadio (JSF) 367Serializable, interface 189, 268session

interface 424, 425, 426, 427, 432, 433, 434, 438, 440, 441, 445, 450, 453, 454, 458

objet implicite 382SessionContext

classe 293, 294, 297, 305, 306interface 247

sessionScope, objet implicite 382setDataSource (JSTL) 353setFirstResult(), méthode de Query 174set (JSTL) 350, 355setLocale (JSTL) 352setLockMode(), méthode de Query 174setMaxResults(), méthode de Query 174setParameter(), méthode de Query 173, 174setProperty, action JSP 345setTimeZone (JSTL) 352SGML (Standard Generalized Markup Language)

332SIZE(), fonction JPQL 167SOAP, protocole 14SOA (Service-Oriented Architecture) 465split (JSTL) 358SQRT(), fonction JPQL 167startsWith (JSTL) 358StAX, API 13Struts, framework web 317substringAfter (JSTL) 358substringBefore (JSTL) 358SUBSTRING(), fonction JPQL 167substring (JSTL) 358SUM(), fonction JPQL 167SVG (Scalable Vector Graphics) 313synchronized, mot-clé 238, 393sysinfo, programme 40

T

Tableprimaire 79secondaire 79

taglib, directive JSP 342

Page 571: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

554 Java EE 6 et GlassFish 3

Timer, classe 262TimerService, interface 261timeZone (JSTL) 352toLowerCase (JSTL) 358TopicConnectionFactory, interface 430, 431TopicConnection, interface 433, 434Topic, interface 431, 438, 440, 441, 447, 449, 453,

454, 455TopicPublisher, interface 437, 438, 439TopicSession, interface 426, 434, 435TopicSubscriber, interface 427, 439, 440TopLink, ORM 53toUpperCase (JSTL) 358Transaction (JSTL) 353transform (JSTL) 355TRIM(), fonction JPQL 167trim (JSTL) 358Types MIME 513

U

UIViewRoot (JSF) 363Unmarshaller, classe 476, 477UPDATE

instruction JPQL 171(JSTL) 353

UPPER(), fonction JPQL 167UriBuilder, classe 534, 535UriInfo, classe 524, 534, 535, 539url (JSTL) 350useBean, action JSP 345User-Agent, en-tête HTTP 510, 513, 540, 542UserTransaction, interface 297, 298

V

validate, message SOAP 472, 473, 481, 482, 483, 485, 489, 490, 491, 495, 496, 502, 504

validateResponse, message SOAP 482, 483, 484, 485, 504

validateResponse, réponse SOAP 481validator

composant JSF 409interface 408, 409

ValidatorException, exception 408valueHolder (JSF) 377view, objet implicite 382viewScope, objet implicite 382volatile, mot-clé 238

W

WebApplicationException, exception 532, 536WebServiceContex, interface 493webservices.xml, fichier 499, 500web.xml, fichier 327, 387, 538, 542, 544when (JSTL) 350, 355WHERE, clause JPQL 167WML (Wireless Markup Language) 313wsgen, programme 496wsimport, programme 494, 496, 501, 502, 503, 504,

506

X

xjc, programme 477XMLHttpRequest, objet 410, 411, 412

Openmirrors.com

Page 572: Java EE 6 et Glassfish 2 · 2013-03-15 · customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue  Java EE 6 et GlassFish 3 Référence

Propriété de Albiri Sigue <[email protected]>customer 27921 at Fri Mar 11 19:07:20 +0100 2011

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.

Il explore les innovations de Java EE 6, la dernière version de la plate-forme Java Enterprise, et examine les différentes spécifi cations et la façon de les assembler pour développer des applications. Conçu un peu 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 métier transactionnelle avec EJBTM 3.1, à ajouter une couche présentation avec JSFTM 2.0 et à interagir avec d’autres systèmes tels que JMSTM et les services web SOAP et REST. En outre, tous les exemples de ce livre ont été écrits spécifi quement pour GlassFish 3, la toute dernière version de l’implémentation de référence pour la plate-forme Java EE.

Code source téléchargeable sur www.pearson.fr

Référ

ence

ISBN : 978-2-7440-4157-0

Niveau : Tous niveauxConfi guration : Multiplate-forme

Développementweb

Pearson Education FrancePearson Education France47 bis, rue des Vinaigriers47 bis, rue des Vinaigriers75010 Paris75010 ParisTél. : 01 72 74 90 00Tél. : 01 72 74 90 00Fax : 01 42 05 22 17Fax : 01 42 05 22 17www.pearson.frwww.pearson.fr

À propos de l’auteur :

Antonio Goncalves est un architecte logiciel. Membre expert de plusieurs JSR (Java Specifi cation Requests) et co-fondateur du JUG (Java User Group) de Paris, il participe aux grandes conférences internationales consacrées à Java EE, dont JavaOne, le Server Side Symposium, Devoxx et Jazoon. Son premier livre Java EE 5 est paru en 2007 aux éditions Eyrolles.

TABLE DES MATIÈRES

• Tour d’horizon de Java EE 6• Persistance en Java• ORM : Object-Relational Mapping• Gestion des objets persistants• Méthodes de rappel et écouteurs• Enterprise Java Beans• Beans de session et service timer• Méthodes de rappel et intercepteurs• Transactions et sécurité• JavaServer Faces• Pages et composants• Traitement et navigation• Envoi de messages• Services web SOAP• Services web REST

Java EE 6 et GlassFish 3