Sunteți pe pagina 1din 334

Introduction à Java EE

avec Netbeans 5.5.1

serge.tahe at istia.univ-angers.fr
novembre 2007

Introdution à Java EE
1/334
INTRODUCTION

Java EE signifie Java Enterprise Edition. J2EE (Java 2 Enterprise Edition) était le terme précédent. J2EE désigne les
technologies Java utilisées pour créer des applications d'entreprise avec le JDK 1.4 ou antérieur. En même temps que le JDK 1.5
amenait de nombreuses nouveautés dans le langage Java, Sun introduisait de nouvelles technologies s'appuyant sur ce langage
amélioré afin de remédier à des lacunes de ces mêmes technologies dans J2EE. Le terme Java EE 5 ou Java EE a alors été utilisé
pour désigner l'ensemble des technologies qui concourent à créer une application d'entreprise avec la plate-forme Java.

Un livre de référence pour découvrir les technologies qui gravitent dans l'univers Java EE 5 est le livre d'Antonio Goncalves : Java
EE 5, aux éditions Eyrolles. C'est un excellent livre qui m'a ravi. Toutes les technologies importantes de Java EE 5 y sont passées
en revue dans le contexte d'une application réaliste de commerce électronique. Je ne peux qu'inciter le lecteur à se procurer cet
ouvrage. L'auteur a un site [http://www.antoniogoncalves.org]. Il y présente son livre et le code de l'application qu'il y développe
est téléchargeable.

Le document présent est moins ambitieux. Nous y créons une application basique à trois couches [présentation, métier, accès aux
données] déclinée en plusieurs versions :

Une application web avec les technologies suivantes :


• JavaServer Faces : pour la couche web
• Ejb3 ou Spring : pour la couche métier
• Ejb3 ou Spring, Jpa/Hibernate, Jpa/Toplink : pour créer différentes couches d'accès aux données

Une application client / serveur avec les technologies suivantes :


• Swing : pour la couche graphique cliente avec un support Spring
• Ejb3 ou service web : pour la couche serveur

Certaines technologies Java EE ne sont pas présentées telles les MDB (Message Driven Bean) ou les Ejb3 stateful. Pour les
découvrir, on lira le livre d'Antonio Goncalves.

Il existe d'autres technologies Open Source disponibles pour créer des applications trois couches. Une tandem très populaire est
Spring (http://www.springframework.org/) / Hibernate (http://www.hibernate.org/). Afin de permettre au lecteur de comparer
les technologies Ejb3 et Spring, l'application précédente a des versions où Spring remplace les Ejb3.

Le document a deux parties bien distinctes :

• la première partie est un TD utilisé dans le Master 2 professionnel Automatique et Informatique de l'université d'Angers
[http://www.istia.univ-angers.fr/Automatisation/master2iaie.html]. Un TD est un Travail Dirigé. Ce TD décrit
l'application à construire, les technologies Java à utiliser, les endroits où trouver de l'information. La solution proposée est
très cadrée. Le TD pose des questions dont il ne donne pas les réponses. C'est à l'étudiant de les trouver.
• la seconde partie est un cours sur JSF (JavaServer Faces). Il sert d'appui pour écrire la couche web de l'application
exemple.

L'apprentissage Java EE proposé ici nécessite un investissement du lecteur estimé entre 50 et 100 heures. Le document contient
beaucoup de code rendant possible le copier / coller. Par ailleurs, tous les projets Netbeans sont décrits dans le détail. Globalement,
le document donne les squelettes des solutions et il est demandé à l'étudiant d'en donner certains détails. Le document peut être
utile même à quelqu'un ne pouvant ou ne voulant pas s'investir autant. On peut s'intéresser uniquement aux architectures décrites et
délaisser la partie code qui fait l'objet des questions.

Pour développer et exécuter l'application, nous utilisons l'IDE Netbeans 5.5.1. Au moment de l'écriture du document (été 2007),
c'est le seul IDE gratuit qui m'a paru offrir un environnement de développement Java EE 5 abouti, notamment avec le serveur
d'applications Sun AS 9.x intégré. Netbeans est un produit assez lourd : prévoir 1 Go de Ram pour travailler confortablement. On
pourra télécharger Netbeans à l'url [http://www.netbeans.org/]. Début novembre 2007, on y trouve à la fois Netbeans 5.5.1 utilisé
dans cet ouvrage et Netbeans 6.0 beta 2. On pourra télécharger cette dernière version. Même si les copies d'écran NB 5.5.1
présentes dans ce document ne sont pas identiques à celles de NB 6.0, elles en sont néanmoins proches. Le lecteur devrait s'y
retrouver. Si on télécharge NB 5.5.1, il faut prendre la version contenant le serveur d'application Sun Application Server.

Le document fait référence aux cours suivants :

1. Persistance Java 5 par la pratique : [http://tahe.developpez.com/java/jpa] - donne les outils pour construire la couche
d'accès aux données avec Jpa (Java Persistence Api)

Introdution à Java EE
2/334
2. Introduction au langage Java [http://tahe.developpez.com/java/cours] - pour les débutants

Ces supports de cours sont par la suite référencés [ref1] et [ref2].

Un étudiant ayant mené à son terme la lecture active de ce document peut prétendre à des stages Java EE comme le montre
l'expérience que nous avons dans le Master 2 Automatique / Informatique de l'université d'Angers. Un tel stage est ensuite un
atout certain pour trouver un emploi. Actuellement (2007), le domaine Java EE est très pourvoyeur d'emplois notamment dans les
SSII.

Serge Tahé, novembre 2007.

Introdution à Java EE
3/334
1 Architecture d'une application Java en couches
Une application java est souvent découpée en couches chacune ayant un rôle bien défini. Considérons une architecture courante,
celle à trois couches :

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [dao]
1 2 3

• la couche [1], appelée ici [ui] (User Interface) est la couche qui dialogue avec l'utilisateur, via une interface graphique
Swing, une interface console ou une interface web. Elle a pour rôle de fournir des données provenant de l'utilisateur à la
couche [2] ou bien de présenter à l'utilisateur des données fournies par la couche [2].
• la couche [2], appelée ici [metier] est la couche qui applique les règles dites métier, c.a.d. la logique spécifique de
l'application, sans se préoccuper de savoir d'où viennent les données qu'on lui donne, ni où vont les résultats qu'elle
produit.
• la couche [3], appelée ici [dao] (Data Access Object) est la couche qui fournit à la couche [2] des données pré-enregistrées
(fichiers, bases de données, ...) et qui enregistre certains des résultats fournis par la couche [2].

Il existe différentes possibilités pour implémenter la couche [dao]. Examinons-en quelques-unes :

Couche ui Couche métier Couche Couche Base de


utilisateur [JDBC]
[ui] [metier] d'accès aux Données
1 2 données [dao]
3

La couche [JDBC] ci-dessus est la couche standard utilisée en Java pour accéder à des bases de données. Elle isole la couche [dao]
du SGBD qui gère la base de données. On peut théoriquement changer de SGBD sans changer le code de la couche [dao]. Malgré
cet avantage, l'API JDBC présente certains inconvénients :
• toutes les opérations sur le SGBD sont susceptibles de lancer l'exception contrôlée (checked) SQLException. Ceci oblige
le code appelant (la couche [dao] ici) à les entourer par des try / catch rendant ainsi le code assez lourd.
• la couche [dao] n'est pas complètement insensible au SGBD. Ceux-ci ont par exemple des méthodes propriétaires quant à
la génération automatique de valeurs de clés primaires que la couche [dao] ne peut ignorer. Ainsi lors de l'insertion d'un
enregistrement :
• avec Oracle, la couche [dao] doit d'abord obtenir une valeur pour la clé primaire de l'enregistrement puis insérer
celui-ci.
• avec SQL Server, la couche [dao] insère l'enregistrement qui se voit donner automatiquement une valeur de clé
primaire par le SGBD, valeur rendue à la couche [dao].

Ces différences peuvent être gommées via l'utilisation de procédures stockées. Dans l'exemple précédent, la couche [dao]
appellera une procédure stockée dans Oracle ou SQL Server qui prendra en compte les particularités du SGBD. Celles-ci
seront cachées à la couche [dao]. Néanmoins, si changer de SGBD n'impliquera pas de réécrire la couche [dao], cela
implique quand même de réécrire les procédures stockées. Cela peut ne pas être considéré comme rédhibitoire.

De multiples efforts ont été faits pour isoler la couche [dao] des aspects propriétaires des SGBD. Une solution qui a eu un vrai
succès dans ce domaine ces dernières années, est celle d'Hibernate :

4 Objets image Couche


Couche d'accès aux Couche Base de
données [dao] de la BD [Hibernate] [JDBC] Données
3 5 6 7

Introdution à Java EE
4/334
La couche [Hibernate] vient se placer entre la couche [dao] écrite par le développeur et la couche [Jdbc]. Hibernate est un ORM
(Object Relational Mapping), un outil qui fait le pont entre le monde relationnel des bases de données et celui des objets manipulés
par Java. Le développeur de la couche [dao] ne voit plus la couche [Jdbc] ni les tables de la base de données dont il veut exploiter le
contenu. Il ne voit que l'image objet de la base de données, image objet fournie par la couche [Hibernate]. Le pont entre les tables
de la base de données et les objets manipulés par la couche [dao] est fait principalement de deux façons :
• par des fichiers de configuration de type XML
• par des annotations Java dans le code, technique disponible seulement depuis le JDK 1.5

La couche [Hibernate] est une couche d'abstraction qui se veut la plus transparente possible. L'idéal visé est que le développeur de
la couche [dao] puisse ignorer totalement qu'il travaille avec une base de données. C'est envisageable si ce n'est pas lui qui écrit la
configuration qui fait le pont entre le monde relationnel et le monde objet. La configuration de ce pont est assez délicate et
nécessite une certaine habitude.

La couche [4] des objets, image de la BD est appelée "contexte de persistance". Une couche [dao] s'appuyant sur Hibernate fait des
actions de persistance (CRUD, create - read - update - delete) sur les objets du contexte de persistance, actions traduites par
Hibernate en ordres SQL exécutés par la couche Jdbc. Pour les actions d'interrogation de la base (le SQL Select), Hibernate fournit
au développeur, un langage HQL (Hibernate Query Language) pour interroger le contexte de persistance [4] et non la BD elle-
même.

Hibernate est populaire mais complexe à maîtriser. La courbe d'apprentissage souvent présentée comme facile est en fait assez
raide. Dès qu'on a une base de données avec des tables ayant des relations un-à-plusieurs ou plusieurs-à-plusieurs, la configuration
du pont relationnel / objets n'est pas à la portée du premier débutant venu. Des erreurs de configuration peuvent conduire à des
applications peu performantes.

Dans le monde commercial, il existait un produit équivalent à Hibernate appelé Toplink :

4
Couche d'accès aux Objets image Couche Couche Base de
données [dao] de la BD [Toplink] [JDBC] Données
3 5 6 7

Devant le succès des produits ORM, Sun le créateur de Java, a décidé de standardiser une couche ORM via une spécification
appelée JPA apparue en même temps que Java 5. La spécification JPA a été implémentée par divers produits, dont Toplink et
Hibernate. Toplink qui était un produit commercial est devenu depuis un produit libre. Avec JPA, l'architecture précédente
devient la suivante :

4
Couche d'accès aux Objets image Interface Implémentation Couche Base de
données [dao] de la BD [JPA] [Toplink [JDBC] Données
3 5 / Hibernate] 6 7

La couche [dao] dialogue maintenant avec la spécification JPA, un ensemble d'interfaces. Le développeur y a gagné en
standardisation. Avant, s'il changeait sa couche ORM, il devait également changer sa couche [dao] qui avait été écrite pour dialoguer
avec un ORM spécifique. Maintenant, il va écrire une couche [dao] qui va dialoguer avec une couche JPA. Quelque soit le produit
qui implémente celle-ci, l'interface de la couche JPA présentée à la couche [dao] reste la même.

Dans ce document, nous utiliserons une couche [dao] s'appuyant sur une couche JPA/Hibernate ou JPA/Toplink. Par ailleurs nous
utiliserons le framework Spring pour lier ces couches entre-elles.

Introdution à Java EE
5/334
4
Couche Couche Objets image Interface Implémentation Couche
[metier] [dao] de la BD [JPA] [Toplink [JDBC]
2 / Hibernate]
3 6
5
7 Spring

Le grand intérêt de Spring est qu'il permet de lier les couches par configuration et non dans le code. Ainsi si l'implémentation JPA
/ Hibernate doit être remplacée par une implémentation Hibernate sans JPA, parce que par exemple l'application s'exécute dans un
environnement JDK 1.4 qui ne supporte pas JPA, ce changement d'implémentation de la couche [dao] n'a pas d'impact sur le code
de la couche [métier]. Seul le fichier de configuration Spring qui lie les couches entre elles doit être modifié.

Avec Java EE 5, une autre solution existe : implémenter les couches [metier] et [dao] avec des Ejb3 (Enterprise Java Bean version 3)
:

4
Couche Couche Objets image Interface Implémentation Couche
[metier] [dao] de la BD [JPA] [Toplink [JDBC]
2 / Hibernate]
3 6
5
7 conteneur Ejb3

Nous verrons que cette solution n'est pas très différente de celle utilisant Spring. L'environnement Java EE5 est disponible au sein
de serveurs dits serveurs d'applications tels que Sun Application Server 9.x (Glassfish), Jboss Application Server, Oracle Container for Java
(OC4J), ... Un serveur d'applications est essentiellement un serveur d'applications web. Il existe également des environnements EE 5
dits "stand-alone", c.a.d. pouvant être utilisés en-dehors d'un serveur d'applications. C'est le cas de JBoss EJB3.

Dans un environnement EE5, les couches sont implémentées par des objets appelés EJB (Enterprise Java Bean). Dans les
précédentes versions d'EE, les EJB (EJB 2.x) étaient réputés difficiles à mettre en oeuvre, à tester et parfois peu-performants. On
distingue les EJB2.x "entity" et les EJB2.x "session". Pour faire court, un EJB2.x "entity" est l'image d'une ligne de table de base de
données et EJB2.x "session" un objet utilisé pour implémenter les couches [metier], [dao] d'une architecture multi-couches. L'un
des principaux reproches faits aux couches implémentées avec des EJB est qu'elles ne sont utilisables qu'au sein de conteneurs EJB,
un service délivré par l'environnement EE. Cet environnement, plus complexe à mettre en oeuvre qu'un environnement SE
(Standard Edition), peut décourager le développeur à faire fréquemment des tests. Néanmoins, il existe désormais (2007) des
environnements de développement Java qui facilitent l'utilisation d'un serveur d'application en automatisant le déploiement des Ejb
sur le serveur : Eclipse, Netbeans, JDeveloper, IntelliJ IDEA. Nous utiliserons ici Netbeans 5.5 et le serveur d'application Sun AS 9.1.

Le framework Spring est né en réaction à la complexité des EJB2. Spring fournit dans un environnement SE un nombre important
des services habituellement fournis par les environnements EE. Ainsi dans la partie "Persistance de données", Spring fournit les
pools de connexion et les gestionnaires de transactions dont ont besoin les applications. L'émergence de Spring a favorisé la culture
des tests unitaires, devenus plus faciles à mettre en oeuvre dans le contexte SE que dans le contexte EE. Spring permet
l'implémentation des couches d'une application par des objets Java classiques (POJO, Plain Old/Ordinary Java Object), permettant
la réutilisation de ceux-ci dans un autre contexte. Enfin, il intègre de nombreux outils tiers de façon assez transparente, notamment
des outils de persistance tels que Hibernate, Ibatis, ...

Java EE5 a été conçu pour corriger les lacunes de la spécification EJB2. Les EJB 2.x sont devenus les EJB3. Ceux-ci sont des
POJOs tagués par des annotations qui en font des objets particuliers lorsqu'ils sont au sein d'un conteneur EJB3. Dans celui-ci,
l'EJB3 va pouvoir bénéficier des services du conteneur (pool de connexions, gestionnaire de transactions, ...). En-dehors du
conteneur EJB3, l'EJB3 devient un objet Java normal. Ses annotations EJB sont ignorées.

Ci-dessus, nous avons représenté Spring et un conteneur EJB3 comme infrastructure (framework) possible de notre architecture
multi-couches. C'est cette infrastructure qui délivrera les services dont nous avons besoin : un pool de connexions et un gestionnaire
de transactions.

Introdution à Java EE
6/334
• avec Spring, les couches seront implémentées avec des POJOs. Ceux-ci auront accès aux services de Spring (pool
de connexions, gestionnaire de transaction) par injection de dépendances dans ces POJOs : lors de la
construction de ceux-ci, Spring leur injecte des références sur les services dont il vont avoir besoin.
• avec le conteneur Ejb3, les couches seront implémentées avec des Ejb. Une architecture en couches
implémentées avec des Ejb3 est peu différente de celles implémentées avec des POJO instanciés par Spring.
Nous trouverons beaucoup de ressemblances..

✗ pour terminer, nous présenterons un exemple d'application web multi-couches :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[web] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring ou Ejb3

2 PAM - Version 1
On se propose d’écrire une application console ainsi qu'une application graphique permettant d’établir le bulletin de salaire des
assistantes maternelles employées par la "Maison de la petite enfance" d'une commune.

2.1 La base de données


Les données statiques utiles pour construire la fiche de paie seront placées dans une base de données que nous désignerons par la
suite dbpam. Cette base de données pourrait avoir les tables suivantes :

Table EMPLOYES : rassemble des informations sur les différentes assistantes maternelles

Structure :

SS numéro de sécurité sociale de l'employé - clé primaire


NOM nom de l'employé
PRENOM son prénom
ADRESSE son adresse
VILLE sa ville
CODEPOSTA son code postal
L
INDICE son indice de traitement - clé étrangère sur le champ [INDICE] de la table [INDEMNITES]

Son contenu pourrait être le suivant :

Table COTISATIONS : rassemble des pourcentages nécessaires au calcul des cotisations sociales

Structure :

CSGRDS pourcentage : contribution sociale généralisée + contribution au remboursement de la dette sociale


CSGD pourcentage : contribution sociale généralisée déductible
SECU pourcentage : contribution sociale généralisée déductible
RETRAIT pourcentage : retraite complémentaire + assurance chômage
E

Son contenu pourrait être le suivant :

Introdution à Java EE
7/334
Les taux des cotisations sociales sont indépendants du salarié. La table précédente n'a qu'une ligne.

Table INDEMNITES : rassemble les éléments permettant le calcul du salaire à payer.

INDICE indice de traitement - clé primaire


BASEHEURE prix net en euro d’une heure de garde
ENTRETIENJOU indemnité d’entretien en euro par jour de garde
R
REPASJOUR indemnité de repas en euro par jour de garde
INDEMNITESCP indemnité de congés payés. C'est un pourcentage à appliquer au salaire de base.

Son contenu pourrait être le suivant :

On notera que les indemnités peuvent varier d'une assistante maternelle à une autre. Elles sont en effet associées à une assistante
maternelle précise via l'indice de traitement de celle-ci. Ainsi Mme Marie Jouveinal qui a un indice de traitement de 2 (table
EMPLOYES) a un salaire horaire de 2,1 euro (table INDEMNITES).

2.2 Mode de calcul du salaire d'une assistante maternelle


Nous présentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Il ne prétend pas être celui utilisé
dans la réalité. Nous prenons pour exemple, le salaire de Mme Marie Jouveinal qui a travaillé 150 h sur 20 jours pendant le mois à
payer.

Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
travaillées dans le mois [TOTALJOURS]= 20
:
[TOTALJOURS]: total des jours
travaillés dans le mois

Le salaire de base de l'assistante [SALAIREBASE]=([TOTALHEURES]*[BAS [SALAIREBASE]=(150*[2.1])*(1+0.15


EHEURE])*(1+[INDEMNITESCP]/100) )= 362,25
maternelle est donné par la formule
suivante :
Un certain nombre de cotisations sociales Contribution sociale généralisée CSGRDS : 12,64
et contribution au remboursement
doivent être prélevées sur ce salaire de de la dette sociale : CSGD : 22,28
base : [SALAIREBASE]*[CSGRDS/100]
Sécurité sociale : 34,02
Contribution sociale généralisée
déductible :
[SALAIREBASE]*[CSGD/100] Retraite : 28,55

Sécurité sociale, veuvage,


vieillesse :
[SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +


Assurance Chômage :
[SALAIREBASE]*[RETRAITE/100]

Total des cotisations sociales : [COTISATIONSSOCIALES]=[SALAIREBAS [COTISATIONSSOCIALES]=97,48


E]*(CSGRDS+CSGD+SECU+RETRAITE)/10
0

Par ailleurs, l'assistante maternelle a droit, [INDEMNITÉS]=[TOTALJOURS]*(ENTRET [INDEMNITES]=104


IENJOUR+REPASJOUR)
chaque jour travaillé, à une indemnité
d'entretien ainsi qu'à une indemnité de
repas. A ce titre elle reçoit les indemnités
suivantes :

Introdution à Java EE
8/334
Les éléments suivants sont pris en compte [TOTALHEURES]: total des heures [TOTALHEURES]=150
travaillées dans le mois [TOTALJOURS]= 20
:
[TOTALJOURS]: total des jours
travaillés dans le mois

Au final, le salaire net à payer à l'assistante [SALAIREBASE]- [salaire NET]=368,77


[COTISATIONSSOCIALES]+[INDEMNITÉS
maternelle est le suivant : ]

2.3 Fonctionnement de l'application console


Voici un exemple de ce qui est attendu. L'ensemble des exécutables et des fichiers de configuration nécessaires à l'application
console sont rassemblés dans un répertoire :

1 2

• [swing-metier-dao-jpa-spring-hibernate.jar] (1) contient l'application que nous allons écrire


• le dossier [lib] (2) contient les biblithèques de classes nécessaires au projet

Voici un exemple d'exécution de l'application console dans une fenêtre Dos :

1. dos>java -jar swing-metier-dao-jpa-spring-hibernate.jar 254104940426058 150 20


2.
3. Valeurs saisies :
4. N° de sécurité sociale de l'employé : 254104940426058
5. Nombre d'heures travaillées : 150
6. Nombre de jours travaillés : 20
7.
8. Informations Employé :
9. Nom : Jouveinal
10. Prénom : Marie
11. Adresse : 5 rue des Oiseaux
12. Ville : St Corentin
13. Code Postal : 49203
14. Indice : 2
15.
16. Informations Cotisations :
17. CSGRDS : 3.49 %
18. CSGD : 6.15 %
19. Retraite : 7.88 %
20. Sécurité sociale : 9.39 %
21.
22. Informations Indemnités :
23. Salaire horaire : 2.1 euro
24. Entretien/jour : 2.1 euro
25. Repas/jour : 3.1 euro
26. Congés Payés : 15.0 %
27.
28. Informations Salaire :
29. Salaire de base : 362.25 euro
30. Cotisations sociales : 97.48 euro
31. Indemnités d'entretien : 42.0 euro
32. Indemnités de repas : 62.0 euro
33. Salaire net : 368.77 euro

Introdution à Java EE
9/334
On écrira un programme qui recevra les informations suivantes :

1. n° de sécurité sociale de l'assistante maternelle ( 254104940426058 dans l'exemple - ligne 1)


2. nombre total d'heures travaillées (150 dans l'exemple - ligne 1)
3. nombre total de jours travaillés (20 dans l'exemple - ligne 1)

On voit que :

• lignes 9-14 : affichent les informations concernant l'employé dont on a donné le n° de sécurité sociale
• lignes 17-20 : affichent les taux des différentes cotisations
• lignes 23-26 : affichent les indemnités associées à l'indice de traitement de l'employé (ici l'indice 2)
• lignes 29-33 : affichent les éléments constitutifs du salaire à payer

L'application signale les erreurs éventuelles :

Appel sans paramètres :

dos>java -jar swing-metier-dao-jpa-spring-hibernate.jar


Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Appel avec des données erronées :

dos>java -jar swing-metier-dao-jpa-spring-hibernate.jar 254104940426058 150x 20x


Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné

Appel avec un n° de sécurité sociale erroné :

dos>java -jar swing-metier-dao-jpa-spring-hibernate.jar xx 150 20


L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable

2.4 Fonctionnement de l'application graphique


L'application graphique permet le calcul des salaires des assistantes maternelles au-travers d'un formulaire Swing :

1
2 3
4

Introdution à Java EE
10/334
• les informations passées en paramètres au programme console, sont maintenant saisies au moyen des champs de saisie [1,
2, 3].
• le bouton [4] demande le calcul du salaire
• le formulaire affiche les différents éléments du salaire jusqu'au salaire net à payer [5]

La liste déroulante [1, 6] ne présente pas les n°s SS des employés mais les noms et prénoms de ceux-ci. On fait ici l'hypothèse qu'il
n'y a pas deux employés de mêmes nom et prénom.

3 Implémentation JPA de la couche de persistance des données


3.1 Les entités JPA
Nous adopterons l'architecture suivante pour notre application console :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

La persistance des données en base de données sera assurée par une implémentation JPA / Hibernate ou JPA / Toplink. La couche
JPA fera le pont entre les tables [COTISATIONS, INDEMNITES, EMPLOYES] et les entités [Cotisation, Indemnite, Employe].

Question : Donnez un code possible pour les entités [Cotisation, Indemnite, Employe].

Notes :
• les entités feront partie d'un paquetage nommé [jpa]
• chaque entité aura un n° de version
• si deux entités sont liées par une relation, seule la relation principale sera construite. La relation inverse ne le sera pas.

Lectures conseillées : [ref1], paragraphe 2.4.

3.2 Configuration de la couche JPA


Une fois les entités JPA écrites, il est possible de faire une première série de tests en générant la base de données image des entités à
l'aide d'un script ant. Nous ferons ces tests à l'intérieur du projet Netbeans [swing-metier-dao-jpa-spring-hibernate] suivant :

Introdution à Java EE
11/334
4

1 7

5
3 6

8
9

Note : les copies d'écran ci-dessus sont celles du projet complet. On ignorera pour l'instant certains de ses éléments.

• en [1] : le projet Java. On y notera :


• [2] : le paquetage [jpa] et les trois entités [Cotisation, Employe, Indemnite]
• [3] : le fichier [persistence.xml] qui configure la couche JPA
• en [4] : le dossier complet du projet :
• [5, 7] : le dossier [conf] contient les fichiers [persistence.xml] pour divers SGBD
• [6, 8] : le dossier [ddl] contient le schéma de la base de données générée par la couche JPA pour chacun des
SGBD précédents
• en [9] : le script [ant-hibernate.xml] est le script ant qui va générer la base de données à partir des entités JPA.

Question : donner le contenu du fichier [persistence.xml] qui connecterait la couche JPA à la base de données ayant les
caractéristiques suivantes : [SGBD : MySQL5, url : http://localhost:3306/dbpam, propriétaire de la connexion : dbpam, mot de
passe : dbpam].

Notes : suivre l'exemple du paragraphe 2.1.5 de [ref1]

4 Mise en oeuvre des tests de la couche JPA


Nous mettons en place l'environnement de tests de la couche JPA développée précédemment. Pour cela, nous utilisons l'IDE
Netbeans 5.5.1 (http://www.netbeans.org/).

• créer le projet Netbeans :

Introdution à Java EE
12/334
1
2

8 7
11
6
9

10

• [1] : option [File / New Project]


• [2,3] : choisir la catégorie [General] et le type de projet [Java Application]
• [4] : explique le type du projet
• [5] : on passe à l'étape suivante
• [6,7] : à l'aide de [7], on désigne le dossier [6] dans lequel sera créé le sous-dossier du projet.
• [8] : on donne un nom au projet
• [9] : précise le dossier qui va être créé pour le projet
• [10] : par défaut, l'option [Main Class] est cochée. Elle entraîne la création d'une classe dite principale avec la méthode
statique void main (String[] args). Une classe principale est nécessaire pour avoir une génération complète du projet. Nous
l'appelons [Main] et la plaçons dans un paquetage [main]. Nous terminons l'assistant de création du projet avec le bouton
[Finish] non représenté.
• [11] : le projet a été créé. La classe [Main] créée est la suivante :

1. package main;
2.
3. public class Main {
4.
5. /** Creates a new instance of Main */
6. public Main() {
7. }
8.
9. /**
10. * @param args the command line arguments
11. */
12. public static void main(String[] args) {
13. // TODO code application logic here
14. }
15.
16. }

• associer au projet créé les bibliothèques de classes dont il va avoir besoin. Celles-ci ont été rassemblées dans 5 dossiers :

Introdution à Java EE
13/334
4
2

5 7 8
6

• en [2] : l'emplacement du dossier [lib] qui contient les classes externes nécessaires au projet
• en [3] : les cinq sous-dossiers du dossier [lib]
• en [4] : le dossier [divers] contient les pilotes Jdbc des SGBD testés, l'archive log4j qui gère les logs de l'application,
l'archive testng nécessaire aux tests unitaires des couches de l'application
• en [5] : le dossier [hibernate] contient les classes de l'implémentation JPA / Hibernate
• en [6] : le dossier [spring] contient les classes du framework Spring
• en [7] : le dossier [jbossejb3] contient les classes du conteneur Ejb JBoss Ejb3
• en [8] : le dossier [toplink] contient les classes de l'implémentation JPA / Toplink

Pour associer des bibliothèques de classes (.jar) au projet Netbeans, on procède de la façon suivante :

Introdution à Java EE
14/334
2

3
1

4 5

• en [1] : clic droit sur [Libraries] puis sélectionner l'option [Add JAR/Folder] qui permet d'ajouter des archives Java (.jar) au
Classpath du projet.
• en [2] : naviguer jusqu'au dossier qui contient les .jar à ajouter
• en [3] : sélectionner les . jar désirés. Ils apparaissent en [4]
• en [5] : valider la sélection
• on fait ce processus pour les dossiers [divers, hibernate, spring]. [divers] nous apporte les pilotes Jdbc dont nous aurons
besoin pour travailler avec divers SGBD. [hibernate] nous apporte les archives de l'implémentation JPA / Hibernate ainsi
que l'archive [hibernate-tools] dont nous aurons besoin dans un script ant. [spring] nous apporte les classes du framework
spring qui sera utilisé ultérieurement pour l'intégration des couches [ui, métier, dao].

Le résultat obtenu est le suivant (vue partielle) :

• insérer dans le projet le code des entités JPA et celui du fichier de configuration [persistence.xml]
• une fois disponibles les sources Java des entités JPA, le fichier de configuration [persistence.xml], les bibliothèques de
classes nécessaires à la compilation et à l'exécution du projet, on peut générer celui-ci :

1b
1
2

3a

Introdution à Java EE
15/334
3b 7

• [1] : la génération du projet


• [2] : le projet est généré dans le dossier [dist] de l'onglet [Files]. On y trouve trois éléments :
• le dossier <lib> qui contient les bibliothèques de classes tierces associées au projet (cf [7]). Ce dossier n'est pas
généré si le projet n'a pas de classe principale. C'est pourquoi nous avons laissé l'assistant créer une classe
principale dont nous n'avions pas besoin pour les tests ant que nous voulons faire.
• l'archive .jar [3a, 3b] résultat de la compilation de la branche [Source Packages] du projet. Sous forme
compressée, on retrouve l'arborescence et le contenu de la branche [Source Packages] [1b] à la différence que les
.java ont été compilés (4,5). Le .jar [3] porte le nom du projet.
• [5] un fichier [MANIFEST.MF] qui configure l'exécution du .jar. Son contenu est le suivant :

1. Manifest-Version: 1.0
2. Ant-Version: Apache Ant 1.6.5
3. Created-By: 1.5.0_12-b04 (Sun Microsystems Inc.)
4. Main-Class: main.Main
5. Class-Path: lib/antlr-2.7.6.jar lib/asm-attrs.jar lib/asm.jar lib/c3p0 -0.9.0.jar lib/cglib-
2.1.3.jar lib/commons-collections-2.1.1.jar lib/commons-logging-1.0.4.jar lib/dom4j-1.6.1.jar ...
6. X-COMMENT: Main-Class will be added automatically by build

Le fichier .jar peut être exécuté dans une fenêtre Dos / Windows avec la commande suivante :

dos>java -jar "<dist>\swing-metier-dao-jpa-spring-hibernate.jar"

où <dist> représente le chemin du dossier <dist> dans la copie d'écran [2]. La machine virtuelle Java (JVM)
utilise le fichier [META-INF / MANIFEST.MF] pour savoir comment exécuter le .jar :
• ligne 4 : l'attribut [Main-Class] désigne la classe qui contient la méthode statique void main(String[] args)
que la JVM doit exécuter pour lancer l'application.
• ligne 5 : l'attribut [Class-Path] désigne la liste des .jar à explorer par la JVM lorsque l'application réclame
une classe qui n'est pas dans le .jar exécuté.

L'environnement nécessaire à la création de la base de données, image des entités JPA, est désormais prêt. Nous allons utiliser un
script ant pour faire cette génération. Cette technique est décrite au paragraphe [2.1.2] de [ref1].

Introdution à Java EE
16/334
9

Le script [ant-hibernate.xml] (8) va permettre de générer la base de données MySQL5 image de la couche JPA. Il mettra également
le schéma de la base de données générée dans le fichier [ddl/schema.sql]. Son contenu est le suivant :

1. <project name="jpa-hibernate" default="DDL" basedir=".">


2.
3. <!-- nom du projet et version -->
4. <property name="proj.name" value="jpa-hibernate" />
5. <property name="proj.shortname" value="jpa-hibernate" />
6. <property name="version" value="1.0" />
7.
8. <!-- Propriété globales -->
9. <property name="src.java.dir" value="src" />
10. <property name="dist.dir" value="dist" />
11.
12. <!-- le Classpath du projet -->
13. <path id="project.classpath">
14. <fileset dir="${dist.dir}">
15. <include name="**/*.jar" />
16. </fileset>
17. </path>
18.
19. <!-- Hibernate Tools -->
20. <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="project.classpath"/>
21.
22. <!-- Générer le schéma de la base -->
23. <target name="DDL" description="Génération DDL base">
24. <hibernatetool destdir="${basedir}">
25. <!-- Utiliser META-INF/persistence.xml -->
26. <jpaconfiguration />
27. <!-- export -->
28. <hbm2ddl drop="true" create="true" export="false" outputfilename="ddl/schema.sql"
delimiter=";" format="true" />
29. </hibernatetool>
30. </target>
31.
32. <!-- Générer la base -->
33. <target name="BD" description="Génération BD">
34. <hibernatetool destdir="${basedir}">
35. <!-- Utiliser META-INF/persistence.xml -->
36. <jpaconfiguration />
37. <!-- export -->
38. <hbm2ddl drop="true" create="true" export="true" outputfilename="ddl/schema.sql"
delimiter=";" format="true" />
39. </hibernatetool>
40. </target>
41. </project>

• ligne 1 : le projet [ant] s'appelle "jpa-hibernate". Il rassemble un ensemble de tâches dont l'une est la tâche par défaut : ici
la tâche nommée "DDL". Un script ant est appelé pour exécuter une tâche T. Si celle-ci n'est pas précisée, c'est la tâche
par défaut qui est exécutée. basedir="." indique que pour tous les chemins relatifs trouvés dans le script, le point de
départ du chemin est le dossier dans lequel se trouve le script ant.
• lignes 3-10 : définissent des variables de script avec la balise <property name="nomVariable"
value="valeurVariable"/>. La variable peut ensuite être utilisée dans le script avec la notation ${nomVariable}. Les
noms peuvent être quelconques. Attardons-nous sur les variables définies aux lignes 9-11 :

Introdution à Java EE
17/334
• ligne 9 : définit une variable nommée "src.java.dir" (le nom est libre) qui va, dans la suite du script, désigner le
dossier qui contient les codes source Java. Sa valeur est "src", un chemin relatif au dossier désigné par l'attribut
basedir (ligne 1). Il s'agit donc du chemin "./src" où . désigne ici le dossier qui contient le script ant. C'est bien
dans le dossier src que se trouvent les codes source Java (cf [9] plus haut).
• ligne 10 : définit une variable nommée "dist.dir" qui va, dans la suite du script, désigner le dossier qui contient les
archives jars dont ont besoin les tâches Java du script. Sa valeur <dist> désigne le dossier (3) qui, on le sait,
contient une arborescence avec les archives .jar et les fichiers .class du projet.
• lignes 13-17 : la balise <path> sert à définir des éléments du classpath que devront utiliser les tâches ant. Ici, le
path "project.classpath" (le nom est libre) rassemble les archives .jar du dossier <dist>, c.a.d. les jars du dossier
<dist>/lib et celui du projet [swing-metier-dao-jpa-spring-hibernate.jar] (3a, 3b).
• ligne 20 : définition d'une tâche à l'aide de la balise <taskdef>. Une telle tâche a vocation à être réutilisée ailleurs
dans le script. C'est une facilité de codage. Parce que la tâche est utilisée à divers endroits du script, on la définit
une fois avec la balise <taskdef> et on la réutilise ensuite via son nom, lorsqu'on en a besoin.
• la tâche s'appelle hibernatetool (attribut name).
• sa classe est définie par l'attribut classname. Ici, la classe désignée sera trouvée dans l'archive
[hibernate-tools.jar] du dosssier <dist>/lib.
• l'attribut classpathref indique à ant où chercher la classe précédente
• les lignes 51-60 concernent la tâche qui nous intéresse ici, celle de la génération du schéma de la base de données
image des objets @Entity de notre projet Netbeans. Une tâche ant est définie au moyen de la balise <target>.
• ligne 23 : la tâche s'appelle DDL (comme Data Definition Language, le SQL associé à la création des
objets d'une base de données).
• lignes 24-29 : la tâche [hibernatetool] définie ligne 20 est appelée. On lui passe de nombreux paramètes,
outre ceux déjà définis ligne 20 :
• ligne 24 : le dossier de sortie des résultats produits par la tâche sera le dossier courant .
• ligne 26 : indique à la tâche [hibernatetool] comment elle peut connaître son environnement
d'exécution : la balise <jpaconfiguration/> lui indique qu'elle est dans un environnement
JPA et qu'elle doit donc utiliser le fichier [META-INF/persistence.xml] qu'elle trouvera ici
dans son classpath, c.a.d dans le dossier <dist>.
• la ligne 28 fixe les conditions de génération de la base de données : drop=true indique que
des ordres SQL drop table doivent être émis avant la création des tables, create=true indique
que le fichier texte des ordres SQL de création de la base doit être créé, outputfilename
indique le nom de ce fichier SQL - ici schema.sql dans le dossier <ddl> du projet Eclipse,
export=false indique que les ordres SQL générés ne doivent pas être joués dans une
connexion au SGBD. Ce point est important : il implique que pour exécuter la tâche, le SGBD
cible n'a pas besoin d'être lancé. delimiter fixe le caractère qui sépare deux ordres SQL dans le
schéma généré, format=true demande à ce qu'un formatage de base soit fait sur le texte
généré.
• les lignes 33-39 définissent la tâche nommée BD. Elle est identique à la tâche DDL précédente, si ce n'est que
cette fois elle génère la base de données (export="true" de la ligne 38). La tâche ouvre une connexion sur le
SGBD avec les informations trouvées dans [persistence.xml], pour y jouer le schéma SQL et générer la base de
données. Pour exécuter la tâche BD, il faut donc que le SGBD soit lancé.

Nous testons maintenant la couche JPA [entités, persistence.xml] avec le SGBD MySQL5. Le mode opératoire est le suivant :

1. lancer MySQL5
2. avec les outils d'administration de MySQL5, créer une base de données appelée [dbpam] ainsi qu'un utilisateur [dbpam /
dbpam] ayant tous les droits sur la base [dbpam] (cf paragraphe 5.5 de [ref1])
3. dans [Netbeans], lancer la tâche BD du script [ant-hibernate.xml] :

On obtient les résultats suivants dans la console de Netbeans :

1. BD:
2. Executing Hibernate Tool with a JPA Configuration
3. 1. task: hbm2ddl (Generates database schema)

Introdution à Java EE
18/334
4. alter table EMPLOYES
5. drop
6. foreign key FK75C8D6BC73F24A67;
7. drop table if exists COTISATIONS;
8. drop table if exists EMPLOYES;
9. drop table if exists INDEMNITES;
10. create table COTISATIONS (
11. id bigint not null auto_increment,
12. VERSION integer not null,
13. CSGRDS double precision not null,
14. CSGD double precision not null,
15. SECU double precision not null,
16. RETRAITE double precision not null,
17. primary key (id)
18. ) ENGINE=InnoDB;
19. create table EMPLOYES (
20. id bigint not null auto_increment,
21. VERSION integer not null,
22. SS varchar(15) not null unique,
23. NOM varchar(30) not null,
24. PRENOM varchar(20) not null,
25. ADRESSE varchar(50) not null,
26. VILLE varchar(30) not null,
27. CP varchar(5) not null,
28. INDEMNITE_ID bigint not null,
29. primary key (id)
30. ) ENGINE=InnoDB;
31. create table INDEMNITES (
32. id bigint not null auto_increment,
33. VERSION integer not null,
34. INDICE integer not null unique,
35. BASE_HEURE double precision not null,
36. ENTRETIEN_JOUR double precision not null,
37. REPAS_JOUR double precision not null,
38. INDEMNITES_CP double precision not null,
39. primary key (id)
40. ) ENGINE=InnoDB;
41. alter table EMPLOYES
42. add index FK75C8D6BC73F24A67 (INDEMNITE_ID),
43. add constraint FK75C8D6BC73F24A67
44. foreign key (INDEMNITE_ID)
45. references INDEMNITES (id);
46. BUILD SUCCESSFUL (total time: 13 seconds)

Les logs de la console montre le schéma de la base générée. Ce schéma peut également être retrouvé dans le fichier
[ddl/schema.sql] :

• lignes 10-18 : la table [COTISATIONS]


• lignes 19-30 : la table [EMPLOYES]
• lignes 31-40 : la table [INDEMNITES]
• lignes 41-45 : la clé étrangère (INDEMNITES_ID) de la table [EMPLOYES] sur la colonne ID de la table
[INDEMNITES].

Pour vérifier que la base MySQL5 [dbpam] a été générée, nous procédons de la façon suivante :

Introdution à Java EE
19/334
1
3
4

5
6

• dans l'onglet [1] [Runtime], la branche [Databases] liste les connexions à des SGBD configurées par l'utilisateur. Pour créer
à une connexion à un SGBD, il faut donner deux informations à Netbeans :
• où trouver le pilote Jdbc du SGBD
• les coordonnées de l'url de la base de données à laquelle on veut se connecter.
Ces informations se donnent en deux temps : d'abord le pilote Jdbc s'il n'est pas déjà dans la liste des drivers disponibles
[Databases / Drivers], puis les coordonnées de la base de données.
• sélectionner la feuille [Databases / Drivers] de l'arborescence [2], cliquer droit et prendre l'option [New Driver]
• à l'aide de [3], désigner l'emplacement du pilote Jdbc de MySQL5. Dans ce TD, on le trouvera dans le dossier [lib / divers]
(cf page 13). L'archive du pilote Jdbc apparaît en [4]
• le nom de la classe du pilote Jdbc apparaît normalement en [5]. Si rien n'apparaît ou si vous connaissez la classe et que
celle affichée n'est pas la bonne, utiliser le bouton [6] pour trouver la classe.
• ceci fait, cliquer [OK]. Le pilote Jdbc de MySQL5 est ajouté à la liste des pilotes Jdbc disponibles :

2
3
1
4

• en [1] : cliquer droit sur le pilote JDBC MySQL et choisir [Connect Using...] pour créer la connexion à la base MySQL5 /
dbpam.
• en [2] : l'url Jdbc de la base de données
• en [3] : le propriétaire de la connexion
• en [4] : son mot de passe [dbpam]
• faire [OK]

La connexion est alors ajoutée à la liste des connexions configurées :

Introdution à Java EE
20/334
1 3 5

• en [1] : les 3 tables générées par le script [ant-hibernate.xml]


• en [2] : la structure de la table [COTISATIONS]
• en [3] : la structure de table [EMPLOYES] avec en [4] sa clé étrangère sur la colonne INDEMNITES(ID)
• en [5] : la structure de table [INDEMNITES]

Pour voir le contenu d'une table :

2
3

• en [1] : on demande à voir le contenu de la table [employes]


• en [3] : la requête SQL émise
• en [2] : la connexion sur laquelle elle est émise
• en [4] : le résultat de la requête – la table est vide.

Travail pratique :

Refaire le travail précédent avec certains des SGBD suivants : Oracle XE, SQL Server Express, Firebird, Apache Derby, HSQL. A
chaque fois, sauvegarder le fichier [schema.sql] généré par le script [ant-hibernate.xml] dans le dossier [ddl / sgbd] où sgbd désigne
le SGBD utilisé pour le test. Après avoir changé le SGBD dans le fichier [persistence.xml], régénérer le projet (option Build Project)
avant d'exécuter le script ant.

Revenons à l'architecture de notre application :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

Les entités JPA [4] sont désormais écrites. Nous abordons maintenant l'écriture de la couche [dao] [3] mais auparavant nous allons
définir les interfaces présentées par les couches [métier] et [dao].

Introdution à Java EE
21/334
5 Les interfaces des couches [metier] et [dao]
Dans l'architecture ci-dessus, quelle interface doit offrir la couche [dao] à la couche [metier] et quelle interface doit offrir la couche
[metier] à la couche [ui] ? Une première approche pour définir les interfaces des différentes couches est d'examiner les différents cas
d'usage (use cases) de l'application. Ici nous en avons deux, selon l'interface utilisateur choisie : console ou formulaire graphique.

Examinons le mode d'utilisation de l'application console :

34. dos>java -jar swing-metier-dao-jpa-spring-hibernate.jar 254104940426058 150 20


35.
1. Valeurs saisies :
2. N° de sécurité sociale de l'employé : 254104940426058
3. Nombre d'heures travaillées : 150
4. Nombre de jours travaillés : 20
5.
6. Informations Employé :
7. Nom : Jouveinal
8. ...
9.
10. Informations Cotisations :
11. CSGRDS : 3.49 %
12. ...
13.
14. Informations Indemnités :
15. ...
16.
17. Informations Salaire :
18. Salaire de base : 362.25 euro
19. Cotisations sociales : 97.48 euro
20. Indemnités d'entretien : 42.0 euro
21. Indemnités de repas : 62.0 euro
22. Salaire net : 368.77 euro

L'application reçoit trois informations de l'utilisateur (cf ligne 1 ci-dessus)


• le n° de sécurité sociale de l'assistante maternelle
• le nombre d'heures travaillées dans le mois
• le nombre de jours travaillés dans le mois

A partir de ces information et d'autres enregistrées dans des fichiers de configuration, l'application affiche les informations suivantes
:

• lignes 4-6 : les valeurs saisies


• lignes 8-10 : les informations liées à l'employé dont on a donné le n° de sécurité sociale
• lignes 12-14 : les taux des différentes cotisations sociales
• lignes 16-17 : les différentes indemnités versées à l'assistante maternelle
• lignes 19-24 : les éléments de la feuille de salaire de l'assistante maternelle

Un certain nombre d'informations doivent être fournies par la couche [metier] à la couche [ui] :

1. les informations liées à une assistante maternelle identifiée par son n° de sécurité sociale. On trouve ces informations dans
la table [EMPLOYES]. Cela permet d'afficher les lignes 6-8.
2. les montants des divers taux de cotisations sociales à prélever sur le salaire brut. On trouve ces informations dans la table
[COTISATIONS]. Cela permet d'afficher les lignes 10-12.
3. les montants des diverses indemnités liées à la fonction d'assistante maternelle. On trouve ces informations dans la table
[INDEMNITES]. Cela permet d'afficher les lignes 14-15.
4. les éléments constitutifs du salaire affichés lignes 18-22.

De ceci, on pourrait décider d'une première écriture de l'interface [IMetier] présentée par la couche [metier] à la couche [ui] :
1. package metier;
2.
3. public interface IMetier {
4. // obtenir la feuille de salaire
5. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
6. }

• ligne 1 : les éléments de la couche [metier] sont mis dans le paquetage [metier]

Introdution à Java EE
22/334
• ligne 5 : la méthode [ calculerFeuilleSalaire ] prend pour paramètres les trois informations acquises par la couche [ui] et
rend un objet de type [FeuilleSalaire] contenant les informations que la couche [ui] affichera sur la console. La classe
[FeuilleSalaire] pourrait être la suivante :

1. package metier;
2.
3. import jpa.Cotisation;
4. import jpa.Employe;
5. import jpa.Indemnite;
6.
7. public class FeuilleSalaire {
8. // champs privés
9. private Employe employe;
10. private Cotisation cotisation;
11. private Indemnite indemnite;
12. private ElementsSalaire elementsSalaire;
13.
14. ...
15. }

• ligne 9 : l'employé concerné par la feuille de salaire - information n° 1 affichée par la couche [ui]
• ligne 10 : les différents taux de cotisation - information n° 2 affichée par la couche [ui]
• ligne 11 : les différentes indemnités liées à l'indice de l'employé - information n° 3 affichée par la couche [ui]
• ligne 12 : les éléments constitutifs de son salaire - information n° 4 affichée par la couche [ui]

Un second cas d'usage de la couche [métier] apparaît avec l'interface graphique :

On voit ci-dessus, que la liste déroulante [1, 2] présente tous les employés. Cette liste doit être demandée à la couche [métier].
L'interface de celle-ci évolue alors de la façon suivante :
1. package metier;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
9. // liste des employés
10. public List<Employe> findAllEmployes();
11. }

• ligne [10] : la méthode qui va permettre à la couche [ui] de demander la liste de tous les employés à la couche [métier].

La couche [metier] ne peut initialiser les champs [Employe, Cotisation, Indemnite] de l'objet [FeuilleSalaire] ci-dessus qu'en
questionnant la couche [dao] car ces informations sont dans les tables de la base de données. Il en est de même pour obtenir la liste
de tous les employés. On peut créer une interface [dao] unique gérant l'accès aux trois entités [Employe, Cotisation, Indemnite].
Nous décidons plutôt ici de créer une interface [dao] par entité.

L'interface [dao] pour les accès aux entités [Cotisation] de la table [COTISATIONS] sera la suivante :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7. // créer une nouvelle cotisation
8. public Cotisation create(Cotisation cotisation);
9. // modifier une cotisation existante
10. public Cotisation edit(Cotisation cotisation);
11. // supprimer une cotisation existante

Introdution à Java EE
23/334
12. public void destroy(Cotisation cotisation);
13. // chercher une cotisation particulière
14. public Cotisation find(Long id);
15. // obtenir tous les objets Cotisation
16. public List<Cotisation> findAll();
17.
18. }

• ligne 6, l'interface [ICotisationDao] gère les accès à l'entité [Cotisation] et donc à la table [COTISATIONS] de la base de
données. Notre application n'a besoin que de la méthode [findAll] de la ligne 16 qui permet de retrouver tout le contenu
de la table [COTISATIONS]. On a voulu ici se mettre dans un cas plus général où toutes les opérations CRUD (Create,
Read, Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Cotisation]
• ligne 10 : la méthode [edit] modifie une entité [Cotisation] existante
• ligne 12 : la méthode [destroy] supprime une entité [Cotisation] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Cotisation] existante via son identifiant id
• ligne 16 : la méthode [findAll] rend dans une liste toutes les entités [Cotisation] existantes

Attardons-nous sur la signature de la méthode [create] :

1. // créer une nouvelle cotisation


2.Cotisation create(Cotisation cotisation);

La méthode create a un paramètre cotisation de type Cotisation. Le paramètre cotisation doit être persisté, c.a.d. ici mis dans la table
[COTISATIONS]. Avant cette persistance, le paramètre cotisation a un identifiant id sans valeur. Après la persistance, le champ id a
une valeur qui est la clé primaire de l'enregistrement ajouté à la table [COTISATIONS]. Le paramètre cotisation est donc un
paramètre d'entrée / sortie de la méthode create. Il ne semble pas nécessaire que méthode create rende de plus le paramètre cotisation
comme résultat. La méthode appelante détenant une référence sur l'objet [Cotisation cotisation], si celui-ci est modifié, elle a accès à
l'objet modifié puisqu'elle a une référence dessus. Elle peut donc connaître la valeur que la méthode create a donné au champ id de
l'objet [Cotisation cotisation]. La signature de la méthode pourrait donc être plus simplement :

1. // créer une nouvelle cotisation


2. void create(Cotisation cotisation);

Lorsqu'on écrit une interface, il est bon de se rappeler qu'elle peut être utilisée dans deux contextes différents : local et distant. Dans
le contexte local, la méthode appelante et la méthode appelée sont exécutées dans la même Jvm :

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [dao]

JVM

Si la couche [metier] fait appel à la méthode create de la couche [dao], elle a bien une référence sur le paramètre [Cotisation
cotisation] qu'elle passe à la méthode.

Dans le contexte distant, la méthode appelante et la méthode appelée sont exécutées dans des Jvm différentes :

Couche Couche 2 Couche d'accès aux


utilisateur 3 Données
interface métier données [dao]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Ci-dessus, la couche [metier] s'exécute dans la JVM 1 et la couche [dao] dans la JVM 2 sur deux machines différentes. Les deux
couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celle-
ci est composée d'une couche d'émission [2] et d'une couche de réception [3]. Le développeur n'a en général pas à écrire ces
couches de communication. Elles sont générées automatiquement par des outils logiciels. La couche [metier] est écrite comme si
elle s'exécutait dans la même Jvm que la couche [dao]. Il n'y a donc aucune modification de code.

Introdution à Java EE
24/334
Le mécanisme de communication entre la couche [metier] et la couche [dao] est le suivant :

• la couche [metier] fait appel à la méthode create de la couche [dao] en lui passant le paramètre [Cotisation cotisation1]
• ce paramètre est en fait passé à la couche d'émission [2]. Celle-ci va transmettre sur le réseau, la valeur du paramètre
cotisation1 et non sa référence. La forme exacte de cette valeur dépend du protocole de communication utilisé.
• la couche de réception [3] va récupérer cette valeur et reconstruire à partir d'elle un objet [Cotisation cotisation2] image du
paramètre initial envoyé par la couche [metier]. On a maintenant deux objets identiques (au sens de contenu) dans deux
Jvm différentes : cotisation1 et cotisation2.
• la couche de réception va passer l'objet cotisation2 à la méthode create de la couche [dao] qui va le persister en base de
données. Après cette opération, le champ id de l'objet cotisation2 a été initialisé par la clé primaire de l'enregistrement ajouté
à la table [COTISATIONS]. Ce n'est pas le cas de l'objet cotisation1 sur lequel la couche [metier] a une référence. Si on veut
que la couche [metier] ait une référence sur l'objet cotisation2, il faut le lui envoyer. Aussi est-on amenés à changer la
signature de la méthode create de la couche [dao] :

1. // créer une nouvelle cotisation


2. Cotisation create(Cotisation cotisation);

• avec cette nouvelle signature, la méthode create va rendre comme résultat l'objet persisté cotisation2. Ce résultat est rendu à
la couche de réception [3] qui avait appelé la couche [dao]. Celle-ci va rendre la valeur (et non la référence) de cotisation2 à
la couche d'émission [2].
• la couche d'émission [2] va récupérer cette valeur et reconstruire à partir d'elle un objet [Cotisation cotisation3] image du
résultat rendu par la méthode create de la couche [dao].
• l'objet [Cotisation cotisation3] est rendu à la méthode de la couche [metier] dont l'appel à la méthode create de la couche
[dao] avait initié tout ce mécanisme. La couche [metier] peut donc connaître la valeur de clé primaire donné à l'objet
[Cotisation cotisation1] dont elle avait demandé la persistance : c'est la valeur du champ id de cotisation3.

L'architecture précédente n'est pas la plus courante. On trouve plus fréquemment les couches [metier] et [dao] dans la même Jvm :

Couche 2 Couche Couche d'accès


utilisateur 3 Données
interface métier aux données [dao]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Dans cette architecture, ce sont les méthodes de la couche [metier] qui doivent rendre des résultats et non celles de la couche [dao].
Néanmoins la signature suivante de la méthode create de la couche [dao] :

1. // créer une nouvelle cotisation


2. Cotisation create(Cotisation cotisation);

nous permet de ne pas faire d'hypothèses sur l'architecture réellement mise en place. Utiliser des signatures qui fonctionneront
quelque soit l'architecture retenue, locale ou distante, implique que dans le cas où une méthode appelée modifie certains de ses
paramètres :
• ceux-ci doivent faire également partie du résultat de la méthode appelée
• la méthode appelante doit utiliser le résultat de la méthode appelée et non les références des paramètres modifiés qu'elle a
transmis à la méthode appelée.

On se laisse ainsi la possibilité de passer une couche d'une architecture locale à une architecture distante sans modification de code.
Réexaminons, à cette lumière, l'interface [ICotisationDao] :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Cotisation;
5.
6. public interface ICotisationDao {
7. // créer une nouvelle cotisation
8. public Cotisation create(Cotisation cotisation);
9. // modifier une cotisation existante
10. public Cotisation edit(Cotisation cotisation);
11. // supprimer une cotisation existante
12. public void destroy(Cotisation cotisation);
13. // chercher une cotisation particulière

Introdution à Java EE
25/334
14. public Cotisation find(Long id);
15. // obtenir tous les objets Cotisation
16. public List<Cotisation> findAll();
17.
18. }

• ligne 8 : le cas de la méthode create a été traité


• ligne 10 : la méthode edit utilise son paramètre [Cotisation cotisation1] pour mettre à jour l'enregistrement de la table
[COTISATIONS] ayant la même clé primaire que l'objet cotisation. Elle rend comme résultat l'objet cotisation2 image de
l'enregistrement modifié. Le paramètre cotisation1 n'est lui pas modifié. La méthode doit rendre cotisation2 comme résultat
qu'on soit dans le cadre d'une architecture distante ou locale.
• ligne 12 : la méthode destroy supprime l'enregistrement de la table [COTISATIONS] ayant la même clé primaire que l'objet
cotisation passé en paramètre. Celui-ci n'est pas modifié. Il n'a donc pas à être rendu.
• ligne 14 : le paramètre id de la méthode find n'est pas modifié par la méthode. Il n'a pas à faire partie du résultat.
• ligne 16 : la méthode findAll n'a pas de paramètres. On n'a donc pas à l'étudier.

Au final, seule la signature de la méthode create doit être adaptée pour être utilisable dans le cadre d'une architecture distante. Les
raisonnements précédents seront valables pour les autres interfaces [dao]. Nous ne les répèterons pas et utiliserons directement des
signatures utilisables aussi bien dans le cadre d'une architecture distante que locale.

L'interface [dao] pour les accès aux entités [Indemnite] de la table [INDEMNITES] sera la suivante :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Indemnite;
5.
6. public interface IIndemniteDao {
7. // créer une entité Indemnite
8. public Indemnite create(Indemnite indemnite);
9. // modifier une entité Indemnite
10. public Indemnite edit(Indemnite indemnite);
11. // supprimer une entité Indemnite
12. public void destroy(Indemnite indemnite);
13. // rechercher une entité Indemnite via son identifiant
14. public Indemnite find(Long id);
15. // obtenir toutes les entités Indemnite
16. public List<Indemnite> findAll();
17.
18. }

• ligne 6, l'interface [IIndemniteDao] gère les accès à l'entité [Indemnite] et donc à la table [INDEMNITES] de la base de
données. Notre application n'a besoin que de la méthode [findAll] de la ligne 16 qui permet de retrouver tout le contenu
de la table [INDEMNITES]. On a voulu ici se mettre dans un cas plus général où toutes les opérations CRUD (Create,
Read, Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Indemnite]
• ligne 10 : la méthode [edit] modifie une entité [Indemnite] existante
• ligne 12 : la méthode [destroy] supprime une entité [Indemnite] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Indemnite] existante via son identifiant id
• ligne 16 : la méthode [findAll] rend dans une liste toutes les entités [Indemnite] existantes

L'interface [dao] pour les accès aux entités [Employe] de la table [EMPLOYES] sera la suivante :

1. package dao;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IEmployeDao {
7. // créer une nouvelle entité Employe
8. public Employe create(Employe employe);
9. // modifier une entité Employe existante
10. public Employe edit(Employe employe);
11. // supprimer une entité Employe
12. public void destroy(Employe employe);
13. // chercher une entité Employe via son identifiant id
14. public Employe find(Long id);
15. // chercher une entité Employe via son n° SS
16. public Employe find(String SS);
17. // obtenir toutes les entités Employe
18. public List<Employe> findAll();
19. }

Introdution à Java EE
26/334
• ligne 6, l'interface [IEmployeDao] gère les accès à l'entité [Employe] et donc à la table [EMPLOYES] de la base de
données. Notre application n'a besoin que de la méthode [findAll] de la ligne 16 qui permet de retrouver tout le contenu
de la table [EMPLOYES]. On a voulu ici se mettre dans un cas plus général où toutes les opérations CRUD (Create, Read,
Update, Delete) sont effectuées sur l'entité.
• ligne 8 : la méthode [create] crée une nouvelle entité [Employe]
• ligne 10 : la méthode [edit] modifie une entité [Employe] existante
• ligne 12 : la méthode [destroy] supprime une entité [Employe] existante
• ligne 14 : la méthode [find] permet de retrouver une entité [Employe] existante via son identifiant id
• ligne 16 : la méthode [find(String SS)] permet de retrouver une entité [Employe] existante via son n° SS. Nous avons vu
que cette méthode était nécessaire à l'application console.
• ligne 18 : la méthode [findAll] rend dans une liste toutes les entités [Employe] existantes. Nous avons vu que cette
méthode était nécessaire à l'application graphique.

6 La classe [PamException]
La couche [dao] va travailler avec l'API JDBC de Java. Cette API lance des exceptions contrôlées de type [SQLException] qui
présentent deux inconvénients :
• elles alourdissent le code qui doit obligatoirement gérer ces exceptions avec des try / catch.
• elles doivent être déclarées dans la signature des méthodes de l'interface [IDao] par un "throws SQLException". Ceci a
pour conséquence d'empêcher l'implémentation de cette interface par des classes qui lanceraient une exception contrôlée
d'un type différent de [SQLException].

Pour remédier à ce problème, la couche [dao] ne "remontera" que des exceptions non contrôlées de type [PamException]. Ceci a
deux conséquences :
• la couche [metier] n'aura pas l'obligation de gérer les exceptions de la couche [dao] avec des try / catch. Elle pourra
simplement les laisser remonter jusqu'à la couche [ui].
• les méthodes de l'interface [IDao] n'ont pas à mettre dans leur signature la nature de l'exception [PamException], ce qui
laisse la possiblité d'implémenter cette interface avec des classes qui lanceraient un autre type d'exception non contrôlée.

La classe [PamException] sera placée dans le paquetage [exception] du projet Netbeans :

Son code est le suivant :

1. package exception;
2.
3. @SuppressWarnings("serial")
4. public class PamException extends RuntimeException {
5.
6. // code d'erreur
7. private int code;
8.
9. public PamException(int code) {
10. super();
11. this.code = code;
12. }
13.
14. public PamException(String message, int code) {
15. super(message);
16. this.code = code;
17. }
18.
19. public PamException(Throwable cause, int code) {
20. super(cause);
21. this.code = code;
22. }
23.
24. public PamException(String message, Throwable cause, int code) {
25. super(message, cause);
26. this.code = code;

Introdution à Java EE
27/334
27. }
28.
29. // getter et setter
30.
31. public int getCode() {
32. return code;
33. }
34.
35. public void setCode(int code) {
36. this.code = code;
37. }
38.
39. }

• ligne 4 : [PamException] dérive de [RuntimeException]. C'est donc un type d'exceptions que le compilateur ne nous oblige
pas à gérer par un try / catch ou à mettre dans la signature des méthodes. C'est pour cette raison, que [PamException]
n'est pas dans la signature des méthodes de l'interface [IDao]. Cela permet à cette interface d'être implémentée par une
classe lançant un autre type d'exceptions, pourvu que celui-ci dérive également de [RuntimeException].
• pour différencier les erreurs qui peuvent se produire, on utilise le code erreur de la ligne 7. Les trois constructeurs des
lignes 14, 19 et 24 sont ceux de la classe parente [RuntimeException] auxquels on a rajouté un paramètre : celui du code
d'erreur qu'on veut donner à l'exception.

Le fonctionnement de l'application, du point de vue des exceptions, sera le suivant :

• la couche [dao] encapsulera toute exception rencontrée, dans une exception de type [PamException], et relancera cette
dernière pour la couche [métier].
• la couche [métier] laissera remonter les exceptions lancées par la couche [dao]. Elle encapsulera toute exception survenant
dans la couche [métier], dans une exception de type [PamException] et relancera cette dernière pour la couche [ui].
• la couche [ui] intercepte toutes les exceptions qui remontent des couches [métier] et [dao]. Elle se contentera d'afficher
l'exception sur la console ou l'interface graphique.

Examinons maintenant successivement l'implémentation des couches [dao] et [metier].

7 La couche [dao] de l'application [PAM]


Nous nous plaçons dans le cadre de l'architecture suivante :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

7.1 Implémentation
Lectures conseillées : paragraphe 3.1.3 de [ref1]

Question : En utilisant l'intégration Spring / JPA, écrire les classes [CotisationDao, IndemniteDao, EmployeDao]
d'implémentation des interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Chaque méthode de classe interceptera une
éventuelle exception et l'encapsulera dans une exception de type [PamException] avec un code d'erreur propre à l'exception
interceptée.

Les classes d'implémentation feront partie du paquetage [dao] :

Introdution à Java EE
28/334
7.2 Configuration
Lectures conseillées : paragraphe 3.1.5 de [ref1]

L'intégration Dao / JPA est configurée par le fichier Spring [spring-config-dao.xml] et le fichier JPA [persistence.xml] :

Question : écrire le contenu de ces deux fichiers. On supposera que la base de données utilisée est la base MySQL5 utilisée page
18. Le fichier Spring définira les trois beans suivants : employeDao de type EmployeDao, indemniteDao de type IndemniteDao, cotisationDao
de type CotisationDao.

7.3 Tests
Maintenant que la couche [dao] est écrite et configurée, nous pouvons la tester. L'architecture des tests sera la suivante :

4
Couche Couche Objets image Interface Implémentation Couche
[tests] [dao] de la BD [JPA] [Toplink [JDBC]
2 / Hibernate]
3 6
5
7 Spring

Introdution à Java EE
29/334
7.3.1 Tests 1
Lectures conseillées : paragraphes 3.1.6 et 3.1.7 de [ref1]

7.3.1.1 InitDB

Nous allons créer deux programmes de tests de la couche [dao]. Ceux-ci seront placés dans le paquetage [dao] [2] de la branche
[Test Packages] [1] du projet Netbeans. Cette branche n'est pas incluse dans le projet généré par l'option [Build project], ce qui nous
assure que les programmes de tests que nous y plaçons ne seront pas inclus dans le .jar final du projet.

4
1
2 5

Les classes placées dans la branche [Test Packages] ont connaissance des classes présentes dans la branche [Source Packages] ainsi
que des bibliothèques de classes du projet. Si les tests ont besoin de bibliothèques autres que celles du projet, celles-ci doivent être
déclarées dans la branche [Test Libraries] [3]. Pour ce faire, on procèdera comme montré page 13. Le résultat obtenu est montré en
[4]. Ici, seule la bibliothèque TestNG [5] a été ajoutée, la bibliothèque JUnit étant présente par défaut.

Les deux classes de tests utilisent l'outil de tests unitaires TestNG décrit au paragraphe 3.1.7 de [ref1] :
• [InitDB] ne fait aucun test. Elle remplit la base de données avec quelques enregistrements et affiche ensuite ceux-ci sur la
console.
• [TestNGDao] fait une série de tests dont elle vérifie le résultat.

Le squelette de la classe [InitDB] est le suivant :

1. package dao;
2.
3. ...
4.
5. public class InitDB {
6.
7. private IEmployeDao employeDao = null;
8. private ICotisationDao cotisationDao = null;
9. private IIndemniteDao indemniteDao = null;
10.
11. @BeforeClass
12. public void init(){
13. // configuration de l'application
14. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
15. // couches dao
16. employeDao = (IEmployeDao) ctx.getBean("employeDao");
17. cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
18. indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
19. }
20.
21. @Test
22. public void initDB(){
23. // on remplit la base
24. ...
25. // on affiche le contenu de la base
26. ...
27. }
28.
29. @BeforeMethod()
30. public void clean(){
31. // on vide la base
32. ...
33. }
34. }

• la méthode [init] est exécutée avant le début de la série des tests (annotation @BeforeClass). Elle instancie la couche [dao].
• la méthode [clean] est exécutée avant chaque test (annotation @BeforeMethod). Elle vide la base de données.

Introdution à Java EE
30/334
• la méthode [initDB] est un test (annotation @Test). C'est le seul. Un test doit contenir des instructions d'assertion assert
condition. Ici il n'y en aura aucune. La méthode est donc un faux test. Elle a pour rôle de remplir la base de données avec
quelques lignes puis d'afficher le contenu de la base sur la console. Ce sont les méthodes create et findAll des couches [dao]
qui sont ici utilisées.

Question : compléter le code de la classe [InitDB]. On s'aidera de l'exemple du paragraphe 3.1.6 de [ref1]. Le code génèrera le
contenu présenté au paragraphe 2.1, page 7.

7.3.1.2 Mise en oeuvre des tests

Il n'existe pas de plugin Netbeans pour l'outil TestNG. Netbeans est prévu pour travailler avec l'outil JUnit. Il est cependant
possible d'utiliser TestNG en modifiant la configuration du projet :

1 5
1. <project name="swing-metier-dao-jpa-spring-
hibernate" default="default" basedir=".">
2. <description>Builds, tests, and runs the
project swing-metier-dao-jpa-spring-
hibernate.</description>
3. <import file="nbproject/nbtargets-testng.xml"/>
4. <import file="nbproject/build-impl.xml"/>
2 5. ...

• dans le dossier [nbproject] [2] de l'onglet [Files] [1] du projet, on copie le fichier [nbtargets-testng.xml] [3]. Ce fichier peut
être trouvé à l'url [http://www.geocities.com/nimarukan/netbeans/nbtargets/nbtargets-testng.xml]. L'article qui explique
son rôle et son utilisation est à l'url [http://www.geocities.com/nimarukan/netbeans/nbtargets/nbtargets-alt-test-
runners.html].
• le fichier [build.xml] [4] est le script ant qui construit le projet. Afin que TestNG soit utilisé en lieu et place de JUnit, il
faut le modifier comme indiqué en [5] : il faut rajouter la ligne 3 qui importe le script ant [nbproject/nbtargets-testng.xml].

Nous sommes désormais prêts pour exécuter [InitDB]. Nous décrivons la procédure avec le SGBD MySQL5 :

Introdution à Java EE
31/334
7

2
4

3 6

• les classes [1] et les fichiers de configuration [2, 3] de la couche [dao] sont mis en place
• le SGBD MySQL5 est lancé
• le projet est construit [4]
• la classe [InitDB] est exécutée [5]
• la fenêtre [JUnit Test Results] [6] dit que cela s'est mal passé. C'est normal. Cette fenêtre destinée à JUnit n'est pas adaptée
à TestNG.
• la fenêtre [Output] [7] dit que les tests ont été réussis. Ce message n'est pas significatif ici, car le programme [InitDB] ne
contient aucune instruction d'assertion assert condition, qui pourrait provoquer l'échec du test. Néanmoins, cela montre
qu'il n'y a pas eu d'exception à l'exécution du test.

La fenêtre [output] [7] contient les logs de l'exécution, ceux de Spring et ceux du test lui-même :

1. init:
2. deps-jar:
3. compile:
4. compile-test-single:
5. [Parser] Running:
6. dao.InitDB
7.
8. 08:50:31,140 INFO ClassPathXmlApplicationContext:305 - Refreshing
org.springframework.context.support.ClassPathXmlApplicationContext@fa3ac1: display name
[org.springframework.context.support.ClassPathXmlApplicationContext@fa3ac1]; startup date [Wed Aug
29 08:50:31 CEST 2007]; root of context hierarchy
9. 08:50:31,640 INFO ClassPathXmlApplicationContext:317 - Bean factory for application context
[org.springframework.context.support.ClassPathXmlApplicationContext@fa3ac1]:
org.springframework.beans.factory.support.DefaultListableBeanFactory@157fb52
10. ...
11. 08:50:34,984 INFO ClassPathXmlApplicationContext:976 - Bean
'org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor' is not eligible
for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
12. Employés ----------------------
13. jpa.Employe[id=3,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]
14. jpa.Employe[id=4,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code postal=49014,indice=1]
15. Indemnités ----------------------
16. jpa.Indemnite[id=3,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités
CP=12.0]
17. jpa.Indemnite[id=4,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités
CP=15.0]
18. Cotisations ----------------------
19. jpa.Cotisation[id=2,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
20. PASSED: initDB
21.
22. ===============================================
23. dao.InitDB
24. Tests run: 1, Failures: 0, Skips: 0
25. ===============================================

Introdution à Java EE
32/334
26. ...
27. View test results in build/test/results/index.html
28.
29. test-single:
30. BUILD SUCCESSFUL (total time: 8 seconds)

• ligne 6 : la classe exécutée


• lignes 8-11 : les logs de Spring. On se rappelle que la méthode taguée [@BeforeClass] utilise Spring pour instancier la
couche [dao]
• lignes 12-19 : l'affichage du contenu de la base fait par la méthode [initDB] taguée [@Test]. Cette méthode a tout d'abord
initialisé la base avec des données puis listé le contenu de cette même base sur la console. C'est ce qui est vu ici.
• lignes 22-25 : le bilan des tests – il y avait un test et il a été réussi.
• ligne 27 : le bilan des tests a été placé dans une page HTML dont on nous donne le nom.

1 4

2
3

• dans l'onglet [Files] [1], on localise le fichier HTML des résultats [2] et on l'affiche [3] dans un navigateur.
• en [4] : la page web des résultats. Une couleur verte indique que l'ensemble des tests a été réussi, une couleur rouge qu'au
moins un test a échoué. On suit le lien [5] pour avoir le détail des tests.

6 8

10

• en [6] un page web avec divers liens que le lecteur est invité à explorer. Nous suivons le lien [7 – Results].
• la nouvelle page web [8] donne un résumé de l'exécution de la classe de tests [9] et un résultat test par test [10]. Un test
réussi est en vert, un test échoué en rouge. Dans ce cas, un lien dans la colonne [Exception] donne les détails de
l'exception qui a fait échouer le test.

7.3.2 Tests 2

Introdution à Java EE
33/334
7.3.2.1 TestNGDao

Nous nous intéressons maintenant à la seconde classe de tests [TestNGDao] :

Le squelette de la classe sera le suivant :

1. package dao;
2.
3. ...
4. public class TestNGDao {
5.
6. // couches dao
7. private IEmployeDao employeDao;
8. private IIndemniteDao indemniteDao;
9. private ICotisationDao cotisationDao;
10.
11. @BeforeClass
12. public void init(){
13. // log
14. log("init");
15. // configuration de l'application
16. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
17. // couches dao
18. setEmployeDao((IEmployeDao) ctx.getBean("employeDao"));
19. setIndemniteDao((IIndemniteDao) ctx.getBean("indemniteDao"));
20. setCotisationDao((ICotisationDao) ctx.getBean("cotisationDao"));
21. }
22.
23.
24. @BeforeMethod()
25. public void clean(){
26. ...
27. }
28.
29. // logs
30. private void log(String message) {
31. System.out.println("----------- " + message);
32. }
33.
34. // tests
35. @Test
36. public void test01(){
37. log("test01");
38. // liste des cotisations
39. List<Cotisation> cotisations=getCotisationDao().findAll();
40. int nbCotisations=cotisations.size();
41. // on ajoute une cotisation
42. Cotisation cotisation=getCotisationDao().create(new Cotisation(3.49,6.15,9.39,7.88));
43. // on la demande
44. cotisation =getCotisationDao().find(cotisation.getId());
45. // vérification
46. assert cotisation!= null;
47. assert Math.abs(3.49-cotisation.getCsgrds())<1e-6;
48. assert Math.abs(6.15-cotisation.getCsgd())<1e-6;
49. assert Math.abs(9.39-cotisation.getSecu())<1e-6;
50. assert Math.abs(7.88-cotisation.getRetraite())<1e-6;
51. // on la modifie
52. cotisation.setCsgrds(-1);
53. cotisation.setCsgd(-1);
54. cotisation.setRetraite(-1);
55. cotisation.setSecu(-1);
56. Cotisation cotisation2=getCotisationDao().edit(cotisation);
57. // vérifications
58. assert cotisation.getVersion()+1==cotisation2.getVersion();
59. assert Math.abs(-1-cotisation2.getCsgrds())<1e-6;

Introdution à Java EE
34/334
60. assert Math.abs(-1-cotisation2.getCsgd())<1e-6;
61. assert Math.abs(-1-cotisation2.getRetraite())<1e-6;
62. assert Math.abs(-1-cotisation2.getSecu())<1e-6;
63. // on demande l'élément modifié
64. Cotisation cotisation3 =getCotisationDao().find(cotisation2.getId());
65. // vérifications
66. assert cotisation3.getVersion()==cotisation2.getVersion();
67. assert Math.abs(-1-cotisation3.getCsgrds())<1e-6;
68. assert Math.abs(-1-cotisation3.getCsgd())<1e-6;
69. assert Math.abs(-1-cotisation3.getRetraite())<1e-6;
70. assert Math.abs(-1-cotisation3.getSecu())<1e-6;
71. // on supprime l'élément
72. getCotisationDao().destroy(cotisation3);
73. // vérifications
74. Cotisation cotisation4=getCotisationDao().find(cotisation3.getId());
75. assert cotisation4==null;
76. cotisations=getCotisationDao().findAll();
77. assert nbCotisations==cotisations.size();
78.
79. }
80.
81. @Test
82. public void test02(){
83. log("test02");
84. // on demande la liste des indemnités
85. ...
86. // on ajoute une Indemnite indemnite
87. ..
88. // on va chercher indemnite en base – on récupère indemnite1
89. .. B
90. // on vérifie que indemnite1 = indemnite
91. ...
92. // on modifie l'indemnité obtenue et on persiste la modification en BD. On obtient indemnite2
93. ...
94. // on vérifie la version de indemnite2
95. ...
96. // on va chercher indemnite2 en base – on obtient indemnite3
97. ...
98. // on vérifie que indemnite3 = indemnite2
99. ...
100. // on supprime en base l'image de indemnite3
101. ...
102. // on va chercher indemnite3 en base
103. ...
104. // on vérifie qu'on a obtenu une référence null
105. ...
106. }
107.
108. @Test
109. public void test03(){
110. log("test03");
111. // on répète un test analogue aux précédents pour Employe
112. ...
113. }
114.
115. @Test
116. public void test04(){
117. log("test04");
118. // on teste la méthode [IEmployeDao].find(String SS)
119. // d'abord avec un employé existant
120. // puis avec un employé inexistant
121....
122. }
123.
124. @Test
125. public void test05(){
126. log("test05");
127. // on crée deux indemnités avec le même indice
128. // enfreint la contrainte d'unicité de l'indice
129. // on vérifie qu'une exception de type PamException se produit
130. // et qu'elle a le n° d'erreur attendu
131....
132. }
133.
134. @Test
135. public void test06(){
136. log("test06");
137. // on crée deux employés avec le même n° SS
138. // enfreint la contrainte d'unicité sur le n° SS
139. // on vérifie qu'une exception de type PamException se produit
140. // et qu'elle a le n° d'erreur attendu
141....
142.

Introdution à Java EE
35/334
143. }
144.
145. @Test
146. public void test07(){
147. log("test07");
148. // on crée deux employés avec le même n° SS, le 1er avec create, le 2ème avec edit
149. // enfreint la contrainte d'unicité sur le n° SS
150. // on vérifie qu'une exception de type PamException se produit
151. // et qu'elle a le n° d'erreur attendu
152....
153. }
154.
155. @Test
156. public void test08(){
157. log("test08");
158. // supprimer un employé qui n'existe pas ne provoque pas d'exception
159. // il est ajouté puis détruit – on le vérifie
160....
161. }
162.
163. @Test
164. public void test09(){
165. log("test09");
166. // modifier un employé sans avoir la bonne version doit provoquer une exception
167. // on le vérifie
168....
169. }
170.
171. @Test
172. public void test10(){
173. log("test10");
174. // supprimer un employé sans avoir la bonne version doit provoquer une exception
175. // on le vérifie
176....

177. }
178.
179. // getters et setters
180. ...
181.}

Dans la classe de tests précédente, la base est vidée avant chaque test.

Question : écrire les méthodes suivantes :


- test02 : on s'inspirera de test01
- test03 : un employé a un champ de type Indemnite. Il faut donc créer une entité Indemnite et une entité Employe
- test04.

7.3.2.2 Mise en oeuvre des tests

En procédant de la même façon que pour la classe de tests [initDB], on obtient les résultats suivants :

Introdution à Java EE
36/334
3

• en [1], on exécute la classe de tests


• en [2], les résultats des tests dans la fenêtre [output]
• en [3], la page web des résultats

Provoquons une erreur pour voir comment cela est signalé dans la page web des résultats :

1.@Test
2. public void test01(){
3. log("test01");
4. // liste des cotisations
5. List<Cotisation> cotisations=getCotisationDao().findAll();
6. int nbCotisations=cotisations.size();
7. // on ajoute une cotisation
8. Cotisation cotisation=getCotisationDao().create(new Cotisation(3.49,6.15,9.39,7.88));
9. // on la demande
10. cotisation =getCotisationDao().find(cotisation.getId());
11. // vérification
12. assert cotisation!= null;
13. assert Math.abs(0-cotisation.getCsgrds())<1e-6;
14. assert Math.abs(6.15-cotisation.getCsgd())<1e-6;
15. assert Math.abs(9.39-cotisation.getSecu())<1e-6;
16. assert Math.abs(7.88-cotisation.getRetraite())<1e-6;
17. ...
18. }

Ligne 13, l'assertion va provoquer une erreur, la valeur de Csgrds étant 3.49 (ligne 8). L'exécution de la classe de tests donne les
résultats suivants :

Introdution à Java EE
37/334
1

• la page web des résultats [1] montre maintenant qu'il y a eu des tests non réussis [2].
• en [3], un résumé de l'exception qui a fait échouer le test. On y trouve le n° de la ligne du code Java où s'est produite
l'exception.
• [4] permet d'avoir le détail de l'exception [5].

8 La couche [metier] de l'application [PAM]


Maintenant que la couche [dao] a été écrite, nous passons à l'étude de la couche métier [2] :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

8.1 L'interface Java [IMetier]


Celle-ci a été décrite au paragraphe 5, page 23. Nous la rappelons ci-dessous :
1. package metier;
2.
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
9. // liste des employés
10. public List<Employe> findAllEmployes();
11. }

L'implémentation de la couche [metier] sera faite dans un paquetage [metier] :

Introdution à Java EE
38/334
Le paquetage [metier] comprendra, outre l'interface [IMetier] et son implémentation [Metier], deux autres classes [FeuilleSalaire] et
[ElementsSalaire]. La classe [FeuilleSalaire] a été brièvement présentée au paragraphe 5, page 23. Nous revenons dessus maintenant.

8.2 La classe [FeuilleSalaire]


La méthode [calculerFeuilleSalaire] de l'interface [IMetier] rend un objet de type [FeuilleSalaire] qui représente les différents
éléments d'une feuille de salaire. Sa définition est la suivante :

1. package metier;
2.
3. import jpa.Cotisation;
4. import jpa.Employe;
5. import jpa.Indemnite;
6.
7. public class FeuilleSalaire implements Serializable{
8. // champs privés
9. private Employe employe;
10. private Cotisation cotisation;
11. private Indemnite indemnite;
12. private ElementsSalaire elementsSalaire;
13.
14. // constructeurs
15. public FeuilleSalaire() {
16.
17. }
18.
19. public FeuilleSalaire(Employe employe, Cotisation cotisation,
20. Indemnite indemnite, ElementsSalaire elementsSalaire) {
21. setEmploye(employe);
22. setCotisation(cotisation);
23. setElementsSalaire(elementsSalaire);
24. setIndemnite(indemnite);
25. }
26.
27. // toString
28. public String toString() {
29. return "[" + employe + "," + cotisation + "," + indemnite + ","
30. + elementsSalaire + "]";
31. }
32.
33. // accesseurs
34. ...
35. }

• ligne 7 : la classe implémente l'interface Serializable parce que ses instances sont susceptibles d'être échangées sur le réseau.
• ligne 9 : l'employé concerné par la feuille de salaire
• ligne 10 : les différents taux de cotisation
• ligne 11 : les différentes indemnités liées à l'indice de l'employé
• ligne 12 : les éléments constitutifs de son salaire
• lignes 14-25 : les deux constructeurs de la classe
• lignes 28-31 : méthode [toString] identifiant un objet [FeuilleSalaire] particulier
• lignes 34 et au-delà : les accesseurs publics aux champs privés de la classe

La classe [ElementsSalaire] référencée ligne 12 de la classe [FeuilleSalaire] ci-dessus, rassemble les éléments constituant une fiche de
paie. Sa définition est la suivante :

1. package metier;
2.
3. public class ElementsSalaire implements Serializable{
4.
5. // champs privés

Introdution à Java EE
39/334
6. private double salaireBase;
7. private double cotisationsSociales;
8. private double indemnitesEntretien;
9. private double indemnitesRepas;
10. private double salaireNet;
11.
12. // constructeurs
13. public ElementsSalaire() {
14.
15. }
16.
17. public ElementsSalaire(double salaireBase, double cotisationsSociales,
18. double indemnitesEntretien, double indemnitesRepas,
19. double salaireNet) {
20. setSalaireBase(salaireBase);
21. setCotisationsSociales(cotisationsSociales);
22. setIndemnitesEntretien(indemnitesEntretien);
23. setIndemnitesRepas(indemnitesRepas);
24. }
25.
26. // toString
27. public String toString() {
28. return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales +
",indemnités d'entretien="
29. + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
30. + salaireNet + "]";
31. }
32.
33. // accesseurs publics
34. ...
35. }

• ligne 3 : la classe implémente l'interface Serializable parce qu'elle est un composant de la classe FeuilleSalaire qui doit être
sérialisable.
• ligne 6 : le salaire de base
• ligne 7 : les cotisations sociales payées sur ce salaire de base
• ligne 8 : les indemnités journalières d'entretien de l'enfant
• ligne 9 : les indemnités journalières de repas de l'enfant
• ligne 10 : le salaire net à payer à l'assistante maternelle
• lignes 12-24 : les constructeurs de la classe
• lignes 27-31 : méthode [toString] identifiant un objet [ElementsSalaire] particulier
• lignes 34 et au-delà : les accesseurs publics aux champs privés de la classe

8.3 La classe d'implémentation [Metier] de la couche [metier]


La classe d'implémentation [Metier] de la couche [metier] pourrait être la suivante :

1. package metier;
2.
3. ...
4.
5. @Transactional
6. public class Metier implements IMetier {
7.
8. // référence sur la couche [dao]
9. private ICotisationDao cotisationDao = null;
10. private IEmployeDao employeDao=null;
11. private IIndemniteDao indemniteDao=null;
12.
13.
14. // obtenir la feuille de salaire
15. public FeuilleSalaire calculerFeuilleSalaire(String SS,
16. double nbHeuresTravaillées, int nbJoursTravaillés) {
17. ...
18. }
19.
20. // liste des employés
21. public List<Employe> findAllEmployes() {
22. ...
23. }
24.
25. // getters et setters
26. ...
27. }

• ligne 5 : l'annotation Spring @Transactional fait que chaque méthode de la classe se déroulera au sein d'une transaction.

Introdution à Java EE
40/334
• lignes 9-11 : les référence sur les couches [dao] des entités [Cotisation, Employe, Indemnite]
• lignes 15-18 : la méthode [calculerFeuilleSalaire]
• lignes 21-23 : la méthode [findAllEmployes]
• ligne 25 et au-delà : les accesseurs publics des champs privés de la classe

Question : écrire le code de la méthode [findAllEmployes].

Question : écrire le code de la méthode [calculerFeuilleSalaire].

On notera les points suivants :


• le mode de calcul du salaire a été expliqué au paragraphe 2.2, page 8.
• si le paramètre [SS] ne correspond à aucun employé (la couche [dao] a renvoyé un pointeur null), la méthode lancera une
exception de type [PamException] avec un code d'erreur approprié.

8.4 Tests de la couche [metier]


Nous créons deux programmes de test :

2
3

Les classes de tests [3] sont créés dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet.

La classe [TestNGMetier_1] pourrait être la suivante :

1. package metier;
2.
3. ...
4.
5. public class TestNGMetier_1 {
6.
7. // couche métier
8. private IMetier metier;
9.
10. @BeforeClass
11. public void init(){
12. // log
13. log("init");
14. // configuration de l'application
15. // instanciation couche [metier]
16. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
17. metier = (IMetier) ctx.getBean("metier");
18. // couches dao
19. IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
20. ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
21. IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
22. // on vide la base
23. for(Employe employe:employeDao.findAll()){
24. employeDao.destroy(employe);
25. }
26. for(Cotisation cotisation:cotisationDao.findAll()){
27. cotisationDao.destroy(cotisation);
28. }
29. for(Indemnite indemnite : indemniteDao.findAll()){
30. indemniteDao.destroy(indemnite);
31. }
32. // on la remplit
33. Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
34. Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
35. Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue
des oiseaux","St Corentin","49203",indemnite2));
36. Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La
brûlerie","St Marcel","49014",indemnite1));

Introdution à Java EE
41/334
37. Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
38. }
39.
40. // logs
41. private void log(String message) {
42. System.out.println("----------- " + message);
43. }
44.
45. // test
46. @Test
47. public void test01(){
48. // calcul de feuilles de salaire
49. System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
50. System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
51. try {
52. System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
53. } catch (PamException ex) {
54. System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(),
ex.getMessage()));
55. }
56. }
57. }

Il n'y a pas d'assertion assert dans la classe. On cherche simplement à calculer quelques salaires afin de les vérifier ensuite à la main.
L'affichage écran obtenu par l'excéution de la classe précédente est le suivant :

1. ...
2. [Parser] Running:
3. metier.TestNGMetier_1
4.
5. ----------- init
6. 10:55:07,046 INFO ClassPathXmlApplicationContext:305 - Refreshing
org.springframework.context.support.ClassPathXmlApplicationContext@fa3ac1: display name
[org.springframework.context.support.ClassPathXmlApplicationContext@fa3ac1]; startup date [Wed Aug
29 10:55:07 CEST 2007]; root of context hierarchy
7. ...
8. [jpa.Employe[id=20,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code
postal=49014,indice=1],jpa.Cotisation[id=5,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88
],jpa.Indemnite[id=27,version=0,indice=1,base heure=1.93,entretien jour2.0,repas
jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités
d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
9. [jpa.Employe[id=19,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code
postal=49203,indice=2],jpa.Cotisation[id=5,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88
],jpa.Indemnite[id=28,version=0,indice=2,base heure=2.1,entretien jour2.1,repas
jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités
d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
10. PamException[Code=101, message=L'employé de n°[xx] est introuvable]
11. PASSED: test01
12.
13. ===============================================
14. metier.TestNGMetier_1
15. Tests run: 1, Failures: 0, Skips: 0
16. ===============================================
17. ...

• ligne 8 : la feuille de salaire de Justine Laverti


• ligne 9 : la feuille de salaire de Marie Jouveinal
• ligne 10 : l'exception due au fait que l'employé de n° SS 'xx' n'existe pas.

Question : la ligne 17 de [TestNGMetier_1] utilise le bean Spring nommé metier. Donner la définition de ce bean dans le fichier
[spring-config-metier-dao.xml].

La classe [TestNGMetier_2] pourrait être la suivante :

1. package metier;
2.
3. ...
4. public class TestNGMetier_2 {
5.
6. // couches dao
7. private IMetier metier;
8.
9. @BeforeClass
10. public void init(){

Introdution à Java EE
42/334
11. ...
12. }
13.
14. // logs
15. private void log(String message) {
16. System.out.println("----------- " + message);
17. }
18.
19. // test
20. @Test
21. public void test01(){
22. ...
23. }
24. }

La classe [TestNGMetier_2] est une copie de la classe [ TestNGMetier_1] où cette fois, des assertions ont été placées dans la
méthode test01.

Question : écrire la méthode test01.

Lors de l'exécution de la classe [TestNGMetier_2], on obtient les résultats suivants si tout va bien :

9 La couche [ui] de l'application [PAM] – version console


Maintenant que la couche [metier] a été écrite, il nous reste à écrire la couche [ui] [1] :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

Nous créerons deux implémentations différentes de la couche [ui] : une version console et une version graphique swing :

Introdution à Java EE
43/334
9.1.1 La classe [ui.console.Main]
Nous nous intéressons tout d'abord à l'application console implémentée par la classe [ui.console.Main] ci-dessus. Son
fonctionnement a été décrit au paragraphe 2.3, page 9. Le squelette de la classe [Main] pourrait être le suivant :

1. package ui.console;
2.
3. import exception.PamException;
4. import metier.FeuilleSalaire;
5. import metier.IMetier;
6.
7. import java.util.ArrayList;
8. import org.springframework.context.ApplicationContext;
9. import org.springframework.context.support.ClassPathXmlApplicationContext;
10.
11. public class Main {
12.
13. /**
14. * @param args
15. */
16. public static void main(String[] args) {
17. // données locales
18. final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
19. // on vérifie le nombre de paramètres
20. ...
21. // liste des erreurs
22. ArrayList erreurs = new ArrayList();
23. // le second paramètre doit être un nombre réel >0
24. ...
25. // erreur ?
26. if (...) {
27. erreurs.add("Le nombre d'heures travaillées [" + args[1]
28. + "] est erroné");
29. }
30. // le troisième paramètre doit être un nombre entier >0
31. ...
32. // erreur ?
33. if (...) {
34. erreurs.add("Le nombre de jours travaillés [" + args[2]
35. + "] est erroné");
36. }
37. // des erreurs ?
38. if (erreurs.size() != 0) {
39. for (int i = 0; i < erreurs.size(); i++) {
40. System.err.println(erreurs.get(i));
41. }
42. return;
43. }
44. // c'est bon - on peut demander la feuille de salaire
45. // instanciation couche [metier]
46. ...
47. // calcul de la feuille de salaire
48. FeuilleSalaire feuilleSalaire = null;
49. try {
50. ...
51. } catch (...) {
52. ...
53. }
54. // affichage détaillé
55. String output = "Valeurs saisies :\n";
56. output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
57. output += ajouteInfo("Nombre d'heures travaillées", args[1]);
58. output += ajouteInfo("Nombre de jours travaillés", args[2]);
59. output += ajouteInfo("\nInformations Employé", "");
60. output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
61. output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
62. output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
63. output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
64. output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
65. output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
66. output += ajouteInfo("\nInformations Cotisations", "");
67. output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
68. output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
69. output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
70. output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
71. output += ajouteInfo("\nInformations Indemnités", "");
72. output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getIndemnite().getBaseHeure() + "
euro");
73. output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getIndemnite().getEntretienJour() +
" euro");
74. output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getIndemnite().getRepasJour() + "
euro");

Introdution à Java EE
44/334
75. output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getIndemnite().getIndemnitesCP()+ "
%");
76. output += ajouteInfo("\nInformations Salaire", "");
77. output += ajouteInfo("Salaire de base", ""+
feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
78. output += ajouteInfo("Cotisations sociales", ""+
feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
79. output += ajouteInfo("Indemnités d'entretien", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
80. output += ajouteInfo("Indemnités de repas", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
81. output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() +
" euro");
82.
83. System.out.println(output);
84. }
85.
86. static String ajouteInfo(String message, String valeur) {
87. return message + " : " + valeur + "\n";
88. }
89. }

Question : compléter le code ci-dessus.

9.1.2 Exécution

Pour exécuter la classe [ui.console.Main], on procèdera de la façon suivante :

6
7 3

4
5

• en [1], sélectionner les propriétés du projet


• en [2], sélectionner la propriété [Run] du projet
• utiliser le bouton [3] pour désigner la classe (dite classe principale) à exécuter
• sélectionner la classe [4] et valider la sélection [5]

Introdution à Java EE
45/334
• la classe apparaît en [6]. Celle-ci a besoin de trois arguments pour s'exécuter (n° SS, nombre d'heures travaillées, nombre
de jours travaillés). Ces arguments sont placés en [7].
• ceci fait, on peut exécuter le projet [8]. La configuration précédente fait que c'est la classe [ui.console.Main] qui va être
exécutée.

Les résultats de l'exécution sont obtenus dans la fenêtre [output] :

10 La couche [ui] de l'application [PAM] – version graphique


Nous implémentons maintenant la couche [ui] avec une interface graphique :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

Introdution à Java EE
46/334
2

• en [1], la classe [PamJFrame] de l'interface graphique


• en[2] : l'interface graphique

10.1 Un rapide tutoriel


Pour créer l'interface graphique, on pourra procéder de la façon suivante :

2 3

• [1] : on crée un nouveau fichier avec le bouton [1] [New File...]


• [2] : on choisit la catégorie du fichier [Java GUI Forms], c.a.d. formulaires graphiques
• [3] : on choisit le type [JFrame], un type de formulaire vide
• [4] : on passe à l'étape suivante

Introdution à Java EE
47/334
5

• [5] : on donne un nom au formulaire qui sera aussi une classe


• [6] : on place le formulaire dans un paquetage
• [7] : on valide la configuration du formulaire
• [8] : le formulaire est ajouté à l'arborescence du projet
• [9] : le formulaire est accessible selon deux perspectives : [Design] [9] qui permet de dessiner les différents composants du
formulaire, [Source] [10 ci-dessous] qui donne accès au code Java du formulaire. Au final, un formulaire est une classe Java
comme une autre. La perspective [Design] est une facilité pour dessiner le formulaire. A chaque ajout de composant en
mode [Design], du code Java est ajouté dans la perspective [Source] pour le prendre en compte.

11 12

10

• [11] : la liste des composants Swing disponibles pour un formulaire est trouvée dans la fenêtre [Palette].
• [12] : la fenêtre [Inspector] présente l'arborescence des composants du formulaire. Les composants ayant une
représentation visuelle se retrouveront dans la branche [JFrame], les autres dans la branche [Other Components].

13 15

14

• en [13], nous sélectionnons un composant [JLabel] par un clic simple


• en [14], nous le déposons sur le formulaire en mode [Design]
• en [15], nous définissons les propriétés du JLabel (text, font).

Introdution à Java EE
48/334
17
18
16 19

• en [16], le résultat obtenu.


• en [17], on demande la prévisualisation du formulaire
• en [18], le résultat
• en [19], le label [JLabel1] a été ajouté à l'arborescence des composants dans la fenêtre [Inspector]

21
20

• en [20] et [21] : dans la perspective [Source] du formulaire, du code Java a été ajouté pour gérer le JLabel ajouté.

Un tutoriel sur la construction de formulaires avec Netbeans est disponible à l'url


[http://www.netbeans.org/kb/trails/matisse.html].

10.2 L'interface graphique [PamJFrame]


On construira l'interface graphique suivante :

• en [1], l'interface graphique


• en [2], l'arborescence de ses composants : un JLabel et six conteneurs JPanel

Introdution à Java EE
49/334
JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

Introdution à Java EE
50/334
JPanel5

Travail pratique : construire l'interface graphique précédente en s'aidant du tutoriel


[http://www.netbeans.org/kb/trails/matisse.html].

10.3 Les événements de l'interface graphique


Lectures conseillées : chapitre [Interfaces graphiques] de [ref2].

Nous gèrerons le clic sur le bouton [jButtonSalaire]. Pour créer la méthode de gestion de cet événement, on pourra procéder
comme suit :

Le gestionnaire du clic sur le bouton [JButtonSalaire] est généré :

1. private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {


2....
3.}

Le code Java qui associe la méthode précédente au clic sur le bouton [JButtonSalaire] est lui aussi généré :

1. jButtonSalaire.setFont(new java.awt.Font("Tahoma", 1, 14));


2. jButtonSalaire.setText("Salaire");
3. jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
4. public void actionPerformed(java.awt.event.ActionEvent evt) {
5. jButtonSalaireActionPerformed(evt);
6. }
7. });

Ce sont les lignes 3-6 qui indiquent que le clic (evt de type ActionPerformed) sur le bouton [jButtonSalaire] (ligne 3) doit être géré par
la méthode [ jButtonSalaireActionPerformed] (ligne 5).

Nous gèrerons également, l'événement [caretUpdate] (déplacement du curseur de saisie) sur le champ de saisie [jTextFieldHT].
Pour créer le gestionnaire de cet événement, nous procédons comme précédemment :

Introdution à Java EE
51/334
Le gestionnaire de l'événement [caretUpdate] sur le champ de saisie [jTextFieldHT] est généré :

private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {


...
}

Le code Java qui associe la méthode précédente à l'événement [caretUpdate] sur le champ de saisie [jTextFieldHT] est lui aussi
généré :

1. jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
2. public void caretUpdate(javax.swing.event.CaretEvent evt) {
3. jTextFieldHTCaretUpdate(evt);
4. }
5. });

Les lignes 1-4 indiquent que l'événement [caretUpdate] (ligne 2) sur le bouton [jTextFieldHT] (ligne 1) doit être géré par la méthode
[ jTextFieldHTCaretUpdate] (ligne 3).

10.4 Initialisation de l'interface graphique


Revenons à l'architecture de notre application :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring

La couche [ui] a besoin d'une référence sur la couche [metier]. Rappelons comment cette référence avait été obtenue dans
l'application console :

1. // instanciation couche [metier]


2. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
3. IMetier metier = (IMetier) ctx.getBean("metier");

La méthode est la même dans l'application graphique. Il faut que lorsque celle-ci s'initialise, la référence [IMetier metier] de la ligne
3 ci-dessus soit également initialisée. Le code généré pour l'interface graphique est pour l'instant le suivant :

1. package ui.swing;
2.
3. ...
4. public class PamJFrame extends javax.swing.JFrame {
5.
6. /** Creates new form PamJFrame */
7. public PamJFrame() {
8. initComponents();
9. }
10.
11. /** This method is called from within the constructor to
12. * initialize the form.
13. * WARNING: Do NOT modify this code. The content of this method is
14. * always regenerated by the Form Editor.
15. */
16. // <editor-fold defaultstate="collapsed" desc=" Generated Code ">
17. private void initComponents() {
18. ...
19. }// </editor-fold>
20.
21. private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {
22. ...
23. }
24.
25. private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
26. ...
27. }
28.

Introdution à Java EE
52/334
29. public static void main(String args[]) {
30. java.awt.EventQueue.invokeLater(new Runnable() {
31. public void run() {
32. new PamJFrame().setVisible(true);
33. }
34. });
35. }
36.
37. // Variables declaration - do not modify
38. private javax.swing.JButton jButtonSalaire;
39. ...
40. // End of variables declaration
41.
42. }

• lignes 29-35 : la méthode statique [main] qui lance l'application


• ligne 32 : une instance de l'interface graphique [PamJFrame] est créée et rendue visible.
• lignes 7-9 : le constructeur de l'interface graphique.
• ligne 8 : appel à la méthode [initComponents] définie ligne 17. Cette méthode est auto-générée à partir du travail fait en
mode [Design]. On ne doit pas y toucher.
• ligne 21 : la méthode qui va gérer le déplacement du curseur de saisie dans le champ [jTextFieldHT]
• ligne 25 : la méthode qui va gérer le clic sur le bouton [jButtonSalaire]

Pour ajouter au code précédent, nos propres initialisations, nous pouvons procéder comme suit :

1. /** Creates new form PamJFrame */


2. public PamJFrame() {
3. initComponents();
4. doMyInit();
5. }
6.
7. ...
8.
9. // variables d'instance
10. private IMetier metier=null;
11. private List<Employe> employes=null;
12. private String[] employesCombo=null;
13. private double heuresTravaillées=0;
14.
15. // initialisations propriétaires
16. public void doMyInit(){
17. // init contexte
18. try{
19. // instanciation couche [metier]
20. ...
21. // liste des employés
22. ...
23. }catch (PamException ex){
24. // le message de l'exception est placé dans [jTextAreaStatus]
25. ...
26. // retour
27. return;
28. }
29. // bouton salaire inhibé
30. ...
31. // jScrollPane1 caché
32. ...
33. // spinner jours travaillés
34. jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
35. // combobox employés
36. employesCombo=new String[employes.size()];
37. int i=0;
38. for(Employe employe : employes){
39. employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
40. }
41. jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
42. }

• ligne 4 : on appelle une méthode propriétaire pour faire nos propres initialisations. Celles-ci sont définies par le code des
lignes 10-42

Question : en vous aidant des commentaires, compléter le code de la procédure [doMyInit].

Introdution à Java EE
53/334
10.5 Gestionnaires d'événements

Question : écrire la méthode [jTextFieldHTCaretUpdate]. Cette méthode doit faire en sorte que si la donnée présente dans le
champ [jTextFieldHT] n'est pas un nombre réel >=0, alors le bouton [jButtonSalaire] doit être inactif.

Question : écrire la méthode [jButtonSalaireActionPerformed] qui doit afficher la feuille de salaire de l'employé sélectionné dans
[jComboBoxEmployes].

10.6 Exécution de l'interface graphique


Pour exécuter l'interface graphique, on modifiera la configuration [Run] du projet :

• en [1], mettre la classe de l'interface graphique

Le projet doit être complet avec ses fichiers de configuration (persistence.xml, spring-config-metier-dao.xml) et la classe de
l'interface graphique. On lancera Le SGBD cible avant d'exécuter le projet.

11 Implémentation de la couche JPA avec Toplink


Nous nous intéressons à l'architecture suivante où la couche JPA est désormais implémentée par Toplink :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Toplink] [JDBC]
1 2 3 6
5
7 Spring

11.1 Le projet Netbeans


Le nouveau projet Netbeans est obtenu par recopie du projet précédent :

Introdution à Java EE
54/334
4
3
2
5

• en [1] : après un clic droit sur le projet Hibernate, choisir Copy Project
• à l'aide du bouton [2], choisir le dossier parent du nouveau projet. Le nom du dossier apparaît en [3].
• en [4], donner un nom au nouveau projet
• en [5], le nom du dossier du projet
• avec [6], valider la copie du projet

Le projet doit être modifié en deux points pour l'adapter à la nouvelle couche JPA / Toplink :

1. en [1], les fichiers de configuration de Spring doivent être modifiés. On y trouve en effet la configuration de la couche
JPA.
2. en [2], les bibliothèques du projet doivent être modifiées : celles d'Hibernate doivent être remplacées par celles de Toplink.

Commençons par ce dernier point. On pourra procéder de la façon suivante :

Introdution à Java EE
55/334
1
2 3

• en [1], demander les propriétés de la bibliothèque [Libraries]


• en [2], sélectionner tous les .jar et les supprimer [3]

• en [4], passer à la phase d'ajout des nouveaux .jar


• en [5], sélectionner les .jar des dossiers [divers, spring, toplink]. Le contenu de ces dossiers a été expliqué au paragraphe 4,
page 13.
• en [6], la liste finale des .jar avec en [7], les 2 archives de l'implémentation Jpa / Toplink.

Question : pour le point 1, indiquer quelles modifications apporter aux fichiers de configuration de Spring. On s'inspirera de
l'exemple du paragraphe 3.1.9 de [ref1]

11.2 Mise en oeuvre des tests


Avant de tester l'application entière, il est bon de vérifier si les tests passent avec la nouvelle implémentation JPA. Avant de les faire,
on commencera par supprimer les tables de la base de données. Pour cela, dans l'onglet [Runtime] de Netbeans, si besoin est, on
créera une connexion sur la base dbpam / MySQL5 comme expliqué au paragraphe 4, page 19. Une fois connecté à la base dbpam
/ MySQL5, on pourra procéder à la suppression des tables comme montré ci-dessous :

• [1] : avant la suppression


• [2] : après la suppression

Introdution à Java EE
56/334
2

Ceci fait, on peut exécuter le premier test sur la couche [dao] : InitDB qui remplit la base. Pour que les tables détruites
précédemment soient recréées par l'application, la configuration Jpa / Toplink de Spring doit contenir la ligne suivante :

<property name="generateDdl" value="true" />

Nous exécutons maintenant le test [InitDB] :


FAILED CONFIGURATION: @BeforeClass init
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'entityManagerFactory' defined in class
path resource [spring-config-dao.xml]: Invocation of init
method failed; nested exception is
java.lang.IllegalStateException: Must start with Java agent to
use InstrumentationLoadTimeWeaver. See Spring documentation.
Caused by: java.lang.IllegalStateException: Must start with
Java agent to use InstrumentationLoadTimeWeaver. See Spring
documentation.
at
org.springframework.instrument.classloading.InstrumentationLoad
2
1 TimeWeaver.addTransformer(InstrumentationLoadTimeWeaver.java:51
)

• en [1], le test InitDB est exécuté.


• en [2], il échoue. L'exception est lancée par Spring et non par un test qui aurait échoué. Spring indique qu'il y a un
problème de configuration. Le message n'est pas clair. La raison de l'exception a été expliquée au paragraphe 3.1.9 de
[ref1]. Pour que la configuration Spring / Toplink fonctionne, la Jvm qui exécute l'application doit être lancé avec un
paramètre particulier, un agent Java. La forme de ce paramètre est la suivante :

-javaagent:C:\data\2007-2008\netbeans\lib\spring\spring-agent.jar

[spring-agent.jar] est l'agent Java dont a besoin la Jvm pour gérer la configuration Spring / Toplink. Cet agent est fourni
par Spring.

La difficulté avec Netbeans est de passer l'argument -javaagent à la Jvm qui exécute la classe de tests. Lorsqu'on exécute le projet, il
est possible de passer des arguments à la Jvm :

Introdution à Java EE
57/334
2 3

• en [1], on accède aux propriétés du projet


• en [2], les propriété du Run
• en [3], on passe le paramètre -javaagent à la Jvm

On n'a pas cette possibilité avec la Jvm qui exécute les tests. Il faut alors aller dans le fichier ant qui les exécute pour y définir le
paramètre -javaagent. On pourra procéder ainsi :

• si on a procédé comme il a été indiqué jusqu'à maintenant, l'agent [spring-agent.jar] fait partie des bibliothèques du projet
[1]. Cela n'est pas nécessaire, mais va nous aider pour la suite de cette configuration des tests.
• dans l'onglet [2, Files], le fichier [nbtargets-testng.xml] [3] qui configure les tests TestNG (cf paragraphe 7.3.1.2, page 31)
doit être modifié.

Le fichier [project.properties] [3] contient les propriétés du projet sous la forme de lignes variable=valeur. On y retrouve des
informations qu'on a pu donner lorsqu'on a configuré le projet ainsi que des valeurs par défaut :

1. application.args=
2. application.title=swing-metier-dao-jpa-spring-toplink
3. application.vendor=ST
4. build.classes.dir=${build.dir}/classes
5. build.classes.excludes=**/*.java,**/*.form
6. ...
7. file.reference.commons-collections-2.1.1.jar-1=../../lib/spring/commons-collections-2.1.1.jar
8. file.reference.commons-dbcp.jar=../../lib/spring/commons-dbcp.jar
9. ...
10. file.reference.spring-agent.jar=../../lib/spring/spring-agent.jar
11. file.reference.spring.jar=../../lib/spring/spring.jar
12. ...
13. run.jvmargs=-javaagent:C:/data/2007-2008/netbeans/lib/spring/spring-agent.jar
14. ...

Introdution à Java EE
58/334
Les informations contenues dans ce fichier de propriétés sont utilisées dans les scripts ant qui assurent différentes actions sur le
projet, à savoir : compilation, compression en .jar, exécution, que ce soit pour les classes du projet ou celles des tests. Ces scripts ant
sont ci-dessus :
• build.xml [5]
• build-impl.xml [4]
• nbtargets-testng.xml [3]

On ne s'intéressera ici qu'au fichier [nbtargets-testng.xml] qui configure les tests TestNG.

Dans le fichier [project.properties], on notera ligne 10 la présence du fichier [spring-agent.jar], ceci parce qu'il a été inclus dans les
bibliothèques du projet. Il est référencé par la variable file.reference.spring-agent.jar que nous allons utiliser par la suite.

Le fichier ant [nbtargets-testng.xml] a différentes cibles (target). Celle qui nous intéresse s'appelle [-testng-run-single] :

1.<!-- TESTNG-RUN-SINGLE -->


2.
3. <!-- Run selected test.includes file with TestNG -->
4. <target name="-testng-run-single">
5. ...
6. <!-- Call <java> rather than <testng> so NetBeans' Ant logger
7. will link output stack trace lines to source files. -->
8. <java classname="org.testng.TestNG"
9. classpath="${run.test.classpath}"
10. fork="true" resultproperty="testng.result.code">
11. <jvmarg line="-enableassertions"/> <!-- TestNG tests often assert -->
12. <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/>
13. <jvmarg line="-javaagent:${file.reference.spring-agent.jar}"/>
14. <arg line="-d ${build.test.results.dir}"/>
15....
16. </java>
17. ...
18. </target>

Les lignes 8-16 définissent les paramètres de la Jvm qui va lancer les tests ainsi que ceux de la classe de test qui va être exécutée.
Ligne 13, on ajoute une balise <jvmarg> qui sert à définir un paramètre pour la Jvm. On y définit le paramètre -javaagent et plutôt
que d'écrire en dur le chemin du fichier spring-agent.jar, on utilise la variable file.reference.spring-agent.jar définie dans le fichier
[project.properties]. Si le fichier spring-agent.jar est mis dans la bibliothèque du projet, on est assurés que cette variable pointera sur le
bon fichier.

11.3 Tests de la couche [dao]

11.3.1 InitDB

Maintenant, nous sommes prêts pour tester de nouveau [InitDB]. Cette fois-ci les résultats obtenus sont les suivants :

1. ----------------- TESTNG-RUN -----------------------


2. init:
3. deps-jar:
4. compile:
5. compile-test-single:
6. ----------------- TESTNG-RUN-SINGLE -----------------------
7. [Parser] Running:
8. dao.InitDB
9.
10. 11:23:47,859 INFO ClassPathXmlApplicationContext:305 - Refreshing
org.springframework.context.support.ClassPathXmlApplicationContext@1754ad2: display name
[org.springframework.context.support.ClassPathXmlApplicationContext@1754ad2]; startup date [Mon
Sep 10 11:23:47 CEST 2007]; root of context hierarchy
11. ...
12. [TopLink Info]: 2007.09.10 11:23:49.343--ServerSession(18179071)--TopLink, version: Oracle TopLink
Essentials - 2.0 (Build b41-beta2 (03/30/2007))
13. ...
14. Call: CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGRDS DOUBLE NOT NULL, CSGD DOUBLE NOT NULL,
VERSION INTEGER NOT NULL, SECU DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, PRIMARY KEY (ID))
15. ...
16. Call: CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR
DOUBLE NOT NULL, INDICE INTEGER UNIQUE NOT NULL, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT
NULL, INDEMNITES_CP DOUBLE NOT NULL, PRIMARY KEY (ID))
17. ...

Introdution à Java EE
59/334
18. Call: CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, PRENOM VARCHAR(20) NOT NULL, ADRESSE VARCHAR(50)
NOT NULL, SS VARCHAR(15) UNIQUE NOT NULL, VILLE VARCHAR(30) NOT NULL, VERSION INTEGER NOT NULL, CP
VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
19. ...
20. Call: ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID)
REFERENCES INDEMNITES (ID)
21. ...
22. Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY
(SEQ_NAME))
23. ...
24. Employés ----------------------
25. jpa.Employe[id=554,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]
26. jpa.Employe[id=555,version=1,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code postal=49014,indice=1]
27. Indemnités ----------------------
28. jpa.Indemnite[id=552,version=1,indice=1,base heure=1.93,entretien jour2.0,repas
jour=3.0,indemnités CP=12.0]
29. jpa.Indemnite[id=553,version=1,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités
CP=15.0]
30. Cotisations ----------------------
31. jpa.Cotisation[id=556,version=1,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
32. PASSED: initDB
33.
34. ===============================================
35. dao.InitDB
36. Total tests run: 1, Failures: 0, Skips: 0
37. ===============================================
38.
39. View test results in build/test/results/index.html
40.
41. test-single:
42. BUILD SUCCESSFUL (total time: 4 seconds)

• lignes 10-11 : logs de Spring


• ligne 12 : début des logs de Toplink
• ligne 14 : création de la table COTISATIONS
• ligne 16 : création de la table INDEMNITES
• ligne 18 : création de la table EMPLOYES
• ligne 20 : création de la clé étrangère qu'a EMPLOYES sur INDEMNITES
• ligne 22 : création de la table SEQUENCE qu'utilise Toplink pour générer dynamiquement les valeurs des clés primaires
des différentes tables. Contrairement à Hibernate, Toplink n'utilise jamais les méthodes propriétaires des SGBD pour ce
travail. On rappelle qu'Hibernate avait pour le SGBD MySQL5 utilisé l'attribut autoincrement pour chaque clé primaire. Ici
cet attribut n'est pas utilisé.
• lignes 24-31 : exécution du test [InitDB] qui remplit les tables avec des données.
• lignes 35-36 : résultats des tests.

Les résultats peuvent être vérifiés sur la connexion qu'a Netbeans avec la base [dbpam] :

1 2

• en [1], on voit la base [dbpam] et ses quatre tables


• en [2], on visualise le contenu des tables

Introdution à Java EE
60/334
3

• en [3], la table EMPLOYES


• en [4], la table INDEMNITES
• en [5], la table COTISATIONS

11.3.2 TestNGDao

L'exécution de la classe de tests [TestNGDao] peut échouer, même si avec l'implémentation Jpa / Hibernate, elle avait réussi. Pour
comprendre pourquoi, analysons un exemple.

La méthode testée est la méthode IndemniteDao.create suivante :

1. package dao;
2.
3. ...
4. @Transactional(propagation=Propagation.REQUIRED)
5. public class IndemniteDao implements IIndemniteDao{
6.
7. @PersistenceContext
8. private EntityManager em;
9.
10. // constructeur
11. public IndemniteDao() {
12. }
13.
14. // créer une indemnité
15. public Indemnite create(Indemnite indemnite) {
16. try{
17. em.persist(indemnite);
18. }catch(Throwable th){
19. throw new PamException(th,31);
20. }
21. return indemnite;
22. }
23.
24. ...
25. }

• lignes 15-22 : la méthode testée

La méthode de test est la suivante :

1. package dao;
2.
3. ...
4. public class TestNGDao {
5.
6. // couches dao
7. private IEmployeDao employeDao;
8. private IIndemniteDao indemniteDao;
9. private ICotisationDao cotisationDao;
10.
11. @BeforeClass
12. public void init(){
13. // log
14. log("init");
15. // configuration de l'application

Introdution à Java EE
61/334
16. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
17. // couches dao
18. setEmployeDao((IEmployeDao) ctx.getBean("employeDao"));
19. setIndemniteDao((IIndemniteDao) ctx.getBean("indemniteDao"));
20. setCotisationDao((ICotisationDao) ctx.getBean("cotisationDao"));
21. }
22.
23.
24. @BeforeMethod()
25. public void clean(){
26. // on vide la base
27. for(Employe employe:employeDao.findAll()){
28. employeDao.destroy(employe);
29. }
30. for(Cotisation cotisation:cotisationDao.findAll()){
31. cotisationDao.destroy(cotisation);
32. }
33. for(Indemnite indemnite : indemniteDao.findAll()){
34. indemniteDao.destroy(indemnite);
35. }
36. }
37.
38. // logs
39. private void log(String message) {
40. System.out.println("----------- " + message);
41. }
42. ...
43. @Test
44. public void test05(){
45. log("test05");
46. // on crée deux indemnités avec le même indice
47. // enfreint la contrainte d'unicité de l'indice
48. boolean erreur=true;
49. Indemnite indemnite1=null;
50. Indemnite indemnite2=null;
51. Throwable th=null;
52. try{
53. indemnite1=getIndemniteDao().create(new Indemnite(1,1.93,2,3,12));
54. indemnite2=getIndemniteDao().create(new Indemnite(1,1.93,2,3,12));
55. erreur=false;
56. }catch(PamException ex){
57. th=ex;
58. // vérifications
59. assert 31==ex.getCode();
60. }catch(Throwable th1){
61. th=th1;
62. }
63. // vérifications
64. assert erreur==true;
65. // chaîne des exceptions
66. System.out.println("Chaîne des exceptions --------------------------------------");
67. System.out.println(th.getClass().getName());
68. while(th.getCause()!=null){
69. th=th.getCause();
70. System.out.println(th.getClass().getName());
71. }
72. // la 1ère indemnité a du être persistée
73. Indemnite indemnite =getIndemniteDao().find(indemnite1.getId());
74. // vérification
75. assert indemnite!=null;
76. assert 1==indemnite.getIndice();
77. assert Math.abs(1.93-indemnite.getBaseHeure())<1e-6;
78. assert Math.abs(2-indemnite.getEntretienJour())<1e-6;
79. assert Math.abs(3-indemnite.getRepasJour())<1e-6;
80. assert Math.abs(12-indemnite.getIndemnitesCP())<1e-6;
81. // la seconde indemnité n'a pas du être persistée
82. List<Indemnite> indemnites=getIndemniteDao().findAll();
83. int nbIndemnites=indemnites.size();
84. assert nbIndemnites==1;
85. }
86. ...
87.
88. // getters et setters
89. ...
90. }

Question : expliquer ce que fait le test test05 et indiquer les résultats attendus.

Les résultats obtenus avec une couche Jpa / Hibernate sont les suivants :

1. ----------- test05

Introdution à Java EE
62/334
2. 14:42:15,562 WARN JDBCExceptionReporter:71 - SQL Error: 1062, SQLState: 23000
3. 14:42:15,562 ERROR JDBCExceptionReporter:72 - Duplicate entry '1' for key 2
4. Chaîne des exceptions --------------------------------------
5. exception.PamException
6. javax.persistence.EntityExistsException
7. org.hibernate.exception.ConstraintViolationException
8. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Le test passe, c.a.d. que les assertions sont vérifiées et qu'il n'y a pas d'exception qui sort de la méthode de test.

Question : expliquer ce qui s'est passé.

Les résultats obtenus avec une couche Jpa / Toplink sont les suivants :

1. ----------- test05
2. [TopLink Warning]: 2007.09.10 02:44:49.062--UnitOfWork(30390274)--Exception [TOPLINK-4002] (Oracle
TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))):
oracle.toplink.essentials.exceptions.DatabaseException
3. Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException:
Duplicate entry '1' for key 2
4. Error Code: 1062
5. Call: INSERT INTO INDEMNITES (ID, BASE_HEURE, ENTRETIEN_JOUR, INDICE, REPAS_JOUR, VERSION,
INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?, ?)
6. bind => [8, 1.93, 2.0, 1, 3.0, 1, 12.0]
7. Query: InsertObjectQuery(jpa.Indemnite[id=8,version=1,indice=1,base heure=1.93,entretien
jour2.0,repas jour=3.0,indemnités CP=12.0])
8. Chaîne des exceptions --------------------------------------
9. org.springframework.transaction.UnexpectedRollbackException
10. javax.persistence.RollbackException
11. oracle.toplink.essentials.exceptions.DatabaseException
12. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Comme précédemment avec Hibernate, le test passe, c.a.d. que les assertions sont vérifiées et qu'il n'y a pas d'exception qui sort de
la méthode de test.

Question : expliquer ce qui s'est passé.

Question : de ces deux exemples, que peut-on conclure de l'interchangeabilité des implémentations Jpa ? Est-elle totale ici ?

11.4 Les autres tests


Une fois la couche [dao] testée et considérée correcte, on pourra passer aux tests de la couche [metier] et à ceux du projet lui-même
dans sa version console ou graphique. Le changement d'implémentation Jpa n'influe en rien sur les couches [metier] et [ui] et donc
si ces couches fonctionnaient avec Hibernate, elles fonctionneront avec Toplink à quelques exceptions près : l'exemple précédent
montre en effet que les exceptions lancées par les couches [dao] peuvent différer. Ainsi dans le cas d'utilisation du test, Spring / Jpa
/ Hibernate lance une exception de type [PamException], une exception propre à l'application [pam] alors que Spring / Jpa /
Toplink lui, lance une exception de type [UnexpectedRollbackException], une exception du framework Spring. Si dans le cas
d'usage du test, la couche [ui] attend une exception de type [PamException] parce qu'elle a été construite avec Hibernate, elle ne
fonctionnera plus lorsqu'on passera à Toplink.

Travail pratique : refaire les tests des applications console et swing avec différents SGBD : MySQL5, Oracle XE, SQL Server.

12 Version 2 - Ejb3 / Jpa dans JBoss


Lectures conseillées : [ref1], paragraphe 3.2.

Nous nous intéressons maintenant à l'architecture suivante où l'application s'exécute dans le conteneur Ejb Jboss Ejb3 :

Introdution à Java EE
63/334
4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
1 2 3 6
5
7 Jboss Ejb3

L'implémentation Jpa sera celle d'Hibernate supportée nativement pas Jboss Ejb3. Cet outil ne livre pas d'exemples de configuration
permettant d'utiliser une autre implémentation, Toplink par exemple. Cela ne signifie cependant pas que c'est impossible.

12.1 Le projet Netbeans / Jboss Ejb3 / Jpa / Hibernate


Le nouveau projet Netbeans est le suivant :

7
1

2
4

5 8

• en [1], le nouveau projet s'appelle [ui-metier-dao-jpa-jbossejb3-hibernate]


• le framework Spring est remplacé par le conteneur Jboss Ejb3. Les fichiers de configuration de Spring laissent place aux
fichiers de configuration de Jboss Ejb3 [2, 3].
• de même les bibliothèques de Spring laissent place aux bibliothèques de Jboss Ejb3 [5,7,8]. Les autres bibliothèques du
projet [4] sont celles du dossier [divers] [7] déjà utilisées dans les projets précédents. On rappelle que le contenu des
dossiers de [7] a été décrit page 13.

Nous allons étudier les différentes couches de la nouvelle application :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
1 2 3 6
5
7 Jboss Ejb3

Nous étudierons dans l'ordre :

• la couche Jpa / Hibernate et sa configuration au sein de Jboss Ejb3


• la couche [dao] et ses tests
• la couche [metier] et ses tests

Introdution à Java EE
64/334
• la couche [ui] et ses tests

12.2 La couche Jpa / Hibernate


La couche Jpa est définie à la fois par ses entités [1] et ses fichiers de configuration [2,3] :

Les entités Jpa [1] sont celles des versions précédentes. Passer de Spring aux Ejb ne change rien aux entités Jpa. Les fichiers de
configuration [3] sont livrés avec Jboss Ejb3 et on n'a pas à les modifier. Ils sont en partie décrits au paragraphe 3.2.5 de [ref1].

La configuration de la couche Jpa se fait avec les fichiers de configuration de [2].

Question : en vous aidant du paragraphe 3.2.5 de [ref1], donner le contenu des fichiers [persistence.xml] et [jboss-config.xml] afin
que la couche Jpa ait accès à la base de données dbpam / MySQL5 utilisée dans les versions précédentes de l'application.

12.3 La couche [dao]


Rappelons ici certains éléments d'une discussion que nous avons déjà eue au paragraphe 5, page 24, lorsqu'il s'est agi de définir les
interfaces des couches [metier] et [dao].

Lorsqu'on écrit une interface, il est bon de se rapeler qu'elle peut être utilisée dans deux contextes différents : local et distant. Dans le
contexte local, la méthode appelante et la méthode appelée sont exécutées dans la même Jvm :

Couche interface Couche métier Couche d'accès aux Données


utilisateur
utilisateur [ui] [metier] données [dao]

JVM

Si la couche [metier] fait appel à la méthode CotisationDao.create(Cotisation cotisation) de la couche [dao], elle a bien une référence sur le
paramètre [Cotisation cotisation] qu'elle passe à la méthode.

Dans le contexte distant, la méthode appelante et la méthode appelée sont exécutées dans des Jvm différentes :

Introdution à Java EE
65/334
1

Couche Couche 2 Couche d'accès aux


utilisateur 3 Données
interface métier données [dao]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Ci-dessus, la couche [metier] s'exécute dans la JVM 1 et la couche [dao] dans la JVM 2 sur deux machines différentes. Les deux
couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celle-
ci est composée d'une couche d'émission [2] et d'une couche de réception [3]. Le développeur n'a en général pas à écrire ces
couches de communication. Elles sont générées automatiquement par des outils logiciels. La couche [metier] est écrite comme si
elle s'exécutait dans la même Jvm que la couche [dao]. Il n'y a donc aucune modification de code. La couche de communication [1]
s'occupe de sérialiser les objets échangés entre les deux Jvm :

• les paramètres que la couche [metier] passent aux méthodes de la couche [dao]
• le résultat rendu par ces méthodes

Cette sérialisation se fait de façon transparente pour les couches [metier] et [dao]. Afin qu'elles n'aient pas à être modifiées lorsqu'on
passe d'une exécution locale à une exécution distante, on les écrira pour une exécution distante :

• la couche [metier] appelle les méthodes de la couche [dao] en considérant que les paramètres passés sont uniquement des
paramètres d'entrée. Les informations qu'elle attend doivent être entièrement contenues dans le résultat rendu par la
méthode.
• la signature des méthodes de la couche [dao] doit correspondre à ce mode de fonctionnement.
• les objets échangés (paramètres et résultats) doivent être déclarés sérialisables. En Java, si l'objet échangé n'est pas un
type Java existant mais un type utilisateur, il doit implémenter l'interface Serializable avec l'attribut implements Serializable
pour devenir sérialisable. L'implémentation de l'interface Serializable n'implique en général aucun codage supplémentaire
de la part du développeur. Parmi les objets échangés, on a bien souvent les entités Jpa qui transitent de la couche [dao]
jusqu'à la couche [ui]. Ces entités doivent donc être taguées implements Serializable.

1. package jpa;
2.
3. import java.io.Serializable;
4. ...
5. @Entity
6. @Table(name="COTISATIONS")
7. public class Cotisation implements Serializable {
8. ...

Dans ce cas, le portage des couches d'un environnement local à un environnement distant, ou le portage inverse, se fait sans
modification de code.

L'architecture précédente n'est pas la plus courante. On trouve plus fréquemment les couches [metier] et [dao] dans la même Jvm :

Couche 2 Couche Couche d'accès


utilisateur 3 Données
interface métier aux données [dao]
utilisateur [ui] [metier]
Réseau
JVM 1 tcp /ip JVM 2

Là encore, on écrira les couches [ui] et [metier] comme si elles s'exécutaient à priori dans un environnement distant, pour avoir une
portabilité totale entre les deux environnements distant ou local. Pour cela, les objets échangés sur la couche réseau entre les couches
[ui] et [metier] doivent être sérialisables. C'est le cas des classes [FeuilleSalaire] et [ElementsSalaire] :

1. package metier;
2.
3. import java.io.Serializable;
4. ...
5.
6. public class FeuilleSalaire implements Serializable{

Introdution à Java EE
66/334
7. ...

1. package metier;
2.
3. import java.io.Serializable;
4.
5. public class ElementsSalaire implements Serializable{
6. ...

Le conteneur JBoss Ejb3 permet de tester indifféremment les deux environnements d'exécution local ou distant :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
1 2 3 6
5
7 Jboss Ejb3

Pour tester la couche [dao] dans ces deux environnements, celle-ci doit implémenter deux interfaces : une interface dite distante et
une dite locale. Nous faisons évoluer la couche [dao] de la façon suivante :

Les couches [dao] gérant les entités Jpa [Cotisation, Employe, Indemnites] sont implémentées par les classes [CotisationDao,
EmployeDao, IndemnitesDao]. Chacune de ces classes implémente deux interfaces :

• l'une dite locale préfixée ici par I et suffixée par le terme Local [ICotisationDaoLocal, IEmployeDaoLocal,
IIndemnitesDaoLocal]
• l'une dite distante préfixée ici par I et suffixée par le terme Remote [ICotisationDaoRemote, IEmployeDaoRemote,
IIndemnitesDaoRemote]

Ces deux interfaces sont identiques à un détail près. Prenons l'exemple de l'interface ICotisationDao :

Sa version locale est la suivante :

1. package dao;
2.
3. import java.util.List;
4. import javax.ejb.Local;
5. import jpa.Cotisation;
6.
7. @Local
8. public interface ICotisationDaoLocal {
9. // créer une nouvelle cotisation
10. Cotisation create(Cotisation cotisation);
11. // modifier une cotisation existante
12. Cotisation edit(Cotisation cotisation);
13. // supprimer une cotisation existante
14. void destroy(Cotisation cotisation);
15. // chercher une cotisation particulière
16. Cotisation find(Long id);
17. // obtenir tous les objets Cotisation
18. List<Cotisation> findAll();

Introdution à Java EE
67/334
19. }

et sa version distante :

1. package dao;
2.
3. import java.util.List;
4. import javax.ejb.Remote;
5. import jpa.Cotisation;
6.
7. @Remote
8. public interface ICotisationDaoRemote {
9. // créer une nouvelle cotisation
10. Cotisation create(Cotisation cotisation);
11. // modifier une cotisation existante
12. Cotisation edit(Cotisation cotisation);
13. // supprimer une cotisation existante
14. void destroy(Cotisation cotisation);
15. // chercher une cotisation particulière
16. Cotisation find(Long id);
17. // obtenir tous les objets Cotisation
18. List<Cotisation> findAll();
19. }

Ces deux interfaces sont identiques à celle que nous avions dans l'implémentation Spring / Jpa. Seule la ligne 7 diffère d'avec la
version Spring. L'annotation @Local désigne l'interface d'un Ejb exécuté dans un contexte local et l'annotation @Remote
l'interface d'un Ejb exécuté dans un contexte distant.

L'Ejb [CotisationDao] implémente simultanément ces deux interfaces :

1. package dao;
2. ...
3. @Stateless()
4. @TransactionAttribute(TransactionAttributeType.REQUIRED)
5. public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
6.
7. @PersistenceContext
8. private EntityManager em;
9.

• l'annotation @Stateless, ligne 3, désigne un Ejb sans état. Le conteneur Ejb gère un pool d'Ejb et les distribue aux
threads qui les demande. A la fin d'exécution d'un thread, les Ejb qui lui avaient été accordés retournent dans le pool d'Ejb
du conteneur. Puisqu'ils sont sans état, ils peuvent être recyclés pour les besoins d'un autre thread. L'Ejb avec état
(@Stateful) lui, conserve des informations stockées là par le thread qui l'a utilisé. On a alors une notion de propriétaire de
l'Ejb. Un tel Ejb ne sera recyclé qu'entre les threads d'un même propriétaire.
• il existe une différence entre l'implémentation [CotisationDao] de Spring et celle des Ejb : celle de Spring est un singleton :
un unique exemplaire [CotisationDao] est construit au démarrage de l'application et est utilisé ensuite par tous les threads.
Dans l'implémentation Ejb, il peut y avoir plusieurs exemplaires [CotisationDao] que le conteneur Ejb gère alors dans un
pool. Hormis ce détail, les deux visions sont proches. Le code de [CotisationDao] est identique dans les deux cas.
• ligne 4 : l'annotation @TransactionAttribute, fixe l'aspect transactionnel des méthodes de l'Ejb. Placé avant la
déclaration de la classe, il s'applique alors à toutes les méthodes de celle-ci. Placé avant une méthode, il ne s'applique qu'à
celle-ci. La valeur TransactionAttributeType.REQUIRED indique que la méthode doit s'excécuter dans une
transaction. C'est le mode de fonctionnement normal qui assure que les éléments du contexte de persistance géré par la
couche Jpa seront toujours synchronisés avec la base de données à la fin de l'exécution de la méthode. Il assure également
un rollback de toutes les opérations de persistance assurées par la méthode si l'une de ces opérations échoue. Parce que
c'est le fonctionnement normal, la ligne 4 est facultative et est la valeur par défaut pour un Ejb.
• ligne 5 : la classe [CotisationDao] implémente l'interface locale et l'interface distante et est identique à ce qu'elle était dans la
version Spring / Jpa. Il est important de comprendre ici, que c'est parce que la classe [CotisationDao] de la version Spring
/ Jpa a été écrite initialement pour être utilisable aussi bien dans un environnement local que distant que la même classe
peut implémenter les deux interfaces. C'est le même type d'objet qui sera instancié dans les deux contextes d'exécution.
Seulement dans le contexte d'exécution distant, les paramètres et le résultat de chaque méthode de [CotisationDao]
subiront une sérialisation qui ne se produira pas dans le contexte d'exécution local.

Les couches [dao] des entités [Indemnite] et [Employe] évoluent de façon analogue à ce qui vient d'être décrit pour la couche [dao]
de l'entité [Cotisation].

Introdution à Java EE
68/334
12.4 Tests de la couche [dao]
La couche [dao] sera testée dans deux contextes :

C 4
Couche Couche Objets image Interface Implémentation Couche
2
[tests] [dao] de la BD [JPA] [Hibernate] [JDBC]
3 6
5
7 Jboss Ejb3

1. dans un environnement d'exécution distant : la couche [tests] utilisera les interfaces taguées "Remote" de la couche [dao].
Les couches [tests] et [dao] partageront les objets échangés par valeur. Ceux-ci (Cotisation, Indemnite, Employe) seront
sérialisés par la couche de communication [C].
2. dans un environnement d'exécution local : la couche [tests] utilisera les interfaces taguées "Local" de la couche [dao]. La
couche de communication [C] n'existera alors pas. Les couches [tests] et [dao] partageront les objets échangés par
référence.

Le projet Netbeans des tests de la couche [dao] aura l'allure suivante :

Le code des classes de test [InitDB*] est celui de la classe de test [InitDB] étudiée au paragraphe , page . Celui de la classe
[InitDBLocal] est par exemple le suivant :

1. package dao;
2. ...
3. public class InitDBLocal {
4.
5. private IEmployeDaoLocal employeDao = null;
6. private ICotisationDaoLocal cotisationDao = null;
7. private IIndemniteDaoLocal indemniteDao = null;
8.
9. @BeforeClass
10. public void init() throws NamingException{
11. // on démarre le conteneur EJB3 JBoss
12. ...
13. // On initialise le contexte JNDI. Le fichier jndi.properties est exploité
14. ...
15.
16. // instanciation couches dao
17. ...
18. }
19.
20. @Test
21. public void initDB(){
22. ...
23. }
24.
25. @BeforeMethod()
26. public void clean(){
27. ...
28. }
29.
30. @AfterClass
31. public void terminate() {

Introdution à Java EE
69/334
32. ...
33. }
34.
35. }

• lignes 5-7 : la classe de test utilise les versions locales des Ejb.
• ligne 10 : la méthode init exécutée pour mettre en place l'environnement d'exécution des tests. Elle lance le conteneur
Jboss Ejb3 et instancie les Ejb des lignes 5-7, c.a.d. les couches [dao] des trois entités.
• lignes 20-29 : le code est celui de la classe de test [InitDB] étudiée au paragraphe , page .
• lignes 31-33 : la méthode terminate exécutée une fois que tous les tests auront été exécutés (annotation @AfterClass de la
ligne 30) : elle arrête le conteneur Jboss Ejb3.

Question : en vous aidant de l'exemple du paragraphe 3.2.7 de [ref1], écrire les méthodes init et terminate.

Le code de la classe [InitDBRemote] est analogue :

1. package dao;
2. ...
3. public class InitDBRemote {
4.
5. IEmployeDaoRemote employeDao = null;
6. ICotisationDaoRemote cotisationDao = null;
7. IIndemniteDaoRemote indemniteDao = null;
8.
9. @BeforeClass
10. public void init() throws NamingException{
11. ...
12. }
13.
14. @Test
15. public void initDB(){
16. ...
17. }
18.
19. @BeforeMethod()
20. public void clean(){
21. ...
22. }
23.
24. @AfterClass
25. public void terminate() {
26. ...
27. }
28. }

Question : Ecrire les méthodes init et terminate.

En mode local, le test suivant réussit :

1.@Test
2. public void initDB(){
3. // on la remplit
4. Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
5. indemniteDao.create(indemnite1);
6. Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
7. indemniteDao.create(indemnite2);
8. employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St
Corentin","49203",indemnite2));
9. employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",indemnite1));
10. Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
11. // on l'affiche
12. System.out.println("Employés ----------------------");
13. for(Employe employe:employeDao.findAll()){
14. System.out.println(employe);
15. }
16. System.out.println("Indemnités ----------------------");
17. for(Indemnite indemnite : indemniteDao.findAll()){
18. System.out.println(indemnite);
19. }
20. System.out.println("Cotisations ----------------------");
21. for(Cotisation cotisation:cotisationDao.findAll()){
22. System.out.println(cotisation);
23. }
24. }

Introdution à Java EE
70/334
En mode distant, il échoue avec l'exception suivante :

1. javax.ejb.EJBException: exception.PamException: javax.persistence.PersistenceException:


org.hibernate.PropertyValueException: not-null property references a null or transient value:
jpa.Employe.indemnite
2. at org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:69)
3. ...
4. at dao.InitDBRemote_1.initDB(InitDBRemote_1.java:49)

La ligne 1 indique :
• qu'il sest produit une exception de type [EJBException]
• causée par une exception de type [PamException]
• causée par une exception de type [PersistenceException]
• causée par une exception de type [org.hibernate.PropertyValueException] due au fait qu'on essaie de persister un objet de
type [Employe] qui a un champ de type [Indemnite]. Or ce champ n'appartient pas au contexte de persistance (transient) et
ne peut donc être persisté.

La méthode où se produit l'exception de type [PamException] est la suivante :

1. // créer un employé
2. public Employe create(Employe employe) {
3. try{
4. em.persist(employe);
5. return employe;
6. }catch(Throwable th){
7. throw new PamException(th,21);
8. }
9.}

Question : expliquer ce qui, dans le test InitDB a provoqué l'exception. Comment le test doit-il être réécrit pour réussir aussi bien
en mode local qu'en mode distant ? On notera que c'est grâce à ce type de résultats qu'on voit que les modes d'exécution local et
distant sont différents dans Jboss Ejb3. Sinon, c'est tellement transparent qu'on peut se demander s'il existe une réelle différence.

Les classes de test [TestNGDao*] suivent le même modèle de test. Elles sont identiques à la classe de test [TestNGDao] étudiée au
paragraphe 7.3.2.1, page 34.

Question : Ecrire les méthodes init et terminate des deux classes. On tiendra compte de la question précédente pour faire en sorte
que les mêmes tests passent aussi bien en local qu'à distance.

Travail pratique : mettre en oeuvre les tests de la couche [dao]. Il peut être nécessaire de supprimer les tables de la table [dbpam]
si elles ont été créées par Toplink. On n'oubliera pas non plus de modifier la configuration de Netbeans pour que les tests TestNG
remplacent les tests JUnit (cf paragraphe 7.3.1.2, page 31).

12.5 Tests de la couche [metier]


La couche [metier] sera testée dans deux contextes :

C 4
Couche Couche Couche Objets image Interface Implémentation Couche
[tests] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
2 3 6
5
7 Jboss Ejb3

1. dans un environnement d'exécution distant : la couche [tests] utilisera les interfaces taguées "Remote" de la couche
[metier]. Les couches [tests] et [metier] partageront les objets échangés par valeur. Ceux-ci (FeuilleSalaire,
ElementsSalaire) seront sérialisés par la couche de communication [C].

Introdution à Java EE
71/334
2. dans un environnement d'exécution local : la couche [tests] utilisera les interfaces taguées "Local" de la couche [metier]. La
couche de communication [C] n'existera alors pas. Les couches [tests] et [metier] partageront les objets échangés par
référence.

Le projet Netbeans des tests de la couche [metier] aura l'allure suivante :

Le code des classes de test [TestNGMetier*] est celui de la classe de test [TestNGMetier_2] étudiée au paragraphe 8.4, page 41. Là
également, seules les méthodes init et terminate sont changées.

Travail pratique : écrire et mettre en oeuvre les tests de la couche [metier]. On se rappellera que les objets métier FeuilleSalaire et
ElementsSalaire doivent être tagués Serializable. Faire en sorte que les mêmes tests puissent être utilisés indifféremment dans un
contexte d'exécution local ou distant.

12.6 Tests de la couche [ui]


La couche [ui] sera testée dans deux contextes :

C 4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
2 3 6
5
7 Jboss Ejb3

1. dans un environnement d'exécution distant : la couche [ui] utilisera les interfaces taguées "Remote" de la couche [metier].
Les couches [ui] et [metier] partageront les objets échangés par valeur.
2. dans un environnement d'exécution local : la couche [ui] utilisera les interfaces taguées "Local" de la couche [metier]. La
couche de communication [C] n'existera alors pas. Les couches [ui] et [metier] partageront les objets échangés par
référence.

Le projet Netbeans des tests de la couche [ui] aura l'allure suivante :

Travail pratique : écrire et mettre en oeuvre les tests de la couche [ui]. Les applications console et swing sont identiques à celles
décrites respectivement au paragraphe 9.1.1, page 44 et au paragraphe 10.4, page 52. Seules changent les méthodes d'initialisation de
l'application. Ici, elles doivent lancer le conteneur Jboss Ejb3. Par ailleurs à la fin de l'application, elles doivent l'arrêter.
Faire en sorte que les applications puissent être utilisées indifféremment dans un contexte d'exécution local ou distant, en-dehors du
fait qu'elles utilisent une couche [metier] qui est soit locale soit distante.

Introdution à Java EE
72/334
13 Version 3 – client / serveur Ejb
Dans cette nouvelle version, l'application [Pam] va s'exécuter en mode client / serveur dans deux Jvm différentes. Revenons sur
l'architecture de l'application lorsqu'elle s'exécutait dans le conteneur Jboss Ejb3 :

C 4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
2 3 6
5
7 Jboss Ejb3

Nous avons testé deux contextes d'exécution : local et distant. Dans ce dernier mode, la couche [ui] était cliente de la couche [metier],
couche implémentée par des Ejb. Pour fonctionner en mode client / serveur, dans lequel le client et le serveur s'exécutent dans
deux Jvm différentes, nous allons placer les couches [metier, dao, jpa] sur le serveur Java EE " Sun Application Server 9 ". Ce
serveur est livré avec Netbeans 5.5.1 édition entreprise. Il existe des versions dites " Glassfish " qui semblent identiques ou en tout cas
très proches de " Sun Application Server 9 ". Ces serveurs Glassfish sont maintenus par la communauté Sun et non par la société
Sun elle-même. Glassfish semble être la version communautaire du serveur Sun Application Server 9.1. Ainsi, lorsque le serveur
Glassfish v2 b58f est lancé, on trouve parmi les logs le suivant :

Starting Sun Java System Application Server 9.1 (build b58f-fcs) ...

L'application suivante a été testée avec " Sun Application Server 9 " livré avec Netbeans Entreprise et " Glassfish v2 b58 "
téléchargé à l'url [https://glassfish.dev.java.net/]. Par la suite, nous désignerons le serveur utilisé, par l'appellation serveur Sun ou
serveur Glassfish selon les cas.

L'architecture client /serveur sera la suivante :

Couche C
Couche Couche Couche Couche
[ui] [dao] 4
[metier] [JPA / [JDBC]
1 3 Toplink]
2 6

Jvm1 - Java SE 7 Jvm2 – Java EE - serveur Sun / Glassfish

• la couche [ui] s'exécutera dans un environnement Java SE (Standard Edition)


• les couches [metier, dao, jpa] s'exécuteront dans un environnement Java EE (Enterprise Edition) sur un serveur Sun /
Glassfish
• le client communiquera avec le serveur via un réseau tcp-ip. Les échanges réseau sont transparents pour le développeur, si
ce n'est qu'il doit être quand même conscient que le client et le serveur s'échangent des objets sérialisés pour communiquer
et non des références d'objets. Le protocole réseau utilisé pour ces échanges s'appelle RMI (Remote Method Invocation),
un protocole utilisable uniquement entre deux applications Java.
• l'implémentation Jpa utilisée sur le serveur Sun sera Toplink qui est supportée nativement pas ce serveur. Hibernate est
utilisable à condition d'ajouter les bibliothèques de son implémentation dans le dossier des bibliothèques du serveur Sun.

13.1 Découverte du serveur Java EE Sun AS 9


Nous nous proposons ici de découvrir comment utiliser le serveur Sun à partir de Netbeans et également au travers de son interface
web d'administration.

13.1.1 Gérer le serveur Sun avec Netbeans

Introdution à Java EE
73/334
1

2 4
3

• le serveur Sun est accessible dans l'onglet [1 – Runtime] de Netbeans


• [2] : Netbeans Enterprise est livré avec deux serveurs :
• Tomcat qui est un conteneur de servlets mais pas un conteneur Ejb. Il ne convient pas pour l'application client /
serveur que nous voulons construire.
• Sun Java System Application Server 9, un serveur Java EE 5.
• le serveur Sun est lancé par [3]. Les logs produits par son lancement apparaissent dans l'onglet [Output / Sun Java System
Application Server 9] [4].
• le lancement du serveur Sun lance également le SGBD Java Derby, appelé aussi Java DB [5].

Un serveur Java EE est constitué de plusieurs conteneurs dont deux que nous serons amenés à utiliser :
• un conteneur web qui contient le module web d'une application (servlets, pages JSP, pages JSF, ...)
• un conteneur d'Ejb3 qui contient le module Ejb d'une application

Conteneur
Client Conteneur web Jpa
Ejb3 Données
Java SE
tcp-ip serveur Java EE

• le client et le serveur communiquent via un réseau tcp-ip avec le protocole RMI. Le client utilise les versions Remote des
Ejb du serveur.

Une fois le serveur Sun lancé, Netbeans nous donne des informations sur ses conteneurs :

• en [1], nous avons accès aux diverses applications hébergées par le serveur Sun. Une application Java EE est dite application
d'entreprise. Elle est composée de divers modules hébergés par différents conteneurs du serveur. Les applications
d'entreprise que nous développerons auront au plus deux modules :
• un module web : celui apparaîtra dans la branche [Web Applications]
• un module Ejb : celui apparaîtra dans la branche [EJB Modules]
• en [2], on voit le résultat du déploiement sur le serveur Sun de l'application que nous allons écrire. Elle sera constituée
d'un unique module Ejb avec dedans les Ejb de nos couches [metier] et [dao].

Introdution à Java EE
74/334
• en [3], nous avons accès aux ressources gérées par le serveur. Seules nous intéresseront les ressources dites JDBC : elles
représentent les bases de données utilisées par les différentes applications gérées par le serveur. Une application
d'entreprise désigne ses sources de données via son fichier de persistance [persistence.xml]. Revenons sur celui utilisé dans
le conteneur Jboss Ejb3 :

1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
3. http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
4.
5. <persistence-unit name="jpa">
6.
7. <!-- le fournisseur JPA est Hibernate -->
8. <provider>org.hibernate.ejb.HibernatePersistence</provider>
9.
10. <!-- la DataSource JTA gérée par l'environnement Java EE5 -->
11. <jta-data-source>java:/datasource</jta-data-source>
12.
13. <properties>
14. ...
15. </properties>
16. </persistence-unit>
17.
18. </persistence>

• ligne 5 : l'unité de persistance jpa n'a pas l'attribut transaction-type qu'on avait par exemple dans la version Spring de
l'application :

1.<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL" />

En l'absence de valeur, l'attribut transaction-type a la valeur par défaut "JTA" (pour Java Transaction Api). Un gestionnaire
"JTA" peut faire davantage qu'un gestionaire "RESOURCE_LOCAL" : il peut gérer des transactions qui couvrent
plusieurs connexions. Avec JTA, on peut ouvrir une transaction t1 sur une connexion c1 sur un SGBD 1, une transaction
t2 sur une connexion c2 avec un SGBD 2 et être capable de considérer (t1,t2) comme une unique transaction dans laquelle
soit toutes les opérations réussissent (commit) soit aucune (rollback). Ici, nous fonctionnons avec le gestionnaire JTA du
conteneur Ejb3 du serveur Sun.

• ligne 11 : déclare la source de données que doit utiliser le gestionnaire JTA. Celle-ci est donnée sous la forme d'un nom
JNDI (Java Naming and Directory Interface). Cette source de données était définie dans [jboss-config.xml] :

jboss-config.xml

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


2.
3. <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
4. xmlns="urn:jboss:bean-deployer:2.0">
5.
6. <!-- factory de la DataSource -->
7. <bean name="datasourceFactory" class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
8. <!-- nom JNDI de la DataSource -->
9. <property name="jndiName">java:/datasource</property>
10. <!-- nom JNDI de la DataSource -->
11. <property name="jndiName">java:/datasource</property>
12.
13. <!-- base de données gérée -->
14. <property name="driverClass">com.mysql.jdbc.Driver</property>
15. <property name="connectionURL">jdbc:mysql://localhost:3306/jpa</property>
16. <property name="userName">jpa</property>
17. <property name="password">jpa</property>
18.
19. <!-- propriétés pool de connexions -->
20. <property name="minSize">0</property>
21. <property name="maxSize">10</property>
22. <property name="blockingTimeout">1000</property>
23. <property name="idleTimeout">100000</property>
24. ...
25. </deployment>

• ligne 7 : une source de données est définie, plus exactement une fabrique (factory) de telles sources.
• ligne 9 : son nom JNDI, celui-la même utilisé dans [persistence.xml]
• lignes 13-23 : ses caractéristiques Jdbc (lignes 14-17) et celles de son pool de connexions (lignes 20-23).

Introdution à Java EE
75/334
Pour une source de données gérée par le serveur Sun, le fichier [persistence.xml] de l'application d'entreprise définissant celle-ci
suivra le même modèle que celui de Jboss Ejb3. Voici par exemple, celui de notre future application :

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="serveur-metier-dao-ejb-jpa-toplink-glassfishPU" transaction-type="JTA">
4. <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
5. <jta-data-source>jdbc/dbpam</jta-data-source>
6. <properties>
7. <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
8. </properties>
9. </persistence-unit>
10. </persistence>

• ligne 3 : l'unité de persistance avec un type de transactions JTA


• ligne 4 : l'implémentation JPA utilisée, ici Toplink
• ligne 5 : le nom JNDI de la source de données.
• lignes 6-8 : initialisations de propriétés de l'implémentation Toplink

Les caractéristiques Jdbc ainsi que le pool de connexions de la source de données ne seront pas définies dans le fichier
[persistence.xml] mais au sein du serveur Sun qui gère cette source de données. Netbeans nous donne accès à ces propriétés :

• en [1], dans la branche [JDBC ressources], les ressources Jdbc sont désignées par leur nom JNDI
• en [2], on demande à voir les propriétés de la source jdbc / dbpam
• parmi celles-ci, il y a le nom du pool de connexions qu'elle utilise [3], ici mysqlPool.

5
6

• en [4], dans la branche [Connection Pools], on demande à voir les propriétés du pool nommé [mysqlPool]

Introdution à Java EE
76/334
• la fenêtre [5] donne les propriétés du pool. Celles-ci sont en général des valeurs par défaut que le développeur n'a pas
besoin de modifier dans la phase de développement. Dans la phase de production, elles dépendront du niveau d'utilisation
de la source de données par les clients.
• en [6], le nom de la classe qui gère le pool, ici une classe MySQL, parce que dans cet exemple, la source de données est
gérée par MySQL5.
• en [7], des propriétés dites additionnelles du pool.

• en [8], les propriétés additionnelles du pool de connexion mysqlPool sont les caractéristiques de la connexion Jdbc à la
source de données.

13.1.2 Gérer le serveur Sun via son interface web

L'interface [Netbeans / Runtime] ne permet pas de créer de nouvelles ressources pour le serveur Sun, par exemple une nouvelle
ressource Jdbc, en-dehors d'un projet Netbeans. Si on veut créer une ressource Sun, indépendamment de tout projet de
développement, il nous faut passer par l'interface web d'administration du serveur. Pour illustrer l'utilisation de celle-ci, nous allons
ci-dessous supprimer la ressource Jdbc jdbc/dbpam et le pool de connexions mysqlPool pour ensuite les reconstruire avec l'interface
web du serveur Sun.

1 2 3

• en [3], les ressources supprimées en [1] et [2] n'apparaissent plus.

3
1
4
5
6
2

• en [1], clic droit sur le serveur Sun


• en [2], demander la console d'administration
• en [3], l'interface web de cette console

Introdution à Java EE
77/334
• en [4, 5] : le login (admin) et le mot de passe (adminadmin) de l'administrateur. Ce sont les valeurs par défaut tant qu'on ne
les a pas changées.
• en [6], on se connecte

1 3

• en [1], l'arborescence d'administration du serveur. On peut constater qu'elle est analogue à celle offerte par Netbeans.
• en [2], les ressources que nous voulons administrer
• en [3], l'arborescence déployée des ressources
• en [4], les ressources Jdbc
• en [5], les pools de connexions

Nous allons pouvoir ici créer de nouvelles ressources, ce que l'interface Netbeans ne permettait pas :

1
3

• en [1], nous demandons la page des pools de connexions


• en [2], la page nous présente la liste de ces pools (3). On peut en créer un nouveau (4). Un assistant nous guide dans cette
création :

Introdution à Java EE
78/334
1
5
6
2
3

7
4

• en [1], 1ère étape de l'assistant


• en [2], donner un nom au pool de connexions – peut-être quelconque
• en [3], il existe différents type de pools – choisir [javax.sql.DataSource]
• en [4], choisir le type de SGBD. Ici mysql pour se connecter à une source MySQL5. Passer à l'étape suivante.
• en [5], la seconde page de l'assistant. Elle offre un grand nombre de champs. Nous ne présentons que quelques-uns
d'entre-eux.
• en [6], dans [General Settings], on trouve un rappel des choix de l'étape 1. Le champ [Datasource ClassName] est pré-
rempli à partir du choix du SGBD fait à l'étape 1.
• en [7], on peut donner une description du pool. Facultatif.

• en [1], la configuration du pool de connexions. Les valeurs présentées ici sont les valeurs par défaut.
• en [2], la gestion d'une connexion – valeurs par défaut

2
3

• en [1], on définit le niveau d'étanchéité des transactions


• en [2], on a quatre niveaux d'étanchéité :

Introdution à Java EE
79/334
• read-uncommitted : une transaction T1 peut lire des données modifiées par une transaction T2 et pas encore
validées. Si ainsi, la transaction T2 vient à échouer et qu'elle opère un rollback, c.a.d. qu'elle annule toutes les
modifications qu'elle a pu faire, la transaction T1 aura alors lu des données erronées. On appelle cela le dirty
read.
• read-committed : évite le problème précédent. Une transaction T1 ne peut pas lire des données en cours de
modification par une transaction T2 en cours et non encore terminée. Au cours d'une transaction T1, les lignes
susceptibles d'être modifiées par T1 sont verrouillées en mode exclusif. Aucune autre transaction ne peut les lire,
à fortiori les modifier, avant la fin de la transaction T1. Les lignes simplement lues (pas modifiées) par la
transaction T1 sont elles, verrouillées en lecture. Ce verrouillage dure le temps de leur lecture. Pendant ce
verrouillage, une autre transaction peut les lire mais pas les modifier. Cependant, le verrouillage en lecture cesse
dès que la lecture de la table est finie. Il ne dure pas jusqu'à la fin de la transaction T1. Au cours de la transaction
T1, une table T peut être lue à plusieurs reprises. Le niveau d'étanchéité read-committed n'empêche pas une
transaction T2 de modifier des lignes de la table T entre deux lectures par T1, ni d'ajouter des lignes à la table T
alors même que la transaction T1 n'est pas terminée. Si au cours de T1, la table T est lue plusieurs fois, il est
possible qu'entre deux lectures, des lignes aient été modifiées et / ou que de nouvelles lignes soient apparues. On
appelle ces dernières, des lignes fantômes et la lecture, une lecture non reproductible.
• repeatable-read : ce mode assure que les lignes lues par une transaction T1 ne seront pas modifiées avant que
T1 ne soit terminée. Dans l'exemple précédent, les lignes de la table T lues au cours d'une première lecture seront
les mêmes lors d'une seconde lecture au cours de la même transaction T1. Cela n'empêche cependant pas que la
table T ait des lignes nouvelles, les lignes fantômes.
• serializable : ce mode assure une étanchéité totale entre transactions. Le résultat obtenu est celui qui serait
obtenu si elles étaient exécutées l'une après l'autre et non en parallèle. D'où le nom de ce mode d'étanchéité.

Pour obtenir ces niveaux d'étanchéité progressifs, des verrous de plus en plus forts sont posés sur les tables exploitées par
les différentes transactions, entravant de plus en plus la simultanéité des transactions. Ainsi si le mode serializable assure
une étanchéité parfaite entre transactions, il est également le mode le moins performant car résuisant fortement la
simultanéité des transactions. Dans un compromis étanchéité / performances, c'est habituellement le mode read-
committed qui est choisi. L'aide d'un DBA (DataBase Administrator) peut être nécessaire pour déterminer le niveau
d'étanchéité des transactions nécessaire à une application.
• en [3], le niveau d'étanchéité choisi.

1 2

• en [1] : le tableau des propriétés additionnelles. Le SGBD MySQL se singularise par un très grand nombre de ces
propriétés dont plusieurs semblent redondantes. On se contentera de préciser les paramètres de la connexion Jdbc [2].
Ceci fait, on valide l'assistant avec le bouton [Finish].

3
2

Introdution à Java EE
80/334
• en [1], le nouveau pool de connexions apparaît dans la liste des pools de connexions. On peut demander à consulter /
modifier ses caractéristiques en cliquant sur son lien.
• en [2], un bouton [Ping] permet d'établir une connexion avec la base de données cible. Cela permet de vérifier la validité
du paramétrage de la connexion Jdbc.
• en [3], le résultat si la connexion a pu être établie.

Pour que le Ping réussisse ici, il faut remplir plusieurs conditions :


• s'assurer du paramétrage Jdbc qui a été fait – au besoin le corriger
• lancer le SGBD MySQL5
• s'assurer que la bibliothèque MySQL est accessible au serveur Sun. Les bibliothèques des différents SGBD utilisés dans ce
document ont été placés dans le dossier <Serveur Sun>/domains/domain1/lib/ext [1] :

Il est peut-être possible de placer ces bibliothèques ailleurs, notamment dans le dossier <Serveur Sun>/lib. Pour que les
nouvelles bibliothèques soient prises en compte, il peut être nécessaire de relancer le serveur Sun [2].

L'étape suivante est de créer la source de données qui utilise le pool qui vient d'être créé :

3
1

2 4
5

6
7

• en [1], cliquer sur le lien [JDBC ressources]


• en [2], ajouter une nouvelle ressource JDBC avec le bouton [New]
• en [3], la page de définition des caractéristiques de la ressource JDBC
• en [4], son nom JNDI. Peut être assez quelconque. Ici, on a suivi le modèle des sources Jdbc déjà définies dans le serveur
Sun.
• en [5], utiliser la liste déroulante pour définir le pool de connexions qui sera utilisé par la ressource Jdbc. En associant la
source jdbc/dbpam au pool myPool, on l'associe à la base MySQL5 dbpam.
• en [6], une description facultative
• en [7], la source une fois créée.

La source Jdbc de nom JNDI jdbc/dbpam est désormais utilisable dans le fichier de persistence d'une couche JPA. Ainsi, on pourra
écrire :

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="serveur-metier-dao-ejb-jpa-toplink-glassfishPU" transaction-type="JTA">

Introdution à Java EE
81/334
4. <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
5. <jta-data-source>jdbc/dbpam</jta-data-source>
6. <properties>
7. <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
8. </properties>
9. </persistence-unit>
10. </persistence>

La ligne 5 du fichier [persistence.xml] ci-dessus, indique l'utilisation de la source de données de nom JNDI jdbc/dbpam, celle que
nous venons de définir.

Les nouvelles ressources créées grâce à l'interface web deviennent visibles sous Netbeans :

• dans l'onglet [Runtime] [1] de Netbeans, on rafraîchit [2] l'arborescence du serveur Sun
• ceci fait, en [3] le nouveau pool de connexions apparaît ainsi que la nouvelle ressource Jdbc [4].

Nous verrons qu'il est possible de créer une source de données Jdbc de type JTA directement à partir d'une application d'entreprise
Netbeans et ce beaucoup plus facilement que via l'interface web du serveur Sun. Aussi pouvons-nous supprimer les deux ressources
que nous venons de créer. Pour cela, il suffit de supprimer le pool de connexions myPool. Toutes les sources Jdbc utilisant ce pool
sont alors également détruites. Nous le faisons à partir de Netbeans :

4 5
1 3

• en [1], suppression du pool MyPool


• en [2], il a disparu ainsi que la source jdbc/dbpam [3]
• dans l'interface web d'administration du serveur Sun, cela est confirmé par les vues [4] et [5].

13.1.3 Ajouter un nouveau serveur d'application

Netbeans ne supporte pas que le seul serveur Sun comme le montrent les copies d'écran ci-dessous :

Introdution à Java EE
82/334
Nous allons ajouter un serveur Glassfish v2 b58. Tout d'abord, nous le téléchargeons :

• en [1], le site de téléchargement


• en [2], la version téléchargée
• en [3], les instructions d'installation

Une fois le document téléchargé, on installe Glassfish via une fenêtre de commandes. Dans le dossier où le jar a été téléchargé, on
l'exécute :

Introdution à Java EE
83/334
E:\downloads\glassfish>java -Xmx256m -jar glassfish-installer-v2-b58g.jar

La commande précédente suppose que l'exécutable java.exe est dans le PATH de la fenêtre de commandes. Si ce n'était pas le cas,
on donnera le chemin complet de java.exe, par exemple :

E:\downloads\glassfish>"C:\Program Files\Java\jdk1.6.0\bin\java" -Xmx256m -jar glassfish-installer-v2-b58g.jar

Cette exécution crée un dossier glassfish :

On copiera le dossier glassfish ainsi créé à l'emplacement où on souhaite qu'il soit (cf [1] ci-dessous) puis on passera à l'étape 2 de
l'installation.

• le dossier [1] ci-dessus contient deux versions de Glassfish. Celle téléchargée correspond au dossier [glassfish-v2-b58g].

Pour l'étape suivante, s'assurer qu'aucun serveur Sun ou Glassfish n'est en cours d'exécution. Si besoin est l'arrêter. Avec une
fenêtre de commandes, on se positionne dans le dossier [glassfish-v2-b58g] ci-dessus et on tape la commande :

C:\devjava\glassfish\glassfish-v2-b58g>lib\ant\bin\ant.bat -f setup.xml

Cela achève d'installer le serveur Glassfish. En particulier, des scripts SQL sont exécutés pour générer des bases Java Derby servant
de base à des tutoriels. Des domaines, sortes de serveurs virtuels, sont également créés. Pour cela, le serveur Glassfish a été lancé.
Pour cette raison, il est important de vérifier qu'aucun autre serveur Glassfish ou Sun n'est actif, sinon des conflits apparaîtront et
les domaines ne seront pas créés.

Ceci fait, nous pouvons ajouter le serveur Glassfish aux serveurs connus de Netbeans :

Introdution à Java EE
84/334
2
5
3 4

• en [1], on ajoute un serveur


• en [2], le type du serveur : Glassfish est un serveur de type [Sun Java System Application Server].
• en [3], on lui donne un nom
• avec [4], on désigne son répertoire d'installation. Le nom de celui-ci apparaît en [5].
• en [6], le nom du domaine qui sera utilisé par défaut. Un serveur Sun supporte plusieurs domaines qui peuvent
fonctionner en parallèle. C'est comme si on avait plusieurs serveurs virtuels. Chaque domaine a ses propres ports d'écoute.
Le domaine domain1 écoute sur le port 8080 et s'administre sur le port 4848.

7 9
8 10

• en [7] et [8] le login (admin) de l'administrateur et son mot de passe (adminadmin). Ce sont les valeurs par défaut fixées à
l'installation du serveur.
• une fois l'assistant d'ajout terminé, le nouveau serveur apparaît en [9]. On peut le lancer.
• en [10], il est actif.

13.2 La partie serveur de l'application client / serveur PAM

13.2.1 L'architecture de l'application

Après ce long détour technique, revenons-en à l'application client / serveur que nous voulons construire :

Couche C
Couche Couche Couche Couche
[ui] [dao] 4
[metier] [JPA / [JDBC]
1 3 Toplink]
2 6

Jvm1 - Java SE 7 Jvm2 – Java EE - serveur Sun / Glassfish

• la couche [ui] s'exécutera dans un environnement Java SE (Standard Edition)


• les couches [metier, dao, jpa] s'exécuteront dans un environnement Java EE (Enterprise Edition) sur un serveur Sun
• le client communique avec le serveur via un réseau tcp-ip. Les échanges réseau sont transparents pour le développeur, si ce
n'est qu'il doit être quand même conscient que le client et le serveur s'échangent des objets sérialisés pour communiquer et
non des références d'objets. Le protocole réseau utilisé pour ces échanges s'appelle RMI (Remote Method Invocation), un
protocole utilisable uniquement entre deux applications Java.

Introdution à Java EE
85/334
• l'implémentation Jpa utilisée sur le serveur Sun AS 9.1 sera Toplink supportée nativement pas ce serveur. Hibernate est
utilisable à condition d'ajouter les bibliothèques de son implémentation dans le dossier des bibliothèques du serveur Sun.

Nous étudions ici la partie serveur qui sera hébergée par le conteneur Ejb3 du serveur Sun AS 9.1 :

Conteneur
Client Jpa / Toplink
Ejb3 Données
Java SE
serveur Java EE

Il s'agit de faire un portage vers le serveur Sun de ce qui a déjà été fait et testé avec le conteneur Jboss Ejb3. C'est là l'intérêt de
Jboss Ejb3 : il nous permet de tester l'application dans un environnement d'exécution simplifié. Lorsque l'application a été testée, il
ne reste plus qu'à la porter sur un serveur cible, ici le serveur Sun AS 9.1.

13.2.2 Le projet Netbeans

Commençons par créer un nouveau projet Netbeans :

2 3

• en [1], nouveau projet


• en [2], choisir la catégorie Enterprise et en [3] le type EJB Module. Il s'agit en effet de construire un projet qui sera hébergé et
exécuté par un conteneur Ejb, celui du serveur Sun.

8
5 4a 9
4b
10

6 11
7

• avec le bouton [4a], choisir le dossier parent du dossier du projet ou taper son nom directement en [4b].
• en [5], donner un nom au projet
• en [6], choisir le serveur d'application sur lequel il sera exécuté. Celui choisi ici est l'un de ceux visibles dans l'onglet
[Runtime / Servers].
• en [7], choisir la version de Java nécessaire : Java EE 5 supporte les Ejb3, J2EE les Ejb2. On choisira donc Java EE5. On
notera que cela dépend du serveur d'application choisi. Java EE 5 n'est proposé dans la liste déroulante [7] que si le
serveur choisi en [6] le supporte.
• en [8], le nouveau projet. Il diffère d'un projet Java classique par quelques points :
• une branche [Configuration Files] [9] dans laquelle nous allons trouver les fichiers qui configurent le module Ejb.
Nous découvrirons qu'il y en a peu et que de plus certains sont générés automatiquement par Netbeans. C'est un

Introdution à Java EE
86/334
progrès amené par les Ejb3 : les applications Ejb2 nécessitent de nombreux fichiers de configuration qui
ralentissent le développement.
• une branche [Server Resources] [10]. Cette branche permet de créer des ressources sur le serveur. Ainsi pour
créer une ressource Jdbc :

• en [1], les ressources qui peuvent être créées sur le serveur Sun
• en [2], les catégories d'objets que l'on peut créer et manipuler dans un projet Ejb sont plus nombreuses
que dans un projet Java classique. Nous aurons l'occasion d'utiliser les catégories Enterprise, Persistence et
Web Services.
• une branche [Libraries] [11] dans laquelle on trouve les bibliothèques du serveur Sun :

13.2.3 Configuration de la couche de persistance

Par configuration de la couche de persistance, nous entendons l'écriture du fichier [persistence.xml] qui définit :
• l'implémentation Jpa à utiliser
• la définition de la source de données exploitée par la couche Jpa. Celle-ci sera une source Jdbc gérée par le serveur Sun.

Couche C Couche 4
Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
Toplink] 6
7

Java SE Java EE - serveur Sun

On pourra procéder comme suit. Tout d'abord, dans l'onglet [Runtime / Databases], on créera une connexion sur la base mySQL5
/ dbpam comme il a été décrit au paragraphe 4, page 19.

Introdution à Java EE
87/334
1 3

• en [1], la connexion sur la base MySQL5 / dbpam (MySQL5 a été lancé).


• en [2], on supprime toutes les tables de la base pour partir d'un état neuf
• en [3], la base vide de tables

Ceci fait, on peut passer à la création du fichier [persistence.xml] :

5
2

6
4
1 3

• en [1], créer un nouveau fichier (icône New File) – on s'assurera que le projet Ejb est sélectionné avant de faire cette
opération
• en [2], le projet Ejb
• en [3], on sélectionne la catégorie [Persistence]
• en [4], on veut créer une unité de persistance
• en [5], Netbeans propose un nom par défaut pour l'unité de persistance
• en [6], plusieurs implémentations Jpa sont proposées. On choisira ici [Toplink]. Les autres sont utilisables à condition de
mettre les bibliothèques qui les implémentent avec celles du serveur Sun.

7
8 10

11 12
9

13

• une fois l'implémentation Jpa choisie [6], on définit la source de données gérée par cette implémentation [8]
• [9] : la source jdbc/sample est une source prédéfinie dans le serveur Sun. Elle est liée à une base de données Java Derby
servant aux tutoriels accompagnant le serveur Sun. On choisit ici de définir une nouvelle source de données.
• en [10], on donne un nom JNDI à la source de données. On suit l'exemple du nom jdbc / sample et on nomme la source
jdbc / dbpam.
• en [11], la liste déroulante [Database Connection] présente les connexions de l'onglet [Runtime / Databases]. On choisit la
connexion [dbpam] que nous venons de créer. Cela définit totalement les caractéristiques Jdbc de la source de données
(pilote Jdbc, url de la base, login et mot de passe du propriétaire de la connexion). Ainsi l'implémentation Jpa sait
comment atteindre la base de données.
• en [12], on choisit Toplink comme implémentation Jpa

Introdution à Java EE
88/334
• en [13], nous indiquons que la base doit être générée au déploiement du module : suppression des tables existantes (drop)
suivi d'une recréation (create). La génération des ordres SQL appropriés se fera à partir des annotations trouvées dans les
entités (@Entity) Jpa.

Une fois l'assistant de génération de l'unité de persistance terminé, le projet Netbeans évolue de la façon suivante :

8 2
0 1

4
5
6

• dans la branche [Configuration Files] [0], le fichier [persistence.xml] a été ajouté


• si on double-clique sur le fichier [persistence.xml], on obtient en [1] une vue graphique du fichier, appelée mode [Design]
qui détaille le contenu du fichier.
• le fichier [persistence.xml] peut contenir plusieurs unités de persistance. On en sélectionne une avec la liste [2]. Sont
affichées alors les informations suivantes : [3] le nom de l'unité de persistance, [4] l'implémentation Jpa utilisée, [5] le nom
JNDI de la source Jdbc utilisée, [6] le type de transactions utilisée, JTA pour des transactions contrôlées par un conteneur
Ejb, [7] : la stratégie de génération des tables de la source Jdbc lors de la connexion initiale à la base de données.

Le mode [Design] ne fait que refléter le contenu XML du fichier qui peut être obtenu avec l'onglet XML [8] :

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="serveur-metier-dao-ejb-jpa-toplink-sunPU" transaction-type="JTA">
4. <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
5. <jta-data-source>jdbc/dbpam</jta-data-source>
6. <properties>
7. <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
8. </properties>
9. </persistence-unit>
10. </persistence>

Ecrire ce fichier à la main ne donne pas le même résultat que l'assistant. En effet, au cours de l'exécution de l'assistant, nous avons
donné les caractéristiques Jdbc de la source de données (pilote Jdbc, url de la base, login et mot de passe du propriétaire de la
connexion) qui vont permettre à l'assistant, lors du déploiement du module Ejb sur le serveur Sun de :
• créer un pool de connexions utilisant ces caractéristiques Jdbc
• créer la source jdbc / dbpam utilisant ce pool

Si on crée le fichier [persistence.xml] à la main, la source jdbc / dbpam ne sera pas créée lors du déploiement du module Ejb sur le
serveur Sun. Il faut donc qu'elle existe déjà. Elle peut avoir été créée via l'outil web d'administration du serveur Sun comme nous
avons eu l'occasion de le faire.

La création de la source de données avec l'assistant a généré des ressources serveur dans le projet :

Introdution à Java EE
89/334
1b
3

• en [1], deux ressources ont été créées, l'une pour la source jdbc/dbpam, l'autre pour le pool de connexions utilisé par cette
source.
• un double-clic sur la ressource [jdbc_dbpam.sun-resource] donne accès aux propriétés [1b] de source jdbc/dbpam. On
reconnaîtra là les propriétés présentées par l'interface web d'administration du serveur Sun. On notera en [2] le nom JNDI
de la source, en [3] le nom de son pool de connexions. Ce dernier nom a été attribué par l'assistant de création de la source
Jdbc.

4
6

7b

4b

7a
5a 5b

• un double-clic sur la ressource [mysqlPool.sun-resource] donne accès aux propriétés [4] du pool de connexion nommé
mysqlPool [4b]. Elles sont moins nombreuses que celles présentées dans l'interface web d'administration du serveur Sun. On
notera en [5a], les propriété dites additionnelles du pool. Un clic sur [5b] donne accès à celles-ci [6]. On reconnaîtra les
caractéristiques Jdbc de la base de données de l'unité de persistance.
• on pourra profiter de la fenêtre [4] pour mettre le niveau d'étanchéité des transactions à [read-committed] [7a, 7b].

13.2.4 Génération de la source de données

L'assistant n'a fait que préparer la génération de la source de données. Pour la générer sur le serveur Sun avec Netbeans 5.5.1, on
pourra procéder ainsi :

Introdution à Java EE
90/334
2 5

• en [1], les ressources jdbc/dbpam et mysqlPool n'existent pas sur le serveur Sun.
• en [2], on enregistre d'abord le pool de connexions, puis en [3] la source Jdbc. L'ordre est imposé par le fait que la source
Jdbc utilise le pool de connexions. Il faut que celui-ci existe pour pouvoir créer la source Jdbc.
• en [5], on voit que les deux ressources ont été créées après que l'arborescence du serveur Sun ait été rafraîchie [4].

13.2.5 Déploiement des couches [jpa, dao, metier]

Maintenant que la source de données a été définie et éventuellement déployée sur le serveur Sun, nous pouvons passer au
déploiement des couches [metier, dao, jpa] de l'application d'entreprise [pam] :

Couche C Couche 4
Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 Toplink] 6
7

Java SE Java EE - serveur Sun

Le portage de ces couches de l'application Jboss Ejb3 vers le projet actuel ne pose aucune difficulté. Ces trois couches sont
identiques à ce qu'elles étaient avec Jboss. On peut avec Netbeans procéder à un simple copier / coller entre les deux projets :

• copier les paquetages dans Jboss


• les coller dans l'arborescence [Source packages] du projet Ejb / Sun

1
3

2
5

Introdution à Java EE
91/334
• dans le projet Jboss Ejb3 [1], on copie les paquetages [dao, exception, jpa , metier] [2]
• on les colle [4] dans le projet Ejb / Sun [3]
• en [5], le résultat

La copie des couches [jpa, dao, metier] dans le projet Ejb a créé un nouveau fichier de configuration dans le projet :

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


2. <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD
Application Server 9.0 EJB 3.0//EN"
1 "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-
0.dtd">
3. <sun-ejb-jar> 3
2 4. <enterprise-beans>
5. <name/>
6. </enterprise-beans>
7. </sun-ejb-jar>

• dans la branche [Configuration Files] [1] du projet, un fichier [sun-ejb-jar.xml] a été créé [2]. C'est un fichier de
déploiement destiné au serveur Sun et qui décrit les Ejb à déployer. En [3], on voit le contenu Xml de ce fichier. Les Ejb
déployés n'y sont pas décrits. Ils seront découverts par le serveur Sun qui explorera les classes du .jar du module Ejb à la
recherche des annotations @Stateless @Stateful et @MessageDriven qui définissent des Ejb. Le développeur n'a pas
ici à s'occuper du fichier de déploiement [sun-ejb-jar.xml]. Il est géré par Netbeans.

Dans notre architecture, tous les éléments de la partie serveur ont maintenant été définis :

Couche C Couche 4
Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 Toplink] 6
7

Java SE Java EE - serveur Sun

Il ne nous reste qu'à compiler le projet et à le déployer sur le serveur Sun. On notera que toutes ces couches ont fait l'objet de tests
dans le projet Jboss et qu'il est inutile de répéter ces derniers. Nous aurons cependant l'occasion de refaire les tests " distants "
lorsque nous écrirons la partie client de notre application client / serveur.

Le déploiement d'un projet Ejb sur le serveur Sun va entraîner une succession d'actions. Pour les connaître, nous allons déployer
notre projet Ejb puis nous regarderons les logs.

5 4

1 2

• en [1], déploiement du module Ejb


• en [2], le module a été déployé sur le serveur Sun
• en [3], les fenêtres de logs que nous allons étudier : ceux du processus de déploiement Netbeans [4] et ceux du processus
de déploiement sur le serveur Sun lui-même [5].

Introdution à Java EE
92/334
Dans la fenêtre de logs [4], on a les logs suivants :

1. init:
2. deps-jar:
3. Copying 1 file to C:\data\2007-2008\netbeans\pam\03\serveur-metier-dao-ejb-jpa-toplink-sun\build\jar\META-INF
4. Compiling 18 source files to C:\data\2007-2008\netbeans\pam\03\serveur-metier-dao-ejb-jpa-toplink-sun\build\jar

• lignes 2-4 : un dossier build / jar est créé. Il va contenir toutes les classes compilées du projet ainsi que tous les fichiers de
configuration de ce dernier. L'ensemble de ces fichiers est trouvé dans le dossier [src] du projet Ejb :

5 6
2 3

4
7

• dans l'onglet [Files] [1], le dossier [src] [2] contient


• les fichiers de configuration du projet dans le dossier [conf] [3]
• les classes du projet dans le dossier [java] [4].
• en [5], le dossier [build / jar] a été construit par recopie des dossiers de conf [3] dans le dossier [META-INF] [6] et par
compilation [7] des classes du dossier [java] [4].

Continuons l'examen des logs :

1. compile:
2. library-inclusion-in-archive:
3. Building jar: C:\data\2007-2008\netbeans\pam\03\serveur-metier-dao-ejb-jpa-toplink-sun\dist\serveur-metier-dao-ejb-
jpa-toplink-sun.jar

• ligne 3 : le dossier [build / jar] est mis dans l'archive [dist\serveur-metier-dao-ejb-jpa-toplink-sun.jar]

• en [1], l'archive du projet Ejb et en [2] son contenu.

Logs suivants :

Introdution à Java EE
93/334
1. dist:
2. pre-run-deploy:
3. Distributing C:\data\2007-2008\netbeans\pam\03\serveur-metier-dao-ejb-jpa-toplink-sun\dist\serveur-metier-dao-ejb-
jpa-toplink-sun.jar to [localhost:4848_server]
4. Start registering the project's server resources
5. Finished registering server resources
6. deployment started : 0%
7. deployment finished : 100%
8. Deploying application in domain completed successfully
9. Trying to create reference for application in target server completed successfully
10. Trying to start application in target server completed successfully
11. Enable of serveur-metier-dao-ejb-jpa-toplink-sun in target server completed successfully
12. Enable of application in all targets completed successfully
13. All operations completed successfully

• ligne 3 : l'archive du module Ejb est copiée sur le serveur localhost:4848, c.a.d. sur le serveur Sun.
• lignes 4-5 : les ressources du projet sont enregistrées sur le serveur Sun : il s'agit du pool de connexions mysqlPool et de la
source de données Jdbc jdbc / dbpam.
• lignes 6-7 : début et fin du processus de déploiement effectif du projet Ejb sur le serveur Sun.
• ligne 8 : le déploiement a réussi
• lignes 9-13 : diverses opérations post-déploiement qui rendent l'application déployée disponible aux clients.

Le déploiement du projet Ejb a fait également l'objet de logs de la part du serveur Sun :

1. ADM1006:Uploading the file to:[C:\...\serveur-metier-dao-ejb-jpa-toplink-sun.jar]


2. [TopLink Config]: 2007.09.16 04:19:47.359--ServerSession(23217077)--The alias name for the entity class [class
jpa.Indemnite] is being defaulted to: Indemnite.
3. [TopLink Config]: 2007.09.16 04:19:47.359--ServerSession(23217077)--The column name for element [private
java.lang.Long jpa.Indemnite.id] is being defaulted to: ID.
4. ...
5. [TopLink Config]: 2007.09.16 04:19:47.437--ServerSession(23217077)--Connection(21119302)--Connected:
jdbc:mysql://localhost:3306/dbpam
6. User: dbpam@localhost
7. Database: MySQL Version: 5.0.37-community-nt
8. Driver: MySQL-AB JDBC Driver Version: mysql-connector-java-5.0.5 ( $Date: 2007-03-01 00:01:06 +0100 (Thu,
01 Mar 2007) $, $Revision: 6329 $ )
9. ...
10. deployed with moduleid = serveur-metier-dao-ejb-jpa-toplink-sun
11. ADM1041:Sent the event to instance:[ModuleDeployEvent -- enable ejb/serveur-metier-dao-ejb-jpa-toplink-sun]
12. **RemoteBusinessJndiName: metier.IMetierRemote; remoteBusIntf: metier.IMetierRemote
13. **RemoteBusinessJndiName: dao.ICotisationDaoRemote; remoteBusIntf: dao.ICotisationDaoRemote
14. **RemoteBusinessJndiName: dao.IIndemniteDaoRemote; remoteBusIntf: dao.IIndemniteDaoRemote
15. **RemoteBusinessJndiName: dao.IEmployeDaoRemote; remoteBusIntf: dao.IEmployeDaoRemote
16. LDR5010: All ejb(s) of [serveur-metier-dao-ejb-jpa-toplink-sun] loaded successfully!

• ligne 1 : téléchargement de l'archive jar du module Ejb sur le serveur Sun


• lignes 2 – 4 : des informations de configuration de Toplink qui indique les valeurs par défaut prises lorsque certaines
annotations sont manquantes dans les entités JPA. La ligne 2 signifie par exemple que l'entité jpa.Indemnite n'a pas eu de
nom et que ce nom sera alors celui de la classe : Indemnite. En effet, l'annotation @Entity() admet un paramètre name
permettant de donner un nom à l'entité, @Entity(name="xx") par exemple. En l'absence de ce paramètre name, le nom de la
classe qui a l'annotation @Entity est donné à l'entité.
• ligne 5 : Toplink a réussi à se connecter à la source de données MySQL5 / dbpam
• ligne 10 : le module Ejb déployé s'appellera [serveur-metier-dao-ejb-jpa-toplink-sun], c.a.d. le nom du jar qui a été
téléchargé sur le serveur.
• ligne 11 : le module Ejb est autorisé. Il va être disponible pour les clients. Auparavant les Ejb qu'il contient doivent
pouvoir être référencés par les clients.
• lignes 12-15 : les noms JNDI des versions distantes des Ejb. C'est via ces noms qu'un client distant pourra référencer les
Ejb déployés. Ce sont des lignes importantes dans les logs du serveur Sun.
• ligne 16 : tous les Ejb du module Ejb sont disponibles aux clients.

Introdution à Java EE
94/334
Le déploiement du module Ejb a provoqué également la création des tables de la source jdbc / dbpam. On rappelle la teneur du
fichier [persistence.xml] :

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="serveur-metier-dao-ejb-jpa-toplink-sunPU" transaction-type="JTA">
4. <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
5. <jta-data-source>jdbc/dbpam</jta-data-source>
6. <properties>
7. <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
8. </properties>
9. </persistence-unit>
10. </persistence>

La ligne 7 demande que la base soit générée lorsque la couche JPA fera sa première connexion. Le résultat de cette génération peut
être vue dans l'onglet [Runtime / Databases] (éventuellement faire un Refresh sur la connexion):

13.3 La partie client de l'application client / serveur PAM


Maintenant que nous avons déployé la partie serveur de notre application client / serveur, nous en venons à étudier la partie client :

Couche C Couche 4
Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 Toplink] 6
7

Java SE Java EE - serveur Sun

13.3.1 Le projet Netbeans du client console

Nous créons maintenant un projet Java de base pour la partie client de l'application :

Introdution à Java EE
95/334
1 2

• en [1], le projet de la partie client de l'application [pam]


• rappelons que des clients Ejb ont déjà été écrits dans le projet Jboss Ejb3 [2,3]. Il s'agit ici d'en faire le portage.

Dans le paquetage [ui] [3] des clients Ejb Jboss, nous trouvons deux types d'applications clientes : des applications console et des
applications swing. Nous allons d'abord porter les applications console. En [3], on peut voir que nous avions écrit deux clients console :
un client local (MainLocal) et un client distant (MainRemote). Puisque nous travaillons ici en client / serveur, seul le client distant sera
porté. Son code ressemblait à ceci :

1. package ui.console;
2.
3. import exception.PamException;
4. import javax.naming.NamingException;
5. import metier.FeuilleSalaire;
6. import java.util.ArrayList;
7. import javax.naming.InitialContext;
8. import metier.IMetierRemote;
9. import org.jboss.ejb3.embedded.EJB3StandaloneBootstrap;
10.
11. public class MainRemote {
12.
13. public static void main(String[] args) throws NamingException {
14. ...
15. // instanciation couche [metier]
16. IMetierRemote metier = getEjbMetierRemote();
17. // calcul de la feuille de salaire
18. FeuilleSalaire feuilleSalaire = null;
19. try {
20. feuilleSalaire = metier.calculerFeuilleSalaire(...);
21. } catch (PamException ex) {
22. ...
23. }
24. // affichage résultats sur console
25. ...
26. // on arrête le conteneur Ejb
27. stopJbossEjb3();
28. }
29.
30. // obtenir une référence sur la couche métier
31. private static IMetierRemote getEjbMetierRemote() throws NamingException{
32. // on démarre le conteneur EJB3 JBoss
33. ...
34. // instanciation couche metier distante
35. return (IMetierRemote) initialContext.lookup("Metier/remote");
36. }
37.
38. private static void stopJbossEjb3(){
39. // Shutdown EJB container
40. ...
41. }
42.
43. }

On notera les points suivants :

Introdution à Java EE
96/334
• ligne 16 : on demande une référence sur l'Ejb distant de la couche [metier]. Cette référence est fournie par la méthode
privée [getEjbMetierRemote] qui lance le conteneur Ejb Jboss (ligne 32) et rend la référence de la couche [metier] via une
requête JNDI (ligne 35). Deux choses ne sont pas portables ici :
• nous n'avons plus le conteneur Ejb Jboss
• les requêtes JNDI ne sont pas portables. Chaque serveur d'application a un environnement JNDI qui lui est
propre.
• lignes 3-9 : l'importation d'un certain nombre de classes. Nous voyons que nous avons besoin des classes ou interfaces :
PamException, FeuilleSalaire, IMetierRemote. La classe FeuilleSalaire fait référence à d'autres classes : Employe, Indemnite,
Cotisation, ElementsSalaire.

Nous copions dans le nouveau projet, les paquetages du projet Jboss dont nous avons besoin :

1
2

On notera que dans le paquetage [metier] du nouveau projet [1], nous ne copions pas la totalité du paquetage [metier] du projet
Jboss [2]. En effet, le client n'utilise pas l'interface locale [IMetierLocal] ni la classe d'implémentation [Metier] car celle-ci s'exécute
côté serveur et non côté client.

La classe [ui.console.MainRemote] doit être nettoyée des références au conteneur Jboss Ejb3 :

1. package ui.console;
2.
3. ...
4. public class MainRemote {
5.
6. public static void main(String[] args){
7. ...
8. // instanciation couche [metier]
9. IMetierRemote metier = getEjbMetierRemote();
10. // calcul de la feuille de salaire
11. FeuilleSalaire feuilleSalaire = null;
12. try {
13. feuilleSalaire = metier.calculerFeuilleSalaire(...);
14. } catch (PamException ex) {
15. ...
16. }
17. // affichage résultats sur la console
18. ...
19. }
20.
21. // obtenir une référence sur la couche métier
22. private static IMetierRemote getEjbMetierRemote(){
23. return null;
24. ...
25. }
26.
27. }

• lignes 22-25 : la méthode getEjbMetierRemote est vidée de son contenu


• les autres lignes du code référençant le conteneur Jboss sont éliminées (instructions import, méthode stopJbossEjb3)

Lorsque nous compilons ce projet [3], nous obtenons une série d'erreurs [4] sur les annotations @ des entités de la couche [jpa]
ainsi que sur l'annotation @EJB de l'interface [IMetierRemote] :

Introdution à Java EE
97/334
...: package javax.persistence
does not exist
import javax.persistence.Column;
...
4
3

Les classes manquantes sont dans une archive [javaee.jar] livrée par Sun et qu'on peut trouver par exemple dans <AppServer>/lib
[5] où <AppServer> est le dossier du serveur Sun. On ajoutera donc cette archive aux bibliothèques du client Ejb [6].

...
compile:
5 Created dir: ...\dist
Building jar: ...\dist\client-metier-dao-
ejb-jpa-toplink-sun.jar
6 ...
BUILD SUCCESSFUL (total time: 1 second)

Ceci fait, la compilation du projet se fait sans erreur [7]. Nous en somme au point où syntaxiquement le code est correct. Il ne nous
reste plus qu'à compléter la classe [MainRemote] qui avait été provisoirement allégée :

1. package ui.console;
2.
3. ...
4. public class MainRemote {
5.
6. public static void main(String[] args){
7. ...
8. // instanciation couche [metier]
9. IMetierRemote metier = getEjbMetierRemote();
10. // calcul de la feuille de salaire
11. FeuilleSalaire feuilleSalaire = null;
12. try {
13. feuilleSalaire = metier.calculerFeuilleSalaire(...);
14. } catch (PamException ex) {
15. ...
16. }
17. // affichage résultats sur la console
18. ...
19. }
20.
21. // obtenir une référence sur la couche métier
22. private static IMetierRemote getEjbMetierRemote(){
23. return null;
24. ...
25. }
26.
27. }

Il nous faut écrire la méthode [getEjbMetierRemote] qui doit rendre une référence sur un objet implémentant l'interface
IMetierRemote. Revenons sur l'architecture client / serveur de l'application :

Couche C Couche 4
Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 Toplink] 6
7

Java SE Java EE - serveur Sun

Une implémentation [Metier] de l'interface [IMetierRemote] est déjà présente sur le serveur. La couche [ui] ne peut pas avoir une
référence sur un objet distant. La couche de communication C ci-dessus va mettre en place un mécanisme qui va permettre à la
couche [ui] de dialoguer avec la couche [metier] comme si les deux couches s'exécutaient dans la même Jvm. Deux objets vont
venir s'intercaler entre les deux couches :

Introdution à Java EE
98/334
C
Couche C S Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 Toplink]

Java SE Java EE - serveur Sun

• côté client, l'objet C [1] va implémenter l'interface [IMetierRemote]. La méthode getEjbMetierRemote rendra à la couche [ui]
une référence sur l'objet C.
• côté serveur, un objet S [2] est créé pour répondre aux demandes de l'objet C. L'objet S connaît la couche [metier] et a une
référence dessus (même Jvm).
• lorsque la couche [ui] exécute l' instruction :

feuilleSalaire = metier.calculerFeuilleSalaire(param1, param2, ...);

c'est la méthode calculerFeuilleSalaire de l'objet C qui est exécutée, de façon transparente pour la couche [ui]. L'objet C va
transmettre l'appel de la méthode à l'objet S côté serveur. La forme des objets échangés entre le client C et le serveur S
varie selon les techniques d'échanges utilisées. Il en existe plusieurs. Dans une application client / serveur Ejb, le protocole
d'échanges utilisé est le protocole RMI. Les paramètres et le résultat de la méthode [metier.calculerFeuilleSalaire] vont être
sérialisés (mis sous forme binaire) sur le réseau. Dans une autre technique appelée services web, c'est le protocole HTTP qui
est utilisé entre le client et le serveur. Selon les services web, les objets échangés peuvent avoir diverses formes même s'ils
sont tous échangés avec le protocole HTTP. Nous utiliserons un peu plus loin un service web de type SOAP (Simple
Object Access Protocol). Avec ce protocole, les objets (paramètres, résultat) sont échangés sous forme de lignes XML.
• lorsque l'objet S reçoit la demande de l'objet C, il a tous les éléments pour reconstituer l'appel initial de la couche [ui] :

feuilleSalaire = metier.calculerFeuilleSalaire(param1, param2, ...);

L'objet S a lui une référence sur l'objet metier de l'instruction ci-dessus. Il va donc pouvoir faire exécuter l'instruction.
• une fois celle-ci exécutée, l'objet S va renvoyer le résultat à l'objet C ou éventuellement l'exception qui s'est produite.
• l'objet C va alors transmettre ces résultats à la couche [ui] qui a initié le cycle demande / réponse avec le serveur.

Sur le serveur, l'objet S qui répond aux demandes faites à l'Ejb [metier.Metier] existe déjà. Le déploiement des Ejb dans un
conteneur Ejb crée automatiquement pour chaque Ejb implémentant une interface taguée @Remote, l'interface distante S capable de
répondre aux clients. La couche [dao] ayant été instanciée par un Ejb implémentant elle aussi une interface taguée @Remote, elle
dispose également d'une interface distante S. Ici, elle n'est pas utilisée. En effet, les couches [metier] et [dao] s'exécutant dans une
même Jvm, la couche [metier] va utiliser l'interface de la couche [dao] taguée @Local et non celle taguée @Remote. Notre
architecture actuelle permettrait de mettre la couche [dao] sur une troisième machine ou de passer la couche [metier] sur le client.
On a là une grande souplesse.

La communication entre le client C et le serveur S nécessite des bibliothèques spéciales au moment de l'exécution : ce sont celles
qui vont assurer la communication client / serveur. Elles ont été rassemblées ici dans un dossier [client-ejb] :

4
3

2
1

• en [1], le dossier [lib\client-ejb] qui contient de nouvelles bibliothèques de classes.


• en [2], les trois bibliothèques appserv-* nécessaires aux clients Ejb du serveur Sun.

Introdution à Java EE
99/334
• ces bibliothèques sont disponibles dans le dossier lib du serveur Sun [3]. Avec les bibliothèques du serveur Sun livré avec
Netbeans 5.5.1, une erreur non compréhensible survenait à l'exécution. Avec les bibliothèques du serveur Glassfish v2
b58, cette erreur a disparu. Ce sont ces dernières qui ont été utilisées ici.
• en [4], les nouvelles bibliothèques ont été ajoutées au projet à partir du dossier [client-ejb].

La recherche de bibliothèques nécessaires à l'exécution du client Ejb a été fastidieuse. N'ayant trouvé aucun exemple sur le sujet, j'ai
du tâtonner pour trouver celles qui permettaient au client Ejb de fonctionner. Il est possible qu'il puisse fonctionner avec d'autres
bibliothèques.

Revenons à la méthode [getEjbMetierRemote] que nous avons à écrire. Elle doit créer l'objet C de notre architecture ci-dessus et en
rendre la référence :

1.// obtenir une référence sur la couche métier


2. private static IMetierRemote getEjbMetierRemote(){
3....
4. }

L'objet C doit être le client de l'objet S. Il lui faut connaître un certain nombre d'informations, par exemple le port d'écoute du
serveur S. Un serveur d'application peut avoir de nombreux Ejb et il n'y a pas un service d'écoute pour chacun d'eux. De façon
générale, un serveur d'application dispose d'un certains nombre de ressources. Chacune d'elles a un nom. Le serveur dispose d'un
service d'écoute JNDI (Java Naming Directory Interface) où un client vient demander une ressource via son nom. Pour que le
client puisse demander au serveur une ressource, ici un Ejb, il doit connaître le nom de celui-ci. Il n'y a pas de norme de nommage.
Ainsi dans le conteneur Jboss Ejb3, les Ejb portaient les noms suivants :

• Metier/remote pour l'Ejb [Metier] implémentant l'interface @Remote


• Metier/local pour l'Ejb [Metier] implémentant l'interface @Local

Lorsque nous avons déployé les Ejb de notre partie serveur sur le serveur Sun, celui a donné les noms des Ejb implémentant
l'interface @Remote (cf page 94):

1. **RemoteBusinessJndiName: metier.IMetierRemote; remoteBusIntf: metier.IMetierRemote


2. **RemoteBusinessJndiName: dao.ICotisationDaoRemote; remoteBusIntf: dao.ICotisationDaoRemote
3. **RemoteBusinessJndiName: dao.IIndemniteDaoRemote; remoteBusIntf: dao.IIndemniteDaoRemote
4. **RemoteBusinessJndiName: dao.IEmployeDaoRemote; remoteBusIntf: dao.IEmployeDaoRemote

Le nom JNDI de l'Ejb [Metier] implémentant l'interface @Remote est donc metier.IMetierRemote. C'est ce nom que notre client Ejb
utilisera. Avec ces informations, la méthode getEjbMetierRemote pourrait être la suivante :

1. // obtenir une référence sur la couche métier


2. private static IMetierRemote getEjbMetierRemote() throws NamingException{
3. // initialisation environnement JNDI
4. InitialContext initialContext = new InitialContext();
5. // instanciation couche métier
6. return (IMetierRemote) initialContext.lookup("metier.IMetierRemote");
7.}

• ligne 4 : initialise l'environnement JNDI. Cet environnement est un ensemble de propriétés "paramètre=valeur ". Chaque
implémentation JNDI est spécifique à un serveur d'application donné et pour que le client puisse se connecter au service
JNDI du serveur d'application, il nous faut connaître les spécificités de celui-ci. L'environnement JNDI particulier auquel
on est connectés est représenté par un type InitialContext.
• ligne 6 : une fois l'environnement JNDI initialisé, on peut lui demander des ressources. Ici, on lui demande une référence
sur l'Ejb nommé "metier.IMetierRemote", c.a.d. l'Ejb de la couche [metier] sur le serveur.

Beaucoup de choses sont cachées derrière ces deux lignes. Tout d'abord, comment l'environnement JNDI du serveur Sun est-il
initialisé ici ? Le constructeur de la classe InitialContext a différentes signatures :

Introdution à Java EE
100/334
2

Le constructeur InitialContext(java.util.Hashtable) [1] semble le plus approprié pour initialiser l'environnement JNDI du client.
Lorsque, comme dans notre exemple, le constructeur sans paramètres est utilisé, il recherche un fichier nommé jndi.properties
dans le classpath de l'application. Ici, nous n'avons jamais écrit un tel fichier. Pourtant lorsqu'on lance le client, il fonctionne. Où le
fichier jndi.properties est-il trouvé ? Dans le classpath du projet, il y a toutes les bibliothèques de celui-ci. Elles sont donc explorées
lorsque le fichier jndi.properties est cherché. Il va alors être trouvé [3] dans l'archive [appserver-rt.jar] [2]. Regardons son contenu :

1. # accès JNDI à Sun Application Server


2. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
3. java.naming.factory.url.pkgs=com.sun.enterprise.naming
4. # Required to add a javax.naming.spi.StateFactory for CosNaming that
5. # supports dynamic RMI-IIOP.
6. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl

Nous ne commenterons pas ce contenu abscons. On notera cependant deux points :


• les noms des classes commencent par com.sun, ce qui montre qu'on a là un fichier JNDI pour un serveur Sun. Pour un
serveur JBoss ou Oracle, on aura un autre type de contenu.
• dans ce fichier et dans le code, on ne touve nulle part le nom de la machine sur laquelle se trouve le service JNDI appelé
et son port d'écoute. C'est localhost et le port 3700 qui vont être utilisés par défaut. Si nous voulons nous adresser à un
service JNDI distant, nous aurons à changer le fichier jndi.properties.

Maintenant, que fait exactement la ligne 6 du code de la méthode [getEjbMetierRemote] ?

return (IMetierRemote) initialContext.lookup("metier.IMetierRemote");

Le résultat de cette instruction est que désormais côté client, on a un objet C capable de dialoguer avec l'Ejb metier.IMetierRemote (la
couche métier distante) et implémentant l'interface IMetierRemote :

C
Couche C S Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 Toplink]

Java SE Java EE - serveur Sun

La couche [ui] a désormais une référence sur l'objet C. Elle s'adressera à lui comme s'il implémentait la couche [metier]. En réalité,
l'objet C va transmettre tous les appels de la couche [ui] à l'objet S distant, qui lui, les transmettra à la couche [metier].

13.3.2 Tests du client console

Nous sommes désormais prêts pour tester l'application console :

• si besoin est, lancer le SGBD MySQL5 ainsi que le serveur Sun


• si besoin est, déployer la partie serveur de l'application [pam] sur le serveur Sun (cf paragraphe 13.2.5, page 91)

Introdution à Java EE
101/334
• si besoin est, initialiser la base de données MySQL5 / dbpam, par exemple avec le programme InitDB du projet Spring /
Jpa / Toplink de la page 59 ou toute autre version de InitDB.
• configurer l'exécution de la partie client [1], puis exécuter le projet [2] :

1 2

Si tout va bien, on obtient le résultat suivant dans la console :

1. ...
2. run:
3. Valeurs saisies :
4. N° de sécurité sociale de l'employé : 254104940426058
5. Nombre d'heures travaillées : 150
6. Nombre de jours travaillés : 20
7. ...
8. Salaire net : 368.77 euro
9.
10. BUILD SUCCESSFUL (total time: 3 seconds)

Maintenant, changeons les paramètres du programme console et donnons un n° SS inexistant [1], puis exécutons le projet [2] :

1 2

Les résultats dans la console sont les suivants :

1. ...
2. run:
3. EJBException, chaîne des exceptions --------------------------------------
4. javax.ejb.EJBException:nested exception is: java.rmi.ServerException: RemoteException occurred in
server thread; nested exception is:
5. java.rmi.RemoteException
6. BUILD SUCCESSFUL (total time: 2 seconds)

Il y a eu une exception. Celle-ci a son origine dans la méthode métier suivante :

1. @Stateless()
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class Metier implements IMetierLocal,IMetierRemote {
4.
5. ...
6. // obtenir la feuille de salaire
7. public FeuilleSalaire calculerFeuilleSalaire(String SS,
8. double nbHeuresTravaillées, int nbJoursTravaillés) {
9. // on récupère les informations liées à l'employé
10. Employe employe = getEmployeDao().find(SS);
11. // pas d'employé ?
12. if (employe == null) {
13. throw new PamException("L'employé de n°[" + SS + "] est introuvable",101);
14. }
15. ...

Introdution à Java EE
102/334
La ligne 13 lance une exception de type [PamException] si le n° SS n'existe pas dans la base. On peut être étonné de ne pas la
retrouver dans le texte de l'exception qui a été affiché dans la console. Revenons sur la classe [PamException] :

1. package exception;
2. public class PamException extends RuntimeException {
3. ...

La classe est d'un type dérivé de [RuntimeException]. La méthode [Metier].calculerFeuilleSalaire s'exécute dans une transaction à cause
de l'annotation @TransactionAttribute de la ligne 2 de la classe [Metier]. Lorsqu'une exception de type RuntimeException se produit, elle
est remplacée par une exception de type [EJBException] et on la perd. Assez curieusement, le phénomène n'apparaît pas avec le
conteneur JBoss Ejb3, ce qui montre que les conteneurs Ejb ne sont pas interchangeables à 100%. Pour conserver l'exception de
type [PamException] dans notre application client / serveur, il faut lui ajouter une nouvelle annotation :

1. package exception;
2.
3. import javax.ejb.ApplicationException;
4.
5. @ApplicationException(rollback=true)
6. public class PamException extends RuntimeException {
7. ...

L'annotation @ApplicationException de la ligne 5 indique qu'une exception de type [PamException] doit être considérée comme
une exception de l'application et que en tant que telle, elle doit être propagée et non remplacée par une autre exception lancée par le
conteneur Ejb. Par ailleurs, l'argument rollback=true indique que si l'exception [PamException] se produit à l'intérieur d'une
transaction gérée par le conteneur Ejb, celle-ci doit être annulée par un rollback.

Nous ajoutons cette annotation dans la classe [PamException] à la fois :


• côté serveur - il nous faut alors recompiler et redéployer l'application serveur
• côté client - il faut le recompiler

Ceci fait, l'exécution du client donne cette fois les résultats suivants dans la console :

1. ...
2. run:
3. L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable
4. BUILD SUCCESSFUL (total time: 2 seconds)

Cette fois-ci, on a bien le message qui avait été encapsulé dans l'exception de type [PamException] lancée par la couche [metier].

13.3.3 Client console - version 2

Dans la version précédente, l'environnement JNDI était configuré à partir du fichier [jndi.properties] trouvé dans l'archive [appserv-
rt.jar]. Nous avions indiqué que ce fichier ne permettait pas d'interroger un serveur JNDI autre que localhost ou travaillant sur un
port autre que le port 3700. Si on veut changer ces deux paramètres, on peut construire son propre fichier [jndi.properties] :

7 5

Dans l'onglet [Files - 1], le nouveau fichier [jndi.properties - 2]. Lorsque le projet est compilé, ce fichier est recopié [5] dans le jar [4]
produit par le build du projet dans le dossier dist [3]. Il existe maintenant deux fichiers [jndi.properties] :

Introdution à Java EE
103/334
• celui dans l'archive [dist/lib/appserv-rt.jar]
• celui dans l'archive [dist/client-metier-dao-ejb-jpa-toplink-sun.jar]
Quel sera celui qui sera utilisé par l'application ? Le fichier [jndi.properties] est cherché dans le Classpath du projet. Ce dernier est
défini dans le fichier [MANIFEST.MF] [6]. Ce fichier est généré à partir du fichier [manifest.mf] [7] qu'on trouve à la racine du
dossier du projet Netbeans. Regardons le contenu du fichier [manifest.mf] [7] :

1. Manifest-Version: 1.0
2. X-COMMENT: Main-Class will be added automatically by build

Regardons maintenant le contenu du fichier [MANIFEST.MF] [6] :

1. Manifest-Version: 1.0
2. Ant-Version: Apache Ant 1.6.5
3. Created-By: 1.5.0_12-b04 (Sun Microsystems Inc.)
4. Main-Class: ui.console.MainRemote
5. Class-Path: lib/appserv-deployment-client.jar lib/appserv-ext.jar lib/appserv-rt.jar lib/javaee.jar
6. X-COMMENT: Main-Class will be added automatically by build

Le fichier [MANIFEST.MF] a repris le contenu du fichier [manifest.mf] mais a ajouté de nouvelles lignes. Le ClassPath du projet a
ainsi été défini ligne 5. Si on le laisse tel quel, le fichier [jndi.properties] sera trouvé dans l'archive [lib/appserv-rt.jar]. Celui qui se
trouve dans l'archive [dist/client-metier-dao-ejb-jpa-toplink-sun.jar] ne sera pas exploité.

Une première solution est de définir nous-mêmes le Classpath dans le fichier [manifest.mf] [7] :

1. Manifest-Version: 1.0
2. Class-Path: client-metier-dao-ejb-jpa-toplink-sun.jar lib/appserv-deployment-client.jar lib/appserv-ext.jar lib/
3. appserv-rt.jar lib/javaee.jar
4. X-COMMENT: Main-Class will be added automatically by build

Ligne 3, nous avons ajouté l'archive jar du projet comme première archive du Classpath afin que notre fichier [jndi.properties] soit
trouvé avant celui de l'archive [lib/appserv-rt.jar]. Le fichier [MANIFEST.MF] généré lors du build du projet est alors le suivant :

1. Manifest-Version: 1.0
2. Ant-Version: Apache Ant 1.6.5
3. Created-By: 1.5.0_12-b04 (Sun Microsystems Inc.)
4. Main-Class: ui.console.MainRemote
5. Class-Path: client-metier-dao-ejb-jpa-toplink-sun.jar lib/appserv-deployment-client.jar lib/appserv-ext.jar lib/appserv-
rt.jar lib/javaee.jar
6. X-COMMENT: Main-Class will be added automatically by build

La ligne 5 de [MANIFEST.MF] reprend telle quelle la ligne 2 du fichier [manifest.mf]. Les tests montrent que cette solution
fonctionne. Une autre solution plus souple est la suivante. Le Classpath du fichier [manifest.mf] est défini comme suit :

1. Manifest-Version: 1.0
2. Class-Path: . lib/appserv-deployment-client.jar lib/appserv-ext.jar lib/appserv-rt.jar lib/javaee.jar
3. X-COMMENT: Main-Class will be added automatically by build

On inclut le répertoire courant, noté ., dans le Classpath de la ligne 2. Cette solution fonctionne et a le mérite de ne pas nous lier au
nom de l'archive du projet.

Le nouveau fichier [jndi.properties] [2] est le suivant :

1. # accès JNDI à Sun Application Server


2. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
3. java.naming.factory.url.pkgs=com.sun.enterprise.naming
4. # Required to add a javax.naming.spi.StateFactory for CosNaming that
5. # supports dynamic RMI-IIOP.
6. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
7. org.omg.CORBA.ORBInitialHost=localhost
8. org.omg.CORBA.ORBInitialPort=3700

Les lignes 7 et 8 désignent la machine du service JNDI et le port d'écoute de celui-ci.

Introdution à Java EE
104/334
Lorsqu'on exécute l'application, on peut avoir des doutes sur le fichier [jndi.properties] réellement utilisé, le nôtre ou celui contenu
dans l'archive [lib/appserv-rt.jar]. Pour le savoir, on change le port de la ligne 8 ci-dessus, en 3701, puis on compile (build) et on
exécute l'application. Les logs de la console sont les suivants :

1. run:
2. 23 sept. 2007 16:41:19 com.sun.corba.ee.impl.transport.SocketOrChannelConnectionImpl <init>
3. ATTENTION: "IOP00410201: (COMM_FAILURE) Connection failure: socketType: IIOP_CLEAR_TEXT; hostname:
localhost; port: 3701"
4. ...

La ligne 3 indique que la connexion au port 3701 a échoué. C'est donc bien notre fichier [jndi.properties] qui a été utilisé. On
remettra le port à 3700 dans [jndi.properties].

13.3.4 Client console - version 3

Dans les versions précédentes, l'environnement JNDI était configuré à partir du fichier [jndi.properties] trouvé dans le Classpath du
projet. Dans cette nouvelle version, nous utilisons Spring pour définir l'environnement JNDI.

5 6
2

• en [2], la nouvelle classe de type " Main " qui va exploiter le contenu du fichier de configuration Spring [spring-config-
ui.xml] [3].
• en [5], le fichier [manifest.mf] de la version 2 est renommé [manifest_1.mf].
• en [6], les trois archives nécessaires à Spring.

Le fichier [manifest.mf] [5] retrouve son contenu par défaut :

1. Manifest-Version: 1.0
2. X-COMMENT: Main-Class will be added automatically by build

Le fichier de configuration de Spring est le suivant :

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


2. <beans
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns="http://www.springframework.org/schema/beans"
5. xmlns:jee="http://www.springframework.org/schema/jee"
6. xsi:schemaLocation="
7. http://www.springframework.org/schema/beans
8. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
9. http://www.springframework.org/schema/jee
10. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
11.
12. <!-- métier -->
13. <jee:jndi-lookup id="metier" jndi-name="metier.IMetierRemote">
14. <jee:environment>
15. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
16. java.naming.factory.url.pkgs=com.sun.enterprise.naming
17. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
18. org.omg.CORBA.ORBInitialHost=localhost
19. org.omg.CORBA.ORBInitialPort=3700

Introdution à Java EE
105/334
20. </jee:environment>
21. </jee:jndi-lookup>
22. </beans>

Nous utilisons ici une balise <jee> (ligne 13) apparue avec Spring 2.0. L'usage de cette balise nécessite la définition du schéma
auquel elle appartient, lignes 5, 9 et 10.

• ligne 13 : la balise <jee:jndi-lookup> permet d'obtenir la référence d'un objet auprès d'un service JNDI. Ici, on associe le
bean nommé " metier " à la ressource JNDI nommée "metier.IMetierRemote". On reconnaîtra là le nom JNDI de l'Ejb de
notre couche [metier].
• le contenu du fichier [jndi.properties] utilisé dans les deux versions précédentes devient le contenu de la balise
<jee:environment> qui sert à définir les paramètres de connexion au service JNDI.

La classe [MainSpring] du projet est la suivante :

1. package ui.console;
2.
3. ...
4. public class MainSpring {
5.
6. public static void main(String[] args){
7. ...
8. // instanciation couche [metier]
9. IMetierRemote metier = getEjbMetierRemote();
10. // calcul de la feuille de salaire
11. FeuilleSalaire feuilleSalaire = null;
12. ...
13. }
14.
15. // obtenir une référence sur la couche métier
16. private static IMetierRemote getEjbMetierRemote() {
17. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-ui.xml");
18. return (IMetierRemote) ctx.getBean("metier");
19. }
20. }

Seule la méthode statique [getEjbMetierRemote] des lignes 16-19 change. La référence de type [IMetierRemote] sur la couche
[metier] est demandée à Spring. Cette solution amène de la souplesse à notre architecture. En effet, si l'Ejb de la couche [metier]
devenait local, c.a.d. exécuté dans la même Jvm que notre client [MainSpring], le code de celui-ci ne changerait pas. Seul le contenu
du fichier [spring-config-ui.xml] changerait. On retrouverait alors une configuration analogue à l'architecture Spring / Jpa étudiée
dans la partie 1 du TD.

Le lecteur est invité à tester cette nouvelle version.

13.3.5 Le client Swing

Nous construisons maintenant le client swing de notre application client / serveur Ejb :

• dans le projet en cours [1], on ajoute le client [PamJFrame] [2] obtenu par recopie à partir du projet JBoss Ejb3.

Introdution à Java EE
106/334
• on ajoute aux bibliothèques du projet [3], celles nécessaires aux interfaces swing [4], comme montré ci-dessous :

Travail pratique : en suivant l'exemple du client console précédent, modifier la façon utilisée par la méthode [doMyInit] (cf page
53) pour acquérir une référence sur la couche [metier]. On utilisera la configuration Spring du client console précédent.

13.3.6 Les tests TestNG

Nous souhaitons refaire les tests TestNG distants réalisés avec la version JBoss Ejb3. Nous voulons vérifier l'interchangeabilité des
conteneurs Ejb. S'ils sont réellement interchangeables, les tests déjà réalisés avec Jboss devraient réussir ici également. Dans le
nouveau projet, nous faisons un copier / coller des tests TestNG distants réalisés dans le conteneur JBoss Ejb3 :

2 4

• en [1], les classes de tests,


• en [2], les interfaces distantes des couches [dao]
• en [3], le fichier de configuration Spring utilisé par les tests.
• en [4], les bibliothèques nécessaires aux tests. On peut se demander s'il est bien nécessaire d'inclure les bibliothèques de
Toplink côté client, étant donné que l'implémentation Jpa / Toplink est installée côté serveur. C'est l'exécution des tests
qui nous montre leur nécessité. Nous y reviendrons lorsque nous commenterons les logs de leur exécution.

Les classes TestNG testent les versions distantes des Ejb des couches [metier] et [dao]. Nous aurons ainsi deux architectures de
tests :

Tests de la couche [metier]

Couche C Couche 4
Couche Couche Couche
[tests] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 Toplink] 6
7

Java SE Java EE - serveur Sun

Tests de la couche [dao]

Introdution à Java EE
107/334
Couche C 4
Couche Couche Couche
[tests] [dao] [JPA / [JDBC] SGBD BD
1 3 Toplink] 6
7

Java SE Java EE - serveur Sun

Le fichier [spring-ui-tests.xml] configure les Ejb distants :

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


2. <beans
3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4. xmlns="http://www.springframework.org/schema/beans"
5. xmlns:jee="http://www.springframework.org/schema/jee"
6. xsi:schemaLocation="
7. http://www.springframework.org/schema/beans
8. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
9. http://www.springframework.org/schema/jee
10. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
11.
12. <jee:jndi-lookup id="metier" jndi-name="metier.IMetierRemote">
13. <jee:environment>
14. ...
15. </jee:environment>
16. </jee:jndi-lookup>
17.
18. <jee:jndi-lookup id="cotisationDao" jndi-name="dao.ICotisationDaoRemote">
19. <jee:environment>
20. ...
21. </jee:environment>
22. </jee:jndi-lookup>
23.
24. <jee:jndi-lookup id="employeDao" jndi-name="dao.IEmployeDaoRemote">
25. <jee:environment>
26. ...
27. </jee:environment>
28. </jee:jndi-lookup>
29.
30. <jee:jndi-lookup id="indemniteDao" jndi-name="dao.IIndemniteDaoRemote">
31. <jee:environment>
32. ...
33. </jee:environment>
34. </jee:jndi-lookup>
35.
36. </beans>

L'environnement JNDI est celui utilisé dans le fichier [spring-config-ui.xml]. Il est peut-être possible d'éviter sa répétition (lignes 13-
15, 19-21, 25-27, 31-33).

Travail pratique : modifier la méthode d'initialisation de chacun des tests afin qu'elles acquièrent des références sur les Ejb distants
[metier] et [*Dao] via le fichier [spring-config-ui-tests.xml] et exécuter les tests.

Lors des tests de la couche [dao], on peut voir apparaître une exception qui dépend de la nature des bibliothèques mises dans le
Classpath du projet :

1. ----------- test10
2. 24 sept. 2007 10:27:37 com.sun.corba.ee.impl.encoding.CDRInputStream_1_0 read_value
3. ATTENTION: "IOP00810257: (MARSHAL) Could not load class
oracle.toplink.essentials.exceptions.OptimisticLockException"
4. org.omg.CORBA.MARSHAL: vmcid: SUN minor code: 257 completed: Maybe

L'exception se produit dans le code suivant :

1.@Test
2. public void test10(){
3. log("test10");
4. // supprimer un employé sans avoir la bonne version
5. ...

Introdution à Java EE
108/334
6. }catch(PamException ex){
7. ...
8. if(erreur){
9. // chaîne des exceptions
10. System.out.println("Chaîne des exceptions --------------------------------------");
11. System.out.println(th.getClass().getName());
12. while(th.getCause()!=null){
13. th=th.getCause();
14. System.out.println(th.getClass().getName());
15. }
16. }
17. }

Lignes 8-16, on affiche la chaîne des exceptions, lorsqu'une exception s'est produite côté serveur. Les logs de l'exécution du test n°
10, nous montrent l'une d'elles :

ATTENTION: "IOP00810257: (MARSHAL) Could not load class oracle.toplink.essentials.exceptions.OptimisticLockException"

L'exécution du test n° 10 se passe côté client. Les lignes 11 et 14 ci-dessus chargent la classe de l'exception pour avoir son nom. Or
côté client, la Jvm ne dispose pas de la classe oracle.toplink.essentials.exceptions.OptimisticLockException dans les bibliothèques du
Classpath du projet. D'où l'exception lancée par la Jvm du client. On la résoud en ajoutant les bibliothèques Toplink au projet.

14 Version 4 – client / serveur dans une architecture de service web


Dans cette nouvelle version, l'application [Pam] va s'exécuter en mode client / serveur dans une architecture de service web.
Revenons sur l'architecture de l'application précédente :

Couche C RMI S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 Toplink]

Java SE Java EE - serveur Sun

Ci-dessus, une couche de communication [C, RMI, S] permettait une communication transparente entre le client [ui] et la couche
distante [metier]. Nous allons utiliser une architecture analogue, où la couche de communication [C, RMI, S] sera remplacée par une
couche [C, HTTP / SOAP, S] :

Couche C S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 Toplink]
1 HTTP /
SOAP
Java SE Java EE - serveur Sun

Introdution à Java EE
109/334
Le protocole HTTP / SOAP a l'avantage sur le protocole RMI / EJB précédent d'être multi-plateformes. Ainsi le service web peut
être écrit en Java et déployé sur le serveur Sun alors que le client lui, pourrait être un client VB.NET.

14.1 La partie serveur de l'application client / serveur de type " service web "

14.1.1 Le projet Netbeans

Commençons par créer un nouveau projet Netbeans :

2 3

• en [1], nouveau projet


• en [2], choisir la catégorie Enterprise et en [3] le type EJB Module.

5
8
4

6
7

• en [4], choisir le dossier parent du dossier du projet.


• en [5], donner un nom au projet
• en [6], choisir le serveur d'application sur lequel il sera exécuté. Celui choisi ici est l'un de ceux visibles dans l'onglet
[Runtime / Servers].
• en [7], choisir la version de Java nécessaire.
• en [8], le nouveau projet.

Un service web peut être implémenté de deux façons :

• par une classe annotée @WebService qui s'exécute dans un conteneur web

Client Conteneur
Conteneur web Jpa
du Ejb3 Données
service web tcp-ip serveur Java EE

• par un Ejb annoté @WebService qui s'exécute dans un conteneur Ejb

Introdution à Java EE
110/334
Client Conteneur
Jpa / Toplink
du Ejb3 Données
service web serveur Java EE

Parce que nous avons déjà écrit une application client / serveur Ejb et que son portage vers une application client / serveur de type
service web est assez simple, nous adopterons cette deuxième solution.

Nous faisons un copier / coller des classes du projet Ejb vers le projet WebService des classes qui vont être déployées sur le serveur :

• en [1], le nouveau projet avec les sources Java importés du projet précédent ainsi que le fichier [persistence.xml] qui
configure la couche de persistance.
• en [2], la classe [Metier] qui va devenir un service web.

Dans l'architecture suivante :

Couche C S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 Toplink]
1 HTTP /
SOAP
Java SE Java EE - serveur Sun

la couche [metier] va être le service web contacté par la couche [ui]. Cette classe n'a pas besoin d'implémenter une interface. Ce sont
des annotations qui transforment un POJO (Plain Ordinary Java Object) en service web. La classe [Metier] qui implémente la
couche [metier] ci-dessus, est transformée de la façon suivante :

1. package metier;
2.
3. ...
4. @WebService
5. @Stateless()
6. @TransactionAttribute(TransactionAttributeType.REQUIRED)
7. public class Metier implements IMetierLocal,IMetierRemote {
8.
9. // références sur les couches [dao]
10. @EJB
11. private ICotisationDaoLocal cotisationDao = null;
12. @EJB
13. private IEmployeDaoLocal employeDao=null;
14. @EJB
15. private IIndemniteDaoLocal indemniteDao=null;
16.
17.
18. // obtenir la feuille de salaire
19. @WebMethod

Introdution à Java EE
111/334
20. public FeuilleSalaire calculerFeuilleSalaire(String SS,
21. ...
22. }
23.
24. // liste des employés
25. @WebMethod
26. public List<Employe> findAllEmployes() {
27. ...
28. }
29. ...
30. }

• ligne 4, l'annotation @WebService fait de la classe [Metier] un service web. Un service web expose des méthodes à ses
clients. Celles-ci doivent être annotées par l'attribut @WebMethod.
• lignes 19 et 25 : les deux méthodes de la classe [Metier] deviennent des méthodes du service web.

L'ajout de ces annotation est détecté par Netbeans qui fait alors évoluer la nature du projet :

En [1], une arborescence [Web Services] est apparue dans le projet. On y trouve le service web Metier et ses deux méthodes.
L'application serveur peut être déployée [2]. Il est nécessaire auparavant de supprimer [3] les Ejb du projet précédent pour éviter
des conflits de noms. En effet, notre nouveau projet amène avec lui les mêmes Ejb que ceux du projet précédent.

En [1], nous voyons notre application serveur déployée sur le serveur Sun.

14.2 La partie client de l'application client / serveur de type " service web "

14.2.1 Le projet Netbeans du client console

Nous créons maintenant un projet Java de base pour la partie client de l'application :

Introdution à Java EE
112/334
Une fois le projet créé, nous indiquons qu'il sera client du service web que nous venons de déployer sur le serveur Sun :

2
1

• en [1], nous sélectionnons le nouveau projet et activons le bouton [New File]


• en [2], nous indiquons que nous voulons créer un client de service web

3 4

• avec [3], nous allons désigner le projet Netbeans du service web


• dans la fenêtre [4], sont listés tous les projets ayant une branche [Web Services], ici uniquement le projet [serveur-ws-
metier-dao-jpa-toplink-sun].
• un projet peut déployer plusieurs services web. En [5], nous désignons le service auquel on veut se connecter.

6 8

• en [6], est affichée l'Url de définition du service web.


• l'Url précédente est utilisée par les outils logiciels qui génèrent la couche cliente qui va s'interfacer avec le service web.

Introdution à Java EE
113/334
Couche C S Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 Toplink]
1 HTTP /
SOAP
Java SE Java EE - serveur Sun

• la couche cliente [C] [1] qui va être générée est constituée d'un ensemble de classes Java qui vont être mises dans un même
paquetage. Le nom de celui-ci est fixé en [7].
• une fois l'assistant de création du client du service web terminé avec le bouton [Finish], la couche [C] ci-dessus est créée.

Ceci est reflété par un certain nombre de changements dans le projet :

• en [8] ci-dessus, apparaît une arborescence [Web Service References] qui liste les services web pour lesquels une couche
cliente a été générée.
Dans l'onglet [Files] du projet, un dossier [xml-resources] [9] a été créé avec une arborescence assez profonde. On y trouvera le
fichier [Metier.wsdl] qui définit le service web [Metier] :

Toujours dans l'onglet [Files], des classes Java ont été générées en [9] et compilées en [10].

Introdution à Java EE
114/334
10
9

On notera que nous retrouvons des classes qui ont été déployées côté serveur : Indemnite, Cotisation, Employe, FeuilleSalaire,
ElementsSalaire, Metier. Metier est le service web et les autres classes sont des classes nécessaires à ce service. On pourra avoir la
curiosité de consulter leur code. On verra que la définition des classes qui, instanciées, représentent des objets manipulés par le
service, consiste en la définition des champs de la classe et de leurs accesseurs ainsi qu'à l'ajout d'annotations permettant la
sérialisation de la classe en flux Xml. La classe Metier est devenue une interface avec dedans les deux méthodes qui ont été
annotées @WebMethod. Chacune de celles-ci donne naissance à deux classes, par exemple [CalculerFeuilleSalaire.java] et
[CalculerFeuilleSalaireResponse.java], où l'une encapsule l'appel à la méthode et l'autre son résultat. Enfin, la classe MetierService
est la classe qui permet au client d'avoir une référence sur le service web Metier distant :

1. @WebEndpoint(name = "MetierPort")
2. public Metier getMetierPort() {
3. return (Metier)super.getPort(new QName("http://metier/", "MetierPort"), Metier.class);
4. }

La méthode getMetierPort de la ligne 2 permet d'obtenir une référence sur le service web Metier distant.

14.2.2 Le client console du service web Metier

Il ne nous reste plus qu'à écrire le client du service web Metier. Nous recopions la classe [MainRemote] du projet Ejb précédent
dans le nouveau projet et nous la renommons [MainClientWs].

1 3 4

• en [1], la classe du client du service web


• en [2], le morceau de code où la couche [metier] est instanciée [3]. Auparavant, le code de la classe a été nettoyé :
• les instructions import ont été supprimées puis régénérées (option Fix Imports). Ceci est fait pour tenir compte par
exemple du fait que la classe FeuilleSalaire [2] a changé de paquetage.
• le code JNDI a été supprimé
• en [4], un clic droit sur le code puis le choix de l'option [Web Service Client Ressources / Call Web Service Operation]
permet de générer un code générique d'appel à une méthode de service web. Ce code générique peut être ensuite adapté
par le développeur.

Introdution à Java EE
115/334
6

• en [6], on choisit la méthode pour laquelle on veut générer un appel, et on termine l'assistant. Le code généré ressemble au
suivant :

1. try { // Call Web Service Operation


2. clientws.metier.MetierService service = new clientws.metier.MetierService();
3. clientws.metier.Metier port = service.getMetierPort();
4. // TODO initialize WS operation arguments here
5. java.lang.String arg0 = "";
6. double arg1 = 0.0;
7. int arg2 = 0;
8. // TODO process result here
9. clientws.metier.FeuilleSalaire result = port.calculerFeuilleSalaire(arg0, arg1, arg2);
10. System.out.println("Result = "+result);
11. } catch (Exception ex) {
12. // TODO handle custom exceptions here
13. }

Seules les lignes 2 et 3 nous intéressent. Elles nous montrent comment obtenir une référence sur le service web Metier. C'est tout
ce qui nous intéresse ici. Le code de la classe [MainClientWs] évolue alors comme suit :

1. // instanciation couche [metier]


2. Metier metier=new MetierService().getMetierPort();
3. // calcul de la feuille de salaire
4. FeuilleSalaire feuilleSalaire = null;
5. try {
6. feuilleSalaire = metier.calculerFeuilleSalaire(SS, nbHeuresTravaillées, nbJoursTravaillés);
7. } catch (Throwable th) {
8. // chaîne des exceptions
9. System.out.println("Chaîne des exceptions --------------------------------------");
10. System.out.println(th.getClass().getName()+":"+th.getMessage());
11. while(th.getCause()!=null){
12. th=th.getCause();
13. System.out.println(th.getClass().getName()+":"+th.getMessage());
14. }
15. return;
16. }
17. // affichage détaillé

La ligne 2 récupère une référence sur le service web Metier. Ceci fait, le code de la classe ne change pas, si ce n'est quand même
qu'en ligne 7, ce n'est pas l'exception de type [PamException] qui est gérée mais le type plus général Throwable, la classe parent de la
classe Exception. Ceci parce que les tests montrent que l'exception [PamException] n'est jamais renvoyée par le service web. Elle est
remplacée par une autre exeception que nous allons découvrir.

Nous sommes prêts pour les tests :

• s'assurer que le SGBD MySQL5 est lancé, que la base dbpam est créée et initialisée
• s'assurer que le service web est déployé sur le serveur Sun
• configurer l'exécution du client

• exécuter le client

Introdution à Java EE
116/334
Les résultats dans la console sont les suivants :

1. init:
2. deps-jar:
3. wsimport-init:
4. wsimport-client-check-Metier:
5. wsimport-client-Metier:
6. wsimport-client-generate:
7. wsimport-client-compile:
8. compile:
9. run:
10. Valeurs saisies :
11. ...
12. Salaire net : 368.77 euro
13.
14. BUILD SUCCESSFUL (total time: 1 second)

Avec la configuration suivante :

on obtient les résultats suivants :

1. run:
2. Chaîne des exceptions --------------------------------------
3. javax.xml.ws.soap.SOAPFaultException:L'employé de n°[xx] est introuvable
4. BUILD SUCCESSFUL (total time: 5 seconds)

On notera que le service web [Metier] envoie une exception de type [PamException] qui a été remplacée ici par une exception de
type [SOAPFaultException].

14.2.3 Le client swing du service web Metier

Travail à faire : porter le client swing / Ejb dans le nouveau projet afin que lui également soit client du service web déployé sur le
serveur Sun.

15 Version 5 - Application PAM deux couches - interface web / JSF


Dans cette partie, nous abordons la construction d'une interface web avec JSF (JavaServer Faces). Le lecteur ne connaissant pas
JSF trouvera en page 211, un cours présentant les bases indispensables à connaître et qui seront utilisées ici.

15.1 Architecture de l'application


L'architecture de l'application web PAM sera la suivante :

Couche Couche Couche Objets image Interface Implémentation Couche


[web / Ejb3 Ejb3 de la BD [JPA] [Hibernate] [JDBC]
jsf] [metier] [dao]

7 Sun AS 9

Introdution à Java EE
117/334
Dans cette version, le serveur Sun hébergera la totalité des couches de l'application :
• la couche [web] est hébergée par le conteneur de servlets du serveur (1 ci-dessous)
• les autres couches [metier, dao, jpa] sont hénergées par le conteneur Ejb3 du serveur (2 ci-dessous)

Conteneur web Conteneur 2 Jpa 3


Navigateur [web / jsf] Ejb3 [metier, dao] Toplink SGBD
1
HTTP serveur Java EE

Les éléments [metier, dao] de l'application s'exécutant dans le conteneur Ejb3 ont déjà été écrits dans l'application client / serveur
étudiée au paragraphe 13.2, page 85 et dont l'architecture était la suivante :

client serveur
Couche Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC]
Toplink]

Jvm1 - Java SE Jvm2 – Java EE - serveur Sun / Glassfish

Les couches [metier, dao] s'exécutaient dans le conteneur Ejb3 du serveur Sun et la couche [ui] dans une application console ou
swing sur une autre machine :

client serveur

Client Conteneur Ejb3 Jpa / Toplink Données


Java SE serveur Java EE

Dans l'architecture de la nouvelle application :

Conteneur web Conteneur 2 Jpa 3


Navigateur [web / jsf] Ejb3 [metier, dao] Toplink SGBD
1
HTTP serveur Java EE

seule la couche [web / jsf] est à écrire. Les autres couches [metier, dao, jpa] sont acquises.

Dans le document [jsf], il est montré qu'une application web où la couche web est implémentée avec JavaServer Faces a une
architecture similaire à la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSP1 [metier, dao, jpa]
4b JSP2 2c
Modèles 4a
JSPn

Introdution à Java EE
118/334
Cette architecture implémente le Design Pattern MVC (Modèle, Vue, Contrôleur). Le traitement d'une demande d'un client se
déroule de la façon suivante :

Si la demande est faite avec un GET, les deux étapes suivantes sont exécutées :

1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des
clients. C'est la porte d'entrée de l'application. C'est le C de MVC.
2. réponse - le contrôleur C demande à la page JSP choisie de s'afficher. C'est la vue, le V de MVC. La page JSP utilise un modèle
M pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. Ce modèle est une classe Java qui peut faire
appel à la couche [métier] [4a] pour fournir à la vue V les données dont elle a besoin.

Si la demande est faite avec un POST, deux étapes suppémentaires s'insèrent entre la demande et la réponse :

1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet].


2. traitement - le contrôleur C traite cette demande. En effet, une demande POST est accompagnée de données qu'il faut traiter.
Pour ce faire, le contrôleur se fait aider par des gestionnaires d'événements spécifiques à l'application écrite [2a]. Ces
gestionnaires peuvent avoir besoin de la couche métier [2b]. Le gestionnaire de l'événement peut être amené à mettre à jour
certains modèles M [2c]. Une fois la demande du client traitée, celle-ci peut appeler diverses réponses. Un exemple classique est
:
• une page d'erreurs si la demande n'a pu être traitée correctement
• une page de confirmation sinon
Le gestionnaire d'événement rend au contrôleur [Faces Servlet] un résultat de type chaîne de caractères appelée une clé de
navigation.
3. navigation - le contrôleur choisit la page JSP (= vue) à envoyer au client. Ce choix se fait à partir de la clé de navigation rendue
par le gestionnaire d'événement.
4. réponse - la page JSP choisie va envoyer la réponse au client. Elle utilise son modèle M pour initialiser ses parties dynamiques.
Ce modèle peut lui aussi faire appel à la couche [métier] [4a] pour fournir à la page JSP les données dont elle a besoin.

Dans un projet JSF :


• le contrôleur C est la servlet [javax.faces.webapp.FacesServlet]. On trouve celle-ci dans la bibliothèque [javaee.jar].
• les vues V sont implémentées par des pages JSP.
• les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing
beans".
• les règles de navigation sont définies dans le fichier [faces-config.xml]. On y trouve la liste des vues et les règles de
transition de l'une à l'autre.

15.2 Fonctionnement de l'application


Lorsque l'application est demandée la première fois, on obtient la page suivante :

On remplit alors le formulaire puis on demande le salaire :

Introdution à Java EE
119/334
B

On obtient le résultat suivant :

Cette version calcule un salaire fictif. Il ne faut pas prêter attention au contenu de la page mais à sa mise en forme. Lorsqu'on utilise
le bouton [Raz], on revient à la page [A].

Les saisies erronées sont signalées comme le montre l'exemple suivant :

Introdution à Java EE
120/334
15.3 Le projet Netbeans
Nous allons construire une première version de l'application où la couche [métier] sera simulée. Nous aurons l'architecture suivante
:

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche
Navigateur JSP1 [metier]
4b JSP2 2c simulée
Modèles 4a
JSPn

Lorsque les gestionnaires d'événement ou les modèles demanderont des données à la couche [métier] [2b, 4a], celle-ci leur donnera
des données fictives. Le but est d'obtenir une couche web répondant correctement aux sollicitations de l'utilisateur. Lorsque ceci
sera atteint, il ne nous restera qu'à installer la couche serveur développée au paragraphe 13.2, page 85 :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSP1 [metier, dao, jpa]
4b JSP2 2c
Modèles 4a
JSPn

Ce sera la version 2 de la version web notre application PAM.

Le projet Netbeans de la version 1 sera le suivant :

5
1 6

• en [1], les fichiers de configuration


• en [2], les pages Jsp et la feuille de style

Introdution à Java EE
121/334
• en [3], les classes de la couche [web]
• en [4], le fichier des messages pour l'internationalisation de l'application
• en [5], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [6], les bibliothèques de l'application

Nous passons en revue certains de ces éléments.

15.3.1 Les fichiers de configuration

Le fichier [web.xml] est celui généré par défaut par Netbeans avec de plus la configuration d'une page d'exception :

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


2. <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">
3. ...
4. <error-page>
5. <error-code>500</error-code>
6. <location>/exception.jsp</location>
7. </error-page>
8. <error-page>
9. <exception-type>java.lang.Exception</exception-type>
10. <location>/exception.jsp</location>
11. </error-page>
12. </web-app>

La page [exception.jsp] reprend la technique développée dans l'exemple du paragraphe 5.4 "Gestion des exceptions" du cours [jsf].
Son code est le suivant :

1. <%@page contentType="text/html" pageEncoding="UTF-8"%>


2. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
3. <%@taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt_rt" %>
4. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
5. "http://www.w3.org/TR/html4/loose.dtd">
6.
7. <%
8. // on change le code Http pour IE
9. response.setStatus(HttpServletResponse.SC_OK);
10. %>
11. <!--
12. <fmt_rt:setLocale value="${sessionScope['codeLocale']}"/>
13. -->
14. <fmt_rt:setBundle basename="messages"/>
15. <html>
16. <head>
17. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
18. <title>JSF</title>
19. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
20. </head>
21. <body background="<c:url value="/ressources/standard.jpg"/>">
22.
23. <h3><fmt_rt:message key="exception.header"/></h3>
24. <c:out value="${requestScope['javax.servlet.error.message']}"/>
25. </body>
26. </html>

Toute exception non explicitement gérée par le code de l'application web provoquera l'affichage de la page ci-dessus.

Le fichier [faces-config.xml] qui configure la navigation et les beans managés de l'application Jsf sera le suivant :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <managed-bean>
5. <managed-bean-name>form</managed-bean-name>
6. <managed-bean-class>web.forms.Form</managed-bean-class>
7. <managed-bean-scope>request</managed-bean-scope>
8. </managed-bean>
9. <managed-bean>
10. <managed-bean-name>locale</managed-bean-name>
11. <managed-bean-class>web.utils.ChangeLocale</managed-bean-class>
12. <managed-bean-scope>application</managed-bean-scope>

Introdution à Java EE
122/334
13. </managed-bean>
14. <application>
15. <resource-bundle>
16. <base-name>
17. messages
18. </base-name>
19. <var>msg</var>
20. </resource-bundle>
21. <message-bundle>messages</message-bundle>
22. </application>
23. </faces-config>

On notera les points suivants :

• lignes 4-8 : la classe [web.forms.Form] est le modèle de la page [form.jsp]. Sa durée de vie est la requête du client. Elle sera
accessible dans les pages Jsp via la clé form.
• lignes 9-13 : la classe [web.utils.ChangeLocale] permet de changer la langue des pages. Cette fonctionnalité n'est pas
utilisée ici mais nous écrirons l'application puisse facilement être internationalisée.
• lignes 15-20 : le fichier [messages.properties] sera utilisé pour l'internationalisation des pages. Il sera accessible dans les
pages Jsp via la clé msg.
• ligne 21 : définit le fichier [messages.properties] comme devant être exploré en priorité pour les messages d'erreur affichés
par les balises <h:messages> et <h:message>. Cela permet de redéfinir certains messages d'erreur par défaut de Jsf. Cette
possibilité n'est pas utilisée ici.

15.3.2 La feuille de style

Le fichier [styles.css] est le suivant :

1. .libelle{
2. background-color: #ccffff;
3. font-family: 'Times New Roman',Times,serif;
4. font-size: 14px;
5. font-weight: bold
6. }
7. body{
8. background-color: #ffccff
9. }
10.
11. .error{
12. color: #ff3333
13. }
14.
15. .info{
16. background-color: #99cc00
17. }
18.
19. .titreInfos{
20. background-color: #ffcc00
21. }

Voici des exemples de code Jsp utilisant ces styles :

<h:outputText value="#{msg['form.infos.employé']}"
styleClass="titreInfos"/>

<h:panelGrid columns="3" rowClasses="libelle,info">

<h:message for="heuresTravaillées" styleClass="error"/>

15.3.3 Le fichier des messages

Le fichier des messages [messages_fr.properties] est le suivant :

Introdution à Java EE
123/334
1. form.titre=Feuille de salaire
2. form.comboEmployes.libellé=Employé
3. form.heuresTravaillées.libellé=Heures travaillées
4. form.joursTravaillés.libellé=Jours travaillés
5. form.heuresTravaillées.required=Indiquez le nombre d'heures travaillées
6. form.heuresTravaillées.validation=Donnée incorrecte
7. form.joursTravaillés.required=Indiquez le nombre de jours travaillés
8. form.joursTravaillés.validation=Donnée incorrecte
9. form.btnSalaire.libellé=Salaire
10. form.btnRaz.libellé=Raz
11. exception.header=L'exception suivante s'est produite :
12. form.infos.employé=Informations Employé
13. form.employe.nom=Nom
14. form.employe.prénom=Prénom
15. form.employe.adresse=Adresse
16. form.employe.ville=Ville
17. form.employe.codePostal=Code postal
18. form.employe.indice=Indice
19. form.infos.cotisations=Informations Cotisations sociales
20. form.cotisations.csgrds=CSGRDS
21. form.cotisations.csgd=CSGD
22. form.cotisations.retraite=Retraite
23. form.cotisations.secu=Sécurité sociale
24. form.infos.indemnites=Informations Indemnités
25. form.indemnites.salaireHoraire=Salaire horaire
26. form.indemnites.entretienJour=Entretien / Jour
27. form.indemnites.repasJour=Repas / Jour
28. form.indemnites.congésPayés=Congés payés
29. form.infos.salaire=Informations Salaire
30. form.salaire.base=Salaire de base
31. form.salaire.cotisationsSociales=Cotisations sociales
32. form.salaire.entretien=Indemnités d'entretien
33. form.salaire.repas=Indemnités de repas
34. form.salaire.net=Salaire net

Ces messages sont tous utilisés dans la page [form.jsp] à l'exception de celui de la ligne 11 utilisé dans la page [exception.jsp].

15.3.4 La couche [métier]

La couche [métier] implémente l'interface IMetierLocal suivante :

1. package metier;
2.
3. import java.util.List;
4. import javax.ejb.Local;
5. import jpa.Employe;
6.
7. @Local
8. public interface IMetierLocal {
9. // obtenir la feuille de salaire
10. FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
11. // liste des employés
12. List<Employe> findAllEmployes();
13. }

Cette interface est celle utilisée dans la partie serveur de l'application client / serveur décrite au paragraphe 13.2, page 85.

La classe Metier que nous allons utiliser pour tester la couche [web] implémente cette interface de la façon suivante :

1. package metier;
2.
3. ...
4. public class Metier implements IMetierLocal {
5.
6. // dictionnaire des employes indexé par le n° SS
7. private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
8. // liste des employés
9. private List<Employe> listEmployes;
10.
11. // obtenir la feuille de salaire
12. public FeuilleSalaire calculerFeuilleSalaire(String SS,
13. double nbHeuresTravaillées, int nbJoursTravaillés) {
14. // on récupère l'employé de n° SS
15. Employe e=hashEmployes.get(SS);
16. // on rend une feuille de salaire fiictive
17. return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),e.getIndemnite(),new
ElementsSalaire(100,100,100,100,100));

Introdution à Java EE
124/334
18. }
19.
20. // liste des employés
21. public List<Employe> findAllEmployes() {
22. if(listEmployes==null){
23. // on crée une liste de deux employés
24. listEmployes=new ArrayList<Employe>();
25. listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St
Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
26. listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",new Indemnite(1,1.93,2,3,12)));
27. // dictionnaire des employes indexé par le n° SS
28. for(Employe e:listEmployes){
29. hashEmployes.put(e.getSS(),e);
30. }
31. }
32. // on rend la liste des employés
33. return listEmployes;
34. }
35. }

Nous laissons au lecteur le soin de décrypter ce code. On notera la méthode utilisée : afin de ne pas avoir à mettre en place la partie
Ejb de l'application, nous simulons la couche [métier]. Lorsque la couche [web] sera déclarée correcte, nous pourrons alors la
remplacer par la véritable couche [métier].

15.4 Le formulaire [form.jsp] et son modèle [Form.java]


Nous construisons maintenant la page Jsp du formulaire ainsi que son modèle.

Lectures conseillées :
• exemple n° 3 (intro-03), page 246, pour la listes des balises utilisables dans un formulaire
• exemple n° 4 (intro-04), page 280, pour les listes déroulantes remplies par le modèle
• exemple n° 6 (intro-06), page 300, pour la validation des saisies
• exemple n° 7 (intro-07), page 321, pour la gestion du bouton [Raz]

15.4.1 étape 1

Question : Construire le formulaire [form.jsp] et son modèle [Form.java] nécessaires pour obtenir la page suivante :

4 5
2 3
1

Les composants de saisie sont les suivants :

n° type Jsf modèle


id rôle
1
comboEmployes <h:selectOneMenu> String comboEmployesValue contient la liste des employés sous la forme
SelectItem[] getEmployesItems() "prénom nom".
2
heuresTravaillées <h:inputText> String heuresTravaillées nombre d'heures travaillées - nombre réel
3
joursTravaillés <h:inputText> String joursTravaillés nombre de jours travaillés - nombre entier
4 <h:commandButton>
btnSalaire lance le calcul du salaire
5 <h:commandButton>
btnRaz remet le formulaire dans son état premier

Introdution à Java EE
125/334
• la méthode getEmployesItems rendra une liste d'employés qu'elle obtiendra auprès de la couche [métier]. Les objets de type
SelectItem construits par la méthode getEmployesItems auront pour attribut value, le n° SS de l'employé et pour attribut label,
une chaîne constituée du prénom et du nom de l'employé.
• les boutons [Salaire] et [Raz] ne seront pour l'instant pas connectés à des gestionnaires d'événement.
• la validité des saisies sera vérifiée.

Testez cette version. Vérifiez notamment que les erreurs de saisie sont bien signalées.

15.4.2 étape 2

Question : compléter le formulaire [form.jsp] et son modèle [Form.java] nécessaires pour obtenir la page suivante une fois que le
bouton [Salaire] a été cliqué :

Le bouton [Salaire] sera connecté au gestionnaire d'événement calculerSalaire du modèle. Cette méthode utilisera la méthode
calculerFeuilleSalaire de la couche [métier]. Cette feuille de salaire sera faite pour l'employé sélectionné en [1].

Dans le modèle, la feuille de salaire sera représentée par le champ privé suivant :

private FeuilleSalaire feuilleSalaire;

disposant des méthodes get et set.

Pour obtenir les informations contenues dans cet objet, on pourra écrire dans la page Jsp, des expressions comme la suivante :

<h:outputText value="#{form.feuilleSalaire.employe.nom}"/>

L'expression de l'attribut value sera évaluée comme suit :

[form].getFeuilleSalaire().getEmploye().getNom() où [form] représente une instance de la classe [Form.java]. Le lecteur pourra vérifier que
les méthodes get utilisées ici existent bien respectivement dans les classes [Form], [FeuilleSalaire] et [Employe]. Si ce n'était pas le
cas, une exception serait lancée lors de l'évaluation de l'expression.

Testez cette nouvelle version.

Introdution à Java EE
126/334
15.4.3 étape 3

Question : compléter le formulaire [form.jsp] et son modèle [Form.java] pour obtenir les informations supplémentaires suivantes :

On suivra la même démarche que précédemment. Il y a une difficulté pour le signe monétaire euro que l'on a en [1] par exemple.
Dans le cadre d'une application internationalisée, il serait préférable d'avoir le format d'affichage et le signe monétaire de la locale
utilisée (en, de, fr, ...). Cela peut s'obtenir de la façon suivante :

1. <h:outputFormat value="{0,number,currency}">
2. <f:param value="#{form.feuilleSalaire.indemnite.entretienJour}"/>
3. </h:outputFormat>

On aurait pu écrire :

<h:outputText value="#{form.feuilleSalaire.indemnite.entretienJour} є">

mais avec la locale en_GB (Anglais GB) on continuerait à avoir un affichage en euros alors qu'il faudrait utiliser la livre £. La balise
<h:outputFormat> permet d'afficher des informations en fonction de la locale de la page Jsp affichée :

• ligne 1 : affiche le paramètre {0} qui est un nombre (number) représentant une somme d'argent (currency)*
• ligne 2 : la balise <f:param> donne une valeur au paramètre {0}. Une deuxième balise <f:param> donnerait une valeur
au paramètre noté {1} et ainsi de suite.

15.4.4 étape 4

Lectures conseillées : exemple n° 7 (intro-07), paragraphe 1.7.5, page 326.

Question : compléter le formulaire [form.jsp] et son modèle [Form.java] pour gérer le bouton [Raz].

Le bouton [Raz] ramène le formulaire dans l'état qu'il avait lorsqu'on l'a demandé la première fois par un GET. Il y a plusieurs
difficultés ici. Certaines ont été expliquées dans [jsf] (voir lectures conseillées).

Le formulaire rendu par le bouton [Raz] n'est pas tout le formulaire mais seulement la partie saisie de celui-ci :

Introdution à Java EE
127/334
Ce résultat peut être obtenu avec une balise <f:subview> utilisée de la façon suivante :

1. <f:subview id="viewInfos" rendered="#{form.viewInfosIsRendered}">


2. ... la partie du formulaire qu'on veut pouvoir ne pas afficher
3. </f:subview>

La balise <f:subview> encadre toute la partie du formulaire qui est susceptible d'être affichée ou cachée. Tout composant peut être
affiché ou caché grâce à l'attribut rendered. Si rendered="true", le composant est affiché, si rendered="false", il ne l'est pas. Si l'attribut
rendered prend sa valeur dans le modèle, alors l'affichage du composant peut être contrôlé par programme.

Ci-dessus, on contrôlera l'affichage de la vue viewInfos avec le champ suivant :

private boolean viewInfosIsRendered;

accompagné de ses méthodes get et set. Les méthodes gérant les clics sur les bouton [Salaire] [Raz] mettront à jour ce booléen selon
que la vue viewInfos doit être affichée ou non.

16 Version 6 - Intégration de la couche web dans une architecture 3


couches Jsf / Ejb

16.1 Architecture de l'application


L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche
Navigateur JSP1 [metier]
4b JSP2 2c simulée
Modèles 4a
JSPn

Nous remplaçons la couche [métier] simulée, par les couches [métier, dao, jpa] implémentées par des Ejb au paragraphe 13.2, page
85 :

Introdution à Java EE
128/334
Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couches Données
Navigateur JSP1 [metier, dao, jpa]
4b JSP2 2c
Modèles 4a
JSPn

16.2 Le projet Netbeans de la couche web


Le projet Netbeans de la version web n° 2 est obtenue par copie du projet précédent :

Nous avons très peu de modifications à faire pour adapter cette couche web à son nouvel environnement : la couche [metier]
simulée doit être remplacée par la couche [metier, dao, jpa] du serveur construit au paragraphe 13.2, page 85. Pour cela, nous
faisons deux choses :

• nous supprimons les paquetages [exception, metier, jpa] qui étaient présents dans le précédent projet.
• nous ajoutons aux bibliothèques du projet web, l'archive jar du serveur construit au paragraphe 13.2, page 85. Cette
archive est trouvée dans le dossier [dist] du projet Netbeans du serveur :

1
4
2

Introdution à Java EE
129/334
• en [1], ajout d'une bibliothèque
• en [2], choix de l'archive des couches [metier, dao, jpa] du serveur
• en [3], l'archive a été ajoutée aux bibliothèques
• en [4], on a supprimé les paquetages [exception, metier, jpa] du précédent projet. Ceux-ci sont désormais trouvés dans la
bibliothèque [serveur-metier-dao-ejb-jpa-toplink-sun.jar] [5] :

Il nous faut également modifier le code du bean [Form.java] :

1.public class Form {


2.
3. public Form() {
4. }
5.
6. // couche métier
7. private IMetierLocal metier=new Metier();
8.
9. // champs du formulaire
10....

La ligne 7 instanciait la couche [métier] simulée. Désormais elle doit référencer la couche [métier] réelle. Le code précédent devient
le suivant :

1. public class Form {


2.
3. public Form() {
4. }
5.
6. // couche métier
7. @EJB
8. private IMetierLocal metier;
9.
10. // champs du formulaire

Ligne 7, l'annotation @EJB indique au conteneur de servlets qui va exécuter la couche web, d'injecter dans le champ metier de la
couche 8, l'Ejb qui implémente l'interface locale IMetierLocal.

Pourquoi l'interface locale IMetierLocal plutôt que l'interface IMetierRemote ? Parce que la couche web et la couche Ejb s'exécutent
dans la même Jvm :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche Données
Navigateur JSP1 [metier, dao, jpa]
4b JSP2 2c
Modèles 4a
JSPn

Conteneur de servlets Conteneur Ejb

Les classes du conteneur de servlets peuvent référencer directement les classes Ejb du conteneur Ejb.

C'est tout. Notre couche web est prête. La transformation a été simple parce qu'on avait pris soin de simuler la couche [métier] par
une classe qui respectait l'interface IMetierLocal implémentée par la couche [métier] réelle.

Introdution à Java EE
130/334
16.3 Le projet Netbeans de l'application d'entreprise
Une application d'entreprise permet le déploiement simultané sur un serveur d'application, de la couche [web] et de la couche [ejb]
d'une application, respectivement dans le conteneur de servlets et dans le conteneur Ejb.

Nous procédons de la façon suivante :

3
2
1

• en [1], on crée un nouveau projet


• en [2], on choisit la catégorie [Enterprise]
• en [3], on choisit le type [ Enterprise Application]

• en [4], on donne un nom au projet


• en [5], un projet d'entreprise peut comprendre jusqu'à trois types de modules :
• un module Ejb
• un module web
• un module dit client
On peut demander en même temps que la création du projet d'entreprise, la création de ces trois modules qui seront vides
au départ. Un projet d'entreprise ne sert qu'au déploiement des modules qui en font partie. En-dehors de ça, c'est une
coquille vide. Ici, nous voulons déployer :
• un module web existant (pam-jsf). Il est donc inutile de créer un nouveau module web.
• un module Ejb existant non pas sous forme de projet mais sous forme d'une archive jar : [serveur-metier-dao-ejb-
jpa-toplink-sun.jar]. Là également, il est inutile d'en créer un nouveau.
En [5], nous créons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultérieurement.
• en [6], le projet d'eentreprise. Sa branche [Java EE modules] est vide.

Nous ajoutons le module web au projet d'entreprise :

Introdution à Java EE
131/334
1
2
3

• en [1], ajout d'un nouveau module


• en [2], choix du projet web [pam-jsf-ejb]
• en [3], le projet d'entreprise a un nouveau module

Ceci-fait, construisons le binaire de l'application d'entreprise :

1 4

5
3

• en [1], construction du projet


• en [2], dans l'onglet [Files], le dossier [dist] contient l'archive [ea-pam-jsf-ejb.ear] du projet. On notera son suffixe
particulier : ear [Enterprise ARchive].
• en [3] : [pam-jsf-ejb.war] est l'archive du projet web [pam-jsf-ejb]. Le suffixe war signifie Web ARchive. On trouve
également en [3], l'archive [serveur-metier-dao-ejb-jpa-toplink-sun.jar]. Elle a été encapsulée dans l'archive EAR avec
l'archive du projet web parce que celui-ci l'avait incluse dans ses bibliothèques (Libraries).

En [4], on trouve deux fichiers de configuration :


• [application.xml] : un fichier de déploiement standard pour une application d'entreprise
• [sun-application.xml] : un fichier de déploiement spécifique au serveur Sun

Ces fichiers sont des copies des fichiers de la branche [Configuration Files] du projet en [5].

Regardons le contenu du fichier de déploiement [application.xml] :

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


2. <application version="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/application_5.xsd">
3. <display-name>ea-pam-jsf-ejb</display-name>
4. <module>
5. <web>
6. <web-uri>pam-jsf-ejb.war</web-uri>
7. <context-root>/pam-jsf-ejb</context-root>
8. </web>
9. </module>
10. </application>

• ligne 3, le nom d'affichage de l'application d'entreprise


• lignes 5-9 : description du module web
• ligne 6 : l'archive dans laquelle sera trouvée le projet web dans l'archive EAR de l'application d'entreprise
• ligne 7 : le nom (contexte) via lequel le projet web sera disponible aux navigateurs [http://machine:port/pam-jsf-ejb]

Une application d'entreprise peut être limitée à un module web comme ci-dessus. Dans le cas de notre application, ce n'est pas le
cas : la couche [web] référence des Ejb. Il faut donc déployer également le module Ejb de l'archive [serveur-metier-dao-ejb-jpa-

Introdution à Java EE
132/334
toplink-sun.jar]. Si le module Ejb avait été disponible sous la forme d'un projet Ejb, nous l'aurions ajouté à l'application d'entreprise
[ea-pam-jsf-ejb] de la même façon qu'on a ajouté le module web (le lecteur n'a pas à faire cette opération - une autre méthode va lui
être proposée) :

Le fichier [application.xml] aurait été alors le suivant :

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


2. <application version="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/application_5.xsd">
3. <display-name>ea-pam-jsf-ejb</display-name>
4. <module>
5. <ejb>serveur-metier-dao-ejb-jpa-toplink-sun.jar</ejb>
6. </module>
7. <module>
8. <web>
9. <web-uri>pam-jsf-ejb.war</web-uri>
10. <context-root>/pam-jsf-ejb</context-root>
11. </web>
12. </module>
13. </application>

En lignes 4-6, le module Ejb est déclaré via son archive. Celle-ci sera recherchée dans l'archive EAR de l'application d'entreprise.
Rappelons le contenu actuel de celle-ci :

En [1], l'archive de la couche Ejb est déjà présente car la couche [web] la référençait. Ici, nous voulons utiliser l'archive plutôt que le
projet Netbeans. Nous pouvons nous contenter d'ajouter les lignes manquantes (4 à 6) au fichier [application.xml] actuel pour qu'il
devienne celui décrit ci-dessus. Le lecteur est invité à faire cette modification du fichier [application.xml].

Pour créer la source de données jdbc/dbpam, on suivra la méthode décrite au paragraphe 13.2.3, pages 87 - 90 en créant un
nouveau projet Ejb vide, dont le seul rôle sera de créer la source de données [jdbc/dbpam]. Le pool de connexion mysqlpool pourra
par ailleurs être configuré comme suit :

1 2

• en [1], dans l'onglet [Runtime], on accède aux propriétés du pool de connexions mysqlpool

Introdution à Java EE
133/334
• en [2], on met à true, la propriété IsConnectionValidationRequired. Avec un pool de connexions, les connexions au SGBD sont
ouvertes au démarrage du pool. Une fois les connexions ouvertes, elles ne sont par défaut plus vérifiées. Si le SGBD
"tombe" les connexions ouvertes au démarrage du pool deviennent inutilisables. Si l'administrateur système "remonte" le
SGBD, ces mêmes connexions restent toujours inutilisables. L'attribut IsConnectionValidationRequired=true demande au pool
de vérifier ses connexions. Lorsqu'il trouve qu'une connexion est "tombée", il doit recréer une nouvelle connexion au
SGBD. Si celui-ci a été remonté, de nouvelles connexions peuvent ainsi être recréées. Si l'attribut
IsConnectionValidationRequired a la valeur false et que le SGBD "tombe", l'application web devient inutilisable même si le
SGBD est remonté par la suite. Il faut alors relancer l'application web elle-même.

Ceci fait, nous sommes prêts à déployer l'application d'entreprise [ea-pam-jsf-ejb]. Pour suivre ce déploiement, nous créons une
connexion avec la base MySQL5 / dbpam dans l'onglet [Runtime] comme il a été expliqué à diverses reprises, notamment page 19.

4
3

2
5
1

On prendra soin de supprimer toutes les tables avant le déploiement afin de partir d'un état connu.

On pourra également regarder les propriétés du projet [ea-pam-jsf-ejb] [clic droit sur projet / Properties]. Dans les propriétés Run
[2], on trouve :
• en [3], le nom du serveur sur lequel l'application va être déployée
• en [4], l'application qui sera lancée si on exécute le projet par [Run Project]. Ici ce sera l'application web qui sera exécutée.
Un navigateur sera ouvert [5] et l'Url [http://localhost:8080/pam-jsf-ejb] demandée (cf application.xml).

Ceci fait, nous lançons le serveur Sun si besoin est. La couche serveur [métier, dao, jpa] implémentée par des Ejb et décrite au
paragraphe 13.2, page 85, utilise une source de données de nom JNDI jdbc/dbpam. Si nous déployons notre couche serveur sans
que cette source de données existe, alors le déploiement échouera. Pour créer cette source, on pourra suivre la méthode décrite aux
paragraphes 13.2.3, page 87 et 13.2.4 page 90 en créant un module Ejb vide et sa couche de persistance.

Ceci fait, nous pouvons déployer l'application d'entreprise [ea-pam-jsf-ejb] :

Introdution à Java EE
134/334
1
3
2

• en [1], le serveur Sun est lancé


• en [2], l'application d'entreprise est déployée
• en [3], l'application d'entreprise [ea-pam-jsf-ejb] a bien été déployée. On voit son module web symbolisée par son contexte
[/pam-jsf-ejb] et son module Ejb symbolisé par son archive [serveur-metier-dao-ejb-jpa-toplink-sun.jar]

Maintenant, regardons la connexion [Runtime / Databases / dbpam] :

En [1], les tables de la base dbpam ont été créées. On rappelle que cela est dû à la configuration [persistence.xml] de la couche [jpa]
:

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="serveur-metier-dao-ejb-jpa-toplink-sunPU" transaction-type="JTA">
4. <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
5. <jta-data-source>jdbc/dbpam</jta-data-source>
6. <properties>
7. <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
8. </properties>
9. </persistence-unit>
10. </persistence>

La ligne 7 ci-dessus demande à Toplink de créer les tables.

Ces tables sont créées mais elles sont vides. Pour tester notre application web, il faut y mettre quelques données. On peut pour cela
utiliser le programme InitDB décrit au paragraphe 11.3.1, page 59.

Introdution à Java EE
135/334
Ceci fait, ouvrons un navigateur et demandons l'url [http://localhost:8080/pam-esf-ejb] :

• en [1], l'Url affichée après la redirection faite par [index.jsp]


• en [2], la liste des employés a été remplie avec les éléments de la table [Employes] de la base dbpam.

Le lecteur est invité à refaire les tests de la version web n° 1. Voici un exemple d'exécution :

Introdution à Java EE
136/334
17 Version 7 - Application web PAM multi-vues / mono-page
Nous revenons ici à l'architecture initiale où la couche [métier] était simulée. Nous savons désormais que celle-ci peut être aisément
remplacée par la couche [métier] réelle. La couche [métier] simulée facilite les tests.

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaires
3 d'évts couche
Navigateur JSP1 [metier]
4b JSP2 2c simulée
Modèles 4a
JSPn

Une application JSF est de type MVC (Modèle Vue Contrôleur) :


• la servlet [Faces Servlet] est le contrôleur générique fourni par JSF. Ce contrôleur est étendu par les gestionnaires
d'événements spécifiques à l'application. Les gestionnaires d'événements rencontrés jusqu'ici étaient des méthodes des
classes servant de modèles aux pages JSP

Introdution à Java EE
137/334
• les pages JSP envoient les réponses au navigateur client. Ce sont les vues de l'application.
• les pages JSP comportent des éléments dynamiques qu'on appelle le modèle de la page. On rappelle que pour certains
auteurs, le modèle recouvre les entités manipulées par l'application, telles par exemple les classes FeuilleSalaire ou Employe.
Pour distinguer ces deux modèles, on pourra parler de modèle de l'application et modèle d'une page JSP.

Dans l'architecture JSF, le passage d'une page JSPi à une page JSPj est un peu problématique.
• la page JSPi a été affichée. A partir de cette page, l'utilisateur provoque un POST par un événement quelconque [1]
• en JSF, ce POST sera traité [2a,2b] en général par une méthode C du modèle M i de la page JSPi. On peut dire que la
méthode C est un contrôleur secondaire.
• si à l'issue de cette méthode, la page JSPj doit être affichée, le contrôleur C doit :
1. mettre à jour [2c] le modèle Mj de la page JSPj
2. rendre [2a] au contrôleur principal, la clé de navigation qui permettra l'affichage de la page JSPj
L'étape 1 nécessite que le modèle Mi de la page JSPi ait une référence sur modèle Mj de la page JSPj. Cela complique un
peu les choses rendant les modèles Mi dépendant les uns des autres. En effet, le gestionnaire C du modèle Mi qui met à
jour le modèle Mj doit connaître celui-ci. Si on est amené à changer le modèle M j, on sera alors amené à changer le
gestionnaire C du modèle Mi.
Il existe un cas où la dépendance des modèles entre-eux peut être évitée : celui où il y a un unique modèle M qui sert à
toutes les pages JSP. Cette architecture n'est utilisable que dans les applications n'ayant que quelques vues mais elle se
révèle alors très simple d'usage. C'est celle que nous utilisons maintenant.

Nous nous plaçons dans le contexte suivant :


• les vues V seront générées à partir d'une unique page JSF par le mécanisme des vues internes <f:subView>.
• une unique classe servira de modèle M à la page JSF et de contrôleur C qui traitera les événements des vues V avec des
méthodes internes à la classe.

Dans ce contexte, l'architecture de l'application est la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
V1 [metier]
4 [V] Modèle M
V2 JSP Gestionnaires
simulée
form d'évts
Vn

Pour la demande GET initiale :


• [Faces Servlet] reçoit une demande GET d'affichage de la page [form.jsp] en [1]
• il fait afficher celle-ci en [3]. La page utilise son modèle / contrôleur [Form.java] pour initialiser ses parties dynamiques.
En [4], une vue Vi parmi d'autres est affichée, les autres restant cachées.

Pour les demandes POST qui suivent :


• [Faces Servlet] reçoit une demande POST pour la page [form.jsp] en [1]
• l'événement qui a provoqué le POST est traité par l'une des méthodes du modèle / contrôleur [Form.java] [2a, 2b]. Celle-
ci met à jour le modèle M afin qu'une vue Vi particulière soit affichée et les autres cachées. La méthode rend comme clé
de navigation la clé null.
• [Faces Servlet] ayant reçu la clé null, fait afficher de nouveau [form.jsp] en [3]. La page utilise son modèle [Form.java]
pour initialiser ses parties dynamiques. En [4], la vue Vi est affichée, les autres restant cachées.

17.1 Les vues de l'application


Les différentes vues présentées à l'utilisateur seront les suivantes :

- la vue [VueSaisies] qui présente le formulaire de simulation

Introdution à Java EE
138/334
- la vue [VueSimulation] utilisée pour afficher le résultat détaillé de la simulation :

- la vue [VueSimulations] qui donne la liste des simulations faites par le client

- la vue [VueSimulationsVides] qui indique que le client n'a pas ou plus de simulations :

Introdution à Java EE
139/334
– la vue [VueErreur] qui indique une ou plusieurs erreurs :

17.2 Le projet Netbeans de la couche [web]


Le projet Netbeans de cette version sera le suivant :

1 6

• en [1], les fichiers de configuration


• en [2], les pages Jsp et la feuille de style
• en [3], les classes de la couche [web]
• en [4], le fichier des messages pour l'internationalisation de l'application
• en [5], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [6], les bibliothèques de l'application

Nous passons en revue certains de ces éléments.

17.2.1 Les fichiers de configuration

Le fichier [web.xml] est celui des versions précédentes. Le fichier [faces-config.xml] évolue de la façon suivante :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">

Introdution à Java EE
140/334
4. <managed-bean>
5. <managed-bean-name>applicationData</managed-bean-name>
6. <managed-bean-class>web.beans.application.ApplicationData</managed-bean-class>
7. <managed-bean-scope>application</managed-bean-scope>
8. </managed-bean>
9. <managed-bean>
10. <managed-bean-name>form</managed-bean-name>
11. <managed-bean-class>web.beans.session.Form</managed-bean-class>
12. <managed-bean-scope>session</managed-bean-scope>
13. <managed-property>
14. <property-name>applicationData</property-name>
15. <value>#{applicationData}</value>
16. </managed-property>
17. </managed-bean>
18. <managed-bean>
19. <managed-bean-name>locale</managed-bean-name>
20. <managed-bean-class>web.utils.ChangeLocale</managed-bean-class>
21. <managed-bean-scope>application</managed-bean-scope>
22. </managed-bean>
23. <application>
24. <resource-bundle>
25. <base-name>
26. messages
27. </base-name>
28. <var>msg</var>
29. </resource-bundle>
30. <message-bundle>messages</message-bundle>
31. </application>
32. </faces-config>

Les lignes 4-8 déclarent un bean nommé applicationData (ligne 5) de portée application (ligne 7). Nous mettrons dans ce bean,
les données qui peuvent être partagées par toutes les requêtes de tous les navigateurs clients. Dans cette application, il s'agit de la
liste des employés. Dans la version précédente, cette liste était maintenue dans le bean form (ligne 10), de portée session (ligne 12).
Elle était obtenue en début de session par appel à la méthode [metier].findAllEmployes().
• dans la chaîne de couches [métier, dao] l'une des couches peut mémoriser la liste des employés en mémoire une fois
qu'elle a été obtenue depuis la base de données. La méthode [metier].findAllEmployes() ne rend alors qu'une référence sur cet
emplacement mémoire. Les beans form des différentes sessions utilisateurs ont alors une référence de plus sur ce même
emplacement.
• si aucune des couches [métier, dao] n'a mémorisé la liste des employés, la méthode [metier].findAllEmployes() fait alors à
chaque fois qu'elle est exécutée un accès à la base de données. On a alors un accès par session utilisateur. Si par ailleurs,
chaque bean form mémorise la liste obtenue, on a, à un moment donné, la liste des employés présente n fois en mémoire.
On a alors des performances dégradées vis à vis de la solution précédente.

La couche [web] ne sait pas nécessairement comment la méthode [metier].findAllEmployes() obtient la liste des employés. Elle
cherchera à optimiser les performances quelque soit la solution adoptée par les couches [métier, dao], mémorisation ou non de la
liste des employés. Aussi il est de l'intérêt de la couche [web] de mémoriser elle-même cette liste et de la partager entre toutes les
sessions utilisateurs. Ce sera fait dans le bean applicationData de portée application.
• si cette liste d'employés est déjà mémorisée par l'une des couches [métier,dao], le bean applicationData ne fera qu'avoir
une référence de plus sur la liste. La liste n'est pas dupliquée.
• si cette liste d'employés n'était mémorisée par aucune des couches, alors la liste temporaire créée par la méthode
[metier].finAllEmployes reste en mémoire parce que le bean applicationData garde une référence dessus.

Les lignes 9-17 de [faces-config.xml] définissent le bean form (ligne 10) de portée session (ligne 12). Ce bean sera peu différent de
ce qu'il était dans les versions précédentes. C'est lui notamment qui alimente la liste déroulante des employés dans le formulaire.
Aussi a-t-il toujours besoin de la liste des employés. Celle-ci étant désormais maintenue dans le bean nommé applicationData
(ligne 5), le bean form devra avoir une référence sur ce bean. C'est ce qui sera fait dans [Form.java] :

1.package web.beans.session;
2.
3....
4.public class Form {
5.
6. // autres beans
7. private ApplicationData applicationData;
8.
9. // métier
10. private IMetierLocal metier=new Metier();;
11.
12. // les vues
13....

Ligne 7, le bean [Form] a une référence sur le bean [ApplicationData]. Cette référence sera initialisée par injection d'une référence
opérée par le conteneur de servlets grâce à la configuration du bean [Form] faite dans [faces-config.xml] :

Introdution à Java EE
141/334
1. <managed-bean>
2. <managed-bean-name>applicationData</managed-bean-name>
3. <managed-bean-class>web.beans.application.ApplicationData</managed-bean-class>
4. <managed-bean-scope>application</managed-bean-scope>
5. </managed-bean>
6. <managed-bean>
7. <managed-bean-name>form</managed-bean-name>
8. <managed-bean-class>web.beans.session.Form</managed-bean-class>
9. <managed-bean-scope>session</managed-bean-scope>
10. <managed-property>
11. <property-name>applicationData</property-name>
12. <value>#{applicationData}</value>
13. </managed-property>
14.</managed-bean>

• lignes 10-13 : initialisation d'une propriété du bean [Form]


• ligne 11 : nom de la propriété – sera accédée via getApplicationData et setApplicationData
• ligne 12 : valeur à injecter dans la propriété – la notation #{applicationData} désigne le bean défini ligne 2.

17.2.2 La feuille de style

Le fichier [styles.css] définit quelques styles supplémentaires pour la vue [VueSimulations] et la vue [VueErreur] :

1. .simulationsHeader {
2. text-align: center;
3. font-style: italic;
4. color: Snow;
5. background: Teal;
6. }
7.
8. .simuNum {
9. height: 25px;
10. text-align: center;
11. background: MediumTurquoise;
12. }
13. .simuNom {
14. text-align: left;
15. background: PowderBlue;
16. }
17. .simuPrenom {
18. width: 6em;
19. text-align: left;
20. color: Black;
21. background: MediumTurquoise;
22. }
23. .simuHT {
24. width: 3em;
25. text-align: center;
26. color: Black;
27. background: PowderBlue;
28. }
29. .simuJT {
30. width: 3em;
31. text-align: center;
32. color: Black;
33. background: MediumTurquoise;
34. }
35. .simuSalaireBase {
36. width: 9em;
37. text-align: center;
38. color: Black;
39. background: PowderBlue;
40. }
41. .simuIndemnites {
42. width: 3em;
43. text-align: center;
44. color: Black;
45. background: MediumTurquoise;
46. }
47. .simuCotisationsSociales {
48. width: 6em;
49. text-align: center;
50. background: PowderBlue;
51. }
52.
53. .simuSalaireNet {
54. width: 10em;

Introdution à Java EE
142/334
55. text-align: center;
56. background: MediumTurquoise;
57. }
58.
59. .erreursHeaders {
60. background: Teal;
61. background-color: #ff6633;
62. color: Snow;
63. font-style: italic;
64. text-align: center
65.
66. }
67.
68. .erreurClasse {
69. background: MediumTurquoise;
70. background-color: #ffcc66;
71. height: 25px;
72. text-align: center
73. }
74.
75. .erreurMessage {
76. background: PowderBlue;
77. background-color: #ffcc99;
78. text-align: left
79. }

Voici des exemples de code Jsp utilisant ces styles :

Vue Simulations

<h:dataTable value="#{form.simulations}" var="simulation"


headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSoc
iales,simuSalaireNet">

Le résultat obtenu est le suivant :

Vue Erreur

<h:dataTable value="#{form.erreurs}" var="erreur"


headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">

17.2.3 Le fichier des messages

Le fichier des messages [messages_fr.properties] s'enrichit de nouveaux messages pour les vues [VueSimulations] et [VueErreur] :

1. simulations.headers.nom=Nom
2. simulations.headers.prenom=Prénom
3. simulations.headers.heuresTravaillees=Heures travaillées
4. simulations.headers.joursTravailles=Jours Travaillés
5. simulations.headers.salaireBase=Salaire de base
6. simulations.headers.indemnites=Indemnités
7. simulations.headers.cotisationsSociales=Cotisations sociales
8. simulations.headers.salaireNet=SalaireNet
9. simulations.headers.numero=N°

Introdution à Java EE
143/334
10. erreur.titre=Une erreur s'est produite.
11. erreur.exceptions=Chaîne des exceptions
12. exception.type=Type de l'exception
13. exception.message=Message associé

17.2.4 La couche [métier]

La couche [métier] simulée est modifiée de la façon suivante :

1. package metier;
2.
3. ...
4. public class Metier implements IMetierLocal {
5.
6. // liste des employes
7. private Map<String,Employe> hashEmployes=new HashMap<String,Employe>();
8. private List<Employe> listEmployes;
9.
10. // obtenir la feuille de salaire
11. public FeuilleSalaire calculerFeuilleSalaire(String SS,
12. double nbHeuresTravaillées, int nbJoursTravaillés) {
13. // on récupère l'employé
14. Employe e=hashEmployes.get(SS);
15. // on rend une exception si l'employé n'existe pas
16. if(e==null){
17. throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
18. }
19. // on rend une feuille de salaire fictive
20. return new FeuilleSalaire(e,new Cotisation(3.49,6.15,9.39,7.88),e.getIndemnite(),new
ElementsSalaire(100,100,100,100,100));
21. }
22.
23. // liste des employés
24. public List<Employe> findAllEmployes() {
25. if(listEmployes==null){
26. // on crée une liste de trois employés
27. listEmployes=new ArrayList<Employe>();
28. listEmployes.add(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St
Corentin","49203",new Indemnite(2,2.1,2.1,3.1,15)));
29. listEmployes.add(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",new Indemnite(1,1.93,2,3,12)));
30. // dictionnaire des employes
31. for(Employe e:listEmployes){
32. hashEmployes.put(e.getSS(),e);
33. }
34. // on ajoute un employé qui n'existera pas dans le dictionnaire
35. listEmployes.add(new Employe("X","Y","Z","La brûlerie","St Marcel","49014",new
Indemnite(1,1.93,2,3,12)));
36. }
37. // on rend la liste des employés
38. return listEmployes;
39. }
40. }

• ligne 8 : la liste des employés


• ligne 7 : la même liste sous forme de dictionnaire indexé par le n° SS des employés
• lignes 24-39 : la méthode findAllEmployes qui rend la liste des employés. Cette méthode crée une liste en dur et la référence
par le champ employés de la ligne 8.
• lignes 27-33 : une liste et un dictionnaire de deux employés sont créés
• ligne 35 : un employé est ajouté à la liste employes (ligne 8) mais pas au dictionnaire hashEmployes (ligne 7). Ceci pour
qu'il apparaisse dans le combo des employés mais qu'ensuite il ne soit pas reconnu par la méthode calculerFeuilleSalaire
(ligne 14) afin que celle-ci lance une exception (ligne 17).
• lignes 11-21 : la méthode calculerFeuilleSalaire
• ligne 14 : l'employé est cherché dans le dictionnaire hashEmployes via son n° SS. S'il n'est pas trouvé, une exception est
lancée (lignes 16-18). Ainsi aurons-nous une exception pour l'employé de n° SS X ajouté ligne 35 dans la liste employés
mais pas dans le dictionnaire hashEmployes.
• ligne 20, une feuille de salaire fictive est créée et rendue.

17.3 Le bean [ApplicationData]

Introdution à Java EE
144/334
Le bean [ApplicationData] a pour but premier d'avoir une référence sur :
• la liste des employés afin qu'elle soit disponible à toutes les requêtes de tous les navigateurs clients. Ceci est possible parce
que la liste est en lecture seule.
• la couche [métier]. En effet, dans l'architecture de l'application, celle-ci est partagée par toutes les requêtes de toutes les
applications. Comme elle est implémentée par une classe sans état (stateless, sans champs privés mémmorisant des
informations propres à un utilisateur donné), elle peut être partagée.

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
V1 [metier]
4 [V] Modèle M
V2 JSP Gestionnaires
simulée
form d'évts
Vn

Dans le schéma ci-dessus, on voit la couche [métier] qui peut être partagée par toutes les requêtes de toutes les applications.

La portée du bean est l'aplication elle-même. Il est créé une fois puis partagé par toutes les requêtes de tous les utilisateurs. Son
code est le suivant :

1. package web.beans.application;
2.
3. ...
4. public class ApplicationData {
5.
6. public ApplicationData() {
7. }
8.
9. // couche métier
10. private IMetierLocal metier=new Metier();
11. // employés
12. private SelectItem[] employesItems;
13.
14. @PostConstruct
15. private void init(){
16. // la liste des employés est demandée à la couche métier
17. List<Employe>employes=metier.findAllEmployes();
18. // on génère la liste des éléments du combo
19. setEmployesItems(new SelectItem[employes.size()]);
20. for(int i=0;i<employes.size();i++){
21. getEmployesItems()[i]=new SelectItem(employes.get(i).getSS(),employes.get(i).getPrenom()+"
"+employes.get(i).getNom());
22. }
23. }
24.
25. // getters et setters
26. ....
27. }

• ligne 10 : référence sur la couche [métier] simulée


• ligne 12 : le tableau d'éléments SelectItem qui sert à remplir le combo des employés. En effet, plutôt que de mémoriser la
liste des employés, il est préférable de mémoriser le tableau d'élements SelectItem qui alimente le combo des pages
[form.jsp] affichées par les différents navigateurs clients.
• ligne 15 : une méthode annotée par @PostConstruct. Cette annotation désigne la méthode qui doit être exécutée juste
après l'exécution du constructeur de la classe. Nous utilisons la méthode init pour initialiser le champ employesItems de
la ligne 12.
• ligne 17 : la liste des employés est demandée à la couche [métier)
• lignes 19-22 : le tableau employesItems d'éléments de type SelectItem est initialisé. Les éléments générés dans le combo
auront pour attribut value, le n° SS de l'employé et pour attribut label (ce qui est affiché) le prénom et le nom de
l'employé. Cela signifie que lorsque la valeur du combo sera postée, c'est un n° SS qui sera envoyé au serveur, celui de
l'employé sélectionné.

Une fois le bean ApplicationData créé, le bean [Form] qui a une référence nommée applicationData sur ce bean, aura accès
• à la liste des éléments du combo via la méthode applicationData.getEmployesItems()

Introdution à Java EE
145/334
• à la couche [métier] via la méthode applicationData.getMetier()

On se rappellera que le bean ApplicationData est disponible pour toutes les requêtes de tous les clients.

17.4 Implémentation du modèle MVC

17.4.1 Le formulaire [form.jsp] et ses vues

Le squelette du formulaire [form.jsp] est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10. <script language="javascript">
11. function raz(){
12. // on change les valeurs postées
13. document.forms['formulaire'].elements['formulaire:vueSaisies:comboEmployes'].value="0";
14. document.forms['formulaire'].elements['formulaire:vueSaisies:heuresTravaillées'].value="0";
15. document.forms['formulaire'].elements['formulaire:vueSaisies:joursTravaillés'].value="0";
16. }
17. </script>
18.
19. <f:view>
20. <html>
21. <head>
22. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
23. <title><h:outputText value="#{msg['form.titre']}"/></title>
24. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
25. </head>
26. <body background="<c:url value="/ressources/standard.jpg"/>">
27. <h:form id="formulaire">
28. <!-- entete -->
29. ...
30. <!-- saisie -->
31. <f:subview id="vueSaisies" rendered="#{form.vueSaisiesIsRendered}">
32. ...
33. </f:subview>
34. <!-- simulation -->
35. <f:subview id="vueSimulation" rendered="#{form.vueSimulationIsRendered}">
36. ...
37. </f:subview>
38. <f:subview id="vueSimulations" rendered="#{form.vueSimulationsIsRendered}">
39. ...
40. </f:subview>
41. <f:subview id="vueSimulationsVides" rendered="#{form.vueSimulationsVidesIsRendered}">
42. <h2>Votre liste de simulations est vide.</h2>
43. </f:subview>
44. <!-- vue Erreur -->
45. <f:subview id="vueErreur" rendered="#{form.vueErreurIsRendered}">
46. ...
47. </f:subview>
48. </h:form>
49. </body>
50. </html>
51. </f:view>

Le formulaire [form.jsp] est composé d'un certain nombre de vues


• ligne 28 : l'entête présent dans toutes les vues

Introdution à Java EE
146/334
• lignes 31-33 : la vue [vueSaisies]

• lignes 35-37 : la vue [vueSimulation] (vue partielle ci-dessous)

• lignes 38-40 : la vue [vueSimulations]

• lignes 41-43 : la vue [vueSimulationsVides]

• lignes 45-47 : la vue [vueErreur]

Nous examinerons ces vues les unes après les autres en liaison avec le contrôleur / modèle [Form.java].

17.4.2 Le contrôleur / modèle [Form.java]

La page JSP [form.jsp] est contrôlée par la classe [Form.java] suivante :

1. package web.beans.session;
2.
3. ...
4. public class Form {
5.
6. public Form() {
7. }
8.
9. // autres beans
10. private ApplicationData applicationData;
11.
12. // les vues

Introdution à Java EE
147/334
13. private boolean vueSaisiesIsRendered=true;
14. private boolean vueSimulationIsRendered=false;
15. private boolean vueSimulationsIsRendered=false;
16. private boolean vueSimulationsVidesIsRendered=false;
17. private boolean vueErreurIsRendered=false;
18.
19. // les menus
20. private boolean menuFaireSimulationIsRendered=true;
21. private boolean menuEffacerSimulationIsRendered=true;
22. private boolean menuEnregistrerSimulationIsRendered=false;
23. private boolean menuVoirSimulationsIsRendered=false;
24. private boolean menuRetourSimulateurIsRendered=false;
25. private boolean menuTerminerSessionIsRendered=true;
26.
27. // le modèle des vues
28. private String comboEmployesValue="";
29. private String heuresTravaillées="";
30. private String joursTravaillés="";
31. private FeuilleSalaire feuilleSalaire;
32. private SelectItem[] employesItems;
33. private Integer numSimulationToDelete;
34. private List<Erreur> erreurs=new ArrayList<Erreur>();
35. private List<Simulation> simulations=new ArrayList<Simulation>();
36.
37. // actions du menu
38. public String faireSimulation(){
39. ...
40. }
41.
42. public String enregistrerSimulation(){
43. ...
44. }
45.
46. public String effacerSimulation(){
47. ...
48. }
49.
50. public String voirSimulations(){
51. ...
52. }
53.
54. public String retourSimulateur(){
55. ...
56. }
57. public String terminerSession(){
58. ...
59. }
60.
61. public String retirerSimulation(){
62. ...
63. }
64.
65. // gestion des vues
66. private void setVues(boolean vueSaisiesIsRendered, boolean vueSimulationIsRendered, boolean
vueSimulationsIsRendered, boolean vueSimulationsVidesIsRendered, boolean vueErreurIsRendered){
67. this.vueSaisiesIsRendered=vueSaisiesIsRendered;
68. this.vueSimulationIsRendered=vueSimulationIsRendered;
69. this.vueSimulationsIsRendered=vueSimulationsIsRendered;
70. this.vueSimulationsVidesIsRendered=vueSimulationsVidesIsRendered;
71. this.vueErreurIsRendered=vueErreurIsRendered;
72. }
73.
74. // gestion des menus
75. private void setMenu(boolean menuFaireSimulationIsRendered, boolean
menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean
menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered){
76. this.menuFaireSimulationIsRendered=menuFaireSimulationIsRendered;
77. this.menuEnregistrerSimulationIsRendered=menuEnregistrerSimulationIsRendered;
78. this.menuVoirSimulationsIsRendered=menuVoirSimulationsIsRendered;
79. this.menuEffacerSimulationIsRendered=menuEffacerSimulationIsRendered;
80. this.menuRetourSimulateurIsRendered=menuRetourSimulateurIsRendered;
81. this.menuTerminerSessionIsRendered=menuTerminerSessionIsRendered;
82. }
83.
84. // getters et setters
85. ..
86. }

• lignes 13-17 : les champs qui contrôlent l'affichage ou non des vues

Introdution à Java EE
148/334
• lignes 66-72 : une méthode utilitaire setVues qui permet de contrôler les champs précédents.
• lignes 20-25 : les champs qui contrôlent l'affichage des options du menu
• lignes 38-63 : les méthodes qui traitent l'événement clic sur les options du menu
• lignes 75-82 : une méthode utilitaire setMenu qui permet de contrôler l'affichage ou non des 6 options du menu.

17.4.3 La vue [vueEntete]

La vue [vueEntete] est la suivante :

et son code JSP :

1. <!-- entete -->


2. <h:panelGrid columns="2">
3. <h:panelGroup>
4. <h2><h:outputText value="#{msg['form.titre']}"/></h2>
5. </h:panelGroup>
6. <h:panelGroup>
7. <h:panelGrid columns="1">
8. <h:commandLink value="#{msg['form.menu.faireSimulation']}"
action="#{form.faireSimulation}" rendered="#{form.menuFaireSimulationIsRendered}"/>
9. <h:commandLink id="cmdEffacerSimulation"
value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}"
rendered="#{form.menuEffacerSimulationIsRendered}" onclick="raz()"/>
10. <h:commandLink value="#{msg['form.menu.enregistrerSimulation']}" immediate="true"
action="#{form.enregistrerSimulation}" rendered="#{form.menuEnregistrerSimulationIsRendered}"/>
11. <h:commandLink value="#{msg['form.menu.voirSimulations']}"
action="#{form.voirSimulations}" immediate="true"
rendered="#{form.menuVoirSimulationsIsRendered}"/>
12. <h:commandLink value="#{msg['form.menu.retourSimulateur']}"
action="#{form.retourSimulateur}" rendered="#{form.menuRetourSimulateurIsRendered}"/>
13. <h:commandLink value="#{msg['form.menu.terminerSession']}" immediate="true"
action="#{form.terminerSession}" rendered="#{form.menuTerminerSessionIsRendered}"/>
14. </h:panelGrid>
15. </h:panelGroup>
16. </h:panelGrid>
17. <hr/>

On notera que :
• chaque lien peut être affiché ou non via l'attribut rendered contrôlé par un champ du modèle [Form]
• chaque lien est lié à une méthode du contrôleur secondaire [Form].
• le lien [Effacer la simulation] de la ligne 9 est liée à la fonction Javascript raz() dont on peut voir le code dans le listing de
[form.jsp] page 146.

La classe [Form] a les éléments suivants liés à l'entête :

1. ...
2. public class Form {
3.
4. ...
5.
6. // les menus
7. private boolean menuFaireSimulationIsRendered=true;
8. private boolean menuEffacerSimulationIsRendered=true;
9. private boolean menuEnregistrerSimulationIsRendered=false;
10. private boolean menuVoirSimulationsIsRendered=false;
11. private boolean menuRetourSimulateurIsRendered=false;
12. private boolean menuTerminerSessionIsRendered=true;
13.
14. // actions du menu
15. public String faireSimulation(){
16. ...
17. }
18.
19. public String enregistrerSimulation(){

Introdution à Java EE
149/334
20. ...
21. }
22.
23. public String effacerSimulation(){
24. ...
25. }
26.
27. public String voirSimulations(){
28. ...
29. }
30.
31. public String retourSimulateur(){
32. ...
33. }
34. public String terminerSession(){
35. ...
36. }
37.
38. public String retirerSimulation(){
39. ...
40. }
41.
42.
43. // gestion des menus
44. private void setMenu(boolean menuFaireSimulationIsRendered, boolean
menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean
menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered){
45. this.menuFaireSimulationIsRendered=menuFaireSimulationIsRendered;
46. this.menuEnregistrerSimulationIsRendered=menuEnregistrerSimulationIsRendered;
47. this.menuVoirSimulationsIsRendered=menuVoirSimulationsIsRendered;
48. this.menuEffacerSimulationIsRendered=menuEffacerSimulationIsRendered;
49. this.menuRetourSimulateurIsRendered=menuRetourSimulateurIsRendered;
50. this.menuTerminerSessionIsRendered=menuTerminerSessionIsRendered;
51. }
52.
53. // getters et setters
54. ..
55. }

• lignes 7-12 : les champs qui contrôlent l'affichage des options du menu
• lignes 15-40 : les méthodes qui traitent l'événement clic sur les options du menu
• lignes 44-51 : une méthode utilitaire setMenu qui permet de contrôler l'affichage ou non des 6 options du menu.

17.4.4 La vue [vueSaisie]

La vue [vueSaisie] est la suivante :

• en [1], l'entête, en [2], la vue [vueSaisies]

Son code JSP :

1. <!-- saisie -->


2. <f:subview id="vueSaisies" rendered="#{form.vueSaisiesIsRendered}">
3. <h:panelGrid columns="3">
4. <!-- ligne 1 -->
5. <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
6. <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
7. <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
8. <!-- ligne 2 -->
9. <h:selectOneMenu id="comboEmployes" value="#{form.comboEmployesValue}">

Introdution à Java EE
150/334
10. <f:selectItems ..."/>
11. </h:selectOneMenu>
12. <h:inputText id="heuresTravaillées" value="#{form.heuresTravaillées}" required="true"
requiredMessage="#{msg['form.heuresTravaillées.required']}"
validatorMessage="#{msg['form.heuresTravaillées.validation']}">
13. <f:validateDoubleRange minimum="0" maximum="300"/>
14. </h:inputText>
15. <h:inputText id="joursTravaillés" value="#{form.joursTravaillés}" required="true"
requiredMessage="#{msg['form.joursTravaillés.required']}"
validatorMessage="#{msg['form.joursTravaillés.validation']}">
16. <f:validateLongRange minimum="0" maximum="31"/>
17. </h:inputText>
18. <!-- ligne 3 -->
19. <h:panelGroup></h:panelGroup>
20. <h:message for="heuresTravaillées" styleClass="error"/>
21. <h:message for="joursTravaillés" styleClass="error"/>
22. </h:panelGrid>
23. <hr/>
24. </f:subview>

Question : compléter la ligne 10. La liste des éléments du combo des employés est fournie par une méthode d'un des beans de
l'application. Indiquer lequel et écrire la méthode. Celle-ci devra fournir un tableau d'éléments de type SelectItem dont la propriété
value aura pour valeur le n° SS d'un employé et la propriété label une chaîne formée du prénom et du nom de celui-ci.

Question : comment doit être initialisé le modèle [Form.java] pour que lors de la requête GET initiale faite au formulaire, la page
ci-dessus soit envoyée au navigateur client ?

17.5 Les actions du contrôleur

17.5.1 L'action [faireSimulation]

Le code JSP de l'action [faireSimulation] est le suivant :

<h:commandLink value="#{msg['form.menu.faireSimulation']}" action="#{form.faireSimulation}"


rendered="#{form.menuFaireSimulationIsRendered}"/>

L'action [faireSimulation] calcule une feuille de salaire. Elle provoque les échanges client / serveur suivants :

A partir de la page précédente, on obtient le résultat suivant :

Introdution à Java EE
151/334
Question : écrire la méthode [faireSimulation] de la classe [Form]. On reprendra le code de la version précédente en précisant ce
qui change.

17.5.2 L'action [effacerSimulation]

L'action [effacerSimulation] permet à l'utilisateur de retrouver un formulaire vide :

Le code JSP du lien [effacerSimulation] est le suivant :

1. ....
2. <script language="javascript">
3. function raz(){
4. // on change les valeurs postées

Introdution à Java EE
152/334
5. document.forms['formulaire'].elements['formulaire:vueSaisies:comboEmployes'].value="0";
6. document.forms['formulaire'].elements['formulaire:vueSaisies:heuresTravaillées'].value="0";
7. document.forms['formulaire'].elements['formulaire:vueSaisies:joursTravaillés'].value="0";
8. }
9. </script>
10.
11. <f:view>
12. <html>
13. <head>
14. ...
15. </head>
16. <body background="<c:url value="/ressources/standard.jpg"/>">
17. <h:form id="formulaire">
18. <!-- entete -->
19. ...
20. <h:panelGrid columns="1">
21. ...
22. <h:commandLink id="cmdEffacerSimulation"
value="#{msg['form.menu.effacerSimulation']}" action="#{form.effacerSimulation}"
rendered="#{form.menuEffacerSimulationIsRendered}" onclick="raz()"/>
23. ....
24. </h:panelGrid>
25. </h:panelGroup>
26. </h:panelGrid>
27. ...

Ligne 22, le code du lien [EffacerSimulation]. Un clic dessus provoque d'abord l'appel de la fonction Javascript raz() (ligne 22).
Cette méthode définie lignes 3-8 change les valeurs postées. On notera que
• les valeurs postées sont des valeurs valides, c.a.d. qu'elles passeront les tests de validation des champs de saisie
heuresTravaillées et joursTravaillés.
• la fonction raz ne poste pas le formulaire. En effet, celui-ci va être posté par le lien cmdEffacerSimulation de la ligne 22. Ce
post se fera après exécution de la fonction Javascript raz.

Au cours du post, les valeurs postées vont suivre un cheminement normal : validation puis affectation aux champs du modèle. Ceux-
ci sont les suivants dans la classe [Form] :

// le modèle des vues


private String comboEmployesValue;
private String heuresTravaillées;
private String joursTravaillés;
...

Ces trois champs vont recevoir les trois valeurs postées {"0","0","0"}. Une fois cette affectation opérée, la méthode effacerSimulation
va être exécutée.

Question : écrire la méthode [effacerSimulation] de la classe [Form]. On fera en sorte que :


- seule la zone des saisies soit affichée
- le combo soit positionné sur son 1er élément
- les zones de saisie heuresTravaillées et joursTravaillés affichent des chaînes vides

17.5.3 L'action [enregistrerSimulation]

Le code JSP du lien [enregistrerSimulation] est le suivant :

<h:commandLink id="cmdEnregistrerSimulation" immediate="true"


value="#{msg['form.menu.enregistrerSimulation']}" action="#{form.enregistrerSimulation}"
rendered="#{form.menuEnregistrerSimulationIsRendered}"/>

L'action [enregistrerSimulation] associée au lien permet d'enregistrer la simulation courante dans une liste de simulations maintenue
dans la classe [Form] :

1. // le modèle des vues


2....
3.private List<Simulation> simulations=new ArrayList<Simulation>();

La classe Simulation est la suivante :

1. package web.entities;
2.
3. import metier.FeuilleSalaire;

Introdution à Java EE
153/334
4.
5. public class Simulation {
6.
7. public Simulation() {
8. }
9.
10. // champs d'une simulation
11. private Integer num;
12. private FeuilleSalaire feuilleSalaire;
13. private String heuresTravaillées;
14. private String joursTravaillés;
15.
16. // constructeur
17. public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire
feuilleSalaire){
18. this.setNum(num);
19. this.setFeuilleSalaire(feuilleSalaire);
20. this.setHeuresTravaillées(heuresTravaillées);
21. this.setJoursTravaillés(joursTravaillés);
22. }
23.
24. public double getIndemnites(){
25. return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+
feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
26. }
27.
28. // getters et setters
29. ...
30. }

Cette classe permet de mémoriser une simulation faite par l'utilisateur :


• ligne 11 : le n° de la simulation
• ligne 12 : la feuille de salaire qui a été calculée
• ligne 13 : le nombre d'heures travaillées
• ligne 14 : le nombre de jours travaillés

Voici un exemple d'enregistrement :

A partir de la page précédente, on obtient le résultat qui suit :

Introdution à Java EE
154/334
Le n° de la simulation est un nombre incrémenté à chaque nouvel enregistrement. La logique voudrait que ce n° soit maintenu dans
la classe [Form] avec la liste des simulations :

1. // simulations
2. private List<Simulation> simulations=new ArrayList<Simulation>();
3.private int numDerniereSimulation=0;

Le champ numDerniereSimulation serait incrémenté à chaque nouvel enregistrement. Notons que nous ne pouvons pas utiliser la taille
de la liste des simulations pour numéroter les simulations successives puisque l'utilisateur peut retirer des simulations de la liste (cf
ci-dessous). Ainsi, s'il retire la simulation n° 2 d'une liste de trois simulations, nous aurons le tableau suivant :

Le n° de la prochaine simulation doit être 4 même si la liste n'a que deux simulations.

Comme la classe [Form] est de portée session, le champ numDerniereSimulation serait correctement conservé au fil des
requêtes. Comme exercice, nous stockerons le n° de la dernière simulation faite, non pas comme un champ de la classe [Form] mais
comme attribut de la session de l'utilisateur. Cela nous permettra de découvrir comment dans un bean JSF on peut avoir accès à la
session de l'utilisateur. Cela peut être utile si l'application web mélange servlets, pages JSP et JSF qui peuvent alors utiliser la session
comme mémoire commune.

Dans une méthode de la classe [Form], la session de l'utilisateur est accessible de la façon suivante :

1. // on récupère la requête courante


2. HttpServletRequest
request=(HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
3. // on récupère dans la session le n° de la dernière simulation
4. Integer numDerniereSimulation=(Integer) request.getSession().getAttribute("numDerniereSimulation");

• ligne 2 : récupère l'objet HttpServletRequest request qui encapsule la requête faite par le navigateur client
• ligne 4 : la session de l'utilisateur est récupérée par request.getSession().

On peut stocker des informations dans une session via ses attributs. On dispose pour cela de deux méthodes :

• [session].setAttribute(String clé, Object objet) : pour stocker objet associé à clé.


• [session].getAttribute(String clé) : pour récupérer l'objet associé à clé. On récupère la valeur null, si la clé n'existe pas
dans les attributs de la session.

Dans l'exemple ci-dessus, le n° de la dernière simulation faite est associée à la clé numDerniereSimulation.

La méthode [enregistrerSimulation] peut procéder ainsi :

• récupérer le n° de la dernière simulation dans la session. Si ce n° n'existe pas, l'y mettre avec une valeur 0.
• incrémenter le n°
• le remettre dans la session (si besoin est)
• ajouter la nouvelle simulation à la liste des simulations maintenue par la classe [Form] :

1. // simulations
2. private List<Simulation> simulations;

• faire afficher le tableau des simulations :

Introdution à Java EE
155/334
Le tableau des simulations peut être affiché avec une balise <h:dataTable> :

1. <f:subview id="vueSimulations" rendered="#{form.vueSimulationsIsRendered}">


2. <!-- tableau des simulations -->
3. <h:dataTable value="#{form.simulations}" var="simulation"
4. headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisa
tionsSociales,simuSalaireNet">
5. <h:column>
6. <f:facet name="header">
7. <h:outputText value="#{msg['simulations.headers.numero']}"/>
8. </f:facet>
9. <h:outputText value="#{simulation.num}"/>
10. </h:column>
11. <h:column>
12. <f:facet name="header">
13. <h:outputText value="#{msg['simulations.headers.nom']}"/>
14. </f:facet>
15. <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
16. </h:column>
17. <h:column>
18. <f:facet name="header">
19. <h:outputText value="#{msg['simulations.headers.prenom']}"/>
20. </f:facet>
21. <h:outputText value="#{simulation.feuilleSalaire.employe.prenom}"/>
22. </h:column>
23. <h:column>
24. <f:facet name="header">
25. <h:outputText value="#{msg['simulations.headers.heuresTravaillees']}"/>
26. </f:facet>
27. <h:outputText value="#{simulation.heuresTravaillées}"/>
28. </h:column>
29. <h:column>
30. <f:facet name="header">
31. <h:outputText value="#{msg['simulations.headers.joursTravailles']}"/>
32. </f:facet>
33. <h:outputText value="#{simulation.joursTravaillés}"/>
34. </h:column>
35. <h:column>
36. <f:facet name="header">
37. <h:outputText value="#{msg['simulations.headers.salaireBase']}"/>
38. </f:facet>
39. <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireBase}"/>
40. </h:column>
41. <h:column>
42. <f:facet name="header">
43. <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
44. </f:facet>
45. <h:outputText value="#{simulation.indemnites}"/>
46. </h:column>
47. <h:column>
48. <f:facet name="header">
49. <h:outputText value="#{msg['simulations.headers.cotisationsSociales']}"/>
50. </f:facet>
51. <h:outputText
value="#{simulation.feuilleSalaire.elementsSalaire.cotisationsSociales}"/>
52. </h:column>
53. <h:column>
54. <f:facet name="header">
55. <h:outputText value="#{msg['simulations.headers.salaireNet']}"/>
56. </f:facet>
57. <h:outputText value="#{simulation.feuilleSalaire.elementsSalaire.salaireNet}"/>
58. </h:column>
59. <h:column>
60. <h:commandLink value="Retirer" action="#{form.retirerSimulation}">

Introdution à Java EE
156/334
61. <f:setPropertyActionListener target="#{form.numSimulationToDelete}"
value="#{simulation.num}"/>
62. </h:commandLink>
63. </h:column>
64. </h:dataTable>
65. </f:subview>

• ligne 3, la balise <h:dataTable> utilise le champ #{form.simulations} comme source de données, c.a.d. le champ suivant
:

1. // simulations
2.private List<Simulation> simulations;

- l'attribut var="simulation" fixe le nom de la variable représentant la simulation courante à l'intérieur de la balise
<h:datatable>
- l'attribut headerClass="simulationsHeaders" fixe le style des titres des colonnes du tableau.
- l'attribut columnClasses="...." fixe le style de chacune des colonnes du tableau

Examinons l'une des colonnes du tableau et voyons comment elle est construite :

Le code JSP de la colonne Nom est le suivant :

1. <h:column>
2. <f:facet name="header">
3. <h:outputText value="#{msg['simulations.headers.nom']}"/>
4. </f:facet>
5. <h:outputText value="#{simulation.feuilleSalaire.employe.nom}"/>
6. </h:column>

• lignes 2-4 : la balise <f:facet name="header"> définit le titre de la colonne


• ligne 5 : le nom de l'employé est écrit :
• simulation fait référence à l'attribut var de la balise <h:dataTable ...> :

<h:dataTable value="#{form.simulations}" var="simulation" ...>

simulation désigne la simulation courante de la liste des simulations : d'abord la 1ère, puis la 2ème, ...
• simulation.feuilleSalaire fait référence au champ feuilleSalaire de la simulation courante
• simulation.feuilleSalaire.employe fait référence au champ employe du champ feuilleSalaire
• simulation.feuilleSalaire.employe.nom fait référence au champ nom du champ employe

La même technique est répétée pour toutes les colonnes du tableau. Il y a une difficulté pour la colonne Indemnités qui est
générée avec le code suivant :

1. <h:column>
2. <f:facet name="header">
3. <h:outputText value="#{msg['simulations.headers.indemnites']}"/>
4. </f:facet>
5. <h:outputText value="#{simulation.indemnites}"/>
6. </h:column>

Ligne 5, on affiche la valeur de simulation.indemnites. Or la classe Simulation n'a pas de champ indemnites. Il faut se rappeler
ici que le champ indemnites n'est pas utilisé directement mais via la méthode simulation.getIndemnites(). Il suffit donc que
cette méthode existe. Le champ indemnites peut lui ne pas exister. La méthode getIndemnites doit rendre le total des indemnités de

Introdution à Java EE
157/334
l'employé. Cela nécessite un calcul intermédiaire car ce total n'est pas disponible directement dans la feuille de salaire. La métode
getIndemnites est donnée page 153.

Question : écrire la méthode [enregistrerSimulation] de la classe [Form].

17.5.4 L'action [retourSimulateur]

Le code JSP du lien [retourSimulateur] est le suivant :

<h:commandLink id="cmdRetourSimulateur" value="#{msg['form.menu.retourSimulateur']}"


action="#{form.retourSimulateur}" rendered="#{form.menuRetourSimulateurIsRendered}"/>

L'action [retourSimulateur] associée au lien permet à l'utilisateur de revenir de la vue [vueSimulations] à la vue [vueSaisies] :

Le résultat obtenu :

Question : écrire la méthode [retourSimulateur] de la classe [Form]. Le formulaire de saisie présenté doit être vide comme ci-
dessus.

17.5.5 L'action [voirSimulations]

Le code JSP du lien [voirSimulations] est le suivant :

<h:commandLink id="cmdVoirSimulations" value="#{msg['form.menu.voirSimulations']}"


action="#{form.voirSimulations}" immediate="true" rendered="#{form.menuVoirSimulationsIsRendered}"/>

L'action [voirSimulations] associée au lien permet à l'utilisateur d'avoir le tableau des simulations, ceci quelque soit l'état de ses
saisies :

Introdution à Java EE
158/334
Le résultat obtenu :

Question : écrire la méthode [voirSimulations] de la classe [Form].

On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vueSimulationsVides] :

17.5.6 L'action [retirerSimulation]

L'utilisateur peut retirer des simulations de sa liste :

Le résultat obtenu est le suivant :

Introdution à Java EE
159/334
Si ci-dessus, on retire la dernière simulation, on obtiendra le résultat suivant :

Le code JSP de la colonne [Retirer] du tableau des simulations est le suivant :

1. <f:subview id="vueSimulations" rendered="#{form.vueSimulationsIsRendered}">


2. <!-- tableau des simulations -->
3. <h:dataTable value="#{form.simulations}" var="simulation"
4. headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisa
tionsSociales,simuSalaireNet">
5. ...
6. <h:column>
7. <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
8. <f:setPropertyActionListener target="#{form.numSimulationToDelete}"
value="#{simulation.num}"/>
9. </h:commandLink>
10. </h:column>
11. </h:dataTable>
12. </f:subview>

• ligne 7 : le lien [Retirer] est associé à la méthode [retirerSimulation] de la classe [Form]. Cette méthode a besoin de
connaître le n° de la simulation à retirer. Celui-ci lui est fourni par la balise <f:setPropertyActionListener> de la ligne 8.
Cette balise a deux attributs target et value : l'attribut target désigne un champ du modèle auquel la valeur de l'attribut
value sera affectée. Ici le n° de la simulation à retirer #{simulation.num} sera affectée au champ
numSimulationToDelete de la classe [Form] :

1. // le modèle des vues


2. ...
3. private Integer numSimulationToDelete;

Lorsque la méthode [retirerSimulation] de la classe [Form] s'exécutera, elle pourra utiliser la valeur qui aura été stockée
auparavant dans le champ numSimulationToDelete.

Question : écrire la méthode [retirerSimulation] de la classe [Form].

17.5.7 L'action [terminerSession]

Le code JSP du lien [Terminer la session] est le suivant :

1. <h:commandLink id="cmdTerminerSession" immediate="true"


value="#{msg['form.menu.terminerSession']}" action="#{form.terminerSession}"
rendered="#{form.menuTerminerSessionIsRendered}"/>

L'action [terminerSession] associée au lien permet à l'utilisateur d'abandonner sa session et de revenir au formulaire de saisies vide :

Introdution à Java EE
160/334
Si l'utilisateur avait une liste de simulations, celle-ci est vidée. Par ailleurs, la numérotation des simulations repart de 1.

Question : écrire la méthode [terminerSession] de la classe [Form].

17.6 La vue [vueErreur]


On veut pouvoir gérer proprement les exceptions qui peuvent survenir lors du calcul d'une simulation. Pour cela le code de la
méthode [faireSimulation] utilisera un try / catch :

1. // action du menu
2. public String faireSimulation(){
3. try{
4. // on calcule la feuille de salaire
5. feuilleSalaire= ...
6. // on affiche la simulation
7. ...
8. // on met à jour le menu
9. ...
10. }catch(Throwable th){
11. // on vide la liste des erreurs précédentes
12. ...
13. // on crée la nouvelle liste des erreurs
14. ...
15. // on affiche la vue vueErreur
16. ...
17. // on met à jour le menu
18. ...
19. }
20. // on rend la même page
21. return null;
22.}

La liste des erreurs créée ligne 14 est la suivante :

1. // le modèle des vues


2. ...
3. private List<Erreur> erreurs=new ArrayList<Erreur>();
4....

La classe Erreur est définie comme suit :

1. package web.entities;
2.
3. public class Erreur {

Introdution à Java EE
161/334
4.
5. public Erreur() {
6. }
7.
8. // champ
9. private String classe;
10. private String message;
11.
12. // constructeur
13. public Erreur(String classe, String message){
14. this.setClasse(classe);
15. this.message=message;
16. }
17.
18. // getters et setters
19. ...
20. }

Les erreurs seront des exceptions dont on mémorise le nom de la classe dans le champ classe et le message dans le champ
message.

La liste des erreurs construite dans la méthode [faireSimulation] est constituée de :


• l'exception initiale th de type Throwable qui s'est produite
• de sa cause th.getCause() si elle en a une
• de la cause de la cause h.getCause().getCause() si elle existe
• ...

Voici un exemple de liste d'erreurs :

Ci-dessus, l'employé [Z Y] n'existe pas dans le dictionnaire des employés utilisé par la couche [métier] simulée. Dans ce cas, la
couche [métier] simulée lance une exception :

1. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int


nbJoursTravaillés) {
2. // on récupère l'employé
3. Employe e=hashEmployes.get(SS);
4. // on rend une exception si l'employé n'existe pas
5. if(e==null){
6. throw new PamException(String.format("L'employé de n° SS [%s] n'existe pas",SS),1);
7. }
8....
9.}

Le résultat obtenu est le suivant :

Pour obtenir cette page, le code JSP suivant a été utilisé :

Introdution à Java EE
162/334
1. <!-- vue Erreur -->
2. <f:subview id="vueErreur" rendered="#{form.vueErreurIsRendered}">
3. <h3><h:outputText value="#{msg['erreur.titre']}"/></h3>
4. <h:dataTable value="#{form.erreurs}" var="erreur"
5. headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
6. <f:facet name="header">
7. <h:outputText value="#{msg['erreur.exceptions']}"/>
8. </f:facet>
9. <h:column>
10. <f:facet name="header">
11. <h:outputText value="#{msg['exception.type']}"/>
12. </f:facet>
13. <h:outputText value="#{erreur.classe}"/>
14. </h:column>
15. <h:column>
16. <f:facet name="header">
17. <h:outputText value="#{msg['exception.message']}"/>
18. </f:facet>
19. <h:outputText value="#{erreur.message}"/>
20. </h:column>
21. </h:dataTable>
22. </f:subview>

Question : compléter la méthode [faireSimulation] afin que lors d'une exception, elle fasse afficher la vue [vueErreur].

17.7 Intégration de la couche web dans une architecture 3 couches Jsf / Ejb
L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
V1 [metier]
4 [V] Modèle M
V2 JSP Gestionnaires
simulée
form d'évts
Vn

Nous remplaçons la couche [métier] simulée, par les couches [métier, dao, jpa] implémentées par des Ejb au paragraphe 13.2, page
85 :

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java
V1 couches
4 [V] Modèle M [metier, dao, jpa]
V2 JSP Gestionnaires
form d'évts
Vn

Travail pratique : réaliser l'intégration des couches Jsf et Ejb en suivant la méthodologie du paragraphe 16, page 128.

Introdution à Java EE
163/334
18 Version 8 - Application web PAM multi-vues / mono-page - 2
Nous reprenons la même architecture que précédemment : 1 page JSP, 1 modèle / contrôleur pour cette page, des vues toutes
issues de la page JSP.

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
V1 [metier]
4 [V] Modèle M
V2 JSP Gestionnaires
simulée
form d'évts
Vn

Pour la demande GET initiale :


• [Faces Servlet] reçoit une demande GET d'affichage de la page [form.jsp] en [1]
• il fait afficher celle-ci en [3]. La page utilise son modèle / contrôleur [Form.java] pour initialiser ses parties dynamiques.
En [4], une vue Vi parmi d'autres est affichée, les autres restant cachées.

Pour les demandes POST qui suivent :


• [Faces Servlet] reçoit une demande POST pour la page [form.jsp] en [1]
• l'événement qui a provoqué le POST est traité par l'une des méthodes du modèle / contrôleur [Form.java] [2a, 2b]. Celle-
ci met à jour le modèle M afin qu'une vue Vi particulière soit affichée et les autres cachées. La méthode rend comme clé
de navigation la clé null.
• [Faces Servlet] ayant reçu la clé null, fait afficher de nouveau [form.jsp] en [3]. La page utilise son modèle [Form.java]
pour initialiser ses parties dynamiques. En [4], la vue Vi est affichée, les autres restant cachées.

Le projet Netbeans précédent avait l'allure suivante :

1 6

• en [1], les fichiers de configuration


• en [2], les pages Jsp et la feuille de style
• en [3], les classes de la couche [web]
• en [4], le fichier des messages pour l'internationalisation de l'application
• en [5], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [6], les bibliothèques de l'application

La nouvelle version amène des modifications uniquement dans le paquetage [web.beans] [3] qui regroupe les beans de l'application
JSF. Les deux beans ApplicationData et Form de [3] ci-dessus étaient définis de la façon suivante dans le fichier [faces-config.xml]
:

Introdution à Java EE
164/334
33. <?xml version="1.0" encoding="UTF-8"?>
34. <!-- =========== FULL CONFIGURATION FILE ================================== -->
35. <faces-config version="1.2" 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_1_2.xsd">
36. <managed-bean>
37. <managed-bean-name>applicationData</managed-bean-name>
38. <managed-bean-class>web.beans.application.ApplicationData</managed-bean-class>
39. <managed-bean-scope>application</managed-bean-scope>
40. </managed-bean>
41. <managed-bean>
42. <managed-bean-name>form</managed-bean-name>
43. <managed-bean-class>web.beans.session.Form</managed-bean-class>
44. <managed-bean-scope>session</managed-bean-scope>
45. <managed-property>
46. <property-name>applicationData</property-name>
47. <value>#{applicationData}</value>
48. </managed-property>
49. </managed-bean>
50. ...
51. </faces-config>

• lignes 36-40 : définition du bean applicationData (ligne 37) de portée application (ligne 39)
• lignes 41-49 : définition du bean form (ligne 42) de portée session (ligne 44)

Le bean [Form] défini ligne 43 était le suivant :

1. package web.beans.session;
2.
3. ...
4. public class Form {
5.
6. public Form() {
7. }
8.
9. // autres beans
10. private ApplicationData applicationData;
11.
12. // les vues
13. ...
14.
15. // les menus
16. ...
17.
18. // le modèle des vues
19. private String comboEmployesValue="";
20. private String heuresTravaillées="";
21. private String joursTravaillés="";
22. private FeuilleSalaire feuilleSalaire;
23. private SelectItem[] employesItems;
24. private Integer numSimulationToDelete;
25. private List<Erreur> erreurs=new ArrayList<Erreur>();
26. private List<Simulation> simulations=new ArrayList<Simulation>();
27. ..

La classe [Form] maintenait au fil des requêtes d'un utilisateur donné, la liste de simulations de celui-ci (ligne 26). Pour cette raison,
le bean [Form] devait rester dans la session de l'utilisateur et c'est pourquoi il a la portée session dans [faces-config.xml].

Dans cette version, nous allons isoler tout ce qui doit rester dans la session dans un bean appelé sessionData et allons passer la
portée du bean form de session à request.

18.1 Le projet Netbeans


Le projet Netbeans de cette version est obtenu d'abord par recopie du projet précédent puis par modification de certains de ces
éléments :

Introdution à Java EE
165/334
5

1 6

• en [1], les fichiers de configuration


• en [2], les pages Jsp et la feuille de style
• en [3], les classes de la couche [web]. Le changement se produit ici :
• la classe [Form] qui était auparavant dans le paquetage [web.beans.session] passe dans le paquetage
[web.beans.request] pour refléter le fait que sa portée passe de session à request.
• la classe [SessionData] qui va nous servir à stocker les informations de portée session apparaît dans le
paquetage [web.beans.session].
• en [4], le fichier des messages pour l'internationalisation de l'application
• en [5], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [6], les bibliothèques de l'application

18.2 Le fichier [faces-config.xml]


Le fichier [faces-config.xml] évolue de la façon suivante :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <managed-bean>
5. <managed-bean-name>form</managed-bean-name>
6. <managed-bean-class>web.beans.request.Form</managed-bean-class>
7. <managed-bean-scope>request</managed-bean-scope>
8. <managed-property>
9. <property-name>applicationData</property-name>
10. <value>#{applicationData}</value>
11. </managed-property>
12. <managed-property>
13. <property-name>sessionData</property-name>
14. <value>#{sessionData}</value>
15. </managed-property>
16. </managed-bean>
17. <managed-bean>
18. <managed-bean-name>locale</managed-bean-name>
19. <managed-bean-class>web.utils.ChangeLocale</managed-bean-class>
20. <managed-bean-scope>application</managed-bean-scope>
21. </managed-bean>
22. <managed-bean>
23. <managed-bean-name>applicationData</managed-bean-name>
24. <managed-bean-class>web.beans.application.ApplicationData</managed-bean-class>
25. <managed-bean-scope>application</managed-bean-scope>
26. </managed-bean>
27. <managed-bean>
28. <managed-bean-name>sessionData</managed-bean-name>
29. <managed-bean-class>web.beans.session.SessionData</managed-bean-class>
30. <managed-bean-scope>session</managed-bean-scope>
31. </managed-bean>
32. <application>

Introdution à Java EE
166/334
33. <resource-bundle>
34. <base-name>
35. messages
36. </base-name>
37. <var>msg</var>
38. </resource-bundle>
39. <message-bundle>messages</message-bundle>
40. </application>
41. </faces-config>

• lignes 22-26 : le bean applicationData de portée application


• lignes 27-31 : le bean sessionData de portée session
• lignes 4-16 : le bean form de portée request. Ce bean a une référence sur le bean applicationData (lignes 8-11) et sur le
bean sessionData (lignes 12-15).

18.3 Le bean SessionData


Le bean SessionData mémorise toutes les informations qui doivent être maintenues dans la session de l'utilisateur. Sa classe est la
suivante :

1. package web.beans.session;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5. import web.entities.Simulation;
6.
7. public class SessionData {
8.
9. public SessionData() {
10. }
11.
12. // simulations
13. private List<Simulation> simulations=new ArrayList<Simulation>();
14. private int numDerniereSimulation=0;
15. private Simulation simulation;
16.
17. // les vues
18. private boolean vueSaisiesIsRendered=true;
19. private boolean vueSimulationIsRendered;
20. private boolean vueSimulationsIsRendered;
21. private boolean vueSimulationsVidesIsRendered;
22. private boolean vueErreurIsRendered;
23.
24. // menus
25. private boolean menuFaireSimulationIsRendered=true;
26. private boolean menuEffacerSimulationIsRendered=true;
27. private boolean menuEnregistrerSimulationIsRendered;
28. private boolean menuVoirSimulationsIsRendered;
29. private boolean menuRetourSimulateurIsRendered;
30. private boolean menuTerminerSessionIsRendered=true;
31.
32. // gestion des vues
33. public void setVues(boolean vueSaisiesIsRendered, boolean vueSimulationIsRendered, boolean
vueSimulationsIsRendered, boolean vueSimulationsVidesIsRendered, boolean vueErreurIsRendered){
34. // état des vues
35. this.setVueSaisiesIsRendered(vueSaisiesIsRendered);
36. this.setVueSimulationIsRendered(vueSimulationIsRendered);
37. this.setVueSimulationsIsRendered(vueSimulationsIsRendered);
38. this.setVueSimulationsVidesIsRendered(vueSimulationsVidesIsRendered);
39. this.setVueErreurIsRendered(vueErreurIsRendered);
40. }
41. // gestion des menus
42. public void setMenu(boolean menuFaireSimulationIsRendered, boolean
menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean
menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered){
43. this.setMenuFaireSimulationIsRendered(menuFaireSimulationIsRendered);
44. this.setMenuEnregistrerSimulationIsRendered(menuEnregistrerSimulationIsRendered);
45. this.setMenuVoirSimulationsIsRendered(menuVoirSimulationsIsRendered);
46. this.setMenuEffacerSimulationIsRendered(menuEffacerSimulationIsRendered);
47. this.setMenuRetourSimulateurIsRendered(menuRetourSimulateurIsRendered);
48. this.setMenuTerminerSessionIsRendered(menuTerminerSessionIsRendered);
49. }
50.
51. // getters et setters

Introdution à Java EE
167/334
52. ...
53. }

Si la présence des champs simulations, numDerniereSimulation, simulation (lignes 13-15) dans la session se comprend, celle
des champs vues (lignes 18-22) et menu (lignes 25-30) est plus difficile à expliquer. Tout d'abord, on constate que si on laisse ces
options de menu dans le bean Form et que celui est de portée request, alors l'application ne fonctionne pas. Si on change la portée
du bean Form de request à session, alors l'application fonctionne de nouveau. C'est le point de départ de notre réflexion.
Essayons d'imaginer ce qui peut se passer.

Lorsque le formulaire est envoyé la première fois, l'utilisateur reçoit la page Html suivante :

L'utilisateur fait une simulation (vue partielle) :

Si le bean Form est de portée request et qu'il contient les vues et options de menu, l'utilisation du bouton [Enregistrer la
simulation] donne le résultat suivant :

On est ramenés à la page initiale tout en gardant les valeurs saisies. Tout semble indiquer que la procédure [Form].enregistrerSimulation
n'est pas exécutée. Cela peut être confirmé en insérant des logs dans le code. La question est donc de savoir pourquoi elle n'est pas
exécutée.

Le cycle de traitement du post JSF provoqué par le clic [Enregistrer la simulation] est le cycle standard suivant :

Introdution à Java EE
168/334
A B C

E D
F

• en [A] l'arbre des composants de la page envoyée lors du cycle précédent est reconstitué. Cet arbre était celui de la
simulation où :
• les vues [vueSaisie, vueSimulation] étaient visibles, les autres pas
• les options de menu [Effacer la simulation, Enregistrer la simulation, Terminer la session] étaient visibles, les
autres pas.
On sait que la page embarque un champ caché nommé [javax.faces.ViewState] dont la valeur est une chaîne de caractères
codée représentant l'arbre des composants envoyé au navigateur client. C'est le champ [javax.faces.ViewState] qui posté lui
aussi par le navigateur lors du POST, permet de reconstituer l'arbre des composants en A. Dans notre exemple, le champ
[javax.faces.ViewState] code l'état des vues [vueSaisie, vueSimulation] et des options de menu visibles dans ces vues. Il ne
code pas l'état des vues qui ont l'attribut rendered="false".
• en [B], les valeurs postées sont affectées à l'arbre des composants. Dans cette phase, des conversions peuvent être faites.
Ainsi si une valeur postée est associée à un champ de type int, une conversion String -> int a lieu. Une conversion peut
échouer. Dans ce cas, une erreur est associée au composant erroné mais on passe quand même à la phase [C].
• en [C], les valeurs postées qui doivent être validées le sont. Si l'une des conversions ou des validations a échoué, le cycle
JSF s'arrête et la page est renvoyée telle qu'elle a été postée.
• en [D], les valeurs postées sont affectées aux champs du modèle auxquels elles sont associées.
• en [E], la méthode associée à l'événement qui a provoqué le POST est exécutée et rend une clé de navigation au
contrôleur [Faces Servlet]
• en [F], le contrôleur [Faces Servlet] envoie la page correspondant à la clé de navigation reçue.

Ce que ne dit pas l'explication ci-dessus, c'est quand le bean ou modèle mis à jour dans la phase [D] est créé. Dans notre cas, le bean
[Form] est de portée request. Il est donc recréé à chaque nouvelle requête par une opération new Form(). A quel moment, on ne le
sait donc pas. Toujours est-il que la phase [D] met à jour un bean [Form] tout neuf. Cette mise à jour a bien lieu comme le montre
l'expérience suivante :

On change le code JSP pour faire afficher les valeurs du modèle sous les trois champs de saisie :

1. <h:panelGrid columns="3">
2. <!-- ligne 1 -->
3. <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
4. <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
5. <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
6. <!-- ligne 2 -->
7. <h:selectOneMenu id="comboEmployes" value="#{form.comboEmployesValue}">
8. <f:selectItems value="#{applicationData.employesItems}"/>
9. </h:selectOneMenu>
10. <h:inputText id="heuresTravaillées" value="#{form.heuresTravaillées}"
required="true" requiredMessage="#{msg['form.heuresTravaillées.required']}"
validatorMessage="#{msg['form.heuresTravaillées.validation']}">
11. <f:validateDoubleRange minimum="0" maximum="300"/>
12. </h:inputText>
13. <h:inputText id="joursTravaillés" value="#{form.joursTravaillés}" required="true"
requiredMessage="#{msg['form.joursTravaillés.required']}"
validatorMessage="#{msg['form.joursTravaillés.validation']}">
14. <f:validateLongRange minimum="0" maximum="31"/>
15. </h:inputText>
16. <!-- ligne 3 -->
17. <h:panelGroup>
18. <h:outputText value="#{form.comboEmployesValue}"/>
19. </h:panelGroup>
20. <h:panelGroup>
21. <h:outputText value="#{form.heuresTravaillées}"/>
22. <h:message for="heuresTravaillées" styleClass="error"/>
23. </h:panelGroup>

Introdution à Java EE
169/334
24. <h:panelGroup>
25. <h:outputText value="#{form.joursTravaillés}"/>
26. <h:message for="joursTravaillés" styleClass="error"/>
27. </h:panelGroup>
28. </h:panelGrid>

Les lignes 18, 21 et 25 affichent les valeurs du modèle. On fait l'expérience suivante sur deux cycles demande / réponse :

Cycle 1 : on fait une simulation

On obtient la page suivante :

En [1], on voit que le modèle [Form] a été correctement mis à jour. En [2], la présence de la vue [vueSimulation] montre que la
méthode [Form].faireSimulation a été exécutée.

Cycle 2 : on utilise le lien [Enregistrer la simulation] en changeant les valeurs saisies :

En [1], les valeurs saisies sont changées. En [2], elles sont postées par le lien [Enregistrer la simulation]. On obtient alors le résultat
suivant :

Introdution à Java EE
170/334
1

En [1], les valeurs du modèles sont bien les valeurs postées. Ce qui montre que la phase [D] du cycle JSF a été exécutée :

A B C

E D
F

La phase [E], elle, n'est pas exécutée pour une raison inconnue. Avec des logs, on peut montrer que le flux d'exécution ne passe pas
par la méthode [Form].enregistrerSimulation. On passe alors à la phase [F]. Celle-ci se déroule normalement : elle affiche le modèle
[Form]. Comme la méthode [Form].enregistrerSimulation. n'a pas été exécutée, le modèle [Form] est celui issu de l'opération new Form()
exécutée pour recréer le bean de portée request. Or ce bean est configuré par défaut de la façon suivante :

1. // les vues
2. private boolean vueSaisiesIsRendered=true;
3. private boolean vueSimulationIsRendered=false;
4. private boolean vueSimulationsIsRendered=false;
5. private boolean vueSimulationsVidesIsRendered=false;
6. private boolean vueErreurIsRendered=false;
7.
8. // menus
9. private boolean menuFaireSimulationIsRendered=true;
10. private boolean menuEffacerSimulationIsRendered=true;
11. private boolean menuEnregistrerSimulationIsRendered=false;
12. private boolean menuVoirSimulationsIsRendered=false;
13. private boolean menuRetourSimulateurIsRendered=false;
14.private boolean menuTerminerSessionIsRendered=true;

Il affiche donc :
• la vue [vueSaisies]
• les liens [Faire la simulation, Effacer la simulation, Terminer la session]

Ce qui explique la page reçue. Toute la question est donc de savoir pourquoi la méthode [Form].enregistrerSimulation. associée au lien
[Enregistrer la simulation] n'a pas été exécutée. Une hypothèse pourrait être la suivante :

• dans le modèle [Form] issu de l'opération new Form(), le champ menuEnregistrerSimulationIsRendered est initialisé à false:
• dans la phase [E] l'événement "il y a eu un clic sur le lien Enregistrer la simulation" est peut-être ignoré parce que dans le
modèle [Form], ce lien est désactivé.

De façon plus générale, le modèle [Form] issu de l'opération new Form() n'est pas celui qui a été utilisé pour générer la page envoyée
précédemment au navigateur client :
• les vues cachées / montrées ne sont pas les mêmes
• les liens actifs / inactifs ne sont pas les mêmes

Introdution à Java EE
171/334
Dès qu'on redonne la portée session au modèle [Form], l'application fonctionne correctement. Cette fois-ci, le modèle Form utilisé
au début du cycle n, est bien le même que celui utilisé à la fin du cycle n-1 pour générer la réponse au navigateur. Pour reproduire
ce fonctionnement tout en donnant une portée request au modèle [Form], on décide donc de mettre en session l'état des vues et
des liens du modèle [Form] dans le bean #{sessionData}.

Ce bean qui mémorise les états des vues et des liens leur donne une valeur par défaut correspondant à la valeur qu'ils doivent avoir
pour la requête initiale :

1.// les vues


2. private boolean vueSaisiesIsRendered=true;
3. ...
4.
5. // menus
6. private boolean menuFaireSimulationIsRendered=true;
7. private boolean menuEffacerSimulationIsRendered=true;
8. ...
9. private boolean menuTerminerSessionIsRendered=true;

• ligne 2 : dans la page initiale, seule la vue [vueSaisie] est affichée


• lignes 6, 7 et 9 : dans la page initiale, seuls les liens [Faire la simulation, Effacer la simulation, Terminer la session] sont
actifs.

18.4 Le bean Form


Le bean [Form] désormais de portée request évolue de la façon suivante :

1. package web.beans.request;
2.
3. ...
4. public class Form {
5.
6. public Form() {
7. }
8.
9. // autres beans
10. private ApplicationData applicationData;
11. private SessionData sessionData;
12.
13. // le modèle des vues
14. private String comboEmployesValue="";
15. private String heuresTravaillées="";
16. private String joursTravaillés="";
17. private Integer numSimulationToDelete;
18. private List<Erreur> erreurs=new ArrayList<Erreur>();
19.
20.
21. // action du menu
22. public String faireSimulation(){
23. ...
24. }
25.
26. public String enregistrerSimulation(){
27. // on récupère dans la session le n° de la dernière simulation
28. getSessionData().setNumDerniereSimulation(getSessionData().getNumDerniereSimulation()+1);
29. // on l'affecte à la simulation qu'on va enregistrer
30. getSessionData().getSimulation().setNum(getSessionData().getNumDerniereSimulation());
31. // on ajoute aux simulations la dernière simulation que l'utilisateur a faite
32. getSessionData().getSimulations().add(getSessionData().getSimulation());
33. // on affiche le formulaire des simulations
34. return voirSimulations();
35. }
36.
37. public String effacerSimulation(){
38. ...
39. }
40.
41. public String voirSimulations(){
42. // affichage vue simulations
43. getSessionData().setVues(false,false,getSessionData().getSimulations().size()>0,getSessionData
().getSimulations().size()==0,false);
44. // menus
45. getSessionData().setMenu(false,false,false,false,true,true);
46. // navigation
47. return null;

Introdution à Java EE
172/334
48. }
49.
50. public String retourSimulateur(){
51. ...
52. }
53.
54. public String terminerSession(){
55. ...
56. }
57.
58. public String retirerSimulation(){
59. ...
60. }
61.
62.
63. // getters et setters
64. ...
65. }

• lignes 10-11 : les références sur les beans de type ApplicationData (ligne 10) et SessionData (ligne 11). Rappelons que ces
deux champs sont initialisés après instanciation de la classe Form, par injection de dépendances par le framework JSF lui-
même. Ceci est dû à la configuration faite dans [faces-config.xml] :

1. ...
2. <faces-config ...>
3. <managed-bean>
4. <managed-bean-name>form</managed-bean-name>
5. <managed-bean-class>web.beans.request.Form</managed-bean-class>
6. <managed-bean-scope>request</managed-bean-scope>
7. <managed-property>
8. <property-name>applicationData</property-name>
9. <value>#{applicationData}</value>
10. </managed-property>
11. <managed-property>
12. <property-name>sessionData</property-name>
13. <value>#{sessionData}</value>
14. </managed-property>
15. </managed-bean>
16.

Les lignes 7-10 ci-dessus provoquent l'initialisation du champ applicationData avec une référence sur le bean
#{applicationData} et les lignes 11-14, celles du champ sessionData avec une référence sur le bean #{sessionData}.

La méthode enregistrerSimulation des lignes 26-35 montre le rôle joué par le bean #{sessionData} :

• ligne 28 : incrémente le n° de la dernière simulation - cette information est maintenue par le bean #{sessionData}
• ligne 30 : ce n° est affecté à la simulation qui va être enregistrée. Celle-ci est dans le bean #{sessionData}. Elle y a été
placée par la méthode faireSimulation des lignes 22-24.
• ligne 32 : la simulation qui doit être enregistrée est mise dans la liste des simulations, elle aussi gérée par le bean
#{sessionData}.
• ligne 34 : le flux d'exécution est passé à la méthode voirSimulations car une fois que la simulation a été enregistrée,
l'application montre la liste des simulations.
• ligne 43 : les vues qui doivent être affichées / cachées sont gérées. Cette information est maintenue par le bean
#{sessionData}.
• ligne 45 : il est fait de même avec les options du menu
• ligne 47 : la clé de navigation null rendue au contrôleur [Faces Servlet] fait que celui-ci va renvoyer le bean Form comme
réponse au client.

Question : compléter le code du bean [Form].

Travail pratique : tester cette nouvelle version.

18.5 Intégration de la couche web dans une architecture 3 couches Jsf / Ejb
L'architecture de l'application web précédente était la suivante :

Introdution à Java EE
173/334
Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java couche
V1 [metier]
4 [V] Modèle M
V2 JSP Gestionnaires
simulée
form d'évts
Vn

Nous remplaçons la couche [métier] simulée, par les couches [métier, dao, jpa] implémentées par des Ejb au paragraphe 13.2, page
85 :

Application web
couche [web]
2a 2b
1
Faces Servlet [MC]
3 Form.java
V1 couches
4 [V] Modèle M [metier, dao, jpa]
V2 JSP Gestionnaires
form d'évts
Vn

Travail pratique : réaliser l'intégrations des couches Jsf et Ejb en suivant la méthodologie du paragraphe 16, page 128.

19 Version 9 - Application web PAM multi-vues / multi-pages


L'architecture précédente atteint vite ses limites :
• dès qu'une application a plus de 10 vues, la page [form.jsp] devient complexe avec ses nombreuses vues <f:subview> et
son modèle [Form.java] s'alourdit à la fois par ajout de champs pour le modèle et de méthodes pour la gestion des
événements.
• elle est viable tant qu'un unique développeur est suffisant mais se prête mal au travail en équipe puisque les développeurs
doivent se partager le même modèle [Form.java].

Néanmoins, elle est bien adaptée aux projets de quelques vues. Nous présentons maintenant une architecture offrant davantage de
possibilités d'évolution où :

• la page envoyée au client sera générée par une unique page JSP, appelée page maître. Nous l'appelerons ici masterPage.jsp.
• la page masterPage.jsp sera constituée de parties générés par d'autres pages JSP. Ici, la page aura deux parties :

• la partie [1] de la page sera générée par la page [entete.jsp]. Elle sera toujours présente.

Introdution à Java EE
174/334
la partie [2] de la page va changer selon ce qu'on veut présenter à l'utilisateur. C'est la partie variable de la page

maître. Elle sera générée par les pages [simulation.jsp, simulations.jsp, simulationsvides.jsp, erreur.jsp, saisies.jsp].
Nous dirons que la page masterPage.jsp est constituée de deux vues V1 et Vi, même si pour l'utilisateur, la page reçue ne
forme qu'une vue. Nous prenons ici un point de vue de développeur. La vue V1 sera toujours générée par [entete.jsp]. La
vue Vi dépendra du contexte.
• une vue V sera associée à une page JSP [V.jsp] et à un modèle M et contrôleur C réunis dans une même classe (bean) MC.
Le bean MC contiendra à la fois le modèle de la vue V et les gestionnaires des événements se produisant à partir de celle-
ci.

Les associations vue V (page Jsp), bean MC (classe Java) seront les suivantes :

vue V bean MC rôle


entete.jsp présente les options de menu
BeanMasterPage
saisies.jsp présente les saisies
BeanSimulation
simulation.jsp présente la simulation faite à partir des saisies
BeanSimulation
simulations.jsp présente la liste des simulations
BeanSimulations
simulationsvides présente une page statique avec le message indiquant que la liste des simulations
est vide
erreur.jsp présente une page d'erreur
BeanErreur

L'architecture proposée est la suivante :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3 couche
V1 [metier]
4 [V] MC2
V2 JSP simulée
masterpage MCn
Vn

Pour la demande GET initiale :


• [Faces Servlet] reçoit une demande GET d'affichage de la page [masterpage.jsp] en [1]
• il fait afficher celle-ci en [3]. La page [masterpage.jsp] sera formée des vues [entete.jsp] et [saisies.jsp]. Chacune de ces vues
utilisera son propre modèle MC pour s'initialiser.

Pour les demandes POST qui suivent :


• [Faces Servlet] reçoit une demande POST pour la page [masterpage.jsp] en [1]
• l'événement qui a provoqué le POST est traité par une méthode de l'un des contrôleurs MC. La page qui provoque le
POST sera toujours constitué de deux vues [V1=entete.jsp,Vi].
• si l'événement peut être rattaché à la vue Vi, alors on fera traiter l'événement par le modèle / contrôleur MC de la
vue Vi. Par exemple, si la vue était [simulations.jsp] qui présente le tableau des simulations, et si l'événement
provient d'un clic sur l'un des liens "Retirer" du tableau, alors cet événement sera géré par le bean BeanSimulations
qui est associé à la vue [simulations.jsp].
• si l'événement ne vise qu'à changer de vue Vi, alors il sera traité par le bean BeanMasterPage associé à la page
maître [masterPage.jsp]. C'est le cas par exemple du clic sur le lien "Voir les simulations" qui a pour effet de
passer de la vue Vi=simulation.jsp qui présente à l'utilisateur la simulation qu'il vient de faire à la vue
Vj=simulations.jsp qui présente la liste de toutes les simulations qu'il a faites. Nous parlerons alors d'action de
navigation.
• une fois que l'événement ayant provoqué le POST a été traité par un bean MC, celui-ci
• mettra à jour le modèle MCj de la vue Vj qui doit faire partie de la page [entete.jsp, Vj] qui va être envoyée en
réponse au navigateur client
• mettra à jour le modèle BeanMasterPage de [masterPage.jsp] afin que celle-ci soit constituée des vues [entete.jsp,
Vj]

Introdution à Java EE
175/334
• rendra comme clé de navigation la clé "masterpage" qui forcera le réaffichage de la page masterpage.jsp.
• [Faces Servlet] ayant reçu la clé "masterpage", fera afficher de nouveau [masterpage.jsp] en [3]. La page utilise son
modèle [BeanMasterPage] pour initialiser ses parties dynamiques. En [4], la page constituée des parties [entete.jsp, Vj] sera
envoyée au client.

19.1 Le projet Netbeans


Le projet Netbeans de cette version est le suivant :

1 6

• en [1], les fichiers de configuration


• en [2], les pages Jsp et la feuille de style
• en [3], les classes de la couche [web].
• en [4], le fichier des messages pour l'internationalisation de l'application
• en [5], les objets échangés entre la couche [web] et la couche [métier] et la couche [métier] elle-même
• en [6], les bibliothèques de l'application

19.2 La configuration de l'application


On se souvient que dans la version précédente, le fichier [faces-config.xml] définissait trois beans :

• applicationData qui maintenait les informations de portée application


• sessionData qui maintenait les informations de portée session
• Form qui servait de modèle et de contrôelur à la page [form.jsp]. Sa durée de vie était celle de la requête.

Dans la nouvelle version, nous avons plusieurs pages Jsp et nous associons à chacune d'elles un bean qui leur sert de modèle et de
contrôleur. Le fichier [faces-config.xml] est le suivant :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <managed-bean>
5. <managed-bean-name>beanSimulation</managed-bean-name>
6. <managed-bean-class>web.beans.request.BeanSimulation</managed-bean-class>
7. <managed-bean-scope>request</managed-bean-scope>
8. <managed-property>
9. <property-name>applicationData</property-name>
10. <value>#{applicationData}</value>

Introdution à Java EE
176/334
11. </managed-property>
12. <managed-property>
13. <property-name>beanSimulations</property-name>
14. <value>#{beanSimulations}</value>
15. </managed-property>
16. <managed-property>
17. <property-name>beanMasterPage</property-name>
18. <value>#{beanMasterPage}</value>
19. </managed-property>
20. <managed-property>
21. <property-name>beanErreur</property-name>
22. <value>#{beanErreur}</value>
23. </managed-property>
24. </managed-bean>
25.
26. <managed-bean>
27. <managed-bean-name>beanSimulations</managed-bean-name>
28. <managed-bean-class>web.beans.session.BeanSimulations</managed-bean-class>
29. <managed-bean-scope>session</managed-bean-scope>
30. <managed-property>
31. <property-name>applicationData</property-name>
32. <value>#{applicationData}</value>
33. </managed-property>
34. <managed-property>
35. <property-name>beanMasterPage</property-name>
36. <value>#{beanMasterPage}</value>
37. </managed-property>
38. </managed-bean>
39.
40. <managed-bean>
41. <managed-bean-name>beanMasterPage</managed-bean-name>
42. <managed-bean-class>web.beans.session.BeanMasterPage</managed-bean-class>
43. <managed-bean-scope>session</managed-bean-scope>
44. </managed-bean>
45.
46. <managed-bean>
47. <managed-bean-name>beanErreur</managed-bean-name>
48. <managed-bean-class>web.beans.request.BeanErreur</managed-bean-class>
49. <managed-bean-scope>request</managed-bean-scope>
50. </managed-bean>
51.
52. <managed-bean>
53. <managed-bean-name>locale</managed-bean-name>
54. <managed-bean-class>web.utils.ChangeLocale</managed-bean-class>
55. <managed-bean-scope>application</managed-bean-scope>
56. </managed-bean>
57.
58. <managed-bean>
59. <managed-bean-name>applicationData</managed-bean-name>
60. <managed-bean-class>web.beans.application.ApplicationData</managed-bean-class>
61. <managed-bean-scope>application</managed-bean-scope>
62. </managed-bean>
63.
64.
65. <application>
66. <resource-bundle>
67. <base-name>
68. messages
69. </base-name>
70. <var>msg</var>
71. </resource-bundle>
72. <message-bundle>messages</message-bundle>
73. </application>
74.
75. <navigation-rule>
76. <from-view-id>/masterPage.jsp</from-view-id>
77. <navigation-case>
78. <from-outcome>masterpage</from-outcome>
79. <to-view-id>/masterPage.jsp</to-view-id>
80. </navigation-case>
81. </navigation-rule>
82. </faces-config>

• lignes 58-62 : le bean applicationData tel qu'il existait dans la version précédente. Il n'a pas changé. Il est de portée
application.
• lignes 40-44 : le bean beanMasterPage est le modèle / contrôleur de la page masterPage.jsp. Il est de portée session.
• lignes 4-24 : le bean beanSimulation est le modèle / contrôleur de la page simulation.jsp. Il est de portée request.
• lignes 26-38 : le bean beanSimulations est le modèle / contrôleur de la page simulations.jsp. Il est de portée session.
• lignes 46-50 : le bean beanErreur est le modèle / contrôleur de la page erreur.jsp. Il est de portée request.

Introdution à Java EE
177/334
Certains de ces beans ont des références sur d'autres beans. Nous y reviendrons lorsque nous les examinerons plus en détail.

• lignes 75-81 : il n'y a qu'une règle de navigation : chaque gestionnaire d'événement rendra au contrôleur [Faces Servlet] la
clé masterpage afin de faire afficher la page masterPage.jsp. On peut se demander si cette règle de navigation est bien
nécessaire. Après tout, on sait que si un gestionnaire d'événement rend la clé null au contrôleur [Faces Servlet], celui-ci
réaffiche la page qui a provoqué le POST, donc masterPage.jsp. Néanmoins ce mode de navigation ne convient pas ici.
Nous essaierons de comprendre pourquoi.

19.3 Les vues Jsp et leurs beans modèles / contrôleurs

19.3.1 La page maître [masterPage.jsp]

La page maître est la suivante :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10.
11. <f:view>
12. <html>
13. <head>
14. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
15. <title><h:outputText value="#{msg['form.titre']}"/></title>
16. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
17. </head>
18. <body background="<c:url value="/ressources/standard.jpg"/>">
19. <h:form id="formulaire">
20. <!-- entete -->
21. <c:import url="entete.jsp"/>
22. <!-- contenu -->
23. <c:import url="${beanMasterPage.vue}"/>
24. </h:form>
25. </body>
26. </html>
27. </f:view>

La page maître est composée de deux vues :


• ligne 21 : l'entête de la page généré par [entete.jsp]. Elle est insérée dans la page par une directive <c:import>. La
bibliothèque de balises jstl/core nécessaire est déclarée ligne 6.
• ligne 23 : une vue dont le nom est défini par le champ vue du bean beanMasterPage. On notera la syntaxe de
l'expression ${beanMasterPage.vue}. C'est une expression JSTL et non JSF.

En changeant la valeur de l'expression ${beanMasterPage.vue} on change la nature de la page envoyée au navigateur client. Ainsi
si la page est constituée des parties [entete.jsp, saisies.jsp], le navigateur affiche la page suivante :

alors que si elle est constituée des parties [entete.jsp, erreur.jsp], le navigateur affiche la page suivante :

Introdution à Java EE
178/334
Le bean BeanMasterPage qui sert de modèle et de contrôleur à la page [masterPage.jsp] est le suivant :

1. package web.beans.session;
2.
3. ...
4. public class BeanMasterPage {
5.
6. public BeanMasterPage() {
7. }
8.
9.
10. // menus
11. private boolean menuFaireSimulationIsRendered = true;
12. private boolean menuEffacerSimulationIsRendered = true;
13. private boolean menuEnregistrerSimulationIsRendered;
14. private boolean menuVoirSimulationsIsRendered;
15. private boolean menuRetourSimulateurIsRendered;
16. private boolean menuTerminerSessionIsRendered = true;
17.
18. // la vue JSP à afficher
19. private String vue = "saisies.jsp";
20.
21. // gestion des menus
22. public void setMenu(boolean menuFaireSimulationIsRendered, boolean
menuEnregistrerSimulationIsRendered, boolean menuEffacerSimulationIsRendered, boolean
menuVoirSimulationsIsRendered, boolean menuRetourSimulateurIsRendered, boolean
menuTerminerSessionIsRendered) {
23. this.setMenuFaireSimulationIsRendered(menuFaireSimulationIsRendered);
24. ...
25. }
26.
27.
28. public String retourSimulateur() {
29. ...
30. // navigation
31. setVue("saisies.jsp");
32. return "masterpage";
33. }
34.
35. public String terminerSession() {
36. ...
37. // navigation
38. setVue("saisies.jsp");
39. return "masterpage";
40. }
41.
42. // voir la liste des simulations
43. public String voirSimulations() {
44. ...
45. // navigation
46. setVue("simulations.jsp");
47. return "masterpage";
48. }
49.
50. // getters et setters
51. ...
52. }

Le bean BeanMasterPage sert à deux choses :

• mémoriser l'état des menus de la vue [entete.jsp]. C'est pour cette raison que la portée du bean est la session dans [faces-
config.xml] (cf page 176). La mémorisation de l'état des menus est faite par les champs des lignes 11-16.

Introdution à Java EE
179/334
• fixer la vue V qui doit être affichée par la page [masterPage.jsp] composée de deux parties [entete.jsp, V]. Le nom de la vue
V est fixé par le champ vue de la ligne 19. La première vue V à être affichée, au moment du GET initial, est la vue
[saisies.jsp].

Le bean BeanMasterPage traite trois événements :

• ligne 28 : le clic sur le lien [Retour au simulateur] :


• ligne 35 : le clic sur le lien [Terminer la session] :

• ligne 43 : le clic sur le lien [Voir les simulations] :

Ces trois événements ont pour but premier de faire apparaître une autre vue. C'est ce que nous avons appelé précédemment une
action de navigation. Nous faisons traiter ce type d'actions par le bean BeanMasterPage. Nous aurions pu faire d'autres choix.

19.3.2 La page [entete.jsp]

La page [entete.jsp] est celle qui génère les options du menu. Celles-ci sont présentes sur toutes les pages envoyées au navigateur.

Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10. <script language="javascript">
11. function raz(){
12. // on change les valeurs postées
13. document.forms['formulaire'].elements['formulaire:comboEmployes'].value="0";
14. document.forms['formulaire'].elements['formulaire:heuresTravaillées'].value="0";
15. document.forms['formulaire'].elements['formulaire:joursTravaillés'].value="0";

Introdution à Java EE
180/334
16. }
17. </script>
18.
19. <!-- entete -->
20. <h:panelGrid columns="2">
21. <h:panelGroup>
22. <h2><h:outputText value="#{msg['form.titre']}"/></h2>
23. </h:panelGroup>
24. <h:panelGroup>
25. <h:panelGrid columns="1">
26. <h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}"
action="#{beanSimulation.faireSimulation}"
rendered="#{beanMasterPage.menuFaireSimulationIsRendered}"/>
27. <h:commandLink id="cmdEffacerSimulation" value="#{msg['form.menu.effacerSimulation']}"
action="#{beanSimulation.effacerSimulation}"
rendered="#{beanMasterPage.menuEffacerSimulationIsRendered}" onclick="raz()"/>
28. <h:commandLink id="cmdEnregistrerSimulation"
value="#{msg['form.menu.enregistrerSimulation']}"
action="#{beanSimulations.enregistrerSimulation}"
rendered="#{beanMasterPage.menuEnregistrerSimulationIsRendered}"/>
29. <h:commandLink id="cmdVoirSimulations" value="#{msg['form.menu.voirSimulations']}"
action="#{beanMasterPage.voirSimulations}" immediate="true"
rendered="#{beanMasterPage.menuVoirSimulationsIsRendered}"/>
30. <h:commandLink id="cmdRetourSimulateur" value="#{msg['form.menu.retourSimulateur']}"
action="#{beanMasterPage.retourSimulateur}"
rendered="#{beanMasterPage.menuRetourSimulateurIsRendered}"/>
31. <h:commandLink id="cmdTerminerSession" immediate="true"
value="#{msg['form.menu.terminerSession']}" action="#{beanMasterPage.terminerSession}"
rendered="#{beanMasterPage.menuTerminerSessionIsRendered}"/>
32. </h:panelGrid>
33. </h:panelGroup>
34. </h:panelGrid>
35. <hr/>

C'est un code qui était déjà présent dans la page [form.jsp] de la version précédente. On notera les points suivants :

• [entete.jsp] est insérée dans une autre page Jsp, à savoir [masterPage.jsp] :

1. ...
2. <f:view>
3. <html>
4. <head>
5. ...
6. </head>
7. <body background="<c:url value="/ressources/standard.jpg"/>">
8. <h:form id="formulaire">
9. <!-- entete -->
10. <c:import url="entete.jsp"/>
11. <!-- contenu -->
12. <c:import url="${beanMasterPage.vue}"/>
13. </h:form>
14. </body>
15. </html>
16. </f:view>

Ligne 10, le contenu de [entete.jsp] est inséré dans une balise <h:form>. C'est pourquoi dans [entete.jsp], ne trouvons-
nous pas les balises <f:view>,<html>,<body>,<h:form> déjà présentes dans [masterPage.jsp].
• chaque lien définit son modèle et son contrôleur, par exemple :

<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}"


action="#{beanSimulation.faireSimulation}"
rendered="#{beanMasterPage.menuFaireSimulationIsRendered}"/>

19.3.3 La page [saisies.jsp]

La page [saisies.jsp] est celle qui génère le panneau des saisies :

Introdution à Java EE
181/334
Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10. <!-- saisie -->
11. <h:panelGrid columns="3">
12. <!-- ligne 1 -->
13. <h:outputText value="#{msg['form.comboEmployes.libellé']}"/>
14. <h:outputText value="#{msg['form.heuresTravaillées.libellé']}"/>
15. <h:outputText value="#{msg['form.joursTravaillés.libellé']}"/>
16. <!-- ligne 2 -->
17. <h:selectOneMenu id="comboEmployes" value="#{beanSimulation.comboEmployesValue}">
18. <f:selectItems value="#{applicationData.employesItems}"/>
19. </h:selectOneMenu>
20. <h:inputText id="heuresTravaillées" value="#{beanSimulation.heuresTravaillées}" required="true"
requiredMessage="#{msg['form.heuresTravaillées.required']}"
validatorMessage="#{msg['form.heuresTravaillées.validation']}">
21. <f:validateDoubleRange minimum="0" maximum="300"/>
22. </h:inputText>
23. <h:inputText id="joursTravaillés" value="#{beanSimulation.joursTravaillés}" required="true"
requiredMessage="#{msg['form.joursTravaillés.required']}"
validatorMessage="#{msg['form.joursTravaillés.validation']}">
24. <f:validateLongRange minimum="0" maximum="31"/>
25. </h:inputText>
26. <!-- ligne 3 -->
27. <h:panelGroup>
28. </h:panelGroup>
29. <h:panelGroup>
30. <h:message for="heuresTravaillées" styleClass="error"/>
31. </h:panelGroup>
32. <h:panelGroup>
33. <h:message for="joursTravaillés" styleClass="error"/>
34. </h:panelGroup>
35. </h:panelGrid>
36. <hr/>

Ce code est celui de la version précédente. Il n'y a qu'un seul changement : le modèle qui était le bean #{form} est désormais le
bean #{beanSimulation}. Au POST, les valeurs saisies seront enregistrées dans les champs
#{beanSimulation.comboEmployesValue} (ligne 17), #{beanSimulation.heuresTravaillées} (ligne 20),
#{beanSimulation.joursTravaillés} (ligne 23).

Le modèle / contrôleur #{beanSimulation} est le suivant :

1. package web.beans.request;
2.
3. ...
4. public class BeanSimulation {
5.
6. public BeanSimulation() {
7. }
8. // autres beans
9. private ApplicationData applicationData;
10. private BeanMasterPage beanMasterPage;
11. private BeanErreur beanErreur;
12. private BeanSimulations beanSimulations;
13.
14. // le modèle des vues
15. private String comboEmployesValue = "";

Introdution à Java EE
182/334
16. private String heuresTravaillées = "";
17. private String joursTravaillés = "";
18.
19. // actions du menu
20. public String faireSimulation() {
21. try {
22. // on calcule la feuille de salaire
23. ...
24. // on met la simulation dans le bean #{beanSimulations}
25. ...
26. // on met à jour le menu
27. getBeanMasterPage().setMenu(...);
28. // on affiche la simulation
29. getBeanMasterPage().setVue("simulation.jsp");
30. } catch (Throwable th) {
31. // on crée la liste des erreurs dans le bean #{beanErreur}
32. ...
33. // on affiche la vue Erreur
34. getBeanMasterPage().setVue("erreur.jsp");
35. // on met à jour le menu
36. getBeanMasterPage().setMenu(...);
37. }
38. // on rend la même page
39. return "masterpage";
40. }
41.
42. public String effacerSimulation() {
43. // on rend la page de saisies vide
44. comboEmployesValue="";
45. heuresTravaillées="";
46. joursTravaillés="";
47. // mise à jour du menu dans le bean #{beanMasterPage}
48. getBeanMasterPage().setMenu(...);
49. // on affiche la vue des saisies
50. getBeanMasterPage().setVue("saisies.jsp");
51. return "masterpage";
52. }
53.
54. // getters et setters
55. ...
56.}

• lignes 15-17 : le modèle de la page [saisies.jsp]


• lignes 20-40 : la méthode qui traite l'événement clic sur le lien "Faire la simulation" de [entete.jsp] (cf attribut action ci-
dessous):

<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}"


action="#{beanSimulation.faireSimulation}"
rendered="#{beanMasterPage.menuFaireSimulationIsRendered}"/>

• lignes 42-52 : la méthode qui traite l'événement clic sur le lien "Effacer la simulation" de [entete.jsp] (cf attribut action ci-
dessous):

<h:commandLink id="cmdEffacerSimulation" value="#{msg['form.menu.effacerSimulation']}"


action="#{beanSimulation.effacerSimulation}"
rendered="#{beanMasterPage.menuEffacerSimulationIsRendered}" onclick="raz()"/>

• lignes 9-12 : référencent les beans nécessaires aux méthodes de la classe :


• le bean #{applicationData} est nécessaire à la méthode [faireSimulation] pour obtenir une référence sur la
couche [métier]
• le bean #{beanErreur} est nécessaire à la méthode [faireSimulation] s'il se produit une erreur lors du calcul de la
simulation.
• le bean #{beanSimulations} est nécessaire à la méthode
• faireSimulation pour y placer la simulation qui aura été faite afin qu'elle reste dans la session de l'utilisateur
• enregistrerSimulation pour placer la simulation précédente dans la liste des simulations enregistrées
• le bean #{beanMasterPage} est nécessaire à toutes les méthodes pour gérer l'état du menu.

Ces références sur les beans sont instanciées par le moteur JSF à cause de la configuration du bean #{beanSimulation}
faite dans [faces-config.xml] :

1. <managed-bean>
2. <managed-bean-name>beanSimulation</managed-bean-name>
3. <managed-bean-class>web.beans.request.BeanSimulation</managed-bean-class>
4. <managed-bean-scope>request</managed-bean-scope>
5. <managed-property>

Introdution à Java EE
183/334
6. <property-name>applicationData</property-name>
7. <value>#{applicationData}</value>
8. </managed-property>
9. <managed-property>
10. <property-name>beanSimulations</property-name>
11. <value>#{beanSimulations}</value>
12. </managed-property>
13. <managed-property>
14. <property-name>beanMasterPage</property-name>
15. <value>#{beanMasterPage}</value>
16. </managed-property>
17. <managed-property>
18. <property-name>beanErreur</property-name>
19. <value>#{beanErreur}</value>
20. </managed-property>
21.</managed-bean>

19.3.4 La page [simulation.jsp]

La page [simulation.jsp] est celle qui génère la vue de la simulation :

Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10. <!-- saisies -->
11. <c:import url="saisies.jsp"/>

Introdution à Java EE
184/334
12.
13. <!-- simulation -->
14. <!-- informations Employé -->
15. <h:outputText value="#{msg['form.infos.employé']}" styleClass="titreInfos"/>
16. <br/><br/>
17. <h:panelGrid columns="3" rowClasses="libelle,info">
18. <!-- ligne 1 -->
19. <h:outputText value="#{msg['form.employe.nom']}"/>
20. <h:outputText value="#{msg['form.employe.prénom']}"/>
21. <h:outputText value="#{msg['form.employe.adresse']}"/>
22. <!-- ligne 2 -->
23. <h:outputText value="#{beanSimulations.simulation.feuilleSalaire.employe.nom}"/>
24. <h:outputText value="#{beanSimulations.simulation.feuilleSalaire.employe.prenom}"/>
25. <h:outputText value="#{beanSimulations.simulation.feuilleSalaire.employe.adresse}"/>
26. </h:panelGrid>
27. ...
28. <!-- informations Cotisations -->
29. ...
30. <!-- informations Indemnités -->
31. ...
32. <!-- informations Salaire -->
33. ...
34. <!-- Salaire net-->
35. ...

• ligne 11 : la vue [simulation.jsp] inclut la vue [saisies.jsp] afin que l'utilisateur continue à voir ses saisies pour
éventuellement les modifier.
• le code Jsf qui affiche la simulation est celui de la version précédente au changement près suivant : le modèle #{form} a
été remplacé par le modèle #{beanSimulations} (lignes 23-25 par exemple) qui contient la simulation affichée par la vue
(ligne 15 ci-dessous) :

1. package web.beans.session;
2.
3. ...
4. public class BeanSimulations {
5.
6. public BeanSimulations() {
7. }
8.
9. // autres beans
10. private BeanMasterPage beanMasterPage;
11.
12. // champs modèle formulaire
13. private List<Simulation> simulations = new ArrayList<Simulation>();
14. private int numDerniereSimulation = 0;
15. private Simulation simulation;
16. private Integer numSimulationToDelete;
17.
18. // retirer une simulation
19. public String retirerSimulation() {
20. ..
21. }
22.
23. // getters et setters
24. ...
25. }

19.3.5 La page [simulations.jsp]

La page [simulations.jsp] est celle qui génère la vue présentant la liste des simulations :

Introdution à Java EE
185/334
Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10. <!-- tableau des simulations -->
11. <h:dataTable value="#{beanSimulations.simulations}" var="simulation"
12. headerClass="simulationsHeaders"
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisat
ionsSociales,simuSalaireNet">
13. <h:column>
14. <f:facet name="header">
15. <h:outputText value="#{msg['simulations.headers.numero']}"/>
16. </f:facet>
17. <h:outputText value="#{simulation.num}"/>
18. </h:column>
19. ...
20. <h:column>
21. <h:commandLink value="Retirer" action="#{beanSimulations.retirerSimulation}">
22. <f:setPropertyActionListener target="#{beanSimulations.numSimulationToDelete}"
value="#{simulation.num}"/>
23. </h:commandLink>
24. </h:column>
25. </h:dataTable>

Ce code est celui de la version précédente au détail près suivant : le bean #{form} a été remplacé par le bean #{beanSimulations}.
Celui-ci est le suivant :

26. package web.beans.session;


27.
28. ...
29. public class BeanSimulations {
30.
31. public BeanSimulations() {
32. }
33.
34. // autres beans
35. private BeanMasterPage beanMasterPage;
36.
37. // champs modèle formulaire
38. private List<Simulation> simulations = new ArrayList<Simulation>();
39. private int numDerniereSimulation = 0;
40. private Simulation simulation;
41. private Integer numSimulationToDelete;
42.
43. // retirer une simulation
44. public String retirerSimulation() {
45. // on retire la simulation de n° numSimulationToDelete
46. ...
47. // navigation
48. getBeanMasterPage().setVue(simulations.size() == 0 ? "simulationsvides.jsp" :
"simulations.jsp");
49. return "masterpage";
50. }
51.
52. // enregistrer une simulation
53. public String enregistrerSimulation() {
54. // on incrémente le n° de la dernière simulation
55. ...
56. // on l'affecte à la simulation qu'on va enregistrer
57. ...
58. // on ajoute la simulation à la liste actuelle des simulations
59. ...
60. // option de menu
61. getBeanMasterPage().setMenu(false, false, false, false, true, true);
62. // on affiche le formulaire des simulations
63. getBeanMasterPage().setVue("simulations.jsp");
64. return "masterpage";
65. }
66.
67. // getters et setters
68. ...
69. }

Introdution à Java EE
186/334
Rappelons que ce bean est de portée session (cf configuration page 176).

• ligne 13 : la liste des simulations faites par l'utilisateur


• ligne 14 : le n° de la dernière simulation faite - est incrémenté à chaque nouvelle simulation
• ligne 15 : la dernière simulation faite par l'utilisateur - est placée là par la méthode #{beanSimulation}.faireSimulation.
• ligne 16 : le n° de la simulation à retirer - est placé là à cause du code Jsf des liens [Retirer] du tableau des simulations :

1. <h:column>
2. <h:commandLink value="Retirer" action="#{beanSimulations.retirerSimulation}">
3. <f:setPropertyActionListener target="#{beanSimulations.numSimulationToDelete}"
value="#{simulation.num}"/>
4. </h:commandLink>
5. </h:column>

La ligne 3 ci-dessus indique le n° de la simulation #{simulation.num} doit être placé dans le champ
#{beanSimulations.numSimulationToDelete} lorsque le lien est cliqué.
• ligne 10 : une référence sur le bean #{beanMasterPage}. Celui-ci est nécessaire pour la gestion du menu. Ce champ est
initialisé par le moteur Jsf à cause de la configuration faite pour le bean #{beanSimulations} dans [faces-config.xml] :

1. <managed-bean>
2. <managed-bean-name>beanSimulations</managed-bean-name>
3. <managed-bean-class>web.beans.session.BeanSimulations</managed-bean-class>
4. <managed-bean-scope>session</managed-bean-scope>
5. <managed-property>
6. <property-name>beanMasterPage</property-name>
7. <value>#{beanMasterPage}</value>
8. </managed-property>
9.</managed-bean>

19.3.6 La page [erreur.jsp]

La page [erreur.jsp] est celle qui génère la vue présentant une erreur :

Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9. <!-- vue Erreur -->
10. <h3><h:outputText value="#{msg['erreur.titre']}"/></h3>
11. <h:dataTable value="#{beanErreur.erreurs}" var="erreur"
12. headerClass="erreursHeaders" columnClasses="erreurClasse,erreurMessage">
13. ...
14. </h:dataTable>

Ce code est celui de la version précédente si ce n'est que le bean #{form} a été remplacé par le bean #{beanErreur} (ligne 11). Ce
bean est le suivant :

1. package web.beans.request;

Introdution à Java EE
187/334
2.
3. ...
4. public class BeanErreur {
5.
6. // liste des erreurs à afficher
7.
8. private List<Erreur> erreurs = new ArrayList<Erreur>();
9.
10. // getters et setters
11. ...
12. }

• ligne 8 : la liste des objets Erreur affichée par la page [erreur.jsp].

19.3.7 La page [simulationsvides.jsp]

La page [simulationsvides.jsp] est celle qui génère la vue indiquant que la liste des simulations est vide :

Son code est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4. "http://www.w3.org/TR/html4/loose.dtd">
5.
6. <h2>Votre liste de simulations est vide.</h2>

Cette vue est statique et n'a donc pas de modèle associé.

19.4 Les actions


Revenons à l'architecture de notre application :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3 couche
V1 [metier]
4 [V] MC2
V2 JSP simulée
masterpage MCn
Vn

Chaque vue V associée à une page Jsp a été associée à un bean MC jouant à la fois le rôle de modèle M et de contrôleur C de la
page Jsp. Les vues V et leurs modèles M ont été détaillés précédemment. Nous présentons ici la partie contrôleur des beans de
l'application au travers des actions que ceux-ci exécutent.

19.4.1 L'action [faireSimulation]

Le code JSP de l'action [faireSimulation] dans [entete.jsp] est le suivant :

Introdution à Java EE
188/334
<h:commandLink id="cmdFaireSimulation" value="#{msg['form.menu.faireSimulation']}"
action="#{beanSimulation.faireSimulation}" rendered="#{beanMasterPage.menuFaireSimulationIsRendered}"/>

L'action [faireSimulation] calcule une simulation. Elle a été présentée page 151.

Question : écrire la méthode [faireSimulation] du bean #{beanSimulation} présenté page 182. On s'inspirera du code de la
version précédente.

19.4.2 L'action [effacerSimulation]

Le code JSP de l'action [effacerSimulation] dans [entete.jsp] est le suivant :

<h:commandLink id="cmdEffacerSimulation" value="#{msg['form.menu.effacerSimulation']}"


action="#{beanSimulation.effacerSimulation}" rendered="#{beanMasterPage.menuEffacerSimulationIsRendered}"
onclick="raz()"/>

L'action [effacerSimulation] a été présentée page 152.

Question : écrire la méthode [effacerSimulation] du bean #{beanSimulation} présenté page 182. On s'inspirera du code de la
version précédente.

19.4.3 L'action [enregistrerSimulation]

Le code JSP de l'action [enregistrerSimulation] dans [entete.jsp] est le suivant :

<h:commandLink id="cmdEnregistrerSimulation" value="#{msg['form.menu.enregistrerSimulation']}"


action="#{beanSimulations.enregistrerSimulation}"
rendered="#{beanMasterPage.menuEnregistrerSimulationIsRendered}"/>

L'action [enregistrerSimulation] a été présentée page 153.

Question : écrire la méthode [enregistrerSimulation] du bean #{beanSimulations} présenté page 186. On s'inspirera du code de la
version précédente.

19.4.4 L'action [retourSimulateur]

Le code JSP de l'action [retourSimulateur] dans [entete.jsp] est le suivant :

<h:commandLink id="cmdRetourSimulateur" value="#{msg['form.menu.retourSimulateur']}"


action="#{beanMasterPage.retourSimulateur}" rendered="#{beanMasterPage.menuRetourSimulateurIsRendered}"/>

L'action [retourSimulateur] a été présentée page 158.

Question : écrire la méthode [retourSimulateur] du bean #{beanMasterPage} présenté page 179. On s'inspirera du code de la
version précédente.

On peut rencontrer une difficulté ici. Pour savoir si le menu doit afficher ou non le lien "Voir les simulations", on peut vouloir
savoir si la liste des simulations est vide ou non. On n'affichera le lien que si la liste est non vide. Pour cela, il nous faut avoir accès
aux simulations qui sont dans le bean #{beanSimulations}. Nous sommes dans le bean #{beanMasterPage}. Une première idée
est de demander à JSF, par configuration, d'injecter une référence du bean #{beanSimulations} dans le bean
#{beanMasterPage} :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config ...">
4.
5. <managed-bean>
6. <managed-bean-name>beanSimulations</managed-bean-name>

Introdution à Java EE
189/334
7. <managed-bean-class>web.beans.session.BeanSimulations</managed-bean-class>
8. <managed-bean-scope>session</managed-bean-scope>
9. <managed-property>
10. <property-name>beanMasterPage</property-name>
11. <value>#{beanMasterPage}</value>
12. </managed-property>
13. </managed-bean>
14.
15. <managed-bean>
16. <managed-bean-name>beanMasterPage</managed-bean-name>
17. <managed-bean-class>web.beans.session.BeanMasterPage</managed-bean-class>
18. <managed-bean-scope>session</managed-bean-scope>
19. <managed-property>
20. <property-name>beanSimulations</property-name>
21. <value>#{beanSimulations}</value>
22. </managed-property>
23. </managed-bean>
24.
25. ....
26. </faces-config>

• lignes 19-22 : on injecte le bean #{beanSimulations} dans le bean #{beanMasterPage}

Ici, il faut connaître les règles à observer dans l'injection des beans managés :

• on ne peut injecter dans un bean B1 de portée p1, un bean B2 de portée p2 inférieure à p1. La hiérarchie des durées
de vie (ou portées) est la suivante : application > session > request. Ici, on injecte un bean B2 (#{beanSimulations} ligne
20) de portée session (ligne 8) dans un bean B1 (#{beanMasterPage}, ligne 16) également de portée session (ligne 18). Ceci est
possible.
• on ne peut créer de références circulaires entre beans : on ne peut injecter un bean B2 dans un bean B1, si le bean B1
est lui-même injecté dans le bean B2. Ici, cette propriété n'est pas vérifiée. Le bean #{beanMasterPage} étant injecté dans le
bean #{beanSimulations} (ligne 10), ce dernier ne peut, à son tour, être injecté dans le bean #{beanMasterPage} (ligne 20).

Il nous faut trouver une autre solution. On peut alors utiliser du code. Le bean #{beanSimulations} peut être récupéré avec le code
suivant :

// on récupère le bean des simulations


BeanSimulations beanSimulations = (BeanSimulations)
FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(FacesContext.getCurrentInstance(
), "#{beanSimulations}", BeanSimulations.class);

C'est une expression complexe que nous ne commenterons pas. Nous la prendrons telle quelle. De façon générale pour un bean
#{bean} de type Bean, on écrira :

Bean bean = (Bean) FacesContext.getCurrentInstance().getApplication()


.evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{bean}", Bean.class);

Ce code pourrait remplacer systématiquement le système d'injections par configuration si on le souhaitait.

19.4.5 L'action [voirSimulations]

Le code JSP de l'action [voirSimulations] dans [entete.jsp] est le suivant :

<h:commandLink id="cmdVoirSimulations" value="#{msg['form.menu.voirSimulations']}"


action="#{beanMasterPage.voirSimulations}" immediate="true"
rendered="#{beanMasterPage.menuVoirSimulationsIsRendered}"/>

L'action [voirSimulations] a été présentée page 158.

Question : écrire la méthode [voirSimulations] du bean #{beanMasterPage} présenté page 179. On s'inspirera du code de la
version précédente.

19.4.6 L'action [retirerSimulation]

Le code JSP de l'action [retirerSimulation] dans la page [simulations.jsp] est le suivant :

Introdution à Java EE
190/334
<h:commandLink value="Retirer" action="#{beanSimulations.retirerSimulation}">
<f:setPropertyActionListener target="#{beanSimulations.numSimulationToDelete}"
value="#{simulation.num}"/>
</h:commandLink>

L'action [retirerSimulation] a été présentée page 159.

Question : écrire la méthode [retirerSimulation] du bean #{beanSimulations} présenté page 186. On s'inspirera du code de la
version précédente.

19.4.7 L'action [terminerSession]

Le code JSP de l'action [terminerSession] dans la page [entete.jsp] est le suivant :

<h:commandLink id="cmdTerminerSession" immediate="true" value="#{msg['form.menu.terminerSession']}"


action="#{beanMasterPage.terminerSession}" rendered="#{beanMasterPage.menuTerminerSessionIsRendered}"/>

L'action [terminerSession] a été présentée page 160.

Question : écrire la méthode [terminerSession] du bean #{beanMasterPage} présenté page 179. On s'inspirera du code de la
version précédente.

Travail pratique : tester cette nouvelle version.

19.5 Intégration de la couche web dans une architecture 3 couches avec Jsf - Ejb
L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3 couche
V1 [metier]
4 [V] MC2
V2 JSP simulée
masterpage MCn
Vn

Nous remplaçons la couche [métier] simulée, par les couches [métier, dao, jpa] implémentées par des Ejb au paragraphe 13.2, page
85 :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3
V1 couches
[V] MC2
4 [métier,dao,jpa]
V2 JSP
masterpage MCn
Vn

Introdution à Java EE
191/334
Travail pratique : réaliser l'intégration des couches Jsf et Ejb en suivant la méthodologie du paragraphe 16, page 128.

20 Version 10 - Intégration de la couche web dans une architecture 3


couches avec Jsf et Spring
Note : les applications qui suivent ont été exécutées avec le serveur d'applications Sun AS 9.1 alors que les précédentes avaient été
exécutées sur le serveur Sun AS 9.01. La raison en est donnée un peu plus loin.

20.1 Architecture Jsf / Spring


L'architecture de l'application web précédente était la suivante :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3
V1 couches
[V] MC2
4 [métier,dao,jpa]
V2 JSP
masterpage MCn
Vn

Conteneur de servlets Conteneur Ejb3

• la couche [web] s'exécutait dans un conteneur de servlets


• les couches [métier, dao, jpa] s'exécutaient dans un conteneur Ejb3

Nous utilisons maintenant la nouvelle architecture suivante :

Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3
V1 couches
[V] MC2
4 [métier,dao,jpa/hibernate]
V2 JSP
masterpage MCn
Vn

Conteneur de servlets / Spring

• les couches [métier, dao, jpa] s'exécuteront dans le conteneur léger Spring qui lui-même s'exécutera dans le conteneur de
servlets du serveur web. Le conteneur Ejb3 ne sera pas utilisé.
• la couche [jpa] sera implémentée tout d'abord avec Hibernate. Nous utiliserons ensuite Toplink.

Introdution à Java EE
192/334
20.2 Le projet Netbans Jsf / Spring / Hibernate
Le projet [pam-jsf-spring-hibernate] est d'abord obtenu par recopie du projet [pam-jsf-alone-multivues-multipages] :

Dans une seconde étape, les paquetages [exception, jpa, metier] du nouveau projet [1] sont remplacés par les paquetages [exception,
jpa, dao, metier] du projet [swing-metier-dao-jpa-spring-hibernate] étudié à partir du paragraphe 3.2, page 11.

1 2

Les paquetages et fichiers copiés d'un projet [1] à l'autre [2] sont les suivants :
• paquetages dao, exception, jpa, metier
• fichiers META-INF/persistence.xml, log4j.properties, spring-config-metier-dao.xml

La troisième étape est d'ajouter les bibliothèques de classes nécessaires au nouveau projet : celles de lib/divers, lib/spring et
lib/hibernate comme indiqué au paragraphe 4, page 13.

Introdution à Java EE
193/334
4

3
2
1

Les archives du classpath du projet sont montrées en [1] et [2]. De lib/divers, nous ne conservons que le pilote Jdbc de MySQL5
[3]. L'archive [javaee] [4] doit être supprimée car elle entre en conflit avec l'archive [javaee] déjà présente sur le serveur Sun :

2
1

A ce stade, nous pouvons compiler le projet [Clean and Build]. Nous obtenons le résultat suivant :

1. Compiling 22 source files to C:\data\2007-2008\netbeans\pam\07B\pam-jsf-spring-


hibernate\build\web\WEB-INF\classes
2. C:\data\2007-2008\netbeans\pam\07B\pam-jsf-spring-
hibernate\src\java\web\beans\application\ApplicationData.java:8: cannot find symbol
3. symbol : class IMetierLocal
4. location: package metier
5. import metier.IMetierLocal;

La ligne 3 indique que le compilateur ne trouve pas le symbole IMetierLocal dans la classe [ApplicationData] [1]. Regardons le
contenu du paquetage [métier] du projet [2] :

En [2], nous voyons que l'interface de la couche [métier] s'appelle [IMetier] et non [IMetierLocal]. L'interface [IMetierLocal] avait
été introduite pour la couche [métier] implémentée avec des Ejb. Il y avait alors deux interfaces [IMetierLocal] et [IMetierRemote].
Avec Spring, il y une unique interface [IMetier] implémentée par la classe [Metier].

Nous corrigeons le code de la classe [ApplicationData] :

1. package web.beans.application;
2.
3. ...

Introdution à Java EE
194/334
4. public class ApplicationData {
5.
6. public ApplicationData() {
7. }
8.
9. // couche métier
10. private IMetier metier;
11. // employés
12. private SelectItem[] employesItems;
13. // logger
14. private boolean logsEnabled=true;
15. private final Logger logger=Logger.getLogger("pam");
16.
17. @PostConstruct
18. private void init(){
19. // instanciation couche [metier]
20. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
21. metier = (IMetier) ctx.getBean("metier");
22. // la liste des employés est demandée à la couche métier
23. List<Employe>employes=getMetier().findAllEmployes();
24. // on génère la liste des éléments du combo
25. setEmployesItems(new SelectItem[employes.size()]);
26. for(int i=0;i<employes.size();i++){
27. getEmployesItems()[i]=new SelectItem(employes.get(i).getSS(),employes.get(i).getPrenom()+"
"+employes.get(i).getNom());
28. }
29. }
30.
31. // getters et setters
32. ...
33. }

Les changements vis à vis de la version précédente de la classe [ApplicationData] étudiée au paragraphe 17.3, page 144 sont les
suivants :

• ligne 10 : l'interface est [IMetier] et non [IMetierLocal]


• lignes 20-21 : dans la méthode init exécutée immédiatement après l'instanciation de la classe [ApplicationData], le champ
metier de la ligne 10 est initialisé par lecture du fichier de configuration Spring [spring-config-metier-dao.xml]. Ce fichier
est celui déjà utilisé dans le projet [swing-metier-dao-jpa-spring-hibernate] :

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


2. <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3. xmlns:tx="http://www.springframework.org/schema/tx"
4. xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-
2.0.xsd">
5.
6. <!-- couches applicatives -->
7. <!-- dao -->
8. <bean id="employeDao" class="dao.EmployeDao" />
9. <bean id="indemniteDao" class="dao.IndemniteDao" />
10. <bean id="cotisationDao" class="dao.CotisationDao" />
11. <!-- métier -->
12. <bean id="metier" class="metier.Metier">
13. <property name="employeDao" ref="employeDao"/>
14. <property name="indemniteDao" ref="indemniteDao"/>
15. <property name="cotisationDao" ref="cotisationDao"/>
16. </bean>
17. <!-- couche JPA / HIBERNATE -->
18. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
19. <property name="dataSource" ref="dataSource" />
20. <property name="jpaVendorAdapter">
21. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
22. <!--
23. <property name="showSql" value="true" />
24. -->
25. <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
26. <property name="generateDdl" value="true" />
27. </bean>
28. </property>
29. <property name="loadTimeWeaver">
30. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
31. </property>
32. </bean>
33.
34. <!-- la source de donnéees DBCP -->
35. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

Introdution à Java EE
195/334
36. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
37. <property name="url" value="jdbc:mysql://localhost:3306/dbpam" />
38. <property name="username" value="dbpam" />
39. <property name="password" value="dbpam" />
40. </bean>
41.
42. <!-- le gestionnaire de transactions -->
43. <tx:annotation-driven transaction-manager="txManager" />
44. <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
45. <property name="entityManagerFactory" ref="entityManagerFactory" />
46. </bean>
47.
48. <!-- traduction des exceptions -->
49. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
50.
51. <!-- persistence -->
52. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
53.
54. </beans>

• le bean nommé "metier" utilisé par la méthode init de la classe [ApplicationData] est défini lignes 12-16.

Ces modifications faites, le nouveau projet [pam-jsf-spring-hibernate] doit se compiler sans erreurs. Avant de le déployer, on
procèdera aux préliminaires suivants :

• on lancera le SGBD MySQL5


• on créera une connexion à la base MySQL5 / dbpam comme expliqué au paragraphe 4, page 19.

On prendra soin de supprimer toutes les tables de la base [dbpam].

• on exécutera [2] le programme de test [InitDB] du projet [swing-metier-dao-jpa-spring-hibernate] afin de créer la base
[dbpam] [3] et de l'initialiser [4] :

• on fera en sorte que le fichier [spring-config-metier-dao.xml] qui configure la couche Jpa de l'application web ne détruise
pas la base qui vient d'être créée et initialisée :

1.<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
2. <property name="dataSource" ref="dataSource" />
3. <property name="jpaVendorAdapter">
4. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
5. <!--
6. <property name="showSql" value="true" />

Introdution à Java EE
196/334
7. -->
8. <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
9. <!--
10. <property name="generateDdl" value="true" />
11. -->
12. </bean>
13. </property>
14. <property name="loadTimeWeaver">
15. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
16. </property>
17. </bean>

La ligne 10 qui demande la génération de la base est mise en commentaires.

• on consultera les propriétés du projet web afin de savoir sur quel serveur il va être déployé [5] puis on l'exécutera [6]

Le serveur désigné par [5], Sun AS 9, désigne en fait des serveurs Sun AS 9.x où x désigne des versions. Avec la version 9.01,
l'application [pam-jsf-spring-hibernate] fonctionne. Avec la version 9.1, plus récente donc, elle ne fonctionne pas. Il en est de même
avec le serveur Glassfish v2 b58g qui au jour d'écriture de ce document est considéré comme la version de référence de Glassfish
v2. L'erreur indiquée par ces serveurs vient de la configuration Jpa faite dans [persistence.xml] :

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


2. <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
3. <persistence-unit name="dbpam" transaction-type="RESOURCE_LOCAL"/>
4. </persistence>

Le type de transactions, défini ligne 3, est RESOURCE_LOCAL, c.a.d. que Spring s'occupe des transactions. On se rappelle peut-
être que dans la couche [dao], les classes ont des annotations @Transactional, qui sont des annotations Spring, pour gérer les
transactions :

1. package dao;
2.
3. ...
4. import org.springframework.transaction.annotation.Transactional;
5.
6. @Transactional
7. public class CotisationDao implements ICotisationDao {

Ligne 6, l'annotation @Transactional de la classe CotisationDao indique que toutes les méthodes de cette classe doivent se dérouler
dans une transaction. Ligne 4, on voit que cette annotation est une annotation Spring : c'est Spring qui gère cette annotation et les
transactions qui vont avec.

L'erreur déclarée par les serveurs Sun AS 9.1 et Glassfish v2 b58g est (approximativement) que "un contexte de persistance managé
par un conteneur doit avoir un type de transaction JTA et non RESOURCE_LOCAL". Il ne suffit pas de changer la ligne 3 en

<persistence-unit name="dbpam" transaction-type="JTA"/>

pour résoudre le problème. En effet, dans ce cas les transactions deviennent, comme le contexte de persistance, gérées par le
conteneur Ejb du serveur et ce n'est pas ce que nous voulons car nous revenons alors à une solution déjà étudiée qui n'utilise pas
Spring.

Une recherche sur le web montre que le problème vient de l'annotation @PersistenceContext utilisée dans la couche [dao] :

Introdution à Java EE
197/334
1. package dao;
2.
3. ...
4. import javax.persistence.PersistenceContext;
5. import org.springframework.transaction.annotation.Transactional;
6.
7. @Transactional
8. public class CotisationDao implements ICotisationDao {
9.
10. @PersistenceContext
11. private EntityManager em;
12.
13. // constructeur
14. public CotisationDao() {
15. }
16.
17. // créer une cotisation
18. public Cotisation create(Cotisation cotisation) {
19. try{
20. em.persist(cotisation);
21. return cotisation;
22. }catch (Throwable th){
23. throw new PamException(th,11);
24. }
25. }
26. ...

• ligne 7 : l'annotation @Transactional est une annotation Spring (ligne 5)


• ligne 10 : l'annotation @PersistenceContext est une annotation Jpa (ligne 4) qui peut être interprétée par un conteneur
Ejb. Nous avions cette annotation dans les couches [dao] implémentées par des Ejb.

Finalement, il semble que l'annotation @PersistenceContext de la ligne 10 puisse être interpétée par Spring (elle a été utilisée dans un
contexte standalone hors conteneur Ejb) et par un conteneur Ejb. Pour une raison que j'ignore, il semble qu'avec les serveurs AS 9.1
et Glassfish v2 b58g, elle soit interprétée, sinon par les deux, en tout cas par le conteneur Ejb. Celui-ci comprend alors qu'il doit
injecter dans le champ de la ligne 11 un contexte de persistance. C'est ce qu'on appelle "un contexte de persistance managé par un
conteneur", et qui exige donc, c'est le message d'erreur qui le dit, un type de transaction JTA et non RESOURCE_LOCAL.

Comment résoudre le problème ? Il est possible d'avoir un objet EntityManager de la façon suivante :

1. // récupérer un EntityManagerFactory à partir de l'unité de persistance


2. EntityManagerFactory emf = Persistence.createEntityManagerFactory("dbpam");
3. // récupérer un EntityManager à partir de l'EntityManagerFactory
4. EntityManager em = emf.createEntityManager();

où dbpam est me nom de l'unité de persistance définie dans [persistence.xml] (ligne 3 ci-dessous) :

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


<persistence ...">
<persistence-unit name="dbpam" transaction-type="RESOURCE_LOCAL"/>
</persistence>

La classe CotisationDao pourrait alors être écrite de la façon suivante :

1. @Transactional
2. public class CotisationDao implements ICotisationDao {
3.
4. // contexte de persistance
5. private EntityManager em;
6.
7. // constructeur
8. public CotisationDao() {
9. // récupérer un EntityManagerFactory à partir de l'unité de persistance
10. EntityManagerFactory emf = Persistence.createEntityManagerFactory("dbpam");
11. // récupérer un EntityManager à partir de l'EntityManagerFactory
12. em = emf.createEntityManager();
13. }
14.
15. // créer une cotisation
16. public Cotisation create(Cotisation cotisation) {
17. try{
18. em.persist(cotisation);
19. return cotisation;
20. }catch (Throwable th){
21. throw new PamException(th,11);

Introdution à Java EE
198/334
22. }
23.

• ligne 5 : l'objet EntityManager n'est plus injecté par un conteneur. Il est initialisé par le constructeur lignes 9-12.

Ceci fait, les tests échouent de nouveau. Le message d'erreur change et devient plus obscur que jamais : Toplink indique qu'il ne
trouve pas une source ODBC. On a progressé, le type de transaction RESOURCE_LOCAL est désormais accepté mais la nouvelle
erreur semble encore plus ardue à résoudre.

Après de fastidieuses et longues recherches sur le web, on trouve une réponse à l'url suivante [http://java-
x.blogspot.com/2006/12/data-access-with-spring-and-jpa.html]. L'auteur y montre une implémentation spring / jpa / glassfish et
donne le fichier de configuration de celle-ci :

1. <?xml ...">
2. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
3. <property name="driverClassName" value ="oracle.jdbc.driver.OracleDriver" />
4. <property name="url" value="jdbc:oracle:thin:@localhost:1521/orcl" />
5. <property name="username" value="scott" />
6. <property name="password" value="tiger" />
7. </bean>
8. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
9. <property name="persistenceUnitName" value="myPersistenceUnit" />
10. <property name="dataSource" ref="dataSource" />
11. <property name="loadTimeWeaver">
12. <bean
class="org.springframework.instrument.classloading.glassfish.GlassFishLoadTimeWeaver"/>
13. </property>
14. <property name="jpaDialect">
15. <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaDialect" />
16. </property>
17. <property name="jpaVendorAdapter">
18. <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
19. <property name="showSql" value="true" />
20. <property name="generateDdl" value="false" />
21. <property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.oracle.OraclePlatform" />

22. </bean>
23. </property>
24. </bean>
25. <bean id="dao" class="data.DAO">
26. <property name="entityManagerFactory" ref="entityManagerFactory" />
27. </bean>
28. </beans>

• les lignes 2-7 configurent une source de données - semblable à notre configuration actuelle
• les lignes 8-24 configurent l'EntityManagerFactory - semblable à notre configuration actuelle mais avec quelques différences :
• ligne 9 : l'unité de persistance est nommée - dans notre configuration actuelle, elle ne l'est pas - semble facultative
si [persistence.xml] ne contient qu'une unité de persistance, ce qui est le cas ici où seule existe l'unité de
persistance dbpam.
• lignes 11-13 : un objet de type LoadTimeWeaver, objet obscur depuis toujours. Ici cet objet est implémenté par
la classe GlassFishLoadTimeWeaver alors que dans notre configuration actuelle elle est implémentée par la classe
InstrumentationLoadTimeWeaver. Ces deux classes sont fournies par Spring. Les tests qui vont suivre réussissent
quelque soit la classe choisie. Ils réussissent également si l'objet LoadTimeWeaver n'est pas défini, ce qui montre
qu'il n'est probablement pas utilisé dans la configuration testée. Nous le mettrons en commentaires.
• lignes 14-16 : définissent un dialecte Jpa - cette propriété ne semble pas indispensable.
• lignes 17-23 : l'implémentation Jpa choisie, ici Hibernate - semblable à notre configuration actuelle
• lignes 25-27 : la configuration d'un bean de la couche [dao]. C'est là qu'est la solution à notre problème. On voit que la
classe [data.DAO] (ligne 25) se voit injecter l'EntityManagerFactory défini lignes 8-24. Ce que nous n'avions pas encore fait.

Une fois qu'on a vu cette configuration, on comprend mieux pourquoi le code précédent ne fonctionne pas :

1. // récupérer un EntityManagerFactory à partir de l'unité de persistance


2. EntityManagerFactory emf = Persistence.createEntityManagerFactory("dbpam");
3. // récupérer un EntityManager à partir de l'EntityManagerFactory
4. EntityManager em = emf.createEntityManager();

La classe Persistence de la ligne 2 est une classe javax.persistence.Persistence générique qui n'a aucune notion de Spring. Elle se contente
d'utiliser le fichier [persistence.xml] qui dans la configuration Spring / Jpa utilisée est réduit à sa plus simple expression :

Introdution à Java EE
199/334
<?xml version="1.0" encoding="UTF-8"?>
<persistence ...">
<persistence-unit name="dbpam" transaction-type="RESOURCE_LOCAL"/>
</persistence>

La source de données n'y est par exemple pas indiquée ni l'implémentation Jpa. Ce qui peut expliquer le message d'erreur abscons
rencontré : "Toplink ne trouve pas une source ODBC ...". Toplink a été pris comme implémentation Jpa par défaut et il n'a pas
trouvé de source de données dans [persistence.xml].

Il faut donc injecter l'EntityManagerFactory défini dans le fichier Spring dans toutes les classes qui ont besoin d'un contexte de
persistance, c.a.d. les classes de la couche [dao]. Le fichier [spring-config-metier-dao.xml] évolue de la façon suivante :

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


2. <beans
3. xmlns="http://www.springframework.org/schema/beans"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xmlns:tx="http://www.springframework.org/schema/tx"
6. xsi:schemaLocation="
7. http://www.springframework.org/schema/beans
8. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
9. http://www.springframework.org/schema/tx
10. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
11. ">
12.
13. <!-- couches applicatives -->
14. <!-- dao -->
15. <bean id="employeDao" class="dao.EmployeDao" init-method="init">
16. <property name="emf" ref="entityManagerFactory"/>
17. </bean>
18. <bean id="indemniteDao" class="dao.IndemniteDao" init-method="init">
19. <property name="emf" ref="entityManagerFactory"/>
20. </bean>
21. <bean id="cotisationDao" class="dao.CotisationDao" init-method="init">
22. <property name="emf" ref="entityManagerFactory"/>
23. </bean>
24.
25. <!-- métier -->
26. <bean id="metier" class="metier.Metier">
27. <property name="employeDao" ref="employeDao"/>
28. <property name="indemniteDao" ref="indemniteDao"/>
29. <property name="cotisationDao" ref="cotisationDao"/>
30. </bean>
31.
32. <!-- Entity Manager Factory -->
33. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
34. <property name="dataSource" ref="dataSource" />
35. <property name="jpaVendorAdapter">
36. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
37. <!--
38. <property name="showSql" value="true" />
39. <property name="generateDdl" value="true" />
40. -->
41. <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
42. </bean>
43. </property>
44. <!--
45. <property name="loadTimeWeaver">
46. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
47. </property>
48. -->
49. </bean>
50.
51. <!-- la source de donnéees DBCP -->
52. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
53. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
54. <property name="url" value="jdbc:mysql://localhost:3306/dbpam" />
55. <property name="username" value="dbpam" />
56. <property name="password" value="dbpam" />
57. </bean>
58.
59. <!-- le gestionnaire de transactions -->
60. <tx:annotation-driven transaction-manager="txManager" />
61. <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
62. <property name="entityManagerFactory" ref="entityManagerFactory" />
63. </bean>
64.
65. <!-- traduction des exceptions -->
66. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
67.
68. <!-- persistence -->

Introdution à Java EE
200/334
69. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
70.
71. </beans>

• lignes 15-23 : les classes de la couche [dao] se voient injecter l'EntityManagerFactory défini ligne 33
• lignes 44-48 : l'objet LoadTimeWeaver n'est pas utilisé.

Le code des classes est modifié comme suit :

CotisationDao

1. @Transactional
2. public class CotisationDao implements ICotisationDao {
3.
4. // Entity manager factory
5. private EntityManagerFactory emf;
6. // Entity manager
7. private EntityManager em;
8.
9. // constructeur
10. public CotisationDao() {
11. }
12.
13. // init
14. public void init(){
15. em=getEmf().createEntityManager();
16. }
17.
18. // créer une cotisation
19. public Cotisation create(Cotisation cotisation) {
20. ...
21. }
22. ...

La configuration Spring des lignes 21-23 agit de la façon suivante :


• le constructeur sans paramètres (lignes 10-11) de CotisationDao est utilisé pour construire une instance
• la ligne 22 du fichier de configuration Spring fait que le champ EntityManagerFactory emf de la ligne 5 est ensuite
initialisé
• l'attribut init-method de la ligne 21 de Spring fait qu'ensuite la méthode init des lignes 14-16 est ensuite exécutée
• à la fin du processus, l'EntityManager em de la ligne 7 a été initialisé

Les autres classes de la couche [dao] sont modifiées de façon analogue :

EmployeDao

1. @Transactional
2. public class EmployeDao implements IEmployeDao {
3.
4. // Entity manager factory
5. private EntityManagerFactory emf;
6. // Entity manager
7. private EntityManager em;
8.
9. // constructeur
10. public EmployeDao() {
11. }
12.
13. // init
14. public void init(){
15. em=getEmf().createEntityManager();
16. }
17.
18. // créer un employé
19. public Employe create(Employe employe) {
20. ...
21. }
22. ...

IndemniteDao

1. @Transactional
2. public class IndemniteDao implements IIndemniteDao{
3.
4. // Entity manager factory
5. private EntityManagerFactory emf;

Introdution à Java EE
201/334
6. // Entity manager
7. private EntityManager em;
8.
9. // constructeur
10. public IndemniteDao() {
11. }
12.
13. // init
14. public void init(){
15. em=getEmf().createEntityManager();
16. }
17.
18. // créer une indemnité
19. public Indemnite create(Indemnite indemnite) {
20. ...
21. }
22. ...

Ceci fait, nous pouvons exécuter le projet. Nous obtenons la chose suivante :

Le lecteur est invité à tester l'application. Voici un exemple de simulation :

Introdution à Java EE
202/334
Que retiendrons-nous de cette intégration Jsf / Spring / Jpa / Hibenate / Glassfish ? Qu'il est probablement préférable d'injecter
dans les classes de la couche [dao] un EntityManagerFactory :

1. @Transactional
2. public class CotisationDao implements ICotisationDao {
3.
4. // Entity manager factory
5. private EntityManagerFactory emf;
6. // Entity manager
7. private EntityManager em;
8.
9. // constructeur
10. public CotisationDao() {
11. }
12.
13. // init
14. public void init(){
15. em=getEmf().createEntityManager();
16. }
17. ...

qu'un EntityManager :

1. @Transactional
2. public class CotisationDao implements ICotisationDao {
3.
4. // Entity manager
5. @PersistenceContext
6. private EntityManager em;
7.
8. // constructeur
9. public CotisationDao() {

Introdution à Java EE
203/334
10. }
11. ...

En effet, la première solution convient aux deux environnements standalone et serveur d'application alors que la seconde, on l'a vu,
ne fonctionne pas avec certains serveurs d'application.

20.3 Le projet Netbans Jsf / Spring / Hibernate - 2


Nous dupliquons le projet précédent (Copy Project) pour créer le projet [pam-jsf-spring-hibernate-2] [1] :

Dans ce projet, nous ne modifions qu'une chose : nous voulons que la source de données utilisée par l'application soit la source
JNDI jdbc/dbpam [2] utilisée par les versions Ejb précédentes. Nous bénéficierons ainsi du pool de connexions qui va avec. Pour
cela, une seule modification est nécessaire. Elle a lieu dans le fichier [spring-config-metier-dao.xml] [3] qui configure la source de
données :

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


2. <beans
3. xmlns="http://www.springframework.org/schema/beans"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xmlns:tx="http://www.springframework.org/schema/tx"
6. xmlns:jee="http://www.springframework.org/schema/jee"
7. xsi:schemaLocation="
8. http://www.springframework.org/schema/beans
9. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
10. http://www.springframework.org/schema/tx
11. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
12. http://www.springframework.org/schema/jee
13. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
14. ">
15.
16. <!-- couches applicatives -->
17. <!-- dao -->
18. <bean id="employeDao" class="dao.EmployeDao" init-method="init">
19. <property name="emf" ref="entityManagerFactory"/>
20. </bean>
21. <bean id="indemniteDao" class="dao.IndemniteDao" init-method="init">
22. <property name="emf" ref="entityManagerFactory"/>
23. </bean>
24. <bean id="cotisationDao" class="dao.CotisationDao" init-method="init">
25. <property name="emf" ref="entityManagerFactory"/>
26. </bean>
27.
28. <!-- métier -->
29. <bean id="metier" class="metier.Metier">
30. <property name="employeDao" ref="employeDao"/>
31. <property name="indemniteDao" ref="indemniteDao"/>
32. <property name="cotisationDao" ref="cotisationDao"/>
33. </bean>
34.
35. <!-- Entity Manager Factory -->

Introdution à Java EE
204/334
36. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
37. <property name="dataSource" ref="dataSource" />
38. <property name="jpaVendorAdapter">
39. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
40. <!--
41. <property name="showSql" value="true" />
42. <property name="generateDdl" value="true" />
43. -->
44. <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
45. </bean>
46. </property>
47. <!--
48. <property name="loadTimeWeaver">
49. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
50. </property>
51. -->
52. </bean>
53.
54. <!-- la source de donnéees JNDI -->
55. <jee:jndi-lookup id="dataSource" jndi-name="jdbc/dbpam">
56. <jee:environment>
57. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
58. java.naming.factory.url.pkgs=com.sun.enterprise.naming
59. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
60. org.omg.CORBA.ORBInitialHost=localhost
61. org.omg.CORBA.ORBInitialPort=3700
62. </jee:environment>
63. </jee:jndi-lookup>
64.
65. <!-- le gestionnaire de transactions -->
66. <tx:annotation-driven transaction-manager="txManager" />
67. <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
68. <property name="entityManagerFactory" ref="entityManagerFactory" />
69. </bean>
70.
71. <!-- traduction des exceptions -->
72. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
73.
74. <!-- persistence -->
75. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
76.
77. </beans>

• lignes 55-63 : la définition de la source de données. On y donne son nom jdbc/dbpam (ligne 55) ainsi que les
informations nécessaires pour joindre le serveur JNDI du serveur Sun AS (lignes 57-61).
• la balise <jee:jndi-lookup> de la ligne 55 a été présentée au paragraphe 13.3.4, page 105. Elle permet d'obtenir la
référence d'un objet distant via un serveur JNDI. Cette balise nécessite l'importation d'espaces de noms (lignes 6, 12 et 13).

On notera également que le pilote Jdbc de MySQL n'a plus besoin d'être dans les bibliothèques (Libraries) [1] du projet. En effet, la
source de données n'est plus accédée directement par le projet mais par le serveur Sun. On rappelle que nous avons installé
(paragraphe 13.1.2, page 81) le pilote Jdbc de MySQL dans le dossier <Sun/AppServer>/domains/domain1/lib/ext (cf [2]).

Le lecteur est invité à tester cette nouvelle version.

20.4 Le projet Netbans Jsf / Spring / Toplink


Nous créons une copie du précédent projet pour l'implémenter avec une couche Jpa / Toplink. Nous appelons ce nouveau projet
[pam-jsf-spring-toplink] :

Introdution à Java EE
205/334
1

Pour changer d'implémentation Jpa, il nous faut modifier le fichier de configuration [spring-config-metier-dao.xml]. Celui-ci devient
le suivant :

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


2. <beans
3. xmlns="http://www.springframework.org/schema/beans"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xmlns:tx="http://www.springframework.org/schema/tx"
6. xmlns:jee="http://www.springframework.org/schema/jee"
7. xsi:schemaLocation="
8. http://www.springframework.org/schema/beans
9. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
10. http://www.springframework.org/schema/tx
11. http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
12. http://www.springframework.org/schema/jee
13. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
14. ">
15.
16. <!-- couches applicatives -->
17. <!-- dao -->
18. <bean id="employeDao" class="dao.EmployeDao" init-method="init">
19. <property name="emf" ref="entityManagerFactory"/>
20. </bean>
21. <bean id="indemniteDao" class="dao.IndemniteDao" init-method="init">
22. <property name="emf" ref="entityManagerFactory"/>
23. </bean>
24. <bean id="cotisationDao" class="dao.CotisationDao" init-method="init">
25. <property name="emf" ref="entityManagerFactory"/>
26. </bean>
27.
28. <!-- métier -->
29. <bean id="metier" class="metier.Metier">
30. <property name="employeDao" ref="employeDao"/>
31. <property name="indemniteDao" ref="indemniteDao"/>
32. <property name="cotisationDao" ref="cotisationDao"/>
33. </bean>
34.
35. <!-- Entity Manager Factory -->
36. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
37. <property name="dataSource" ref="dataSource" />
38. <property name="jpaVendorAdapter">
39. <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
40. <!--
41. <property name="showSql" value="true" />
42. <property name="generateDdl" value="true" />
43. -->
44. <property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
45. </bean>
46. </property>
47. <property name="loadTimeWeaver">
48. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
49. </property>
50. </bean>

Introdution à Java EE
206/334
51.
52. <!-- la source de donnéees JNDI -->
53. <jee:jndi-lookup id="dataSource" jndi-name="jdbc/dbpam">
54. <jee:environment>
55. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
56. java.naming.factory.url.pkgs=com.sun.enterprise.naming
57. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
58. org.omg.CORBA.ORBInitialHost=localhost
59. org.omg.CORBA.ORBInitialPort=3700
60. </jee:environment>
61. </jee:jndi-lookup>
62.
63. <!-- le gestionnaire de transactions -->
64. <tx:annotation-driven transaction-manager="txManager" />
65. <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
66. <property name="entityManagerFactory" ref="entityManagerFactory" />
67. </bean>
68.
69. <!-- traduction des exceptions -->
70. <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
71.
72. <!-- persistence -->
73. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
74.
75. </beans>

Les modifications apportées sont les suivantes :

• ligne 39 : la couche Jpa est implémentée par Toplink


• ligne 44 : la propriété databasePlatform est une référence sur une classe Toplink
• lignes 47-49 : déclarent un objet LoadTimeWeaver. Alors qu'avec Hibernate, cette propriété était inutile, ici Spring la
demande explicitement lors de l'exécution si on ne la met pas (ligne 1 ci-dessous) :

1. Caused by: java.lang.IllegalStateException: Cannot apply class transformer without LoadTimeWeaver


specified at
org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.addTransformer(SpringPersist
enceUnitInfo.java:67)

Par ailleurs, nous modifions les bibliothèques attachées au projet. Seules les bibliothèques de Spring sont nécessaires. En effet :

• nous n'avons plus besoin des bibliothèques Hibernate


• les bibliothèques Toplink qui doivent les remplacer sont déjà présentes sur le serveur Sun.

Aussi, les bibliothèques du projet sont-elles les suivantes :

Ceci fait, nous sommes quasiment prêts pour exécuter la nouvelle version. Auparavant, il nous faut quand même créer la base de
données [dbpam] et l'initialiser. Ceci peut être fait avec le projet [swing-metier-dao-jpa-spring-toplink] étudié au paragraphe 11, page
54.

Introdution à Java EE
207/334
2

En [1], on supprime toutes les tables de la base [dbpam] pour les recréer en [2] en exécutant la classe dao.InitDB. Le résultat
obtenu est le suivant :

4
3

En [3] les tables ont été créées et [4,5] montrent qu'elles ont été initialisées. Ceci fait, on peut exécuter l'application. L'exécution se
passe mal et on a l'erreur suivante :

Caused by: java.lang.IllegalStateException: Must start with Java agent to use


InstrumentationLoadTimeWeaver. See Spring documentation.

C'est une erreur que nous avons déjà rencontrée dans le projet [swing-metier-dao-jpa-spring-toplink] étudié au paragraphe 11, page
54 et sa solution décrite au paragraphe 11.2, page 57. La JVM qui exécute l'application doit être lancée avec un agent Java qui va
permettre à Spring de mettre de l'ordre dans le chargement des classes, notamment celles des entités Jpa. Nous n'avions rencontré
ce problème qu'avec Toplink et pas avec Hibernate. Cela est vrai de nouveau ici.

Lorsqu'on dit "La JVM qui exécute l'application doit être lancée avec un agent Java", de quelle JVM parle-t-on ? On n'a pas
beaucoup de choix. Notre application est exécutée par le serveur Sun. Il faut pouvoir "dire" à celui-ci "La JVM qui exécute
l'application doit être lancée avec un agent Java". Ici, Netbeans nous aide :

1
3

Introdution à Java EE
208/334
En [1], on voit que le serveur Sun a une branche JVMs. Celle-ci a des propriétés [2] dont JVMOptions [3] qui semble être la liste des
options au lancement de la JVM. On ne sait pas de quelle JVM il s'agit, si c'est bien celle dont on a besoin, mais on veut bien
essayer.

Nous avons donné au paragraphe 11.2, page 57 la forme de l'option à donner à la JVM pour les besoins de Spring :

-javaagent:<chemin>/spring-agent.jar

Nous procédons ainsi :

• nous plaçons le fichier [spring-agent.jar] [1] dans le dossier [2] [C:\devjava\Sun\AppServer9.1\domains\domain1\lib\ext]


• en [3], nous ajoutons une option à la JVM

5
4

• en [4,5], nous ajoutons l'option -javaagent à la JVM. Elle s'ajoute aux autres options :

• en [6], la nouvelle option a été ajoutée. En [7], nous relançons le serveur Sun afin qu'elle soit prise en compte.

Ceci fait, nous exécutons le projet [pam-jsf-spring-toplink]. Nous obtenons la page suivante :

Introdution à Java EE
209/334
Le lecteur est invité à faire des tests supplémentaires.

Introdution à Java EE
210/334
JavaServer Faces (JSF)
par l'exemple

avec Netbeans 5.5.1

Introdution à Java EE
211/334
1 JSF - JavaServer Faces
Objectif : Introduction à JSF via des exemples Netbeans.

Pour approfondir :

• Java EE5 Tutorial [http://java.sun.com/javaee/5/docs/tutorial/information/download.html]. La référence Sun pour


découvrir Java EE5. On lira la partie II [Web Tier] pour découvrir la technologie web et notamment JSF. Les exemples du
tutoriel peuvent être téléchargés. Ils viennent sous forme de projets Netbeans qu'on peut charger et exécuter.
• Java EE5 de Antonio Goncalves aux éditions Eyrolles. Ce livre, qui montre l'utilisation de différentes technologies Java
EE5 dans le développement d'une application de commerce électronique est particulièrement conseillé.
• JavaServer Faces de Chris Schalk et Ed Burns aux éditions Mc Graw-Hill
• Introduction à la programmation web en Java [http://tahe.developpez.com/java/web/] : donne les bases de la
programmation web en Java : servlets et pages JSP.
• Les bases du développement web MVC en Java [http://tahe.developpez.com/java/baseswebmvc] : préconise le
développement d'applications web avec des architectures à trois couches, où la couche web implémente le modèle de
conception (Design Pattern) MVC (Modèle, Vue, Contrôleur). L'introduction à JSF sera faite dans ce contexte [1] :

Couche Couche Couche Interface Implémentation Couche SGBD


Utilisateur [Jsf/web] [metier] [dao] [JPA] [Toplink [JDBC]
1 / Hibernate]

Nous utiliserons l'IDE Netbeans pour construire les exemples et le serveur Sun Application Server 9.x pour les exécuter.
L'application en couches ci-dessus peut être exécutée dans deux contextes différents :

• 1- totalement dans le conteneur de servlets du serveur :

Conteneur de servlets [web, metier, dao, jpa]


Navigateur SGBD
HTTP serveur Java EE ou conteneur de servlets

• 2 - la couche [web] dans le conteneur de servlets du serveur, les autres couches dans le conteneur Ejb :

Conteneur
Conteneur web [web]
Navigateur Ejb3 [metier, dao, jpa] SGBD
HTTP serveur Java EE

Dans le contexte 1, l'intégration des couches peut être faite avec Spring et l'application exécutée dans un simple conteneur de
servlets comme Tomcat 5.x. Dans le contexte 2, l'intégration des couches est faite par le serveur Java EE. Dans ce document, nous
écrirons des applications le plus souvent réduites à la seule couche [web]. Une telle application s'exécutera alors dans le conteneur
de servlets du serveur Sun. Nous présenterons quelques application avec les couches [web, metier, dao, jpa] où les couches [metier,
dao] seront implémentées avec des Ejb. Dans ce cas, l'application s'exécutera dans le contexte 2.

1.1 Exemple n° 1

1.1.1 Génération du projet

Introdution à Java EE
212/334
1 2

• en [1], créer un nouveau projet


• en [2], choisir la catégorie [web] et le type de projet [Web Application]. Le type [Visual Web Application] permet de créer
des projets web où les pages JSF sont créées par dépôt de composants, de façon analogue à la construction de formulaires
Swing. Nous n'aborderons pas ce type d'applications ici car il est propriétaire à Netbeans. Néanmoins tout ce qui sera vu
dans ce document, est utile à connaître pour construire une application de type [Visual Web Application].

4
6
3

• en [3], désigner le dossier parent du dossier du nouveau projet


• en [4], donner un nom au projet
• en [5], le contexte du projet, c.a.d. le nom sous lequel il sera connu par les navigateurs clients. Le nom du projet est
proposé par défaut. Dans l'exemple ci-dessus, cela signifie que les pages du projet seront accessibles via des url de la forme
[http://machine:port/intro-01/page].
• en [6], on demande le support du framework JavaServer Faces. Cela va générer les fichiers de configuration nécessaires
aux projets de type JSF.

Examinons les éléments du projet ainsi généré et explicitons le rôle de chacun :

2
1

• en [1] : les différentes branches du projet :

Introdution à Java EE
213/334
•[Web Pages] : contiendra les pages web (.html, .jsp), les ressources (images, documents divers), la configuration
de la couche web.
• [Configuration Files] : reprend les fichiers de configuration de la couche [web] déjà présents dans la branche
[Web Pages].
• [Server Resources] : des ressources propres au serveur Sun, telles par exemple une source de données. Dans la
plupart des exemples de ce document, cette branche ne sera pas utilisée.
• [Source packages] : les classes Java du projet
• [Libraries] : les archives .jar nécessaires au projet
• [Test packages] : les classes Java des tests de l'application. Restera vide dans nos exemples.
• [Test Libraries] : les archives .jar nécessaires aux classes de test et non déjà comprises dans la branche [Libraries]
• en [2], on trouve des pages JSP (Java Server Pages) et des fichiers de configuration :
• [index.jsp, welcomeJSF.jsp] : deux pages JSP générées automatiquement par Netbeans. Nous les commenterons
ultérieurement.
• [WEB-INF/web.xml] : le fichier de configuration de l'application web - existe dans toute application web
• [WEB-INF/faces-config.xml] : le fichier de configuration du projet JSF - n'existe que dans un projet JSF
• [WEB-INF/sun-web.xml] : fichier de déploiement sur le serveur Sun - est propre à celui-ci et sera remplacé par
un autre si on change de serveur.
• en [3] : les bibliothèques du projet. Un projet JSF a besoin des bibliothèques [javaee.jar, jsf-impl.jar].

1.1.2 Configuration d'un projet JSF

1.1.2.1 web.xml

Un projet JSF est un projet web et en tant que tel, doit être configuré par un fichier [WEB-INF/web.xml].

Le fichier [web.xml] généré par Netbeans est le suivant :

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


2. <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">
3. <context-param>
4. <param-name>com.sun.faces.verifyObjects</param-name>
5. <param-value>false</param-value>
6. </context-param>
7. <context-param>
8. <param-name>com.sun.faces.validateXml</param-name>
9. <param-value>true</param-value>
10. </context-param>
11. <context-param>
12. <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
13. <param-value>client</param-value>
14. </context-param>
15. <servlet>
16. <servlet-name>Faces Servlet</servlet-name>
17. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
18. <load-on-startup>1</load-on-startup>
19. </servlet>
20. <servlet-mapping>
21. <servlet-name>Faces Servlet</servlet-name>
22. <url-pattern>/faces/*</url-pattern>
23. </servlet-mapping>
24. <session-config>
25. <session-timeout>

Introdution à Java EE
214/334
26. 30
27. </session-timeout>
28. </session-config>
29. <welcome-file-list>
30. <welcome-file>
31. index.jsp
32. </welcome-file>
33. </welcome-file-list>
34. </web-app>

• les lignes 15-19 définissent une servlet, c.a.d. une classe Java capable de traiter les demandes des clients. Une application
JSF fonctionne de la façon suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Cette architecture implémente le Design Pattern MVC (Modèle, Vue, Contrôleur). Le traitement d'une demande d'un client se
déroule selon les quatre étapes suivantes :

1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des
clients. C'est la porte d'entrée de l'application. C'est le C de MVC .
2. traitement - le contrôleur C traite cette demande. Pour ce faire, il se fait aider par des gestionnaires d'événements spécifiques à
l'application écrite [2a]. Ces gestionnaires peuvent avoir besoin de l'aide de la couche métier [2b]. Une fois la demande du client
traitée, celle-ci peut appeler diverses réponses. Un exemple classique est :
• une page d'erreurs si la demande n'a pu être traitée correctement
• une page de confirmation sinon
3. navigation - le contrôleur choisit la réponse (= vue) à envoyer au client. Choisir la réponse à envoyer au client nécessite
plusieurs étapes :
• choisir la page JSP qui va générer la réponse. C'est ce qu'on appelle la vue V, le V de MVC. Ce choix dépend en
général du résultat de l'exécution de l'action demandée par l'utilisateur.
• fournir à cette page Jsp les données dont elle a besoin pour générer cette réponse. En effet, celle-ci contient le
plus souvent des informations calculées par le contrôleur. Ces informations forment ce qu'on appelle le modèle
M de la vue, le M de MVC.
L'étape 3 consiste donc en le choix d'une vue V et en la construction du modèle M nécessaire à celle-ci.
4. réponse - le contrôleur C demande à la page JSP choisie de s'afficher. Celle-ci utilise le modèle M préparé par le contrôleur C
pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. La forme exacte de celle-ci peut être diverse :
ce peut être un flux HTML, PDF, Excel, ...

Dans un projet JSF :


• le contrôleur C est la servlet [javax.faces.webapp.FacesServlet]. On trouve celle-ci dans la bibliothèque [javaee.jar].
• les vues V sont implémentées par des pages JSP.
• les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing
beans".
• les règles de navigation sont définies dans le fichier [faces-config.xml]. On y trouve la liste des vues et les règles de
transition de l'une à l'autre.

Revenons sur le contenu du fichier [web.xml) :

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


2. <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">
3. <context-param>
4. <param-name>com.sun.faces.verifyObjects</param-name>
5. <param-value>false</param-value>
6. </context-param>

Introdution à Java EE
215/334
7. <context-param>
8. <param-name>com.sun.faces.validateXml</param-name>
9. <param-value>true</param-value>
10. </context-param>
11. <context-param>
12. <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
13. <param-value>client</param-value>
14. </context-param>
15. <servlet>
16. <servlet-name>Faces Servlet</servlet-name>
17. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
18. <load-on-startup>1</load-on-startup>
19. </servlet>
20. <servlet-mapping>
21. <servlet-name>Faces Servlet</servlet-name>
22. <url-pattern>/faces/*</url-pattern>
23. </servlet-mapping>
24. <session-config>
25. <session-timeout>
26. 30
27. </session-timeout>
28. </session-config>
29. <welcome-file-list>
30. <welcome-file>
31. index.jsp
32. </welcome-file>
33. </welcome-file-list>
34. </web-app>

• lignes 20-23 : la balise <servlet-mapping> sert à associer une servlet à une Url demandée par le navigateur client. Ici, il est
indiqué que les Url de la forme [/faces/*] doivent être traitées par la servlet de nom [Faces Servlet]. Celle-ci est définie
lignes 15-19. Comme il n'y a pas d'autre balise <servlet-mapping> dans le fichier, cela signifie que la servlet [Faces Servlet]
ne traitera que les url de la forme [/faces/*]. Nous avons vu que le contexte de l'application s'appelait [/intro-01]. Les Url
des clients traitées par la servlet [Faces Servlet] auront donc la forme [http://machine:port/intro-01/faces/*]. Les pages
.html et .jsp seront traitées par défaut par le conteneur de servlets lui-même, et non par une servlet particulière. En effet, le
conteneur de servlets sait comment les gérer.
• lignes 15-19 : définissent la servlet [Faces Servlet]. Comme toutes les Url acceptées sont dirigées vers elle, elle est le
contrôleur C du modèle MVC.
• ligne 18 : indique que la servlet doit être chargée en mémoire dès le démarrage du serveur web. Par défaut, une servlet
n'est chargée qu'à réception de la 1ère demande qui lui est faite.
• lignes 3-14 : les balises <context-param> servent à configurer l'application. Leurs valeurs seront exploitées par la servlet
[Faces Servlet]. Il est trop tôt pour les commenter.
• lignes 24-28 : durée en minutes d'une session. Un client dialogue avec l'application par une suite de cycles demande /
réponse où chaque cycle se déroule comme il a été décrit précédemment. Chaque cycle utilise une connexion tcp-ip qui
lui est propre, nouvelle à chaque nouveau cycle. Aussi, si un client C fait deux demandes D1 et D2, le serveur S n'a pas les
moyens de savoir que les deux demandes appartiennent au même client C. Le serveur S n'a pas la mémoire du client. C'est
le protocole HTTP utilisé (HyperText Transport Protocol) qui veut ça : le client dialogue avec le serveur par une
succession de cycles demande client / réponse serveur utilisant à chaque fois une nouvelle connexion tcp-ip. On parle de
protocole sans état. Dans d'autres protocoles, comme par exemple FTP (File Transfer Protocol), le client C utilise la
même connexion pendant la durée de son dialogue avec le serveur S. Une connexion est donc liée à un client particulier.
Le serveur S sait toujours à qui il a affaire. Afin de pouvoir reconnaître qu'une demande appartient à un client donné, le
serveur web peut utiliser la technique de la session :
• lors de la 1ère demande d'un client, le serveur S lui envoie la réponse attendue plus un jeton, une suite de
caractères aléatoire, unique à ce client.
• lors de chaque demande suivante, le client C renvoie au serveur S le jeton qu'il a reçu, permettant ainsi au serveur
S de le reconnaître.
L'application a désormais la possibilité de demander au serveur de mémoriser des informations associées à un client
donné. On parle de session client. La ligne 26 indique que la durée de vie d'une session est de 30 mn. Cela signifie que si
un client C ne fait pas de nouvelle demande pendant 30 mn, sa session est détruite et les informations qu'elle contenait,
perdues. Lors de sa prochaine demande, tout se passera comme s'il était un nouveau client et une nouvelle session
démarrera.
• lignes 29-33 : la liste des pages à afficher lorsque l'utilisateur demande le contexte sans préciser de page, par exemple ici
[http://machine:port/intro-01]. Dans ce cas, le serveur web (pas la servlet) recherche si l'application a défini une balise
<welcome-file-list>. Si oui, il affiche la 1ère page trouvée dans la liste. Si elle n'existe pas, la deuxième page, et ainsi de
suite jusqu'à trouver une page existante. Ici, lorsque le client demande l'url [http://machine:port/intro-01], c'est l'url
[http://machine:port/intro-01/index.jsp] qui lui sera servie.

Introdution à Java EE
216/334
1.1.2.2 faces-config.xml

Le fichier [faces-config.xml] généré par Netbeans est le suivant :

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


2.
3. <!-- =========== FULL CONFIGURATION FILE ================================== -->
4.
5. <faces-config version="1.2"
6. xmlns="http://java.sun.com/xml/ns/javaee"
7. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
8. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-
facesconfig_1_2.xsd">
9.
10.
11. </faces-config>

Revenons sur l'architecture d'une application Jsf :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

On trouve dans le fichier [faces-config.xml] :


• la liste des gestionnaires d'événements [2a] ainsi que les modèles [3] de l'application. Ces éléments sont des classes Java
qui suivent la norme JavaBean :
• présence d'un constructeur sans paramètres
• présence de méthodes get / set pour chaque champ privé
Ces beans sont tous déclarés dans [faces-config.xml]. Un bean est souvent lié à une page Jsp particulière à qui il sert à la
fois de modèle et de gestionnaire d'événements : les champs du bean servent de modèle et ses méthodes de
gestionnaires d'événements.
• les règles de navigation. Les gestionnaires d'événements doivent rendre au contrôleur [Faces Servlet] ci-dessus, un
résultat sous forme de chaînes de caractères. Les règles de navigation consistent à définir pour tous les événements de
l'application, trois paramètres [jsp1, res1, jsp2] :
• jsp1 est la page Jsp dont un événement a été géré
• res1 est l'une des chaînes de caractères que peut rendre le gestionnaire de cet événement
• jsp2 est la page Jsp qui doit être renvoyée à l'utilisateur lorsque le gestionnaire d'événements rend la valeur res1
Si l'ensemble des gestionnaires des événements de la page jsp1 peut rendre N résultats différents, alors on aura N règles
de navigation dans le fichier [faces-config.xml] pour la page jsp1.

Dans l'exemple étudié, il n'y a ni modèle, ni gestionnaire d'événements, aussi le fichier est-il présent mais sans configuration.

Introdution à Java EE
217/334
1.1.3 Exécution du projet

Avant d'exécuter le projet, regardons ces propriétés :

3
1
4

• en [1], clic droit sur le projet puis option Properties


• en [2], option Run
• en [3], le serveur sur lequel va être déployée l'application
• en [4], son contexte (son nom)

6 8

• en [6], on exécute le projet. Ceci va avoir pour effet de lancer le serveur Sun, si ce n'était fait [7]
• en [8], l'application [intro-01] apparaît dans la branche [Web Applications] du serveur Sun

L'exécution ouvre un navigateur et demande la page d'accueil de l'application [http://localhost:8080/intro-01/] :

10
9

• en [9], la page [index.jsp]


• en [10], la page [welcomeJSF.jsp].

1.1.4 Les pages [index.jsp] et [welcomeJSF.jsp]

Examinons la page [index.jsp] générée par l'assistant de création du projet web :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>

Introdution à Java EE
218/334
3. <%--
4. The taglib directive below imports the JSTL library. If you uncomment it,
5. you must also add the JSTL library to the project. The Add Library... action
6. on Libraries node in Projects view can be used to add the JSTL 1.1 library.
7. --%>
8. <%--
9. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
10. --%>
11.
12. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
13. "http://www.w3.org/TR/html4/loose.dtd">
14.
15. <html>
16. <head>
17. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
18. <title>JSP Page</title>
19. </head>
20. <body>
21.
22. <h1>JSP Page</h1>
23. <br/>
24. <a href="./faces/welcomeJSF.jsp">JavaServer Faces Welcome Page</a>
25.
26. <%--
27. This example uses JSTL, uncomment the taglib directive above.
28. To test, display the page like this: index.jsp?sayHello=true&name=Murphy
29. --%>
30. <%--
31. <c:if test="${param.sayHello}">
32. <!-- Let's welcome the user ${param.name} -->
33. Hello ${param.name}!
34. </c:if>
35. --%>
36.
37. </body>
38. </html>

Cette page est générée comme un exemple de ce qui peut être fait. C'est pourquoi on y trouve de nombreux commentaires. Une
fois ceux-ci enlevés, la page se réduit à :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4. "http://www.w3.org/TR/html4/loose.dtd">
5.
6. <html>
7. <head>
8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
9. <title>JSP Page</title>
10. </head>
11. <body>
12.
13. <h1>JSP Page</h1>
14. <br/>
15. <a href="./faces/welcomeJSF.jsp">JavaServer Faces Welcome Page</a>
16. </body>
17. </html>

ce qui donne à l'affichage :

5
2 1

6
3

Introdution à Java EE
219/334
• en [1], on a demandé le contexte sans mentionner de page. C'est donc la page [http://localhost:8080/intro-01/index.jsp]
qui a été servie.
• en [2] : expression de la balise <title>, ligne 9
• en [3] : expression de la balise <h1>, ligne 13
• en [4] : expression de la balise <a>, ligne 15
• en [5] : l'url cible du lien de la ligne 15. L'url [http://localhost:8080/intro-01/faces/welcomeJSF.jsp] est traitée par la
servlet [Faces Servlet] à cause de sa forme [/faces/*].

Examinons maintenant, la page [welcomeJSF.jsp] qui a été traitée par [Faces Servlet] :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <html>
11. <head>
12. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
13. <title>JSP Page</title>
14. </head>
15. <body>
16. <f:view>
17. <h1><h:outputText value="JavaServer Faces" /></h1>
18. </f:view>
19. </body>
20. </html>

On a là, une page JSP qui contient des balises appartenant au framework JSF. Aussi par la suite, appellera-t-on ce type de page, page
JSF. Deux balises n'appartiennent pas au standard HTML :

• <f:view> (ligne 16) : balise racine de la partie JSF d'une page JSP. Un code situé à l'extérieur de cette balise ne fait l'objet
d'aucun traitement de la part du contrôleur JSF.
• <h:outputText> (ligne 17) : est remplacé dans le flux HTML de la réponse par le texte de son attribut value.

Le code HTML généré par cette page est le suivant :

1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"


2. "http://www.w3.org/TR/html4/loose.dtd">
3.
4. <html>
5. <head>
6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7. <title>JSP Page</title>
8. </head>
9. <body>
10. <h1>JavaServer Faces</h1>
11. </body>
12. </html>

On voit que toute balise non HTML a disparu. Les balises <f:view> et <h:outputText> font partie de bibliothèques de balises
définies lignes 4 et 5 de la page [welcomeJSF.jsp] :

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>


<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

Considérons la 1ère des deux balises. Elle a un attribut uri qui désigne une bibliothèque précise. Deux bibliothèques différentes
doivent avoir des attributs uri différents. A la rencontre de cette attribut, le serveur web va explorer les dossiers [META-INF] du
Classpath de l'application, à la recherche de fichiers avec le suffixe .tld (TagLib Definition). Ici, il va les trouver dans l'archive [jsf-
impl.jar] :

Introdution à Java EE
220/334
En double-cliquant sur le fichier [jsf_core.tld], on a accès à son contenu :

1. <taglib xmlns="http://java.sun.com/xml/ns/javaee"
2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-
jsptaglibrary_2_1.xsd"
4. version="2.1">
5.
6.
7. <!-- ========== Tag Library Description Elements ========================= -->
8.
9. <description>
10. The core JavaServer Faces custom actions that are independent of
11. any particular RenderKit.
12. </description>
13. <tlib-version>1.2</tlib-version>
14. <short-name>f</short-name>
15. <uri>http://java.sun.com/jsf/core</uri>
16. ...
17. <tag>
18. <description>
19. Container for all JavaServer Faces core and custom
20. component actions used on a page.
21. </description>
22. <name>view</name>
23. <tag-class>com.sun.faces.taglib.jsf_core.ViewTag</tag-class>
24. <tei-class>com.sun.faces.taglib.FacesTagExtraInfo</tei-class>
25. <body-content>JSP</body-content>
26. ...
27. </tag>
28. ...
29. </taglib>

• en ligne 15, l'uri de la bibliothèque de balises


• en ligne 22, le tag ou balise view
• en lignes 23-24, les classes d'implémentation de la balise view

Revenons au code de la page JSF :

1. ...
2. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
3. ...
4. <f:view>
5. ...
6. </f:view>
7. ...

En ligne 4, le parseur de la page JSP trouve la balise <f:view>. La ligne 2 lui indique que toute balise commençant par f désigne une
balise de la bibliothèque d'uri [http://java.sun.com/jsf/core]. Il trouve la définition de cette bibliothèque dans [jsf-impl.jar/META-
INF/jsf_core.tld]. Dans ce fichier, il trouve également les classes capable de traiter la balise <f:view> : ViewTag et
FacesTagExtraInfo. Il va chercher ces classes dans le Classpath de l'application. Il va les trouver, là encore, dans l'archive [jsf-impl.jar] :

Introdution à Java EE
221/334
Les préfixes définis pour les balises du framework JSF (cf ci-dessous) sont libres. Les préfixes f et h sont des valeurs qu'on retrouve
dans la plupart des pages JSF et il est préférable de les garder.

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>


<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

1.1.5 Utilisation de la bibliothèque de balises JSTL

Revenons sur la page [index.jsp] :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <%--
4. The taglib directive below imports the JSTL library. If you uncomment it,
5. you must also add the JSTL library to the project. The Add Library... action
6. on Libraries node in Projects view can be used to add the JSTL 1.1 library.
7. --%>
8. <%--
9. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
10. --%>
11.
12. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
13. "http://www.w3.org/TR/html4/loose.dtd">
14.
15. <html>
16. <head>
17. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
18. <title>JSP Page</title>
19. </head>
20. <body>
21.
22. <h1>JSP Page</h1>
23. <br/>
24. <a href="./faces/welcomeJSF.jsp">JavaServer Faces Welcome Page</a>
25.
26. <%--
27. This example uses JSTL, uncomment the taglib directive above.
28. To test, display the page like this: index.jsp?sayHello=true&name=Murphy
29. --%>
30. <%--
31. <c:if test="${param.sayHello}">
32. <!-- Let's welcome the user ${param.name} -->
33. Hello ${param.name}!
34. </c:if>
35. --%>
36.
37. </body>
38. </html>

Comme les commentaires des lignes 3-7 et 26-35 nous y invitent, utilisons la bibliothèque de balises JSTL afin de saluer l'utilisateur :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
4.
5. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
6. "http://www.w3.org/TR/html4/loose.dtd">
7.
8. <html>
9. <head>
10. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
11. <title>JSP Page</title>
12. </head>
13. <body>
14.
15. <h1>JSP Page</h1>
16. <br/>
17. <a href="./faces/welcomeJSF.jsp">JavaServer Faces Welcome Page</a>
18. <c:if test="${param.sayHello}">
19. <!-- Let's welcome the user ${param.name} -->
20. Hello ${param.name}!
21. </c:if>
22. </body>
23. </html>

• lignes 18-21 : un test JSTL

Introdution à Java EE
222/334
• ligne 18 : si le paramètre sayHello existe, alors la ligne 19 sera incluse dans la réponse Html faite au client. Des paramètres
peuvent être passés dans une Url sous la forme [http://machine:port/Url?param1=val1&param2=val2&...]. Dans l'Url
précédente, un paramètre "param1" est passé avec la valeur "val1" et un paramètre "param2" est passé avec la valeur
"val2".
• ligne 20 : ${param.name} sera remplacé par la valeur du paramètre name.

Les classes capables de traiter les balises JSTL se trouvent dans la bibliothèque [appserv-jstl.jar] du projet :

• en [1], la bibliothèque [appserv-jstl.jar] amenée par le serveur Sun. A été incluse à la création du projet dans les
bibliothèques utilisées par celui-ci.
• en [2], le fichier c.tld qui définit la bibliothèque de balises d'uri [http://java.sun.com/jsp/jstl/core] (ligne 3 de index.jsp)

Maintenant, déployons le nouveau projet sans l'exécuter [1] :

Puis, prenons un navigateur pour demander [2] l'url [http://localhost:8080/intro-01/?sayHello=true&name=Master2IAIE] : le


résultat peut être vu en [3].

1.1.6 Redirection dans la page d'accueil

Nous utilisons maintenant la bibliothèque JSTL pour rediriger le client qui demande la page d'accueil, vers la page [welcomeJSF.jsp].
Le fichier [index.jsp] devient le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
4.
5. <c:redirect url="/faces/welcomeJSF.jsp"/>"

• ligne 5 : la balise <c:redirect> envoie en réponse au client, une demande de redirection vers l'url définie par l'attribut url.

Le lecteur est invité à faire la modification et à réexécuter le projet.

Le dialogue HTTP client / serveur qui se produit est le suivant :

Introdution à Java EE
223/334
cycle demande / réponse n° 1

1. GET /intro-01/ HTTP/1.1


2. Host: localhost:8080
3. User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.7) Gecko/20070914 Firefox/2.0.0.7
4. Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
5. Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
6. Accept-Encoding: gzip,deflate
7. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8. Keep-Alive: 300
9. Connection: keep-alive
10.
11. HTTP/1.x 302 Moved Temporarily
12. X-Powered-By: Servlet/2.5, JSP/2.1
13. Set-Cookie: JSESSIONID=4748c6a8e79f2112a2b4ce79d3b93; Path=/intro-01
14. Location: http://localhost:8080/intro-01/faces/welcomeJSF.jsp;jsessionid=4748c6a8e79f2112a2b4ce79d3b93
15. Content-Type: text/html;charset=UTF-8
16. Content-Length: 0
17. Date: Thu, 27 Sep 2007 14:04:52 GMT
18. Server: Sun Java System Application Server Platform Edition 9.0_01

• lignes 1-2 : le navigateur client demande l'url [http://localhost:8080/intro-01]


• ligne 3 : le navigateur s'identifie
• lignes 4-9 : le navigateur envoie ses préférences au serveur web
• ligne 11 : début de la réponse du serveur. Le code 302 indique que le serveur demande au client de se rediriger vers une
autre Url. Celle-ci est définie ligne 14, par l'attribut Location.
• ligne 12 : la réponse est fournie par un conteneur de servlets, celui qui exécute les pages Jsp de l'application
• ligne 13 : le serveur envoie au client un jeton de session que le client devra renvoyer à chacune de ses demandes s'il veut
être reconnu.
• ligne 14 : l'Url de redirection. On remarquera que le jeton de session y a été inclus.
• ligne 18 : identification du serveur web

cycle demande / réponse n° 2

1. GET /intro-01/faces/welcomeJSF.jsp;jsessionid=4748c6a8e79f2112a2b4ce79d3b93 HTTP/1.1


2. Host: localhost:8080
3. User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.7) Gecko/20070914 Firefox/2.0.0.7
4. Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
5. Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
6. Accept-Encoding: gzip,deflate
7. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8. Keep-Alive: 300
9. Connection: keep-alive
10. Cookie: JSESSIONID=4748c6a8e79f2112a2b4ce79d3b93
11.
12. HTTP/1.x 200 OK
13. X-Powered-By: Servlet/2.5, JSP/2.1
14. Content-Type: text/html;charset=UTF-8
15. Content-Language: fr-FR
16. Transfer-Encoding: chunked
17. Date: Thu, 27 Sep 2007 14:04:52 GMT
18. Server: Sun Java System Application Server Platform Edition 9.0_01

• ligne 1 : le navigateur fait sa 2ième demande. Il demande l'Url vers laquelle on lui a demandé de se rediriger, jeton de
session inclus dedans. On retrouve ce jeton en ligne 10. Ce sont deux façons de renvoyer un jeton de session :
• dans l'Url demandée
• dans un entête HTTP Cookie.
• ligne 12 : la réponse du serveur web. Le code 200 indique que la page demandée a été trouvée. Le code HTML de celle-ci
est envoyé derrière les entêtes HTTP : après la ligne 18, il y a une ligne vide puis le texte HTML de la réponse.

1.1.7 Tutoriels Netbeans

Introdution à Java EE
224/334
Il existe de nombreux tutoriels Java pour Netbeans et notamment pour le développement web. Pour y accéder, on procédera ainsi :

1
3

• dans l'option [Help] [1], prendre l'option [2] qui va ouvrir un navigateur et charger la page d'accueil de l'aide Netbeans
• en [3], un ensemble de liens. On suivra le lien [Web Applications].

Les tutoriels Netbeans sont de précieux outils. Ils sont courts, une vingtaine de minutes souvent, et résolument pratiques.
L'utilisateur est constamment guidé dans l'écriture du code Java et dans l'utilisation de Netbeans.

1.2 Exemple n° 2
Mots clés : gestionnaire d'événements, internationalisation, navigation entre pages.

1.2.1 L'application

L'application est la suivante :

4
1

2
5

• en [1], la page d'accueil vue précédemment [1] a été agrémentée de liens


• en [2], deux liens pour changer la langue des pages de l'application
• en [3], un lien de navigation vers une autre page
• lorsqu'on clique sur [3], la page [4] est affichée
• le lien [5] permet de revenir à la page d'accueil

Introdution à Java EE
225/334
1
2

• sur la page d'accueil [1], les liens [2] permettent de changer de langue
• en [3], la page d'accueil en anglais

1.2.2 Le projet Netbeans

On génèrera un nouveau projet Jsf comme expliqué au paragraphe 1.1.1, page 212. On le nommera intro-02 :

1 2

• en [1], le projet généré


• en [2], le projet final. Outre la modification de la page Jsf [welcomeJSF.jsp], nous allons introduire du code Java pour
traiter les événements de cette page.

1.2.3 Les pages Jsf du projet

1.2.3.1 [index.jsp]

La page [index.jsp] redirige le client vers la page [welcomeJSF.jsp] :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
4.
5. <c:redirect url="/faces/welcomeJSF.jsp"/>"

1.2.3.2 [welcomeJSF.jsp]

Le nouveau fichier [welcomeJSF.jsp] envoie la page suivante au navigateur client :

Introdution à Java EE
226/334
Le code qui produit cette page est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <f:view>
11. <html>
12. <head>
13. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
14. <title><h:outputText value="#{msg['welcome.titre']}" /></title>
15. </head>
16. <body>
17. <h:form id="formulaire">
18. <h:panelGrid columns="2">
19. <h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>
20. <h:commandLink value="#{msg['welcome.langue2']}" action="#{locale.setEnglishLocale}"/>
21. </h:panelGrid>
22. <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
23. <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
24. </h:form>
25. </body>
26. </html>
27. </f:view>

Nous ne commentons que ce qui est nouveau :

• ligne 10 : la balise <f:view> sert à délimiter le code que le moteur Jsf doit traiter. En ligne 14, une expression de la forme
#{expression} doit être évaluée par le moteur Jsf. Aussi a-t-on remonté la balise <f:view> afin qu'elle englobe l'ensemble
du code de la page Jsp.
• ligne 14 : la balise <h:outputText> affiche la valeur d'une expression, #{msg['welcome.titre']}. La syntaxe
#{expression} est standard. La forme de expression peut être diverse. Nous l'exprimerons le plus souvent sous la forme
bean['clé'] ou bean.champ. bean doit être un objet déclaré dans le fichier de configuration [faces-config.xml]. Celui-ci
doit donc définir l'objet msg utilisé ligne 14 :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. ...
13. </faces-config>

• lignes 4-11 : la balise <application> sert à configurer l'application Jsf


• lignes 5-10 : la balise <resource-bundle> sert à définir des ressources pour l'application, ici un fichier de messages.
• lignes 6-8 : la balise <base-name> définit le nom du fichier de messages.
• ligne 7 : le fichier s'appellera messages[_CodeLangue][_CodePays].properties. La balise <base-name> ne définit que la
première partie du nom. Le reste est implicite. Il peut exister plusieurs fichiers de messages, un par langue :

Introdution à Java EE
227/334
1 2 3

• en [1], on voit quatre fichiers de messages correspondant au nom de base messages défini dans [faces-config.xml].
• messages_fr.properties : contient les messages en français (code fr)
• messages_en.properties : contient les messages en anglais (code en)
• messages_es_ES.properties : contient les messages en espagnol (code es) de l'Espagne (code ES). Il existe
d'autres type d'espagnol, par exemple celui de Bolivie (es_BO)
• messages.properties : est utilisé par le serveur lorsque la langue de la machine sur laquelle il s'exécute n'a aucun
fichier de messages qui lui est associé. Il serait utilisé par exemple, si l'application s'exécutait sur une machine en
Allemagne où la langue par défaut serait l'allemand (de). Comme il n'existe pas de fichier
[messages_de.properties], l'application utiliserait le fichier [messages.properties].
• en [2] : les codes des langues font l'objet d'un standard international.
• en [3] : idem pour les codes des pays.

Dans notre exemple, le fichier de messages en français [messages_fr.properties] contiendra les éléments suivants :

1. welcome.titre=Tutoriel JSF (JavaServer Faces)


2. welcome.langue1=Fran\u00e7ais
3. welcome.langue2=Anglais
4. welcome.page1=Page 1
5. page1.titre=page1
6. page1.entete=Page 1
7. page1.welcome=Page d'accueil

Netbeans a utilisé le codage UTF-8 pour coder le fichier. Ligne 2, \u00e7 est le code UTF-8 de la cédille ç. Revenons au fichier
[faces-config.xml] qui déclare le fichier des messages :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. ...
13. </faces-config>

La ligne 9 indique qu'une ligne du fichier des messages sera référencée par l'identificateur msg dans les pages Jsf. Cet identificateur
est utilisé dans le fichier [welcomeJsF.jsp] étudié :

1. <f:view>
2. <html>
3. <head>
4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5. <title><h:outputText value="#{msg['welcome.titre']}" /></title>
6. </head>
7. <body>
8. ...
9. </body>
10. </html>
11. </f:view>

Introdution à Java EE
228/334
La balise <h:outputText> de la ligne 5, va afficher la valeur du message (présence de l'identificateur msg) de clé welcome.titre.
Ce message est cherché et trouvé dans le fichier [messages.properties] de la langue active du moment. Par exemple, pour le français
:

welcome.titre=Tutoriel JSF (JavaServer Faces)

Un message est de la forme clé=valeur. La ligne 5 du fichier [welcomeJSF.jsp] devient la suivante après évaluation de l'expression
#{msg['welcome.titre']} :

<title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>

Nous verrons que ce mécanisme des fichiers de messages permet de changer facilement la langue des pages d'un projet Jsf. On
parle d'internationalisation du projet ou plus souvent de son abréviation i18n, parce que le mot internationalisation commence
par i et finit par n et qu'il y a 18 lettres entre le i et le n.

Continuons à explorer le contenu du fichier [welcomeJSF.jsp] :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <f:view>
11. <html>
12. <head>
13. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
14. <title><h:outputText value="#{msg['welcome.titre']}" /></title>
15. </head>
16. <body>
17. <h:form id="formulaire">
18. <h:panelGrid columns="2">
19. <h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>
20. <h:commandLink value="#{msg['welcome.langue2']}" action="#{locale.setEnglishLocale}"/>
21. </h:panelGrid>
22. <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
23. <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
24. </h:form>
25. </body>
26. </html>
27. </f:view>

• lignes 17-24 : la balise <h:form> introduit un formulaire. Un formulaire est généralement constitué de :
• balises de champs de saisie (texte, boutons radio, cases à cocher, listes d'éléments, ...)
• balises de validation du formulaire (boutons, liens). C'est via un bouton ou un lien que l'utilisateur envoie ses
saisies au serveur qui les traitera.
Toute balise Jsf peut être identifiée par un attribut id. Le plus souvent, on peut s'en passer et c'est ce qui a été fait dans la
plupart des balises Jsf utilisées ici. Néanmoins, cet attribut est utile dans certains cas. Ligne 17, le formulaire est identifié
par l'id formulaire. Dans cet exemple, l'id du formulaire ne sera pas utilisé et aurait pu être omis.
• lignes 18-21 : la balise <h:panelGrid> définit ici un tableau Html à deux colonnes. Elle donne naissance à la balise Html
<table>.
• le formulaire dispose de trois liens déclenchant son traitement, en lignes 19, 20 et 23. La balise <h:commandLink> a au
moins deux attributs :
• value : le texte du lien
• action : soit une chaîne de caractères C, soit la référence d'une méthode qui après exécution rend la chaîne de
caractères C. Cette chaîne de caractères C doit être définie dans les règles de navigation du fichier [faces-
config.xml]. Elle permet au contrôleur [Faces Servlet] de déterminer la page qui doit être affichée, une fois que
l'action définie par l'attribut action a été exécutée.

Examinons la mécanique du traitement des formulaires avec l'exemple du lien de la ligne 19 :

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

Tout d'abord, le fichier des messages est exploité pour remplacer l'expression #{msg['welcome.langue1']} par sa valeur. Après
évaluation, la balise devient :

Introdution à Java EE
229/334
<h:commandLink value="Français" action="#{locale.setFrenchLocale}"/>

La traduction Html de cette balise Jsf va être la suivante :

<a href="#" onclick="return


jsfcljs(document.forms['formulaire'],'formulaire:j_id_id21,formulaire:j_id_id21','');">Français</a>

ce qui donnera l'apparence visuelle qui suit :

On notera l'attribut onclick de la balise Html <a>. Lorsque l'utilisateur va cliquer sur le lien [Français], du code Javascript va être
exécuté. Celui-ci est embarqué dans la page que le navigateur a reçue et c'est le navigateur qui l'exécute. Le code Javascript est
largement utilisé dans les technologies Jsf et Ajax (Asynchronous Javascript And Xml). Il a en général pour but d'améliorer
l'ergonomie et la réactivité des applications web. Il est le plus souvent généré de façon automatique par des outils logiciels et il n'est
pas alors utile de le comprendre. Mais parfois (rarement) un développeur peut être amené à ajouter du code Javascript dans ses
pages Jsf. La connaissance de Javascript est alors nécessaire.

Il est inutile ici de comprendre le code Javascript généré pour la balise Jsf <h:commandLink>. On peut cependant noter deux
points :
• le code Javascript utilise l'identifiant formulaire que nous avons donné à la balise Jsf <h:form>
• Jsf génère des identifiants automatiques pour toutes les balises où l'attribut id n'a pas été défini. On en voit un exemple ici
: j_id_id21. Donner un identifiant clair aux balises permet de mieux comprendre le code Javascript généré si cela devient
nécessaire. C'est notamment le cas lorsque le développeur doit lui-même ajouter du code Javascript qui manipule les
composants de la page. Il a alors besoin de connaître les identifiants id de ces composants.

Que va-t-il se passer lorsque l'utilisateur va cliquer sur le lien [Français] de la page ci-dessus ? Considérons l'architecture d'une
application Jsf :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche
JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Le contrôleur [Faces Servlet] va recevoir la requête du navigateur client sous la forme HTTP suivante :

1. POST /intro-02/faces/welcomeJSF.jsp HTTP/1.1


2. Host: localhost:8080
3. ...
4. Content-Type: application/x-www-form-urlencoded
5. Content-Length: 1854
6.
7. formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire%3Aj_id_id21

• lignes 1-2 : le navigateur demande l'Url [http://localhost:8080/intro-02/faces/welcomeJSF.jsp]. C'est toujours ainsi : les
saisies faites dans un formulaire Jsf obtenu avec l'Url urlFormulaire sont envoyées à cette même url. Le navigateur a deux
moyens pour envoyer les valeurs saisies : GET et POST. Avec la méthode GET, les valeurs saisies sont envoyées par le
navigateur dans l'Url qui est demandée. Ci-dessus, le navigateur aurait pu envoyer la première ligne suivante :

Introdution à Java EE
230/334
GET /intro-
02/faces/welcomeJSF.jsp?formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire
%3Aj_id_id21 HTTP/1.1

Avec la méthode POST utilisée ici, le navigateur envoie au serveur les valeurs saisies au moyen de la ligne 7.
• ligne 4 : indique la forme d'encodage des valeurs du formulaire
• ligne 5 : indique la taille en octets de la ligne 7
• ligne 6 : ligne vide qui indique la fin des entêtes HTTP et le début des 1854 octets des valeurs du formulaire
• ligne 7 : les valeurs du formulaire sous la forme element1=valeur1&element2=valeur2& ..., la forme d'encodage définie
par la ligne 4. Dans cette forme de codage, certains caractères sont remplacés par leur valeur hexadécimale. C'est le cas
dans le dernier élément :

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire%3Aj_id_id21

où %3A représente le caractère :. C'est donc la chaîne formulaire:j_id_id21=formulaire:j_id_id21 qui est envoyée au
serveur. On se rappelle peut-être que nous avons déjà rencontré l'identifiant j_id_id21 lorsque nous avons examiné le
code Html généré pour la balise

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

Il avait été généré de façon automatique par Jsf. Ce qui nous importe ici, c'est que la présence de cet identifiant dans la
chaîne des valeurs envoyées par le navigateur client permet à Jsf de savoir que le lien [Français] a été cliqué. Il va alors
utiliser l'attribut action ci-dessus, pour décider comment traiter la chaîne reçue. L'attribut
action="#{locale.setFrenchLocale}" indique à Jsf que la requête du client doit être traitée par la méthode
[setFrenchLocale] d'un objet appelé locale. Cet objet, ou bean, doit être défini dans le fichiers [faces-config.xml] :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. <managed-bean>
13. <description>
14. change la langue des pages JSF
15. </description>
16. <managed-bean-name>locale</managed-bean-name>
17. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
18. <managed-bean-scope>request</managed-bean-scope>
19. </managed-bean>
20.
21. <!-- navigation -->
22. ...
23. </faces-config>

• lignes 4-11 : configuration déjà expliquée, celle du fichier des messages de l'application
• lignes 12-19 : la balise <managed-bean> sert à définir un objet manipulé par les pages Jsf. On a l'habitude
d'appeler ces objets des beans.
• lignes 13-15 : une description facultative du bean
• ligne 16 : le nom sous lequel le bean sera connu dans les pages Jsf. Rappelons la balise <h:commandLink>
étudiée :

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

• ligne 17 : la classe qu'il faut instancier lorsqu'une page Jsf réclame le bean locale. Cette classe sera ajoutée
prochainement à notre projet Netbeans [3] :

Introdution à Java EE
231/334
2

• ligne 18 : la durée de vie du bean. Il y en a trois :


• request : la durée de vie du bean est celle du cycle demande navigateur / reponse serveur. Si pour
traiter une nouvelle requête du même navigateur ou d'un autre, ce bean est de nouveau nécessaire, il
sera instancié de nouveau.
• session : la durée de vie du bean est celle de la session d'un client particulier. Le bean est créé
initialement pour les besoins de l'une des requêtes de ce client. Il restera ensuite en mémoire dans la
session de ce client. Un tel bean mémorise en général des données propres à un client donné. Il sera
détruit lorsque la session du client sera détruite.
• application : la durée de vie du bean est celle de l'application elle-même. Un bean avec cette durée de
vie est le plus souvent partagé par tous les clients de l'application. Il est en général initialisé au début de
l'application.

Netbeans génère par défaut une durée de vie égale à request, toujours utilisable, alors que les durées session et
application nécessitent des conditions particulières. Nous verrons ultérieurement que le bean locale peut avoir
une durée de vie égale à application.

Revenons à la requête du navigateur :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche
JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

et à la balise <h:commandLink> qui a généré le lien [Français] sur lequel ci-dessus on a cliqué :

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

Le contrôleur va transmettre la requête du navigateur au gestionnaire d'événements défini par l'attribut action de la balise
<h:commandLink>. Le gestionnaire d'événements M référencé par l'attribut action d'une commande <h:commandLink> doit
avoir la signature suivante :

public String M();

• il ne reçoit aucun paramètre. Nous verrons qu'il peut néanmoins avoir accès à la requête du client
• il doit rendre un résultat C de type String. La chaîne C doit être déclarée dans les règles de navigation du fichier [faces-
config.xml]. Dans l'architecture Jsf ci-dessus, le contrôleur [Faces Servlet] utilisera la chaîne C rendue par le gestionnaire

Introdution à Java EE
232/334
d'événements et son fichier de configuration [faces-config.xml] pour déterminer quelle page Jsf, il doit envoyer en réponse
au client [4].

On trouve dans le fichier [faces-config.xml] :


• la liste des gestionnaires d'événements [2a] ainsi que les modèles [3] de l'application. Ces éléments sont des classes Java
qui suivent la norme JavaBean :
• présence d'un constructeur sans paramètres
• présence de méthodes get / set pour chaque champ privé
Ces beans sont tous déclarés dans [faces-config.xml]. Un bean est souvent lié à une page Jsp particulière à qui il sert à la
fois de modèle et de gestionnaire d'événements : les champs du bean servent de modèle et ses méthodes de
gestionnaires d'événements.
• les règles de navigation. Les gestionnaires d'événements doivent rendre au contrôleur [Faces Servlet] ci-dessus, un
résultat sous forme de chaînes de caractères. Les règles de navigation consistent à définir pour tous les événements de
l'application, trois paramètres [jsp1, res1, jsp2] :
• jsp1 est la page Jsp dont un événement a été géré
• res1 est l'une des chaînes de caractères que peut rendre le gestionnaire de cet événement
• jsp2 est la page Jsp qui doit être renvoyée à l'utilisateur lorsque le gestionnaire d'événements rend la valeur res1
Si l'ensemble des gestionnaires des événements de la page jsp1 peut rendre N résultats différents, alors on aura N règles
de navigation dans le fichier [faces-config.xml] pour la page jsp1.

Dans la balise

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

le gestionnaire de l'événement clic sur le lien [Français] est la méthode [locale.setFrenchLocale] où locale est une instance de la classe
[utils.ChangeLocale] suivante :

1. package utils;
2.
3. import java.util.Locale;
4. import javax.faces.context.FacesContext;
5.
6. public class ChangeLocale {
7.
8. /** Creates a new instance of ChangeLocale */
9. public ChangeLocale() {
10. }
11.
12. public String setFrenchLocale(){
13. changeLocale(new Locale("fr"));
14. return null;
15. }
16.
17. public String setEnglishLocale(){
18. changeLocale(new Locale("en"));
19. return null;
20. }
21.
22. private void changeLocale(Locale locale){
23. FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
24. }
25. }

La méthode setFrenchLocale a bien la signature des gestionnaires d'événements. Rappelons-nous que le gestionnaire d'événements
doit traiter la requête du client. Puisqu'il ne reçoit pas de paramètres, comment peut-il avoir accès à celle-ci ? Il existe diverses
façons de faire :

• le bean B qui contient le gestionnaire d'événements de la page Jsf P est aussi souvent celui qui contient le modèle M de
cette page. Cela signifie que le bean B contient des champs qui seront initialisés par les valeurs saisies dans la page P. Cela
sera fait par le contrôleur [Faces Servlet] avant que le gestionnaire d'événements du bean B ne soit appelé. Ce gestionnaire
aura donc accès, via les champs du bean B auquel il appartient, aux valeurs saisies par le client dans le formulaire et pourra
les traiter.

Introdution à Java EE
233/334
• la méthode statique [FacesContext.getCurrentInstance()] de type [FacesContext] donne accès au contexte d'exécution de la
requête Jsf courante qui est un objet de type [FacesContext]. Le contexte d'exécution de la requête ainsi obtenu, permet
d'avoir accès aux paramètres postés au serveur par le navigateur client avec la méthode suivante :

Map FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()

Si les paramètres postés (POST) par le navigateur client sont les suivants :

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire%3Aj_id_id21

la méthode getRequestParameterMap() rendra le dictionnaire suivant :

clé valeur
formulaire formulaire
javax.faces.ViewState ...
formulaire:j_id_id21 formulaire:j_id_id21

Dans la balise

<h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>

qu'attend-on du gestionnaire d'événements locale.setFrenchLocale ? On veut qu'il fixe la langue utilisée par l'application. Dans le
jargon Java, on appelle cela " localiser " l'application. Cette localisation est obtenue à partir du contexte d'exécution de la requête, de
la façon suivante :

FacesContext.getCurrentInstance().getViewRoot().setLocale(Locale locale);

– FacesContext FacesContext.getCurrentInstance() : donne le contexte d'exécution Jsf courant


– UIViewRoot FacesContext.getCurrentInstance().getViewRoot() : donne le composant racine de l'arbre des composants Jsf de la page en
cours de traitement. Une page Jsf est un ensemble de balises qui forment ensemble un arbre de balises. Dans la terminologie
Jsf, on parle de composants et d'arbre de composants.
– void FacesContext.getCurrentInstance().getViewRoot().setLocale(Locale locale) : fixe la langue d'affichage de l'arbre des composants de la
page courante.

Un objet de type java.util.Locale a un constructeur qui admet pour paramètre un type String représentant un code Langue et / ou un
code Pays tels ceux qui ont été présentés lors de l'étude du fichier des messages [messages.properties], page 227.

Ceci expliqué, la méthode setFrenchLocale pourrait être la suivante :

1. public String setFrenchLocale(){


2. FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("fr"));
3. return null;
4.}

Nous avons expliqué qu'un gestionnaire d'événements devait rendre une chaîne de caractères C qui serait recherchée dans les règles
de navigation du fichier [faces-config.xml] afin de trouver la page Jsf à envoyer en réponse au navigateur client. Si la page à
renvoyer est la même que celle en cours de traitement, le gestionnaire d'événements peut se contenter de renvoyer la valeur null.
C'est ce qui est fait ici ligne 3 : on veut renvoyer la même page [welcomeJSF.jsp] mais dans une langue différente.

Revenons à l'architecture de traitement de la requête :

Introdution à Java EE
234/334
Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche
JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Le gestionnaire d'événements locale.setFrenchLocale a été exécuté et a rendu la valeur null au contrôleur [Faces Servlet]. Celui-ci va
donc réafficher la page [welcomeJSF.jsp] et son arbre de composants. Revoyons celui-ci :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <f:view>
11. <html>
12. <head>
13. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
14. <title><h:outputText value="#{msg['welcome.titre']}" /></title>
15. </head>
16. <body>
17. <h:form id="formulaire">
18. <h:panelGrid columns="2">
19. <h:commandLink value="#{msg['welcome.langue1']}" action="#{locale.setFrenchLocale}"/>
20. <h:commandLink value="#{msg['welcome.langue2']}" action="#{locale.setEnglishLocale}"/>
21. </h:panelGrid>
22. <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
23. <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
24. </h:form>
25. </body>
26. </html>
27. </f:view>

A chaque fois qu'une valeur de type #{msg['...']} est évaluée, l'un des fichiers des messages [messages.properties] est utilisé. Celui
utilisé est celui qui correspond à la " localisation " de l'arbre des composants. Le gestionnaire d'événements locale.setFrenchLocale
définissant cette localisation à fr, c'est le fichier [messages_fr.properties] qui sera utilisé. Un clic sur le lien [Anglais] (ligne 20)
changera la localisation en en (cf méthode locale.setEnglishLocale). Ce sera alors le fichier [messages_en.properties] qui sera utilisé et la
page apparaîtra en anglais :

Pour une raison qui n'apparaît pas clairement ici, cette localisation faite sur une page est conservée pour les pages suivantes.

Il nous reste un dernier élément de la page [welcomeJSF.jsp] à étudier, la line 23 du code :

<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>

Introdution à Java EE
235/334
On a de nouveau une balise <h:commandLink> avec un attribut action égal à une chaîne de caractères. Dans ce cas, aucun
gestionnaire d'événements n'est appelé pour traiter la page. On passe tout de suite à la page désignée par la règle de navigation
suivante :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. ...
6. </application>
7. <managed-bean>
8. ...
9. </managed-bean>
10.
11. <!-- navigation -->
12. <navigation-rule>
13. <description>
14.
15. </description>
16. <from-view-id>/welcomeJSF.jsp</from-view-id>
17. <navigation-case>
18. <from-outcome>page1</from-outcome>
19. <to-view-id>/page1.jsp</to-view-id>
20. </navigation-case>
21. </navigation-rule>
22. ...
23. </faces-config>

• lignes 12-21 : une règle de navigation


• ligne 16 : la vue de départ
• lignes 17-19 : une règle de navigation pour la vue de départ de la ligne 16
• ligne 18 : la clé de navigation – celle-ci peut être le résultat d'un gestionnaire d'événements ou directement la valeur d'un
attribut action d'une balise <h:commandLink> ou <h:commandButton>. C'est le cas que nous étudions actuellement.
• ligne 19 : la page vers laquelle il faut naviguer lorsque, à partir de la vue de la ligne 16 on obtient la clé de navigation de la
ligne 18.
• pour la vue de la ligne 16, on peut avoir plusieurs cas de navigation. Chacun d'eux sera représenté par une balise
<navigation-case> à l'intérieur de la balise <navigation-rule> des lignes 12-21.

Examinons le fonctionnement de l'application dans ce cas d'utilisation :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche
JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

L'utilisateur clique sur le lien [Page 1]. Le formulaire est posté au contrôleur [Faces Servlet]. Celui reconnaît dans la requête qu'il
reçoit, le fait que le lien [Page 1] a été cliqué. Il examine la balise correspondante :

<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>

Il n'y a pas de gestionnaire d'événements associé au lien. Le contrôleur [Faces Servlet] passe tout de suite à l'étape [3] ci-dessus de
navigation. Il exploite le fichier [faces-config.xml] et découvre qu'il doit afficher la page [/page1.jsp] :

Introdution à Java EE
236/334
1.2.3.3 [page1.jsp]

La fichier [page1.jsp] envoie la page suivante au navigateur client :

Le code qui produit cette page est le suivant :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <f:view>
11. <html>
12. <head>
13. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
14. <title><h:outputText value="#{msg['page1.titre']}"/></title>
15. </head>
16. <body>
17. <h1><h:outputText value="#{msg['page1.entete']}"/></h1>
18. <h:form>
19. <h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>
20. </h:form>
21. </body>
22. </html>
23. </f:view>

Il n'y a dans cette page rien qui n'ait déjà été expliqué. Le lecteur fera la correspondance entre le code Jsf et la page envoyée au
navigateur client. Le lien de retour à la page d'accueil :

<h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>

nécessite une règle de navigation qui est ajoutée au fichier de configuration [faces-config.xml] :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. ...
5. <navigation-rule>
6. <description>
7.
8. </description>
9. <from-view-id>/page1.jsp</from-view-id>
10. <navigation-case>
11. <from-outcome>welcome</from-outcome>
12. <to-view-id>/welcomeJSF.jsp</to-view-id>

Introdution à Java EE
237/334
13. </navigation-case>
14. </navigation-rule>
15.
16. </faces-config>

• lignes 5-14 : les règles de navigation à partir de la page /page1.jsp déclarée ligne 9.
• lignes 10-13 : lorsqu'à l'issue du traitement d'un événement de la page [/page1.jsp], le contrôleur [Faces Servlet] reçoit la
chaîne de caractères welcome (ligne 11), il affiche la page [/welcomeJSF.jsp] (ligne 12).

1.2.4 Le fichier des messages du projet

Rappelons comment est déclaré le fichier des messages du projet Jsf dans [faces-config.xml] :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. ...
13. </faces-config>

Le nom du fichier des messages est défini ligne 7. Il sera cherché dans le classpath du projet. S'il est à l'intérieur d'un paquetage, celui-
ci doit être défini ligne 7, par exemple ressources.messages, si le fichier [messages.properties] se trouve dans le dossier [ressources] du
classpath. Le nom, ligne 7, ne comportant pas de paquetage, le fichier [messages.properties] doit être placé à la racine du dossier des
codes source :

1 2

En [1], dans l'onglet [Projects] du projet Netbeans, le fichier [messages.properties] est présenté comme une liste des différentes
versions de messages définies. Les versions sont identifiées par une suite d'un à trois codes [codeLangue_codePays_codeVariante].
En [1], seul le code [codeLangue] a été utilisé : en pour l'anglais, fr pour le français. Chaque version fait l'objet d'un fichier séparé
dans le système de fichiers [2].

Netbeans offre des facilités pour créer ces fichiers de messages que nous examinons maintenant. Commençons par créer le fichier
[messages.properties] :

Introdution à Java EE
238/334
2

• en [1], on sélectionne la branche [Source Packages] et en [2], on indique qu'on veut y créer un fichier
• en [3], on choisit la catégorie [Other] et en [4] le type [Properties File]

8
5

6
7

• en [5], le nom du fichier (sans .properties qui est implicite)


• en [6], le dossier dans lequel on le crée, ici la racine du dossier des sources Java
• en [7], le fichier [messages.properties] a été créé. Comme il n'a pas de codes langue ou pays, il est appelé le fichier par
défaut, celui que le serveur web utilise si on lui demande une page dans une localisation qui n'a pas été définie pour le fichier
[messages.properties].
• en [8] et [9], on ajoute une nouvelle localisation (langue, pays, variante).

12

10

11

13

• en [10], dans la liste des codes Langue, on choisit le français. On ne choisit pas de code Pays, ni de variante.
• en [11], la variante fr de [messages.properties] apparaît.
• en [12] et [13], on ajoute une ligne clé=valeur au fichier [messages_fr.properties]

Introdution à Java EE
239/334
14
15
16

• en [14], la clé, en [15] la valeur.


• en [16], Netbeans montre la clé créée

Si on double-clique sur la version fr de [messages.properties], on a accès à son contenu :

1. # Sample ResourceBundle properties file


2.
3. welcome.titre=Tutoriel JSF (JavaServer Faces)

Il sera souvent plus simple de construire un fichier de messages directement avec un éditeur de texte que d'utiliser les outils offerts
par Netbeans.

Pour notre projet, nous construirons trois fichiers de messages :

1 2

avec les contenus suivants :

[messages_fr.properties]

welcome.titre=Tutoriel JSF (JavaServer Faces)


welcome.langue1=Fran\u00e7ais
welcome.langue2=Anglais
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Page d'accueil

[messages_en.properties]

welcome.titre=JSF (JavaServer Faces) Tutorial


welcome.langue1=French
welcome.langue2=English
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Welcome page

Le fichier [messages.properties] aura le contenu du fichier [messages_fr.properties].

1.2.5 Les classes Java du projet

Notre projet ne contient qu'une classe, celle qui traite les événements " clic " sur les liens [Français] et [Anglais] :

Introdution à Java EE
240/334
1
2

Dans la branche [Source Packages], la classe [ChangeLocale] a été placée dans le paquetage [utils]. Cette classe a déjà été décrite
page 233.

1.2.6 Le fichier de configuration [faces-config.xml]

Nous avons décrit les différentes éléments du fichier de configuration du projet Jsf [faces-config.xml]. Son code est le suivant :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. <managed-bean>
13. <description>
14. change la langue des pages JSF
15. </description>
16. <managed-bean-name>locale</managed-bean-name>
17. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
18. <managed-bean-scope>application</managed-bean-scope>
19. </managed-bean>
20.
21. <!-- navigation -->
22. <navigation-rule>
23. <description>
24.
25. </description>
26. <from-view-id>/welcomeJSF.jsp</from-view-id>
27. <navigation-case>
28. <from-outcome>page1</from-outcome>
29. <to-view-id>/page1.jsp</to-view-id>
30. </navigation-case>
31. </navigation-rule>
32.
33. <navigation-rule>
34. <description>
35.
36. </description>
37. <from-view-id>/page1.jsp</from-view-id>
38. <navigation-case>
39. <from-outcome>welcome</from-outcome>
40. <to-view-id>/welcomeJSF.jsp</to-view-id>
41. </navigation-case>
42. </navigation-rule>
43.
44.
45. </faces-config>

Le contenu de ce fichier a déjà été décrit. Notons simplement un changement sur la durée de vie (ligne 18) du bean locale défini
lignes 12-19. Nous avons indiqué qu'il y avait trois durées de vie possibles : request, session, application et nous avons expliqué
ce que chacune d'elles recouvrait. La valeur par défaut est request, une valeur qui convient à tous les beans. Si on examine le code
de la classe [utils.ChangeLocale] page 233, on verra que rien ne s'oppose à ce que le bean ait une durée de vie égale à application.
En effet, il ne contient aucun champ privé, ce qui en fait un bean sans état. Un tel bean peut alors être partagé par toutes les
requêtes de tous les clients sans risque de conflit entre elles.

Introdution à Java EE
241/334
Netbeans offre des facilités pour construire le fichier [faces-config.xml] que nous examinons maintenant. Il est créé vide lors de la
création initiale du projet. La définition du fichier des messages est faite à la main avec l'éditeur de texte de Netbeans :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. </faces-config>

L'ajout des beans " managés ", ces classes Java qui vont servir de modèles aux pages Jsf et traiter leurs événements, peut être fait
avec l'assistance de Netbeans :

Dans le code source de [faces-config.xml] en [1], on clique droit pour prendre l'option |JavaServer Faces] [2], puis l'option [Add
Managed Bean] [3] :

3
2
4 1

• utiliser [1] pour indiquer le bean de la classe. L'assistant est peu performant ici et il faut taper les premières lettres du nom
de la classe pour qu'elle soit proposée à la sélection. Il peut être plus rapide de taper directement son nom en [2].
• en [3], donner un nom au nouveau bean
• en [4], préciser sa durée de vie
• en [5], donner une description falcutative

Ceci fait, le fichier [faces-config.xml] est mis à jour :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>

Introdution à Java EE
242/334
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. <managed-bean>
13. <description>
14. sert à changer la langue
15. des pages
16. </description>
17. <managed-bean-name>locale</managed-bean-name>
18. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
19. <managed-bean-scope>application</managed-bean-scope>
20. </managed-bean>
21. </faces-config>

Netbeans offre également un assistant pour l'ajout des règles de navigation. Cela se fait en deux étapes :

• ajout d'une règle de navigation qui consiste à définir la page pour laquelle on va définir des cas de navigation.
• pour cette page, définir tous les cas de navigation possibles

Définissons les cas de navigation pour la page [welcomeJSF.jsp]. On définit d'abord la page pour laquelle on va définir ces cas :

1
2 3 5
6 4

• dans le code de [faces-config.xml], cliquer droit pour choisir l'option [JavaServer Faces] [2] puis l'option [Add Navigation
Rule] [3]
• utiliser le bouton [4] pour désigner la page pour laquelle on va décrire les cas de navigation. On peut également taper son
nom en [5]. En [6], une description facultative de la règle de navigation.

Ceci fait, le fichier [faces-config.xml] est mis à jour :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. <managed-bean>
13. <description>
14. sert à changer la langue
15. des pages
16. </description>
17. <managed-bean-name>locale</managed-bean-name>
18. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
19. <managed-bean-scope>application</managed-bean-scope>
20. </managed-bean>
21. <navigation-rule>
22. <description>
23. page d'accueil
24. </description>
25. <from-view-id>/welcomeJSF.jsp</from-view-id>
26. </navigation-rule>
27. </faces-config>

Les cas de navigation pour la page [welcomeJSF.jsp] sont ajoutés de la façon suivante :

Introdution à Java EE
243/334
1
2 4
5
3 6
7
8
9

• en [1], cliquer droit sur le code de la règle de navigation


• choisir [JavaServer Faces] en [2], puis [Add Navigation Case] en [3]
• en [4] doit apparaître la page sur la règle de navigation de laquelle on a cliqué en [1]. Sinon utiliser le bouton [5] pour la
désigner.
• en [7], la page vers laquelle on veut naviguer. On peut la sélectionner avec le bouton [8].
• en [6], le code de transition de la page [/welcomeJSF.jsp] vers la page [/page1.jsp]. Ce code est obtenu par le contrôleur
[Faces Servlet] de deux façons :
• l'événement de la page [welcomeJSF.jsp] traité est celui d'un clic sur un bouton ou un lien de la page
[welcomeJSF.jsp] qui avait un attribut action="page1"
• l'événement de la page [welcomeJSF.jsp] a été traité par un gestionnaire d'événements qui a rendu la clé page1.
• en [9], une description facultative.

Ceci fait, le fichier [faces-config.xml] est de nouveau mis à jour :

1. <navigation-rule>
2. <description>
3. page d'accueil
4. </description>
5. <from-view-id>/welcomeJSF.jsp</from-view-id>
6. <navigation-case>
7. <from-outcome>page1</from-outcome>
8. <to-view-id>/page1.jsp</to-view-id>
9. </navigation-case>
10.</navigation-rule>

On fait de même pour les cas de navigation à partir de la page [/page1.jsp] :

1. <navigation-rule>
2. <from-view-id>/page1.jsp</from-view-id>
3. <navigation-case>
4. <from-outcome>welcome</from-outcome>
5. <to-view-id>/welcomeJSF.jsp</to-view-id>
6. </navigation-case>
7.</navigation-rule>

1.2.7 Exécution du projet

Notre projet est désormais complet. Nous pouvons l'exécuter :

Introdution à Java EE
244/334
2
3

• en [1], le projet est exécuté. Cela lance


• sa compilation : les fichiers java sont compilés
• la construction de l'archive de distribution [intro-02.war] dans le dossier [dist] [3] de l'onglet [Files] [2]
• le lancement du serveur Sun s'il n'était pas déjà lancé
• le chargement (uploading) de l'archive [intro-02.war] sur le serveur Sun. On appelle cela le déploiement du projet
sur le serveur d'application.
• le lancement automatique de l'application selon les propriétés de celle-ci :

• en [1], le serveur sur lequel a été déployée l'application Jsf


• en [2], le nom de cette application ou contexte
• en [3], il est demandé de lancer un navigateur à l'exécution. Celui-ci va demander le contexte de l'application [2], c.a.d. l'Url
[http://localhost:8080/intro-02]. D'après les règles du fichier [web.xml] (cf page 214), c'est le fichier [index.jsp] qui va être
servi au navigateur client. Celui-ci (cf page 226) contient une redirection vers la page [/faces/welcomeJSF.jsp]. Le
navigateur va donc demander cette 2ième Url. Puisqu'elle est de la forme [/faces/*], elle va être traitée par le contrôleur
[Faces Servlet] (cf [web.xml] page 214). Celui-ci va traiter la page et envoyer le flux Html suivant :

Introdution à Java EE
245/334
• le contrôleur [Faces Servlet] traitera les événements qui vont se produire à partir de cette page.

1.2.8 Conclusion

Revenons sur le projet Netbeans que nous avons écrit :

Ce projet recouvre l'architecture suivante :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Dans chaque projet Jsf, nous trouverons les éléments suivants :


• des pages Jsf [A] qui sont envoyées [4] aux navigateurs clients par le contrôleur [Faces Servlet] [3]
• des fichiers de messages [B] qui permettent de changer la langue des pages Jsf
• des classes Java [C] qui traitent les événements qui se produisent sur le navigateur client [2a, 2b] et / ou qui servent de
modèles aux pages Jsf [3]. Le plus souvent, les couches [metier] et [dao] sont développées et testées séparément. La
couche [web] est alors testée avec une couche [metier] fictive. Si les couches [metier] et [dao] sont disponibles, on travaille
le plus souvent avec leurs archives .jar.
• des fichiers de configuration [D] pour lier ces divers éléments entre-eux. Le fichier [web.xml] a été décrit page 214, et sera
peu souvent modifié. Le principal fichier de configuration est [faces-config.xml] qui définit :
• le fichier des messages
• les beans (classes Java) qui servent de modèles aux pages Jsf ou traitent leurs événements
• les règles de navigation entre pages Jsf

1.3 Exemple n ° 3
Mots clés : formulaire de saisie – composants Jsf

Introdution à Java EE
246/334
1.3.1 L'application

L'application a une unique page :

1 2
3

L'application présente les principaux composants Jsf utilisables dans un formulaire de saisies :
• la colonne [1] indique le nom de la balise Jsf / Html utilisée
• la colonne [2] présente un exemple de saisie pour chacune des balises rencontrées
• la colonne [3] affiche les valeurs du bean servant de modèle à la page
• les saisies faites en [2] sont validées par le bouton [4]. Cette validation ne fait que mettre à jour le bean modèle de la page.
La même page est ensuite renvoyée. Aussi après validation, la colonne [3] présente-t-elle les nouvelles valeurs du bean
modèle permettant ainsi à l'utilisateur de vérifier l'impact de ses saisies sur le modèle de la page.

1.3.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Introdution à Java EE
247/334
1

3 5

• en [1], les fichiers de configuration du projet Jsf.


• en [2], les pages Jsp du projet
• en [3], une feuille de style pour configurer l'aspect de la page [form.jsp]
• en [4], les classes Java du projet.
• en [5], le fichier des messages de l'application en deux langues : français et anglais.

1.3.3 Le fichier [faces-config]

Le fichier [faces-config.xml] de l'application est le suivant :

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <managed-bean>
5. <managed-bean-name>form</managed-bean-name>
6. <managed-bean-class>forms.Form</managed-bean-class>
7. <managed-bean-scope>request</managed-bean-scope>
8. </managed-bean>
9. <managed-bean>
10. <managed-bean-name>locale</managed-bean-name>
11. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
12. <managed-bean-scope>application</managed-bean-scope>
13. </managed-bean>
14. <application>
15. <resource-bundle>
16. <base-name>
17. messages
18. </base-name>
19. <var>msg</var>
20. </resource-bundle>
21. </application>
22. </faces-config>

• lignes 14-22 : le fichier des messages de l'application


• lignes 9-13 : le bean locale qui permet de changer la langue des pages. Ce bean est celui de l'application précédente.
• lignes 4-9 : le bean form qui va servir de modèle à la page Jsf form.jsp. L'application se consacre essentiellement à l'étude
des liens qui unissent une page à son bean modèle.

1.3.4 Le fichier des messages [messages.properties]

Les fichiers des messages (cf [5] dans la copie d'écran du projet) sont les suivants :

[messages_fr.properties]

Introdution à Java EE
248/334
1. form.langue1=Fran\u00e7ais
2. form.langue2=Anglais
3. form.titre=Java Server Faces - les tags
4. form.headerCol1=Type
5. form.headerCol2=Champs de saisie
6. form.headerCol3=Valeurs du modèle de la page
7. form.loginPrompt=login :
8. form.passwdPrompt=mot de passe :
9. form.descPrompt=description :
10. form.selectOneListBox1Prompt=choix unique :
11. form.selectOneListBox2Prompt=choix unique :
12. form.selectManyListBoxPrompt=choix multiple :
13. form.selectOneMenuPrompt=choix unique :
14. form.selectManyMenuPrompt=choix multiple :
15. form.selectBooleanCheckboxPrompt=marié(e) :
16. form.selectManyCheckboxPrompt=couleurs préférées :
17. form.selectOneRadioPrompt=moyen de transport préféré :
18. form.submitText=Valider
19. form.buttonRazText=Raz

Ces messages sont affichés aux endroits suivants de la page :

2
1

3
6
4 5
7
8

11

12

19
13
14

19

15
16

17

18

La version anglaise des messages est la suivante :

[messages_en.properties]

1. form.langue1=French
2. form.langue2=English

Introdution à Java EE
249/334
3. form.titre=Java Server Faces - the tags
4. form.headerCol1=Input Type
5. form.headerCol2=Input Fields
6. form.headerCol3=Page Model Values
7. form.loginPrompt=login :
8. form.passwdPrompt=password :
9. form.descPrompt=description :
10. form.selectOneListBox1Prompt=unique choice :
11. form.selectOneListBox2Prompt=unique choice :
12. form.selectManyListBoxPrompt=multiple choice :
13. form.selectOneMenuPrompt=unique choice :
14. form.selectManyMenuPrompt=multiple choice :
15. form.selectBooleanCheckboxPrompt=married :
16. form.selectManyCheckboxPrompt=preferred colors :
17. form.selectOneRadioPrompt=preferred transport means :
18. form.submitText=Submit
19. form.buttonRazText=Reset

1.3.5 Le modèle [Form.java] de la page [form.jsp]

Dans le projet ci-dessus, la classe [Form.java] va servir de modèle ou backing bean à la page Jsf [form.jsp]. Illustrons cette notion
de modèle avec un exemple tiré de la page [form.jsp] :

1. <!-- ligne 2 -->


2. <h:outputText value="inputText" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.loginPrompt']}"/>
5. <h:inputText id="inputText" value="#{form.inputText}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputText}"/>

A la demande initiale de la page [form.jsp], le code ci-dessus génère la ligne 2 du tableau des saisies :

1 2 3

La ligne 2 affiche la zone [1], les lignes 3-6 : la zone [2], la ligne 7 : la zone [3].

Les lignes 5 et 7 utilisent une expression faisant intervenir le bean form défini dans le fichier [faces-config.xml] de la façon suivante
:

1. <managed-bean>
2. <managed-bean-name>form</managed-bean-name>
3. <managed-bean-class>forms.Form</managed-bean-class>
4. <managed-bean-scope>request</managed-bean-scope>
5.</managed-bean>

Introdution à Java EE
250/334
Le bean nommé form est une instance de la classe forms.Form que nous allons découvrir bientôt et sa durée de vie est celle de la
requête. Cela signifie que dans un cycle demande client / réponse serveur, il est instancié lorsque la requête en a besoin et supprimé
lorsque la réponse au client a été rendue.

Dans le code ci-dessous de la page [form.jsp] :

1. <!-- ligne 2 -->


2. <h:outputText value="inputText" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.loginPrompt']}"/>
5. <h:inputText id="inputText" value="#{form.inputText}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputText}"/>

les lignes 5 et 7 utilisent la valeur inputText du bean form. Pour comprendre les liens qui unissent une page P à son modèle M, il
faut revenir au cycle demande client / réponse serveur qui caractérise une application web :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Il faut distinguer le cas où la page P est envoyée en réponse au navigateur (étape 4), par exemple lors de la demande initiale de la
page, du cas où l'utilisateur ayant provoqué un événement sur la page P, celui-ci est traité par le contrôleur [Faces Servlet] (étape 1).

On peut distinguer ces deux cas en les regardant du point de vue du navigateur :

1. lors de la demande initiale de la page, le navigateur fait une opération GET sur l'Url de la page
2. lors de la soumission des valeurs saisies dans la page, le navigateur fait une opération POST sur l'Url de la page

Dans les deux cas, c'est la même Url qui est demandée. Selon la nature de la demande GET ou POST du navigateur, le traitement
de la requête va différer.

[cas 1 – demande initiale de la page P]

Le navigateur demande l'Url de la page avec un GET. Le contrôleur [Faces Servlet] va passer directement à l'étape [4] de rendu de
la réponse et la page [form.jsp] va être envoyée au client. Le contrôleur Jsf va demander à chaque balise de la page de s'afficher.
Prenons l'exemple de la ligne 5 du code de [form.jsp] :

<h:inputText value="#{form.inputText}"/>

La balise Jsf <h:inputText value= "valeur "/> donne naissance à la balise Html <input type= "text " value= "valeur "/>. La
classe chargée de traiter cette balise rencontre l'expression #{form.inputText} qu'elle doit évaluer :
• si le bean form n'existe pas encore, il est créé par instanciation de la classe forms.Form, comme indiqué dans [faces-
config.xml].
• l'expression #{form.inputText} est évaluée par appel à la méthode form.getInputText().
• le texte <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /> est
inséré dans le flux Html qui va être envoyé au client si on imagine que la méthode form.getInputText() a rendu la chaîne
"texte". Jsf va par ailleurs donner un nom (name) au composant Html mis dans le flux. Ce nom est construit à partir des
identifiants id du composant Jsf analysé et ceux de ses composants parents, ici la balise <h:form id= "formulaire "/>.

On retiendra que si dans une page P, on utilise l'expression #{M.champ} où M est le bean modèle de la page P, celui-ci doit
disposer de la méthode publique getChamp(). Le type rendu par cette méthode doit pouvoir être converti en type String. Un
modèle M possible et fréquent est le suivant :

1. private T champ;
2. public T getChamp(){

Introdution à Java EE
251/334
3. return champ;
4. }

où T est un type qui peut être converti en type String, éventuellement avec une méthode toString.

Toujours dans le cas de l'affichage de la page P, le traitement de la ligne :

<h:outputText value="#{form.inputText}"/>

sera analogue et le flux Html suivant sera créé :

texte

De façon interne au serveur, la page P est représentée comme un arbre de composants, image de l'arbre des balises de la page
envoyée au client. Nous appellerons vue ou état de la page, cet arbre. Cet état est mémorisé. Il peut l'être de deux façons selon
une configuration faite dans le fichier [web.xml] de l'application :

1. <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">
2. ...
3. <context-param>
4. <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
5. <param-value>client</param-value>
6. </context-param>
7. <servlet>
8. <servlet-name>Faces Servlet</servlet-name>
9. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
10. <load-on-startup>1</load-on-startup>
11. </servlet>
12. ...
13. </web-app>

Les lignes 7-11 définissent le contrôleur [Faces Servlet]. Celui-ci est configuré par différentes balises <context-param> dont celle
des lignes 3-6 qui indique que l'état d'une page doit être sauvegardé sur le client (le navigateur). L'autre valeur possible, ligne 5, est
server pour indiquer une sauvegarde sur le serveur.

Lorsque l'état d'une page est sauvegardé sur le client, le contrôleur Jsf ajoute à chaque page Html qu'il envoie, un champ caché dont
la valeur est l'état actuel de la page. Ce champ caché a la forme suivante :

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState"


value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />

Sa valeur représente sous forme codée, l'état de la page envoyée au client. Ce qu'il est important de comprendre, c'est que ce champ
caché fait partie du formulaire de la page et fera donc partie des valeurs postées par le navigateur lors de la validation du formulaire.
A partir de ce champ caché, le contrôleur Jsf est capable de restaurer la vue telle qu'elle a été envoyée au client.

Lorsque l'état d'une page est sauvegardé sur le serveur, l'état de la page envoyée au client est sauvegardé dans la session de celui-ci.
Lorsque le navigateur client va poster les valeurs saisies dans le formulaire, il va envoyer également son jeton de session. A partir de
celui-ci, le contrôleur Jsf retrouvera l'état de la page envoyée au client et la restaurera.

[cas 2 – traitement de la page P]

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Introdution à Java EE
252/334
On est à l'étape [1] ci-dessus où le contrôleur [Faces Servlet] va recevoir une requête POST du navigateur client à qui il a envoyé
précédemment la page [form.jsp]. On est en présence du traitement d'un événement de la page. Plusieurs étapes vont se dérouler
avant même que l'événement ne puisse être traité en [2a]. Le cycle de traitement d'une requête POST par le contrôleur Jsf est le
suivant :

A B C

E D
F

• en [A], grâce au champ caché javax.faces.ViewState la vue initialement envoyée au navigateur client est reconstituée. Ici, les
composants de la page retrouvent la valeur qu'ils avaient dans la page envoyée. Notre composant inputText retrouve sa
valeur "texte".
• en [B], les valeurs postées par le navigateur client sont utilisées pour mettre à jour les composants de la vue. Ainsi si dans
le champ de saisie html nommé inputText, l'utilisateur a tapé "jean", la valeur "jean" remplace la valeur "texte".
Désormais la vue reflète la page telle que l'a modifiée l'utilisateur et non plus telle qu'elle a été envoyée au navigateur.
• en [C], les valeurs postées sont vérifiées. Supposons que le composant inputText précédent soit le champ de saisie d'un
âge. Il faudra que la valeur saisie soit un nombre entier. Les valeurs postées par le navigateur sont toujours de type String.
Leur type final dans le modèle M associé à la page P peut être tout autre. Il y a alors conversion d'un type String vers un
autre type T. Cette conversion peut échouer. Dans ce cas, le cycle demande / réponse est terminé et la page P construite
en [B] est renvoyée au navigateur client avec des messages d'erreur si l'auteur de la page P les a prévus. On notera que
l'utilisateur retrouve la page telle qu'il l'a saisie, sans effort de la part du développeur. Dans une autre technologie, telle que
Jsp, le développeur doit reconstruire lui-même la page P avec les valeurs saisies par l'utilisateur. La valeur d'un composant
peut subir également un processus de validation. Toujours avec l'exemple du composant inputText qui est le champ de
saisie d'un âge, la valeur saisie devra être non seulement un nombre entier mais un nombre entier compris dans un
intervalle [1,N]. Si la valeur saisie passe l'étape de la conversion, elle peut ne pas passer l'étape de la validation. Dans ce
cas, là également le cycle demande / réponse est terminé et la page P construite en [B] est renvoyée au navigateur client.
• en [D], si tous les composants de la page P passent l'étape de conversion et de validation, leurs valeurs vont être affectées
au modèle M de la page P. Si la valeur du champ de saisie généré à partir de la balise suivante :

<h:inputText value="#{form.inputText}"/>

est "jean", alors cette valeur sera affectée au modèle form de la page par exécution du code form.setInputText("jean"). On
retiendra que dans le modèle M de la page P, les champs privés de M qui mémorisent la valeur d'un champ de saisie de P
doivent avoir une méthode set.
• une fois le modèle M de la page P mis à jour par les valeurs postées, l'événement qui a provoqué le POST de la page P
peut être traité. C'est l'étape [E]. On notera que si le gestionnaire de cet événement appartient au bean M, il a accès aux
valeurs du formulaire P qui ont été stockées dans les champs de ce même bean.
• l'étape [E] va rendre au contrôleur Jsf, une clé de navigation. Cette clé sera recherchée dans le fichier [faces-config.xml] et
une page Jsf sera choisie pour être envoyée en réponse au navigateur client. C'est l'étape [F].

Nous retiendrons de ce qui précède que :

• une page P affiche les champs C de son modèle M avec des méthodes [M].getC()
• les champs C du modèle M d'une page P sont initialisés avec les valeurs saisies dans la page P à l'aide des méthodes
[M].setC(saisie). Dans cette étape, peuvent intervenir des processus de conversion et de validation susceptibles
d'échouer. Dans ce cas, l'événement qui a provoqué le POST de la page P n'est pas traité et la page est renvoyée de
nouveau au client telle que celui-ci l'a saisie.

Le modèle [Form.java] de la page [form.jsp] sera le suivant :

Introdution à Java EE
253/334
1. package forms;
2.
3. public class Form {
4.
5. /** Creates a new instance of Form */
6. public Form() {
7. }
8.
9. // champs du formulaire
10. private String inputText="texte";
11. private String inputSecret="secret";
12. private String inputTextArea="ligne1\nligne2\n";
13. private String selectOneListBox1="2";
14. private String selectOneListBox2="3";
15. private String[] selectManyListBox=new String[]{"1","3"};
16. private String selectOneMenu="1";
17. private String[] selectManyMenu=new String[]{"1","2"};
18. private String inputHidden="initial";
19. private boolean selectBooleanCheckbox=true;
20. private String[] selectManyCheckbox=new String[]{"1","3"};
21. private String selectOneRadio="2";
22.
23. // événements
24. public String submit(){
25. return null;
26. }
27.
28. // getters et setters
29. ...
30.}

Les champs des lignes 10-21 sont utilisés aux endroits suivants du formulaire :

Introdution à Java EE
254/334
10 10
11 11

12 12

13 13

14 14

15 15

16 16
17 17

18
19 19
20
20
21
21

1.3.6 La page [form.jsp]

La page [form.jsp] qui génère la vue précédente est la suivante :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10.
11. <f:view>
12. <html>
13. <head>
14. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
15. <title>JSF</title>
16. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
17. </head>
18. <body background="<c:url value="/ressources/standard.jpg"/>">
19. <h:form id="formulaire">
20. <h:panelGrid columns="2">
21. <h:commandLink value="#{msg['form.langue1']}" action="#{locale.setFrenchLocale}"/>
22. <h:commandLink value="#{msg['form.langue2']}" action="#{locale.setEnglishLocale}"/>
23. </h:panelGrid>
24. <h1><h:outputText value="#{msg['form.titre']}"/></h1>
25. <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">

Introdution à Java EE
255/334
26. <!-- ligne 1 -->
27. <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
28. <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
29. <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
30. <!-- ligne 2 -->
31. <h:outputText value="inputText" styleClass="info"/>
32. <h:panelGroup>
33. <h:outputText value="#{msg['form.loginPrompt']}"/>
34. <h:inputText id="inputText" value="#{form.inputText}"/>
35. </h:panelGroup>
36. <h:outputText value="#{form.inputText}"/>
37. <!-- ligne 3 -->
38. <h:outputText value="inputSecret" styleClass="info"/>
39. <h:panelGroup>
40. <h:outputText value="#{msg['form.passwdPrompt']}"/>
41. <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
42. </h:panelGroup>
43. <h:outputText value="#{form.inputSecret}"/>
44. <!-- ligne 4 -->
45. <h:outputText value="inputTextArea" styleClass="info"/>
46. <h:panelGroup>
47. <h:outputText value="#{msg['form.descPrompt']}"/>
48. <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
49. </h:panelGroup>
50. <h:outputText value="#{form.inputTextArea}"/>
51. <!-- ligne 5 -->
52. <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
53. <h:panelGroup>
54. <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
55. <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
56. <f:selectItem itemValue="1" itemLabel="un"/>
57. <f:selectItem itemValue="2" itemLabel="deux"/>
58. <f:selectItem itemValue="3" itemLabel="trois"/>
59. </h:selectOneListbox>
60. </h:panelGroup>
61. <h:outputText value="#{form.selectOneListBox1}"/>
62. <!-- ligne 6 -->
63. <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
64. <h:panelGroup>
65. <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
66. <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
67. <f:selectItem itemValue="1" itemLabel="un"/>
68. <f:selectItem itemValue="2" itemLabel="deux"/>
69. <f:selectItem itemValue="3" itemLabel="trois"/>
70. <f:selectItem itemValue="4" itemLabel="quatre"/>
71. <f:selectItem itemValue="5" itemLabel="cinq"/>
72. </h:selectOneListbox>
73. </h:panelGroup>
74. <h:outputText value="#{form.selectOneListBox2}"/>
75. <!-- ligne 7 -->
76. <h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
77. <h:panelGroup>
78. <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
79. <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}"
size="3">
80. <f:selectItem itemValue="1" itemLabel="un"/>
81. <f:selectItem itemValue="2" itemLabel="deux"/>
82. <f:selectItem itemValue="3" itemLabel="trois"/>
83. <f:selectItem itemValue="4" itemLabel="quatre"/>
84. <f:selectItem itemValue="5" itemLabel="cinq"/>
85. </h:selectManyListbox>
86. <p><input type="button" value="<h:outputText value="#{msg['form.buttonRazText']}" />"
onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
87. </h:panelGroup>
88. <h:outputText value="#{form.selectManyListBoxValue}"/>
89. <!-- ligne 8 -->
90. <h:outputText value="selectOneMenu" styleClass="info"/>
91. <h:panelGroup>
92. <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
93. <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
94. <f:selectItem itemValue="1" itemLabel="un"/>
95. <f:selectItem itemValue="2" itemLabel="deux"/>
96. <f:selectItem itemValue="3" itemLabel="trois"/>
97. <f:selectItem itemValue="4" itemLabel="quatre"/>
98. <f:selectItem itemValue="5" itemLabel="cinq"/>
99. </h:selectOneMenu>
100. </h:panelGroup>
101. <h:outputText value="#{form.selectOneMenu}"/>
102. <!-- ligne 9 -->
103. <h:outputText value="selectManyMenu" styleClass="info"/>
104. <h:panelGroup>
105. <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
106. <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >

Introdution à Java EE
256/334
107. <f:selectItem itemValue="1" itemLabel="un"/>
108. <f:selectItem itemValue="2" itemLabel="deux"/>
109. <f:selectItem itemValue="3" itemLabel="trois"/>
110. <f:selectItem itemValue="4" itemLabel="quatre"/>
111. <f:selectItem itemValue="5" itemLabel="cinq"/>
112. </h:selectManyMenu>
113. <p><input type="button" value="<h:outputText value="#{msg['form.buttonRazText']}"/>"
onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
114. </h:panelGroup>
115. <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
116. <!-- ligne 10 -->
117. <h:outputText value="inputHidden" styleClass="info"/>
118. <h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
119. <h:outputText value="#{form.inputHidden}"/>
120. <!-- ligne 11 -->
121. <h:outputText value="selectBooleanCheckbox" styleClass="info"/>
122. <h:panelGroup>
123. <h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}" styleClass="prompt"
/>
124. <h:selectBooleanCheckbox id="selectBooleanCheckbox"
value="#{form.selectBooleanCheckbox}"/>
125. </h:panelGroup>
126. <h:outputText value="#{form.selectBooleanCheckbox}"/>
127. <!-- ligne 12 -->
128. <h:outputText value="selectManyCheckbox" styleClass="info"/>
129. <h:panelGroup>
130. <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
131. <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
132. <f:selectItem itemValue="1" itemLabel="rouge"/>
133. <f:selectItem itemValue="2" itemLabel="bleu"/>
134. <f:selectItem itemValue="3" itemLabel="blanc"/>
135. <f:selectItem itemValue="4" itemLabel="noir"/>
136. </h:selectManyCheckbox>
137. </h:panelGroup>
138. <h:outputText value="#{form.selectManyCheckboxValue}"/>
139. <!-- ligne 13 -->
140. <h:outputText value="selectOneRadio" styleClass="info"/>
141. <h:panelGroup>
142. <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
143. <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
144. <f:selectItem itemValue="1" itemLabel="voiture"/>
145. <f:selectItem itemValue="2" itemLabel="vélo"/>
146. <f:selectItem itemValue="3" itemLabel="scooter"/>
147. <f:selectItem itemValue="4" itemLabel="marche"/>
148. </h:selectOneRadio>
149. </h:panelGroup>
150. <h:outputText value="#{form.selectOneRadio}"/>
151. </h:panelGrid>
152. <p>
153. <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
154. </p>
155. </h:form>
156. </body>
157. </html>
158.</f:view>

Nous allons étudier successivement les principaux composants de cette page. On notera la structure générale d'un formulaire Jsf :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6.
7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
8. "http://www.w3.org/TR/html4/loose.dtd">
9.
10. <f:view>
11. <html>
12. <head>
13. ...
14. </head>
15. <body>
16. <h:form id="formulaire">
17. ....
18. <h:commandButton value="#{msg['form.submitText']}"/>
19. ....
20. </h:form>
21. </body>
22. </html>
23. </f:view>

Introdution à Java EE
257/334
Les composants d'un formulaire doivent être à l'intérieur d'une balise <h:form> (lignes 16-20), elle-même à l'intérieur d'une balise
<f:view> (lignes 10-23). Par ailleurs, un formulaire doit disposer d'un moyen d'être posté (POST), souvent un lien ou un bouton
comme dans la ligne 18. Il est peut être posté également par de nombreux événements (changement d'une sélection dans une liste,
changement de champ actif, frappe d'un caractère dans un champ de saisie, ...).

1.3.6.1 La feuille de style du formulaire

Afin de rendre plus lisibles les colonnes du tableau du formulaire, celui est accompagné d'une feuille de style :

1. ...
2. <f:view>
3. <html>
4. <head>
5. ...
6. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
7. </head>
8. <body background="<c:url value="/ressources/standard.jpg"/>">
9. <h:form id="formulaire">
10. ...
11. <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
12. <!-- ligne 1 -->
13. <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
14. <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
15. <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
16. <!-- ligne 2 -->
17. <h:outputText value="inputText" styleClass="info"/>
18. <h:panelGroup>
19. <h:outputText value="#{msg['form.loginPrompt']}"/>
20. <h:inputText id="inputText" value="#{form.inputText}"/>
21. </h:panelGroup>
22. <h:outputText value="#{form.inputText}"/>
23. <!-- ligne 3 -->
24.
25. ...
26. </h:panelGrid>
27. <p>
28. <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
29. </p>
30. </h:form>
31. </body>
32. </html>
33. </f:view>

• ligne 7 : la feuille de style de la page est définie à l'intérieur de la balise Html <head>, par une balise <link ...
rel= "stylesheet ">. Nous utilisons ici, la balise Jstl <c:url .../> pour définir l'url de la feuille de style. Celle-ci a été
placée à l'endroit suivant dans le projet Netbeans :

Le fichier [styles.css] qui définit les différents styles utilisés dans le formulaire est à la racine de la branche [Web Pages] et
peut donc être obtenue avec l'Url [/intro-03/styles.css]. On aurait pu écrire la balise <link> de la façon suivante :

<link href="/intro-03/styles.css" rel="stylesheet" type="text/css"/>

La balise <c:url> permet de nous affranchir du nom /intro-03 de l'application. La balise

<c:url value="/styles.css"/>

Introdution à Java EE
258/334
va générer le texte /intro-03/styles.css. La balise <c:url> permet ainsi de changer le nom de l'application sans avoir à
repasser sur tous les liens contenant ce nom. La même technique est utilisée ligne 8 pour générer le lien de l'image de fond
de la page.

• lignes 11-26 : la balise <h:panelGrid columns="3"/> définit un tableau à trois colonnes. L'attribut columnClasses
permet de donner un style à ces colonnes :

<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">

Les valeurs col1,col2,col3 de l'attribut columnClasses désignent les styles respectifs des colonnes 1, 2 et 3 du tableau. Ces
styles sont cherchés dans le feuille de style de la page définie ligne 6 :

1. .info{
2. font-family: Arial,Helvetica,sans-serif;
3. font-size: 14px;
4. font-weight: bold
5. }
6.
7. .col1{
8. background-color: #ccccff
9. }
10.
11. .col2{
12. background-color: #ffcccc
13. }
14.
15. .col3{
16. background-color: #ffcc66
17. }
18.
19. .entete{
20. font-family: 'Times New Roman',Times,serif;
21. font-size: 14px;
22. font-weight: bold
23. }

• lignes 7-9 : le style nommé col1


• lignes 11-13 : le style nommé col2
• lignes 15-17 : le style nommé col3

Ces trois styles définissent la couleur de fond de chacune des colonnes.

• lignes 19-23 : le style entete sert à définir le style des textes de la 1ère ligne du tableau. Il est utilisé lignes 13-15
du code Jsp.
• lignes 1-5 : le style info sert à définir le style des textes de la 1ère colonne du tableau. On en voit un exemple
ligne 17 du code Jsp.

Nous insisterons peu sur l'utilisation des feuilles de style car celle-ci mérite à elle seule un livre et que par ailleurs leur élaboration en
est souvent confiée à des spécialistes. Néanmoins, nous avons souhaité en utiliser une, minimaliste, afin de rappeler que leur usage
est indispensable.

1.3.6.2 Les deux cycles demande client / réponse serveur d'un formulaire

Revnons sur ce qui a déjà été expliqué page 251 dans un cas général et appliquons-le au formulaire étudié. Celui-ci sera testé dans
l'environnement Jsf classique :

Introdution à Java EE
259/334
Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
4 JSP2 Modèles
JSPn

Ici, il n'y aura pas de gestionnaires d'événements ni de couche [metier]. Les étapes [2x] n'existeront donc pas. On distinguera le cas
où le formulaire F est demandée initialement par le navigateur du cas où l'utilisateur ayant provoqué un événement dans le
formulaire F, celui-ci est traité par le contrôleur [Faces Servlet]. Il y a deux cycles demande client / réponse serveur qui sont
différents.

• le premier correspondant à la demande initiale de la page est provoqué par une opération GET du navigateur sur l'Url du
formulaire.
• le second correspondant à la soumission des valeurs saisies dans la page est provoqué par une opération POST sur cette
même Url.

Selon la nature de la demande GET ou POST du navigateur, le traitement de la requête par le contrôleur [Faces Servlet] différe.

[cas 1 – demande initiale du formulaire F]

Le navigateur demande l'Url de la page avec un GET. Le contrôleur [Faces Servlet] va passer directement à l'étape [4] de rendu de
la réponse. Le formulaire [form.jsp] va être initialisé par son modèle [Form.java] et être envoyé au client qui reçoit la vue suivante :

Introdution à Java EE
260/334
Les échanges HTTP client / serveur sont les suivants à cette occasion :

Demande HTTP du client :

1. GET /intro-03/faces/form.jsp HTTP/1.1


2. Host: localhost:8080
3. User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.7) Gecko/20070914
Firefox/2.0.0.7
4. Accept:
text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.
5
5. Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
6. Accept-Encoding: gzip,deflate
7. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8. Keep-Alive: 300
9. Connection: keep-alive
10. Cookie: test_cookie=test cookie

Ligne 1, on voit le GET du navigateur.

Réponse HTTP du serveur :

1. HTTP/1.x 200 OK
2. X-Powered-By: Servlet/2.5, JSP/2.1

Introdution à Java EE
261/334
3. Set-Cookie: JSESSIONID=6f57b611e610e3ec55cb3cbd76aeb; Path=/intro-03
4. Content-Type: text/html;charset=UTF-8
5. Content-Language: fr-FR
6. Transfer-Encoding: chunked
7. Date: Fri, 05 Oct 2007 08:46:00 GMT
8. Server: Sun Java System Application Server Platform Edition 9.0_01

Non montré ici, la ligne 8 est suivie d'une ligne vide et du code Html du formulaire. C'est ce code que le navigateur interprète et
affiche.

[cas 2 – traitement des valeurs saisies dans le formulaire F]

L'utilisateur remplit le formulaire et le valide par le bouton [Valider]. Le navigateur demande alors l'Url du formulaire avec un
POST. Le contrôleur [Faces Servlet] traite cette requête, met à jour le modèle [Form.java] du formulaire [form.jsp], et renvoie de
nouveau le formulaire [form.jsp] mis à jour par ce nouveau modèle. Examinons ce cycle sur un exemple :

Ci-dessus, l'utilisateur a fait ses saisies et les valide. Il reçoit en réponse la vue suivante :

Introdution à Java EE
262/334
Les échanges HTTP client / serveur sont les suivants à cette occasion :

Demande HTTP du client :

1. POST /intro-03/faces/form.jsp;jsessionid=6f57b611e610e3ec55cb3cbd76aeb HTTP/1.1


2. Host: localhost:8080
3. User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.7) Gecko/20070914
Firefox/2.0.0.7
4. Accept:
text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.
5
5. Accept-Language: fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3
6. Accept-Encoding: gzip,deflate
7. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8. Keep-Alive: 300
9. Connection: keep-alive
10. Referer: http://localhost:8080/intro-03/faces/form.jsp
11. Cookie: JSESSIONID=6f57b611e610e3ec55cb3cbd76aeb; test_cookie=test cookie
12. Content-Type: application/x-www-form-urlencoded
13. Content-Length: 6107
14.
15. formulaire=formulaire&javax.faces.ViewState=H4s...wAA&formulaire%3AinputText=nouveau+texte&formula
ire%3AinputSecret=mdp&formulaire%3AinputTextArea=Tutoriel+JSF%0D%0Apartie+1%0D%0A&formulaire%3Asel
ectOneListBox1=3&formulaire%3AselectOneListBox2=5&formulaire%3AselectManyListBox=3&formulaire%3Ase
lectManyListBox=4&formulaire%3AselectManyListBox=5&formulaire%3AselectOneMenu=4&formulaire%3Aselec
tManyMenu=4&formulaire%3AselectManyMenu=5&formulaire%3AinputHidden=initial&formulaire%3AselectMany
Checkbox=2&formulaire%3AselectManyCheckbox=4&formulaire%3AselectOneRadio=4&formulaire%3Asubmit=Val
ider

En ligne 1, le POST fait par le navigateur. En ligne 15, les valeurs saisies par l'utilisateur. On peut par exemple y découvrir le texte
mis dans le champ de saisie :

Introdution à Java EE
263/334
formulaire%3AinputText=nouveau+texte

Ligne 13, on découvre que l'ensemble des valeurs saisies occupe 6107 caractères. Cela est dû à la présence du champ caché
javax.faces.ViewState qui, à lui tout seul, fait plusieurs milliers de caractères. Ce champ représente, sous forme codée, l'état du
formulaire tel qu'il a été envoyé initialement au navigateur lors de son GET initial.

Réponse HTTP du serveur :

1. HTTP/1.x 200 OK
2. X-Powered-By: Servlet/2.5, JSP/2.1
3. Content-Type: text/html;charset=UTF-8
4. Content-Language: fr-FR
5. Transfer-Encoding: chunked
6. Date: Fri, 05 Oct 2007 08:57:16 GMT
7. Server: Sun Java System Application Server Platform Edition 9.0_01

Non montré ici, la ligne 7 est suivie d'une ligne vide et du code Html du formulaire mis à jour par son nouveau modèle isu du
POST.

Nous examinons maintenant les différentes composantes de ce formulaire.

1.3.6.3 Balise <h:inputText>

La balise <h:inputText> génère une balise Html <input type="text" ...>.

Considérons le code suivant :

1. <!-- ligne 2 -->


2. <h:outputText id="inputText" value="inputText" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.loginPrompt']}"/>
5. <h:inputText id="inputText" value="#{form.inputText}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputText}"/>

et son modèle [Form.java] :

1. private String inputText="texte";


2.
3. public String getInputText() {
4. return inputText;
5. }
6.
7. public void setInputText(String inputText) {
8. this.inputText = inputText;
9.}

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

1 3 4
2

• la ligne 2 du code Jsp génère [1]


• la balise <h:panelGroup> (lignes 3-6) permet de regrouper plusieurs éléments dans une même cellule du tableau généré
par la balise <h:panelGrid> de la ligne 24 du code complet de la page. Le texte [2] est généré par la ligne 4. Le champ de
saisie [3] est généré par la ligne [5]. Ici, la méthode getInputText de [Form.java] (lignes 3-5 du code Java) a été utilisée
pour générer le texte du champ de saisie.
• la ligne 7 du code Jsp génère [4]. C'est de nouveau la méthode getInputText de [Form.java] qui est utilisée pour générer
le texte [4].

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">inputText</span></td>
3. <td class="col2">login : <input id="formulaire:inputText" type="text" name="formulaire:inputText"
value="texte" /></td>
4. <td class="col3">texte</td>
5. </tr>

Introdution à Java EE
264/334
Les balises Html <tr> et <td> sont générées par la balise <h:panelGrid> utilisée pour générer le tableau du formulaire.

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous
obtenons en réponse la page [3, 4] :

3 4

La valeur du champ [1] est postée de la façon suivante :

formulaire%3AinputText=nouveau+texte

En [2], le formulaire est validé avec le bouton suivant :

<h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>

La balise <h:commandButton> n'a pas d'attribut action. Dans ce cas, aucun gestionnaire d'événement n'est invoqué ni aucune
règle de navigation. Après traitement, la même page est renvoyée. Revoyons son cycle de traitement :

A B C

E D
F

• en [A] la page P est restaurée telle qu'elle avait été envoyée. Cela signifie que le composant d'id inputText est restauré
avec sa valeur initiale "texte".
• en [B], les valeurs postées par le navigateur (saisies par l'utilisateur) sont affectées aux composants de la page P. Ici, le
composant d'id inputText reçoit la valeur "un nouveau texte".
• en [C], les conversions et validations ont lieu. Ici, il n'y en a aucune. Dans le modèle M, le champ associé au composant
d'id inputText est le suivant :

private String inputText="texte";

Comme les valeurs saisies sont de type String, il n'y a pas de conversion à faire. Par ailleurs, aucune règle de validation n'a
été créée. Nous en construirons ultérieurement.
• en [D], les valeurs saisies sont affectées au modèle. Le champ inputText de [Form.java] reçoit la valeur "un nouveau
texte".
• en [E], rien n'est fait car aucun gestionnaire d'événement n'a été associé au bouton [Valider].
• en [F], la page P est de nouveau envoyée au client car le bouton [Valider] n'a pas d'attribut action. Les lignes suivantes de
[form.jsp] sont alors exécutées :

1. <!-- ligne 2 -->


2. <h:outputText value="inputText" styleClass="info"/>

Introdution à Java EE
265/334
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.loginPrompt']}"/>
5. <h:inputText id="inputText" value="#{form.inputText}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputText}"/>

Les lignes 5 et 7 utilisent la valeur du champ inputText du modèle qui est désormais "un nouveau texte". D'où l'affichage
obtenu :

1.3.6.4 Balise <h:inputSecret>

La balise <h:inputSecret> génère une balise Html <input type="password" ...>. C'est un champ de saisie analogue à celui de la
balise Jsf <h:inputText> si ce n'est que chaque caractère tapé par l'utilisateur est remplacé visuellement par un caractère *.

Considérons le code suivant :

1. <!-- ligne 3 -->


2. <h:outputText value="inputSecret" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.passwdPrompt']}"/>
5. <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputSecret}"/>

et son modèle dans [Form.java] :

1. private String inputSecret="secret";

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

1 3 4
2

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. Le champ de saisie [3] est généré par la ligne [5]. Normalement, la méthode
getInputSecret de [Form.java] aurait du être utilisée pour générer le texte du champ de saisie. Il y a une exception lorsque
celui-ci est de type " mot de passe ". La balise <h:inputSecret> ne sert qu'à lire une saisie, pas à l'afficher.
• la ligne 7 du code Jsp génère [4]. Ici la méthode getInputSecret de [Form.java] a été utilisée pour générer le texte [4] (cf
ligne 1 du code Java).

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">inputSecret</span></td>
3. <td class="col2">mot de passe : <input id="formulaire:inputSecret" type="password"
name="formulaire:inputSecret" value="" /></td>
4. <td class="col3">mdp</td>
5. </tr>

• ligne 3 : la balise Html <input type= "password " .../> générée par la balise Jsf <h:inputSecret>

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous
obtenons en réponse la page [3] :

Introdution à Java EE
266/334
1

La valeur du champ [1] est postée de la façon suivante :

formulaire%3AinputSecret=mdp

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ inputSecret de
[Form.java] a reçu alors la valeur mdp. Parce que le formulaire [form.jsp] n'a défini aucune règle de navigation, ni aucun
gestionnaire d'événement, il est réaffiché après la mise à jour de son modèle. On retombe alors dans l'affichage fait à la demande
initiale de la page [form.jsp] où simplement le champ inputSecret du modèle a changé de valeur [3].

1.3.6.5 Balise <h:inputTextArea>

La balise <h:inputTextArea> génère une balise Html <textarea ...>texte</textarea>. C'est un champ de saisie analogue à celui
de la balise Jsf <h:inputText> si ce n'est qu'ici, on peut taper plusieurs lignes de texte.

Considérons le code suivant :

1. <!-- ligne 4 -->


2. <h:outputText value="inputTextArea" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.descPrompt']}"/>
5. <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.inputTextArea}"/>

et son modèle dans [Form.java] :

1. private String inputTextArea="ligne1\nligne2\n";

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

1 4
3
2

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. Le champ de saisie [3] est généré par la ligne [5]. Son contenu a été généré par appel à
la méthode getInputTextArea du modèle, qui a rendu la valeur définie en ligne 1 du code Java ci-dessus.
• la ligne 7 du code Jsp génère [4]. Ici la méthode getInputTextArea de [Form.java] a été de nouveau utilisée. La chaîne
" ligne1\nligne2 " contenait des sauts de ligne \n. Ils y sont toujours. Mais insérés dans un flux Html, ils sont affichés
comme des espaces par les navigateurs. La balise Html <textarea> qui affiche [3], elle, interprète correctement les sauts
de ligne.

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">inputTextArea</span></td>
3. <td class="col2">description : <textarea id="formulaire:inputTextArea"
name="formulaire:inputTextArea" rows="4">ligne1
4. ligne2
5. </textarea></td>
6. <td class="col3">ligne1
7. ligne2
8. </td>

Introdution à Java EE
267/334
9. </tr>

• lignes 3-5 : la balise Html <textarea>...</textarea> générée par la balise Jsf <h:inputSecret>

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous
obtenons en réponse la page [3] :

La valeur du champ [1] postée est la suivante :

formulaire%3AinputTextArea=Tutoriel+Jsf%0D%0Apartie+1%0D%0A

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ textArea de
[Form.java] a reçu alors la valeur " Tutoriel Jsf\npartie1 ". Le réaffichage de [form.jsp] montre que champ textArea du modèle a
bien été mis à jour [3].

1.3.6.6 Balise <h:selectOneListBox>

La balise <h:selectOneListBox> génère une balise Html <select>...</select>. Visuellement, elle génère une liste déroulante ou
une liste avec ascenseur.

Considérons le code suivant :

1. <!-- ligne 5 -->


2. <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
5. <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}"
size="1">
6. <f:selectItem itemValue="1" itemLabel="un"/>
7. <f:selectItem itemValue="2" itemLabel="deux"/>
8. <f:selectItem itemValue="3" itemLabel="trois"/>
9. </h:selectOneListbox>
10. </h:panelGroup>
11. <h:outputText value="#{form.selectOneListBox1}"/>

et son modèle dans [Form.java] :

1. private String selectOneListBox1="2";

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

4
1 2 3

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. La liste déroulante [3] est générée par les lignes [5-9]. C'est la valeur de l'attribut
size= "1" qui fait que la liste n'affiche qu'un élément. Si cet attribut est manquant, la valeur par défaut de l'attribut size
est 1. Les éléments de la liste ont été générés par les balises <f:selectItem> des lignes 6-8. Ces balises ont la syntaxe
suivante :

Introdution à Java EE
268/334
<f:selectItem itemValue="valeur" itemLabel="texte"/>

La valeur de l'attribut itemLabel est ce qui est affiché dans la liste. La valeur de l'attribut itemValue est la valeur de
l'élément. C'est cette valeur qui sera envoyée au contrôleur [Faces Servlet] si l'élément est sélectionné dans la liste
déroulante.
L'élément affiché en [3] a été déterminé par appel à la méthode getSelectOneListBox1() (ligne 5). Le résultat " 2 "
obtenu (ligne 1 du code Java) a fait que l'élément de la ligne 7 de la liste déroulante a été affiché, ceci parce que son
attribut itemValue vaut " 2 ".
• la ligne 11 du code Jsp génère [4]. Ici la méthode getSelectOneListBox1 de [Form.java] a été de nouveau utilisée.

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">selectOneListBox (size=1)</span></td>
3. <td class="col2">choix unique : <select id="formulaire:selectOneListBox1"
name="formulaire:selectOneListBox1" size="1">
4. <option value="1">un</option>
5. <option value="2" selected="selected">deux</option>
6. <option value="3">trois</option>
7. </select></td>
8. <td class="col3">2</td>
9. </tr>

• lignes 3 et 7 : la balise Html <select ...>...</select> générée par la balise Jsf <h:selectOneListBox>
• lignes 4-6 : les balises Html <option ...> ... </option> générées par les balises Jsf <f:selectItem>
• ligne 5 : le fait que l'élément de valeur= "2 " soit sélectionné dans la liste se traduit par la présence de l'attribut
selected="selected".

Maintenant, ci-dessous, choisissons [1] une nouvelle valeur dans la liste et validons le formulaire avec le bouton [Valider] [2]. Nous
obtenons en réponse la page [3] :

4
3

La valeur du champ [1] postée est la suivante :

formulaire%3AselectOneListBox1=3

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. L'élément Html

<option value="3">trois</option>

a été sélectionné. Le navigateur a envoyé la chaîne " 3 " comme valeur du composant Jsf ayant produit la liste déroulante :

<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">

Le contrôleur Jsf va utiliser la méthode setSelectOneListBox1(" 3 ") pour mettre à jour le modèle de la liste déroulante. Aussi
après cette mise à jour, le champ du modèle [Form.java]

private String selectOneListBox1;

contient-il désormais la valeur " 3 ".

Lorsque la page [form.jsp] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :
• elle détermine l'élément de la liste déroulante qui doit être affiché [3]
• la valeur du champ selectOneListBox1 est affichée en [4].

Introdution à Java EE
269/334
Considérons une variante de la balise <h:selectOneListBox> :

1. <!-- ligne 6 -->


2. <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
5. <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}"
size="3">
6. <f:selectItem itemValue="1" itemLabel="un"/>
7. <f:selectItem itemValue="2" itemLabel="deux"/>
8. <f:selectItem itemValue="3" itemLabel="trois"/>
9. <f:selectItem itemValue="4" itemLabel="quatre"/>
10. <f:selectItem itemValue="5" itemLabel="cinq"/>
11. </h:selectOneListbox>
12. </h:panelGroup>
13. <h:outputText value="#{form.selectOneListBox2}"/>

Le modèle dans [Form.java] de la balise <h:selectOneListBox> de la ligne 5 est le suivant :

1. private String selectOneListBox2="3";

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

2 3
4
1

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. La liste avec ascenseur [3] est générée par les lignes [5-11]. C'est la valeur de l'attribut
size= "3 " qui fait qu'on a une liste avec ascenseur plutôt qu'une liste déroulante. Les éléments de la liste ont été générés
par les balises <f:selectItem> des lignes 6-8.
L'élément sélectionné en [3] a été déterminé par appel à la méthode getSelectOneListBox2() (ligne 5). Le résultat " 3 "
obtenu (ligne 1 du code Java) a fait que l'élément de la ligne 8 de la liste a été affiché, ceci parce que son attribut
itemValue vaut " 3 ".
• la ligne 13 du code Jsp génère [4]. Ici la méthode getSelectOneListBox2 de [Form.java] a été de nouveau utilisée.

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">selectOneListBox (size=3)</span></td>
3. <td class="col2">choix unique : <select id="formulaire:selectOneListBox2"
name="formulaire:selectOneListBox2" size="3">
4. <option value="1">un</option>
5. <option value="2">deux</option>
6. <option value="3" selected="selected">trois</option>
7. <option value="4">quatre</option>
8. <option value="5">cinq</option>
9. </select></td>
10. <td class="col3">3</td>
11. </tr>

• ligne 6 : le fait que l'élément de valeur="3" soit sélectionné dans la liste se traduit par la présence de l'attribut
selected="selected".

Maintenant, ci-dessous, choisissons [1] une nouvelle valeur dans la liste et validons le formulaire avec le bouton [Valider] [2]. Nous
obtenons en réponse la page [3] :

Introdution à Java EE
270/334
1

4
3

La valeur postée pour le champ [1] est la suivante :

formulaire%3AselectOneListBox2=5

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. L'élément Html

<option value="5">cinq</option>

a été sélectionné. Le navigateur a envoyé la chaîne " 5 " comme valeur du composant Jsf ayant produit la liste déroulante :

<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">

Le contrôleur Jsf va utiliser la méthode setSelectOneListBox2(" 5 ") pour mettre à jour le modèle de la liste. Aussi après cette
mise à jour, le champ

private String selectOneListBox2;

contient-il désormais la valeur " 5 ".

Lorsque la page [form.jsp] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :
• elle détermine l'élément de la liste qui doit être sélectionné [3]
• la valeur du champ selectOneListBox2 est affiché en [4].

1.3.6.7 Balise <h:selectManyListBox>

La balise <h:selectmanyListBox> génère une balise Html <select multiple= "multiple ">...</select> qui permet à
l'utilisateur de sélectionner plusieurs éléments dans une liste.

Considérons le code suivant :

1. <!-- ligne 7 -->


2. <h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
5. <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}"
size="3">
6. <f:selectItem itemValue="1" itemLabel="un"/>
7. <f:selectItem itemValue="2" itemLabel="deux"/>
8. <f:selectItem itemValue="3" itemLabel="trois"/>
9. <f:selectItem itemValue="4" itemLabel="quatre"/>
10. <f:selectItem itemValue="5" itemLabel="cinq"/>
11. </h:selectManyListbox>
12. <p><input type="button" value="<h:outputText value="#{msg['form.buttonRazText']}"
/>" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
13. </h:panelGroup>
14. <h:outputText value="#{form.selectManyListBoxValue}"/>

et son modèle dans [Form.java] :

1. private String[] selectManyListBox=new String[]{"1","3"};

Introdution à Java EE
271/334
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

2 3 4

1 5

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. La liste [3] est générée par les lignes [5-11]. L'attribut size= "3" fait que la liste affiche
à un moment donné trois de ces éléments. Les éléments sélectionnés dans la liste ont été déterminés par appel à la
méthode getSelectManyListBox() (ligne 5) du modèle Java. Le résultat {"1","3"} obtenu (ligne 1 du code Java) est un
tableau d'éléments de type String. Chacun de ces éléments sert à sélectionner l'un des éléments de la liste. Ici, les éléments
des lignes 6 et 10 ayant leur attribut itemValue dans le tableau {"1","3"} seront sélectionnés. C'est ce que montre [3].
• la ligne 14 du code Jsp génère [4]. Il est ici fait appel, non pas à la méthode getSelectManyListBox du modèle Java de la
liste mais à la méthode getSelectManyListBoxValue suivante :

1. private String[] selectManyListBox=new String[]{"1","3"};


...
2. public String getSelectManyListBoxValue(){
3. return getValue(selectManyListBox);
4. }
5.
6. private String getValue(String[] chaines){
7. String value="[";
8. for(String chaine : chaines){
9. value+=" "+chaine;
10. }
11. return value+"]";
12.}

Si on avait fait appel à la méthode getSelectManyListBox, on aurait obtenu un tableau de String. Pour inclure cet
élément dans le flux Html, le contrôleur aurait appel à sa méthode toString. Or celle-ci pour un tableau ne fait que rendre
l'adresse mémoire de celui-ci et non pas la liste de ses éléments comme nous le souhaitons. Aussi utilise-t-on la méthode
getSelectManyListBoxValue ci-dessus pour obtenir une chaîne de caractères représentant le contenu du tableau.

• la ligne 12 du code Jsp génère le bouton [5]. Lorsque ce bouton est cliqué, le code Javascript de l'attribut onclick est
exécuté. Il sera embarqué au sein de la page Html qui va être générée par le code Jsf. Pour le comprendre, nous avons
besoin de connaître la nature exacte de celle-ci.

Le flux Html généré par la page Jsp est le suivant :

1. <tr>
2. <td class="col1"><span class="info">selectManyListBox (size=3)</span></td>
3. <td class="col2">choix multiple : <select id="formulaire:selectManyListBox"
name="formulaire:selectManyListBox" multiple="multiple" size="3">
4. <option value="1" selected="selected">un</option>
5. <option value="2">deux</option>
6. <option value="3" selected="selected">trois</option>
7. <option value="4">quatre</option>
8. <option value="5">cinq</option>
9. </select>
10. <p><input type="button" value="Raz"
onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
11. </td>
12. <td class="col3">[ 1 3]</td>
13. </tr>

• lignes 3 et 9 : la balise Html <select multiple= "multiple "...>...</select> générée par la balise Jsf
<h:selectManyListBox>. C'est la présence de l'attribut multiple qui indique qu'on a affaire à une liste à sélection
multiple.
• le fait que le modèle de la liste soit le tableau de String {"1","3"} fait que les éléments de la liste des lignes 4 (value= "1 ")
et 6 (value= "3 ") ont l'attribut selected="selected".
• ligne 10 : lorsqu'on clique sur le bouton [Raz], le code Javascript de l'attribut onclick s'exécute. La page est représentée
dans le navigateur par un arbre d'objets souvent appelé DOM (Document Object Model). Chaque objet de l'arbre est
accessible au code Javascript via sont attribut name. La liste de la ligne 3 du code Html ci-dessus s'appelle
formulaire:selectManyListBox. Le formulaire lui-même peut être désigné de diverses façons. Ici, il est désigné par la notation
this.form où this désigne le bouton [Raz] et this.form le formulaire dans lequel se trouve ce bouton. La liste

Introdution à Java EE
272/334
formulaire:selectManyListBox se trouve dans ce même formulaire. Aussi la notation
this.form['formulaire:selectManyListBox'] désigne-t-elle l'emplacement de la liste dans l'arbre des composants du
formulaire. L'objet représentant une liste a un attribut selectedIndex qui a pour valeur le n° de l'élément sélectionné dans
la liste. Ce n° commence à 0 pour désigner le 1er élément de la liste. La valeur -1 indique qu'aucun élément n'est
sélectionné dans la liste. Le code Javascript affectant la valeur -1 à l'attribut selectedIndex a pour effet de désélectionner
tous les éléments de la liste s'il y en avait.

Maintenant, ci-dessous, choisissons [1] de nouvelles valeurs dans la liste (pour sélectionner plusieurs éléments dans la liste,
maintenir la touche Ctrl appuyée en cliquant) et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la
page [3,4] :

4
3

La valeur du champ [1] postée est la suivante :

formulaire%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire%3AselectManyListBox=5

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Les éléments Html

<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>

ont été sélectionnés. Le navigateur a envoyé les trois chaînes "3", "4", "5" comme valeurs du composant Jsf ayant produit la liste
déroulante :

<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">

La méthode setSelectManyListBox du modèle va être utilisée pour mettre à ce jour ce modèle avec les valeurs envoyées par le
navigateur :

1. private String[] selectManyListBox;


2.....
3. public void setSelectManyListBox(String[] selectManyListBox) {
4. this.selectManyListBox = selectManyListBox;
5.}

Ligne 3, on voit que le paramètre de la méthode est un tableau de String. Ici ce sera le tableau {" 3 ", "4 ", "5 "}. Après cette mise à
jour, le champ

private String[] selectManyListBox;

contient désormais le tableau {" 3 ", "4 ", "5 "}.

Lorsque la page [form.jsp] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :
• elle détermine les éléments de la liste qui doivent être sélectionnés [3]
• la valeur du champ selectManyListBox est affichée en [4].

Introdution à Java EE
273/334
1.3.6.8 Balise <h:selectOneMenu>

La balise <h:selectOneMenu> est identique à la balise <h:selectOneListBox size= "1 ">. Dans l'exemple, le code Jsf exécuté
est le suivant :

1. <!-- ligne 8 -->


2. <h:outputText value="selectOneMenu" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
5. <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
6. <f:selectItem itemValue="1" itemLabel="un"/>
7. <f:selectItem itemValue="2" itemLabel="deux"/>
8. <f:selectItem itemValue="3" itemLabel="trois"/>
9. <f:selectItem itemValue="4" itemLabel="quatre"/>
10. <f:selectItem itemValue="5" itemLabel="cinq"/>
11. </h:selectOneMenu>
12. </h:panelGroup>
13. <h:outputText value="#{form.selectOneMenu}"/>

Le modèle de la balise <h:selectOneMenu> dans [Form.java] est le suivant :

private String selectOneMenu="1";

A la demande initiale de la page [form.jsp], le code précédent génère la vue :

Un exemple d'exécution pourrait être celui qui suit :

La valeur postée pour le champ [1] est la suivante :

formulaire%3AselectOneMenu=4

1.3.6.9 Balise <h:selectManyMenu>

La balise <h:selectManyMenu> est identique à la balise <h:selectManyListBox size= "1 ">. Le code Jsf exécuté dans
l'exemple est le suivant :

1. <!-- ligne 9 -->


2. <h:outputText value="selectManyMenu" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
5. <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
6. <f:selectItem itemValue="1" itemLabel="un"/>
7. <f:selectItem itemValue="2" itemLabel="deux"/>
8. <f:selectItem itemValue="3" itemLabel="trois"/>

Introdution à Java EE
274/334
9. <f:selectItem itemValue="4" itemLabel="quatre"/>
10. <f:selectItem itemValue="5" itemLabel="cinq"/>
11. </h:selectManyMenu>
12. <p><input type="button" value="<h:outputText
value="#{msg['form.buttonRazText']}"/>"
onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
13. </h:panelGroup>
14. <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>

Le modèle de la balise <h:selectManyMenu> dans [Form.java] est le suivant :

private String[] selectManyMenu=new String[]{"1","2"};

A la demande initiale de la page [form.jsp], le code précédent génère la page :

La liste [1] contient les textes " un ", ..., " cinq " avec les éléments " un " et " deux " sélectionnés. Le code Html généré est le suivant
:

1. <tr>
2. <td class="col1"><span class="info">selectManyMenu</span></td>
3. <td class="col2"><span class="prompt">choix multiple : </span><select
id="formulaire:selectManyMenu" name="formulaire:selectManyMenu" multiple="multiple" size="1">
4. <option value="1" selected="selected">un</option>
5. <option value="2" selected="selected">deux</option>
6. <option value="3">trois</option>
7. <option value="4">quatre</option>
8. <option value="5">cinq</option>
9. </select>
10.
11.
12. <p><input type="button" value="Raz"
onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
13. </td>
14. <td class="col3"><span class="prompt">[ 1 2]</span></td>
15. </tr>

On voit ci-dessus, en lignes 4 et 5 que les éléments " un " et " deux " sont sélectionnés (présence de l'attribut selected).
Il est difficile de donner une copie d'écran d'un exemple d'exécution car on ne peut pas montrer les éléments sélectionnés dans le
menu. Le lecteur est invité à faire le test lui-même (pour sélectionner plusieurs éléments dans la liste, maintenir la touche Ctrl
appuyée en cliquant).

1.3.6.10 Balise <h:inputHidden>

La balise <h:inputHidden> n'a pas de représentation visuelle. Elle ne sert qu'à insérer une balise Html <input type= "hidden "
value= "... "/> dans le flux Html de la page. Inclus à l'intérieur d'une balise <h:form>, leurs valeurs font partie des valeurs
envoyées au serveur lorsque le formulaire est posté. Parce que ce sont des champs de formulaire et que l'utilisateur ne les voie pas,
on les appelle des champs cachés. L'intérêt de ces champs est de garder de la mémoire entre les différents cycles demande /
réponse d'un même client :
• le client demande un formulaire F. Le serveur le lui envoie et met une information I dans un champ caché C, sous la
forme <h:inputHidden id= "C " value= "I "/>
• lorsque le client a rempli le formulaire F et qu'il le poste au serveur, la valeur I du champ C est renvoyée au serveur. Celui-
ci peut alors retrouver l'information I qu'il avait stockée dans la page. On a ainsi créé une mémoire entre les deux cycles
demande / réponse
• Jsf utilise lui-même cette technique. L'information I qu'il stocke dans le formulaire F est la valeur de tous les composants
de celui-ci. Il utilise le champ caché suivant pour cela :

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState"


value="H4sIAAAAAAAAANV...8PswawAA" />

Le champ caché s'appelle javax.faces.ViewState et sa valeur est une chaîne qui représente sous forme codée la valeur de
tous les composants de la page envoyée au client. Lorsque celui-ci renvoie la page après avoir fait des saisies dans le
formulaire, le champ caché javax.faces.ViewState est renvoyé avec les valeurs saisies. C'est ce qui permet au contrôleur

Introdution à Java EE
275/334
Jsf de reconstituer la page telle qu'elle avait été envoyée initialement. Ce mécanisme a été expliqué au paragraphe 1.3.5
page 250.

Le code Jsf de l'exemple est le suivant :

1. <!-- ligne 10 -->


2. <h:outputText value="inputHidden" styleClass="info"/>
3. <h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
4. <h:outputText value="#{form.inputHidden}"/>

Le modèle de la balise <h:inputHidden> dans [Form.java] est le suivant :

private String inputHidden="initial";

Ce qui donne l'affichage suivant lors de la demande initiale de la page [form.jsp] :

1 2

• la ligne 2 génère [1], la ligne 4 [2]. La ligne 3 ne génère aucun élément visuel.

Le code Html généré est le suivant :

1. <tr>
2. <td class="col1"><span class="info">inputHidden</span></td>
3. <td class="col2"><input id="formulaire:inputHidden" type="hidden" name="formulaire:inputHidden"
value="initial" /></td>
4. <td class="col3">initial</td>
5. </tr>

Au moment du POST du formulaire, la valeur " initial " du champ nommé formulaire:inputHidden de la ligne 3 sera posté avec les
autres valeurs du formulaire. Le champ

private String inputHidden;

sera mis à jour avec cette valeur, qui est celle qu'il avait déjà initialement. Cette valeur sera intégrée dans la nouvelle page renvoyée
au client. On obtient donc toujours la copie d'écran ci-dessus.

La valeur postée pour le champ caché est la suivante :

formulaire%3AinputHidden=initial

1.3.6.11 Balise <h:selectBooleanCheckBox>

La balise <h:selectBooleanCheckBox> génère une balise Html <input type="checkbox" ...>.

Considérons le code Jsf suivant :

1. <!-- ligne 11 -->


2. <h:outputText value="selectBooleanCheckbox" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}"
styleClass="prompt" />
5. <h:selectBooleanCheckbox id="selectBooleanCheckbox"
value="#{form.selectBooleanCheckbox}"/>
6. </h:panelGroup>
7. <h:outputText value="#{form.selectBooleanCheckbox}"/>

Le modèle de la balise <h:selectBooleanCheckbox> de ligne 5 ci-dessus dans [Form.java] est le suivant:

private boolean selectBooleanCheckbox=true;

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

3 4
1 2

Introdution à Java EE
276/334
• la ligne 2 du code Jsp génère [1]
• le texte [2] est généré par la ligne 4. La case à cocher [3] est générée par la ligne [5]. Ici, la méthode
getSelectBooleanCheckbox de [Form.java] a été utilisée pour cocher ou non la case. La méthode rendant le booléen
true (cf code Java), la case a été cochée.
• la ligne 7 du code Jsp génère [4]. C'est de nouveau la méthode getSelectBooleanCheckbox de [Form.java] qui est utilisée
pour générer le texte [4].

Le flux Html généré par le code Jsf précédent est le suivant :

1. <tr>
2. <td class="col1"><span class="info">selectBooleanCheckbox</span></td>
3. <td class="col2"><span class="prompt">mari&eacute;(e) : </span>
4. <input id="formulaire:selectBooleanCheckbox" type="checkbox"
name="formulaire:selectBooleanCheckbox" checked="checked" /></td>
5. <td class="col3">true</td>
6. </tr>

En [4], on voit la balise Html <input type= "checkbox "> qui a été générée. La valeur true du modèle associé a fait que l'attribut
checked= "checked " a été ajouté à la balise. Ce qui fait que la case est cochée.

Maintenant, ci-dessous, décochons la case [1], validons le formulaire [2] et regardons le résultat obtenu [3] :

4 3

Parce que la case est décochée, il n'y a pas de valeur postée pour le champ [1].

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ
selectBooleanCheckbox de [Form.java] a reçu alors la valeur false. Le réaffichage de [form.jsp] montre que champ
selectBooleanCheckbox du modèle a bien été mis à jour [3] et [4]. Il est intéressant de noter ici que c'est grâce au champ caché
javax.faces.ViewState que Jsf a été capable de dire que la case à cocher initialement cochée avait été décochée par l'utilisateur. En
effet, la valeur d'une case décochée ne fait pas partie des valeurs postées par le navigateur. Grâce à l'arbre des composants stocké
dans le champ caché javax.faces.ViewState Jsf retrouve le fait qu'il y avait une case à cocher nommée "selectBooleanCheckbox"
dans le formulaire et que sa valeur ne fait pas partie des valeurs postées par le navigateur client. Il peut en conclure qu'elle était
décochée dans le formulaire posté, ce qui lui permet d'affecter le booléen false au modèle Java associé :

private boolean selectBooleanCheckbox;

1.3.6.12 Balise <h:selectManyCheckBox>

La balise <h:selectManyCheckBox> génère un groupe de cases à cocher et donc plusieurs balises Html <input
type="checkbox" ...>. Cette balise est le pendant de la balise <h:selectManyListBox>, si ce n'est que les éléments à
sélectionner sont présentés sous forme de cases à cocher contigües plutôt que sous forme de liste. Ce qui a été dit pour la balise
<h:selectManyListBox> reste valide ici.

Considérons le code Jsf suivant :

1. <!-- ligne 12 -->


2. <h:outputText value="selectManyCheckbox" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt"
/>
5. <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
6. <f:selectItem itemValue="1" itemLabel="rouge"/>
7. <f:selectItem itemValue="2" itemLabel="bleu"/>
8. <f:selectItem itemValue="3" itemLabel="blanc"/>
9. <f:selectItem itemValue="4" itemLabel="noir"/>

Introdution à Java EE
277/334
10. </h:selectManyCheckbox>
11. </h:panelGroup>
12. <h:outputText value="#{form.selectManyCheckboxValue}"/>

Le modèle de la balise <h:selectManyCheckbox> de ligne 5 ci-dessus dans [Form.java] est le suivant:

private String[] selectManyCheckbox=new String[]{"1","3"};

Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :

2
4
1
3

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. Les cases à cocher [3] sont générées par les lignes 5-10. Pour chacune d'elles :
• l'attribut itemLabel définit le texte affiché près de la case à cocher
• l'attribut itemvalue définit la valeur qui sera postée au serveur si la case est cochée.

Le modèle des quatre cases est le champ Java suivant :

private String[] selectManyCheckbox=new String[]{"1","3"};

Ce tableau définit :
• lorsque la page est affichée, les cases qui doivent être cochées. Ceci est fait via leur valeur, c.a.d. leur champ
itemValue. Ci-dessus, les cases ayant leurs valeurs dans le tableau {"1","3"} seront cochées. C'est ce qui est vu sur
la copie d'écran ci-dessus.
• lorsque la page est postée, le modèle selectManyCheckbox reçoit le tableau des valeurs des cases que l'utilisateur a
cochées. C'est ce que nous allons voir prochainement.
• la ligne 12 du code Jsp génère [4]. C'est la méthode getSelectManyCheckboxValue suivante qui a généré [4] :

1. public String getSelectManyCheckboxValue(){


2. return getValue(getSelectManyCheckbox());
3. }
4.
5. private String getValue(String[] chaines){
6. String value="[";
7. for(String chaine : chaines){
8. value+=" "+chaine;
9. }
10. return value+"]";
11.}

Le flux Html généré par le code Jsf précédent est le suivant :

1. <tr>
2. <td>
3. <input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:0" value="1"
type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:0">
rouge</label></td>
4. <td>
5. <input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:1" value="2"
type="checkbox" /><label for="formulaire:selectManyCheckbox:1"> bleu</label></td>
6. <td>
7. <input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:2" value="3"
type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:2">
blanc</label></td>
8. <td>
9. <input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:3" value="4"
type="checkbox" /><label for="formulaire:selectManyCheckbox:3"> noir</label></td>
10. </tr>
11. </table></td>
12. <td class="col3">[ 1 3]</td>
13. </tr>

Quatre balises Html <input type= "checkbox " ...> ont été générées. Les balises des lignes 3 et 7 ont l'attribut
checked= "checked " qui font qu'elles apparaissent cochées. On notera qu'elles ont toutes le même attribut
name="formulaire:selectManyCheckbox", autrement dit les quatre champs Html ont le même nom. Si les cases des lignes 5 et
9 sont cochées par l'utilisateur, le navigateur enverra les valeurs des quatre cases à cocher sous la forme :

Introdution à Java EE
278/334
formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

et le modèle des quatre cases

private String[] selectManyCheckbox=new String[]{"1","3"};

recevra le tableau {"2","4"}.

Vérifions-le ci-dessous. En [1], on fait le changement, en [2] on valide le formulaire. En [3] le résultat obtenu :

Les valeurs postées pour les champs [1] sont les suivantes :

formulaire%3AselectManyCheckbox=2&formulaire%3AselectManyCheckbox=4

1.3.6.13 Balise <h:selectOneRadio>

La balise <h:selectOneRadio> génère un groupe de boutons radio exclusifs les uns des autres.

Considérons le code Jsf suivant :

1. <!-- ligne 13 -->


2. <h:outputText value="selectOneRadio" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
5. <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
6. <f:selectItem itemValue="1" itemLabel="voiture"/>
7. <f:selectItem itemValue="2" itemLabel="vélo"/>
8. <f:selectItem itemValue="3" itemLabel="scooter"/>
9. <f:selectItem itemValue="4" itemLabel="marche"/>
10. </h:selectOneRadio>
11. </h:panelGroup>
12. <h:outputText value="#{form.selectOneRadio}"/>

Le modèle de la balise <h:selectOneRadio> de la ligne 5 ci-dessus est le suivant dans [Form.java] :

private String selectOneRadio="2";

Lorsque la page [form.jsp] est demandée la première fois, la vue obtenue est la suivante :

2
4
1
3

• la ligne 2 du code Jsp génère [1]


• le texte [2] est généré par la ligne 4. Les boutons radio [3] sont générés par les lignes 5-10. Pour chacun d'eux :
• l'attribut itemLabel définit le texte affiché près du bouton radio
• l'attribut itemvalue définit la valeur qui sera postée au serveur si le bouton est coché.
Le modèle des quatre boutons radio est le champ Java suivant :

Introdution à Java EE
279/334
private String selectOneRadio="2";

Ce modèle définit :
• lorsque la page est affichée, l'unique bouton radio qui doit être coché. Ceci est fait via leur valeur, c.a.d. leur
champ itemValue. Ci-dessus, le bouton radio ayant la valeur " 2 " sera cochée. C'est ce qui est vu sur la copie
d'écran ci-dessus.
• lorsque la page est postée, le modèle selectOneRadio reçoit la valeur du bouton radio qui a été coché. C'est ce que
nous allons voir prochainement.
• la ligne 12 du code Jsp génère [4].

Le flux Html généré par le code Jsf précédent est le suivant :

1.<tr>
2.<td class="col1"><span class="info">selectOneRadio</span></td>
3.<td class="col2">moyen de transport pr&eacute;f&eacute;r&eacute; : <table
id="formulaire:selectOneRadio">
4. <tr>
5.<td>
6.<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:0" value="1"
/><label for="formulaire:selectOneRadio:0"> voiture</label></td>
7.<td>
8.<input type="radio" checked="checked" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:1"
value="2" /><label for="formulaire:selectOneRadio:1"> v&eacute;lo</label></td>
9.<td>
10.<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:2" value="3"
/><label for="formulaire:selectOneRadio:2"> scooter</label></td>
11.<td>
12.<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:3" value="4"
/><label for="formulaire:selectOneRadio:3"> marche</label></td>
13.</tr>

Quatre balises Html <input type= "radio" ...> ont été générées. La balise de la ligne 8 a l'attribut checked= "checked " qui fait
que le bouton radio correspondant apparaît coché. On notera que les balises ont toutes le même attribut
name="formulaire:selectOneRadio", autrement dit les quatre champs Html ont le même nom. C'est la condition pour avoir un
groupe de boutons radio exclusifs : lorsque l'un est coché, les autres ne le sont pas.

Ci-dessous, en [1], on coche un des boutons radio, en [2] on valide le formulaire, en [3] le résultat obtenu :

La valeur postée pour le champ [1] est la suivante :

formulaire%3AselectOneRadio=4

1.4 Exemple n° 4
Thèmes : formulaires dynamiques

1.4.1 L'application

Introdution à Java EE
280/334
L'application est la même que précédemment :

Les seuls changements proviennent de la façon dont sont générés les éléments des listes des zones [1] et [2]. Ils sont ici générés
dynamiquement par du code Java alors que dans la version précédente ils étaient écrits " en dur " dans le code de la page Jsf.

1.4.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Introdution à Java EE
281/334
Le projet [intro-04] est identique au projet [intro-03] aux différences près suivantes :
• en [1], dans la page Jsf, les éléments des listes ne vont plus être écrites " en dur " dans le code
• en [2], le modèle de la page Jsf [1] va être modifié
• en [3], l'un des messages va être modifié

1.4.3 La page [form.jsp] et son modèle [Form.java]

La page Jsf [form.jsp] devient la suivante :

1. ...
2. <f:view>
3. <html>
4. <head>
5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6. <title>JSF</title>
7. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
8. </head>
9. <body background="<c:url value="/ressources/standard.jpg"/>">
10. <h:form id="formulaire">
11. <h:panelGrid columns="2">
12. <h:commandLink value="#{msg['form.langue1']}" action="#{locale.setFrenchLocale}"/>
13. <h:commandLink value="#{msg['form.langue2']}" action="#{locale.setEnglishLocale}"/>
14. </h:panelGrid>
15. <h1><h:outputText value="#{msg['form.titre']}"/></h1>
16. <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
17. ...
18. <!-- ligne 5 -->
19. <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
20. <h:panelGroup>
21. <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
22. <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
23. <f:selectItems value="#{form.selectOneListbox1Items}"/>
24. </h:selectOneListbox>
25. </h:panelGroup>
26. <h:outputText value="#{form.selectOneListBox1}"/>
27. <!-- ligne 6 -->
28. <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
29. <h:panelGroup>
30. <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
31. <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
32. <f:selectItems value="#{form.selectOneListbox2Items}"/>
33. </h:selectOneListbox>
34. </h:panelGroup>
35. <h:outputText value="#{form.selectOneListBox2}"/>
36. <!-- ligne 7 -->
37. <h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
38. <h:panelGroup>
39. <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
40. <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}"
size="3">
41. <f:selectItems value="#{form.selectManyListBoxItems}"/>
42. </h:selectManyListbox>
43. <p><input type="button" value="<h:outputText value="#{msg['form.buttonRazText']}" />"
onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
44. </h:panelGroup>
45. <h:outputText value="#{form.selectManyListBoxValue}"/>
46. <!-- ligne 8 -->
47. <h:outputText value="selectOneMenu" styleClass="info"/>
48. <h:panelGroup>
49. <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
50. <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
51. <f:selectItems value="#{form.selectOneMenuItems}"/>
52. </h:selectOneMenu>
53. </h:panelGroup>
54. <h:outputText value="#{form.selectOneMenu}"/>
55. <!-- ligne 9 -->
56. <h:outputText value="selectManyMenu" styleClass="info"/>
57. <h:panelGroup>
58. <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
59. <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
60. <f:selectItems value="#{form.selectManyMenuItems}"/>
61. </h:selectManyMenu>
62. <p><input type="button" value="<h:outputText value="#{msg['form.buttonRazText']}"/>"
onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
63. </h:panelGroup>
64. <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
65. <!-- ligne 10 -->
66. <h:outputText value="inputHidden" styleClass="info"/>

Introdution à Java EE
282/334
67. <h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
68. <h:outputText value="#{form.inputHidden}"/>
69. <!-- ligne 11 -->
70. ...
71. <!-- ligne 12 -->
72. <h:outputText value="selectManyCheckbox" styleClass="info"/>
73. <h:panelGroup>
74. <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
75. <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
76. <f:selectItems value="#{form.selectManyCheckboxItems}"/>
77. </h:selectManyCheckbox>
78. </h:panelGroup>
79. <h:outputText value="#{form.selectManyCheckboxValue}"/>
80. <!-- ligne 13 -->
81. <h:outputText value="selectOneRadio" styleClass="info"/>
82. <h:panelGroup>
83. <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
84. <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
85. <f:selectItems value="#{form.selectOneRadioItems}"/>
86. </h:selectOneRadio>
87. </h:panelGroup>
88. <h:outputText value="#{form.selectOneRadio}"/>
89. </h:panelGrid>
90. <p>
91. <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
92. </p>
93. </h:form>
94. </body>
95. </html>
96. </f:view>

Les modifications apportées sont illustrées par les lignes 22 – 24. Là où auparavant, on avait le code :

1. <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">


2. <f:selectItem itemValue="1" itemLabel="un"/>
3. <f:selectItem itemValue="2" itemLabel="deux"/>
4. <f:selectItem itemValue="3" itemLabel="trois"/>
5. </h:selectOneListbox>

on a désormais celui-ci :

a) <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">


b) <f:selectItems value="#{form.selectOneListbox1Items}"/>
c) </h:selectOneListbox>

Les trois balises <f:selectItem> des lignes 2-4 ont été remplacées par l'unique balise <f:selectItems> de la ligne b. Cette balise a
un attribut value dont la valeur est une collection d'éléments de type javax.faces.model.SelectItem. Ci-dessus, la valeur de l'attribut value
va être obtenue par appel de la méthode [form].getSelectOneListbox1Items suivante :

1. public SelectItem[] getSelectOneListbox1Items() {


2. return getItems("A",3);
3. }
4.
5. private SelectItem[] getItems(String label, int qte) {
6. SelectItem[] items=new SelectItem[qte];
7. for(int i=0;i<qte;i++){
8. items[i]=new SelectItem(i,label+i);
9. }
10. return items;
11.}

• ligne 1, la méthode getSelectOneListbox1Items rend un tableau d'éléments de type javax.faces.model.SelectItem construit


par la méthode privée getItems de la ligne 5. On notera que la méthode getSelectOneListbox1Items n'est pas le getter
d'un champ privé selectOneListBox1Items.
• la classe javax.faces.model.SelectItem a divers constructeurs.

Introdution à Java EE
283/334
Nous utilisons ligne 8 de la méthode getItems, le constructeur SelectItem(Object value, String label) qui correspond à la balise
Jsf
<f:selectItem itemValue= "value " labelValue= "label "/>

• lignes 5-10 : la méthode getItems(String label, int qte) construit un tableau de qte éléments de type SelectItem, où
l'élément i est obtenu par le constructeur SelectItem(i, label+i).

Le code Jsf

<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">


<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>

devient alors fonctionnellement équivalent au code Jsf suivant :

<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">


<f:selectItem itemValue="0" itemLabel="A0"/>
<f:selectItem itemValue="1" itemLabel="A1"/>
<f:selectItem itemValue="2" itemLabel="A2"/>
</h:selectOneListbox>

Il est fait de même pour toutes les autres listes de la page Jsf. On trouve ainsi dans le modèle [Form.java] les nouvelles méthodes
suivantes :

1. public SelectItem[] getSelectOneListbox1Items() {


2. return getItems("A",3);
3. }
4.
5. public SelectItem[] getSelectOneListbox2Items() {
6. return getItems("B",4);
7. }
8.
9. public SelectItem[] getSelectManyListBoxItems() {
10. return getItems("C",5);
11. }
12.
13. public SelectItem[] getSelectOneMenuItems() {
14. return getItems("D",3);
15. }
16.
17. public SelectItem[] getSelectManyMenuItems() {
18. return getItems("E",4);
19. }
20.
21. public SelectItem[] getSelectManyCheckboxItems() {
22. return getItems("F",3);
23. }
24.
25. public SelectItem[] getSelectOneRadioItems() {
26. return getItems("G",4);
27. }
28.
29. private SelectItem[] getItems(String label, int qte) {
30. SelectItem[] items=new SelectItem[qte];
31. for(int i=0;i<qte;i++){
32. items[i]=new SelectItem(i,label+i);
33. }
34. return items;

Introdution à Java EE
284/334
35.}

1.4.4 Le fichier des messages

Un seul message est modifié :

[messages_fr.properties]
form.titre=Java Server Faces - remplissage dynamique des listes

[messages_en.properties]
form.titre=Java Server Faces - dynamic filling of lists of elements

1.4.5 Tests

Le lecteur est invité à tester cette nouvelle version.

Le plus souvent les éléments dynamiques d'un formulaire sont les résultats d'un traitement métier ou proviennent d'une base de
données :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
5 JSP2 Modèles
4
JSPn

Etudions la demande initiale de la page Jsf [form.jsp] par un GET du navigateur :


• la page Jsf est demandée [1]
• le contrôleur [Faces Servlet] demande son affichage en [3]. Le moteur Jsf qui traite la page fait appel au modèle
[Form.java] de celle-ci, par exemple à la méthode getSelectOneListBox1Items. Cette méthode pourrait très bien rendre
un tableau d'éléments de type SelectItem, à partir d'informations enregistrées dans une base de données. Pour cela, elle ferait
appel à la couche [métier] [4].

Nous étudierons prochainement une version du formulaire où les données proviendront d'une base de données.

1.5 Exemple n° 5
Thèmes : navigation, session, pages Jsp, gestion des exceptions

1.5.1 L'application

L'application est la même que précédemment si ce n'est que le formulaire se présente désormais sous la forme d'un assistant à
plusieurs pages :

• en [1], la page 1 du formulaire - peut être obtenue également par le lien 1 de [2]

Introdution à Java EE
285/334
1 3

• en [2], un groupe de 5 liens.


• en [3], la page 2 du formulaire obtenue par le lien 2 de [2]

• en [4], la page 3 du formulaire obtenue par le lien 3 de [2]


• en [5], la page obtenue par le lien Lancer une exception de [2]

Introdution à Java EE
286/334
6

• en [6], la page obtenue par le lien 4 de [2]. Elle récapitule les saisies faites dans les pages 1 à 3.

1.5.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Introdution à Java EE
287/334
Le projet [intro-05] introduit deux nouveautés :
1. en [1], la page Jsf [form.jsp] est scindée en trois pages [form1.jsp, form2.jsp, form3.jsp] sur lesquelles ont été réparties les
saisies. La page [form4.jsp] est une copie de la page [form.jsp] du projet précédent. En [2], la classe [Form.java] reste
inchangée. Elle va servir de modèle aux quatre pages Jsf précédentes.
2. en [3], une page Jsp (pas Jsf) est ajoutée : elle sera utilisée lorsque se produira une exception dans l'application.

Nous allons tout d'abord étudier le point 1 qui ne présente pas de difficultés. Nous étudierons ensuite le point 2 qui lui se montre
complexe à résoudre proprement.

1.5.3 Les pages [form.jsp] et leur modèle [Form.java]

1.5.3.1 Le code des pages Jsp

La page Jsf [form1.jsp] est la suivante :

1. ...
2. <f:view>
3. <html>
4. <head>
5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6. <title>JSF</title>
7. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
8. </head>
9. <body background="<c:url value="/ressources/standard.jpg"/>">
10. <h:form id="formulaire">
11. <h:panelGrid columns="2">
12. <h:commandLink value="#{msg['form.langue1']}" action="#{locale.setFrenchLocale}"/>
13. <h:commandLink value="#{msg['form.langue2']}" action="#{locale.setEnglishLocale}"/>
14. </h:panelGrid>
15. <h1><h:outputText value="#{msg['form1.titre']}"/></h1>
16. <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
17. <!-- ligne 1 -->
18. <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
19. <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
20. <!-- ligne 2 -->
21. <h:outputText value="inputText" styleClass="info"/>
22. <h:panelGroup>
23. <h:outputText value="#{msg['form.loginPrompt']}"/>
24. <h:inputText id="inputText" value="#{form.inputText}"/>
25. </h:panelGroup>
26. <!-- ligne 3 -->
27. <h:outputText value="inputSecret" styleClass="info"/>
28. <h:panelGroup>
29. <h:outputText value="#{msg['form.passwdPrompt']}"/>
30. <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
31. </h:panelGroup>
32. <!-- ligne 4 -->
33. <h:outputText value="inputTextArea" styleClass="info"/>
34. <h:panelGroup>
35. <h:outputText value="#{msg['form.descPrompt']}"/>
36. <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
37. </h:panelGroup>
38. </h:panelGrid>
39. <!-- liens -->
40. <h:panelGrid columns="6">
41. <h:commandLink value="1" action="form1"/>
42. <h:commandLink value="2" action="#{form.doAction2}"/>
43. <h:commandLink value="3" action="form3"/>
44. <h:commandLink value="4" action="#{form.doAction4}"/>
45. <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
46. <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
47. </h:panelGrid>
48. </h:form>
49. </body>
50. </html>
51. </f:view>

et correspond à l'affichage suivant :

Introdution à Java EE
288/334
1

On notera les points suivants :


• ligne 16, le tableau qui avait auparavant 3 colonnes, n'en a plus que 2. La colonne 3 qui affichait les valeurs du modèle a
été supprimée. C'est [form4.jsp] qui les affichera.
• lignes 40-46 : un tableau de 6 liens. Les liens des lignes 41et 43 ont une navigation statique : leur attribut action est codé
en dur. Les autres liens ont une navigation dynamique : leur attribut action pointe sur une méthode du bean form chargée
de rendre la clé de navigation. Les méthodes référencées dans [Form.java] sont les suivantes :

1.// événements
2. public String doAction2(){
3. return "form2";
4. }
5.
6. public String doAction4(){
7. return "form4";
8. }
9.
10. public String doAlea(){
11. // un nombre aléatoire entre 1 et 3
12. int i=1+(int)(3*Math.random());
13. // on rend la clé de navigation
14. return "form"+i;
15. }
16.
17. public String throwException() throws java.lang.Exception{
18. throw new Exception("Exception test");
19. }

Nous ignorerons pour l'instant la méthode throwException de la ligne 17. Nous y reviendrons ultérieurement. Les méthodes
doAction2 et doAction4 se contentent de rendre la clé de navigation sans faire de traitement. On aurait donc tout ausi bien
pu écrire :

<!-- liens -->


<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="form2"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="form4"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>

La méthode doAlea génère, elle, une clé de navigation aléatoire qui prend sa valeur dans l'ensemble
{"form1","form2","form3"}. Les règles de navigation correspondant aux 4 clés sont présentes dans le fichier [faces-
config.xml] :

1. ...
2. <!-- règles de navigation -->
3. <navigation-rule>
4. <from-view-id>*</from-view-id>
5. <navigation-case>
6. <from-outcome>form1</from-outcome>
7. <to-view-id>/form1.jsp</to-view-id>
8. </navigation-case>

Introdution à Java EE
289/334
9. <navigation-case>
10. <from-outcome>form2</from-outcome>
11. <to-view-id>/form2.jsp</to-view-id>
12. </navigation-case>
13. <navigation-case>
14. <from-outcome>form3</from-outcome>
15. <to-view-id>/form3.jsp</to-view-id>
16. </navigation-case>
17. <navigation-case>
18. <from-outcome>form4</from-outcome>
19. <to-view-id>/form4.jsp</to-view-id>
20. </navigation-case>
21. </navigation-rule>
22. </faces-config>

• ligne 4, on indique que les cas de navigation qui suivent sont valables quelque soit la page de départ de la
navigation. C'est le sens de la chaîne *.
• lignes 5-8 : lorsque le contrôleur [Faces Servlet] reçoit la clé de navigation form1 (ligne 6), il doit afficher la page
Jsf /form1.jsp.
• on retrouve des cas de navigation similaires lignes 9-21.

Le code des pages [form2.jsp, form3.jsp, form3.jsp] est analogue à celle de la page [form1.jsp].

1.5.3.2 Durée de vie du modèle [Form.java] des pages [form*.jsp]

Considérons la séquence d'actions suivantes :

2
1

• en [1], on remplit la page 1 et on passe à la page 3


• en [2], on remplit la page 3 et on revient à la page 1

Introdution à Java EE
290/334
4
3

• en [3], on retrouve la page 1 telle qu'on l'a saisie. On revient alors à la page 3
• en [4], la page 3 est retrouvée telle qu'elle a été saisie.

Le mécanisme du champ caché [javax.faces.ViewState] ne suffit pas à expliquer ce phénomène.

Lors du passage de [1] à [2], plusieurs étapes ont lieu :


• le modèle [Form.java] est mis à jour avec le POST de [form1.jsp]. Notamment, le champ inputText reçoit la valeur "un
autre texte".
• la clé de navigation "form3" provoque l'affichage de [form3.jsp]. Le ViewState embarqué dans [form3.jsp] est l'état des
seuls composants de [form3.jsp] pas ceux de [form1.jsp].

Lors du passage de [2] à [3] :


• le modèle [Form.java] est mis à jour avec le POST de [form3.jsp]. Si la durée de vie du modèle [Form.java] est request, un
objet [Form.java] tout neuf est créé avant d'être mis à jour par le POST de [form3.jsp]. Dans ce cas, le champ inputText
du modèle retrouve sa valeur par défaut :

private String inputText="texte";

et la garde : en effet dans le POST de [form3.jsp], rien ne vient mettre à jour le champ inputText qui fait partie du
modèle de [form1.jsp] et non de celui de [form3.jsp].
• la clé de navigation "form1" provoque l'affichage de [form1.jsp]. La page affiche son modèle. Dans notre cas, le champ de
saisie login lié au modèle inputText affichera texte et non pas la valeur "un autre texte" saisie en [1]. Pour que le champ
inputText garde la valeur saisie en [1], la durée de vie du modèle [Form.java] doit être session et non request. Dans ce
cas,
• à l'issue du POST de [form1.jsp], il sera placé dans la session du client. Le champ inputText aura la valeur "un
autre texte".
• au moment du POST de [form3.jsp], il sera recherché dans cette session et mis à jour par le POST de
[form3.jsp]. Le champ inputText ne sera pas mis à jour par ce POST mais gardera la valeur "un autre texte"
acquise à l'issue du POST de [form1.jsp] [1].

Le fichier [faces-config.xml] évolue donc comme suit :

1. ...
2. <faces-config version="1.2"
3. xmlns="http://java.sun.com/xml/ns/javaee"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
6.
7. ....
8.
9. <!-- beans managés -->
10. <managed-bean>
11. <managed-bean-name>form</managed-bean-name>
12. <managed-bean-class>forms.Form</managed-bean-class>
13. <managed-bean-scope>session</managed-bean-scope>
14. </managed-bean>
15. <managed-bean>

Introdution à Java EE
291/334
16. <managed-bean-name>locale</managed-bean-name>
17. <managed-bean-class>utils.ChangeLocale</managed-bean-class>
18. <managed-bean-scope>application</managed-bean-scope>
19. </managed-bean>
20.
21. <!-- règles de navigation -->
22. <navigation-rule>
23. ...
24. </navigation-rule>
25. </faces-config>

• ligne 13, la durée de vie session du bean form.

1.5.4 Gestion des exceptions

Revenons sur l'architecture générale d'une application Jsf :

Application web
couche [web]
2a 2b
1
Faces Servlet Gestionnaire
3 d'évts couche couche Données
Navigateur JSP1 [metier] [dao]
5 JSP2 Modèles
4
JSPn

Que se passe-t-il lorsque un gestionnaire d'événement ou bien un modèle récupère une exception provenant de la couche métier,
une déconnexion imprévue d'avec une base de données par exemple ?

• les gestionnaires d'événements [2a] peuvent intercepter toute exception remontant de la couche [métier] et rendre au
contrôleur [Faces Servlet] une clé de navigation vers une page d'erreur spécifique à l'exception.
• pour les modèles cette solution n'est pas utilisable car lorsqu'ils sont sollicités [3,4], on est dans la phase de rendu d'une
page Jsp précise et plus dans la phase de choix de celle-ci. Comment faire pour changer de page alors même qu'on est dans
la phase de rendu de l'une d'elles ? Une solution simple mais qui ne convient pas toujours est de ne pas gérer l'exception
qui va alors remonter jusqu'au conteneur de servlets qui exécute l'application. Celle-ci peut être configurée pour afficher
une page particulière lorsqu'une exception remonte jusqu'au conteneur de servlets. Cette solution est toujours utilisable et
nous l'examinons maintenant.

1.5.4.1 Configuration de l'application web pour la gestion des exceptions

La configuration d'une application web pour la gestion des exceptions se fait dans son fichier [web.xml] :

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


2. <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">
3. <display-name>intro-05</display-name>
4. ...
5. <servlet>
6. <servlet-name>Faces Servlet</servlet-name>
7. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
8. <load-on-startup>1</load-on-startup>
9. </servlet>
10. <servlet-mapping>
11. <servlet-name>Faces Servlet</servlet-name>
12. <url-pattern>/faces/*</url-pattern>
13. </servlet-mapping>
14. ...
15. <error-page>
16. <error-code>500</error-code>
17. <location>/exception.jsp</location>
18. </error-page>
19. <error-page>
20. <exception-type>java.lang.Exception</exception-type>

Introdution à Java EE
292/334
21. <location>/exception.jsp</location>
22. </error-page>
23.
24. </web-app>

Lignes 15-22, on trouve la définition de deux pages d'erreur. On peut avoir autant de balises <error-page> que nécessaires. La
balise <location> indique la page à afficher en cas d'erreur. Le type de l'erreur associée à la page peut être défini de deux façons :

• par la balise <exception-type> qui définit le type Java de l'exception gérée. Ainsi la balise <error-page> des lignes 19-22
indique que si le conteneur de servlets récupère une exception de type [java.lang.Exception] ou dérivé (ligne 20) au cours
de l'exécution de l'application, alors il doit faire afficher la page [/exception.jsp] (ligne 21). En prenant ici, le type
d'exception le plus générique [java.lang.Exception], on s'assure de gérer toutes les exceptions.
• par la balise <error-code> qui définit un code Http d'erreur. Par exemple, si un navigateur demande l'Url
[http://machine:port/contexte/P] et que la page P n'existe pas dans l'application contexte, celle-ci n'intervient pas dans
la réponse. C'est le conteneur de servlets qui génère cette réponse en envoyant une page d'erreur par défaut. La première
ligne du flux Http de sa réponse contient un code d'erreur 404 indiquant que la page P demandée n'existe pas. On peut
vouloir générer une réponse qui par exemple respecte la charte graphique de l'application ou qui donne des liens pour
résoudre le problème. Dans ce cas, on utilisera une balise <error-page> avec une balise <error-code>404</error-
code>.
Ci-dessus, le code Http d'erreur 500 est le code renvoyé en cas de " plantage " de l'application. C'est le code qui serait
renvoyé si une exception remontait jusqu'au conteneur de servlets. Les deux balises <error-page> des lignes 15-22 sont
donc probablement redondantes. On les a mises toutes les deux pour illustrer les deux façons de gérer une erreur.

Nous sommes dans un tutoriel Jsf et pourrions vouloir afficher une page Jsf plutôt qu'une page Jsp comme page d'erreur. Il faudrait
alors écrire dans [web.xml] :

1. <error-page>
2. <error-code>500</error-code>
3. <location>/faces/exception.jsp</location>
4. </error-page>
5. <error-page>
6. <exception-type>java.lang.Exception</exception-type>
7. <location>/faces/exception.jsp</location>
8.</error-page>

Lors d'une erreur, la page /faces/exception.jsp serait affichée. Son Url fait qu'elle serait traitée par le moteur Jsf. David Geary et Cay
HorstMann décrivent une telle solution dans leur livre Core JavaServer Faces aux éditions Sun qui a servi de support à ce
tutoriel. Seulement la solution proposée s'avère ne fonctionner que si l'exception se déclenche sur une demande GET du
navigateur. Si elle se déclenche sur un POST, alors la page d'erreur [/faces/exception.jsp] ne s'affiche pas et c'est la page d'erreur
par défaut du conteneur de servlets qui s'affiche. Je n'ai pas su résoudre ce problème malgré une recherche approfondie. Aussi
présente-t-on ici une solution avec des pages d'erreur Jsp et non Jsf. Nous allons voir que cela complique beaucoup les choses.

1.5.4.2 La simulation de l'exception

Une exception est produite artificiellement par le lien [Lancer une exception] :

Un clic sur le lien [Lancer une exception] [1] provoque l'affichage de la page [2].

Dans le code des pages [formx.jsp], le lien [Lancer une exception] est généré de la façon suivante :

Introdution à Java EE
293/334
1. <!-- liens -->
2. <h:panelGrid columns="6">
3. <h:commandLink value="1" action="form1"/>
4. ...
5. <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
6. </h:panelGrid>

Ligne 5, on voit que lors d'un clic sur le lien, la méthode [form].throwException va être exécutée. Celle-ci est la suivante :

1. public String throwException() throws java.lang.Exception{


2. throw new Exception("Exception test");
3.}

On y lance une exception de type [java.lang.Exception]. Elle va remonter jusqu'au conteneur de servlets qui va alors afficher la page
[/exception.jsp]. L'Url de celle-ci n'est pas préfixée par /faces. C'est le moteur Jsp qui va la traiter et non le moteur Jsf.

1.5.4.3 Les informations liées à une exception

Lorsqu'une exception remonte jusqu'au conteneur de servlets, celui-ci va faire afficher la page d'erreur correspondante en
transmettant à celles-ci des informations sur l'exception. Celles-ci sont placées comme nouveaux attributs de la requête en cours
de traitement. La requête d'un navigateur et la réponse qu'il va recevoir sont encapsulées dans des objets Java de type
[HttpServletRequest request] et [HttpServletResponse response]. Ces objets sont disponibles à tous les étapes de traitement de la
requête du navigateur.

request, response
Conteneur de
Navigateur servlets t1 t2 tn

A réception de la requête Http du navigateur, le conteneur de servlets encapsule celle-ci dans l'objet Java [HttpServletRequest
request] et crée l'objet [HttpServletResponse response] qui va permettre de générer la réponse. Dans cet objet, on trouve
notamment le canal tcp-ip à utiliser pour le flux Http de la réponse. Tous les couches t1, t2, ..., tn qui vont intervenir dans le
traitement de l'objet request, ont accès à ces deux objets. Chacune d'elles peut avoir accès aux éléments de la requête initiale request, et
préparer la réponse en enrichissant l'objet response. Une couche de localisation pourra par exemple fixer la localisation de la réponse par
la méthode response.setLocale(Locale l).

Les différentes couches ti peuvent se passer des informations via l'objet request. Celui-ci a un dictionnaire d'attributs, vide à sa
création, qui peut être enrichi par les couches de traitement successives. Celles-ci peuvent mettre dans les attributs de l'objet request
des informations nécessaires à la couche de traitement suivante. Il existe deux méthodes pour gérer les attributs de l'objet request :

void setAttribute(String s, Object o) qui permet d'ajouter aux attributs un objet o identifié par la chaîne s
Object getAttribute(String s) qui permet d'obtenir l'attribut o identifié par la chaîne s

Lorsqu'une exception remonte jusqu'au conteneur de servlets, ce dernier met les attributs suivants dans la requête en cours de
traitement :

clé valeur
javax.servlet.error.status_code le code d'erreur Http qui va être renvoyé au client
javax.servlet.error.message le message lié à l'exception
javax.servlet.error.exception_type le type Java de l'exception
javax.servlet.error.request_uri l'Url demandée lorsque l'exception s'est produite
javax.servlet.error.servlet_name la servlet qui traitait la requête lorsque l'exception s'est produite

Nous utiliserons ces attributs de la requête dans la page [exception.jsp] pour les afficher.

1.5.4.4 La page d'erreur [exception.jsp]

Introdution à Java EE
294/334
Son contenu est le suivant :

1. <%@page contentType="text/html" pageEncoding="UTF-8"%>


2. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
3. <%@taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt_rt" %>
4. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
5. "http://www.w3.org/TR/html4/loose.dtd">
6.
7. <%
8. // on change le code Http pour IE
9. response.setStatus(HttpServletResponse.SC_OK);
10. %>
11.
12. <fmt_rt:setLocale value="${sessionScope['codeLocale']}"/>
13. <fmt_rt:setBundle basename="messages"/>
14. <html>
15. <head>
16. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
17. <title>JSF</title>
18. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
19. </head>
20. <body background="<c:url value="/ressources/standard.jpg"/>">
21.
22. <h3><fmt_rt:message key="exception.header"/></h3>
23. <table border="1">
24. <tr>
25. <td><fmt_rt:message key="exception.httpCode"/></td>
26. <td><c:out value="${requestScope['javax.servlet.error.status_code']}"/></td>
27. </tr>
28. <tr>
29. <td><fmt_rt:message key="exception.message"/></td>
30. <td><c:out value="${requestScope['javax.servlet.error.message']}"/></td>
31. </tr>
32. <tr>
33. <td><fmt_rt:message key="exception.exceptionClassName"/></td>
34. <td><c:out value="${requestScope['javax.servlet.error.exception_type']}"/></td>
35. </tr>
36. <tr>
37. <td><fmt_rt:message key="exception.requestUri"/></td>
38. <td><c:out value="${requestScope['javax.servlet.error.request_uri']}"/></td>
39. </tr>
40. <tr>
41. <td><fmt_rt:message key="exception.servletName"/></td>
42. <td><c:out value="${requestScope['javax.servlet.error.servlet_name']}"/></td>
43. </tr>
44. </table>
45. <!-- liens -->
46. <table>
47. <tr>
48. <td><a href="<c:url value="/faces/form1.jsp"/>">1</td>
49. <td><a href="<c:url value="/faces/form2.jsp"/>">2</td>
50. <td><a href="<c:url value="/faces/form3.jsp"/>">3</td>
51. <td><a href="<c:url value="/faces/form4.jsp"/>">4</td>
52. </tr>
53. </table>
54. </body>
55. </html>

1.5.4.4.1 Localisation de la page


Nous souhaitons avoir une page d'erreur localisée, c.a.d. capable de s'afficher en plusieurs langues. Parce que [exception.jsp] n'est plus
traitée par le moteur Jsf, nous utilisons une des composantes de la bibliothèque JSTL (Java Standard Tag Library), la composante
<fmt> qui permet de gérer l'internationalisation des pages Jsp. Cette bibliothèque de balises est déclarée ligne 3. Il en existe trois
variantes :

Introdution à Java EE
295/334
1.<taglib>
2. <tlib-version>1.0</tlib-version>
3. <jsp-version>1.2</jsp-version>
4. <short-name>fmt_rt</short-name>
5. <uri>http://java.sun.com/jstl/fmt_rt</uri>
6. <display-name>JSTL fmt RT</display-name>
7.<description>JSTL 1.0 i18n-capable formatting
library</description>

• en [1] : l'archive de la bibliothèque JSTL


• en [2] : les trois fichiers de définition des balises <fmt> qui permettent l'internationalisation.
• en [3] : le début du fichier de définition [fmt-1_0-rt.tld]

Pour fixer la langue d'une page Jsp, on peut utiliser la balise :

<fmt:setLocale value="fr"/>

où value est une chaîne de type Code_Langue_CodePays_CodeVariante. La TLD de la bibliothèque <fmt> indique que l'attribut value
doit être une constante et non pas une expression de type ${expression} évaluée au moment du traitement de la page Jsp. Or ici, nous
voulons utiliser un code langue placé dans la session du client et non pas une constante. Pour pouvoir faire cela, nous sommes
amenés à utiliser la variante [fmt-1_0-rt.tld] de la bibliothèque <fmt>. L'Uri de celle-ci est définie ligne 5 en [3]. Aussi dans la page
[exception.jsp], la déclaration de la bibliothèque <fmt> est-elle faite de la façon suivante :

<%@taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt_rt" %>

Commentons maintenant les lignes de [exception.jsp] liées à l'internationalisation :

• ligne 7 : on fixe la langue de la page. Le code langue est récupéré dans la session. Nous reviendrons sur ce point
ultérieurement.
• ligne 8 : on fixe le fichier des messages. Le mécanisme est le même que celui expliqué pour Jsf. On notera qu'il y a un
ordre dans les balises <fmt_rt:setLocale> et <fmt_rt:setBundle>. La langue doit être fixée avant le fichier des messages.
• ensuite pour afficher un message, on utilise la balise <fmt_rt:message key="clé du message"/>.

Les différents messages nécessaires à la page Jsp [exception.jsp] ont été rajoutés aux fichiers de messages déjà existants :

[messages_fr.properties]

exception.header=L'exception suivante s'est produite


exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.exceptionClassName=Classe de l'exception
exception.requestUri=Url demand\u00E9e lors de l'erreur
exception.servletName=Nom de la servlet demand\u00E9e lorsque l'erreur s'est produite

[messages_en.properties]

exception.header=The following error occurred


exception.httpCode=HTTP error code
exception.message=Exception message
exception.exceptionClassName=Exception class

Introdution à Java EE
296/334
exception.requestUri=Url requested when error occurred
exception.servletName=Servlet requested when error occurred

1.5.4.4.2 Les expressions de la page


Dans une page Jsf, les expressions avaient la forme #{expression}. Dans Jsp, elles ont la forme ${expression}. Les deux formes ne sont
pas équivalentes. Dans une page Jsp, les expressions ne sont évaluées qu'à l'affichage de la page. Dans une page Jsf, elles sont
évaluées également au moment du traitement de la page lorsque celle-ci est postée. Dans #{expression}, expression peut alors désigner
le champ qui va recevoir une valeur postée.

Dans la chaîne de traitement de la requête du client, la page Jsp est normalement le dernier maillon de la chaîne :

request, response
Conteneur de
Navigateur servlets t1 t2 jsp

Tous les éléments de la chaîne sont des classes Java, y compris la page Jsp. Celle-ci est en effet transformée en servlet par le
conteneur de servlets, c.a.d. en une classe Java normale. Plus précisément, la page Jsp est transformée en code Java qui s'exécute au
sein de la méthode suivante :

1. public void _jspService(HttpServletRequest request, HttpServletResponse response)


2. throws java.io.IOException, ServletException {
3.
4. JspFactory _jspxFactory = null;
5. PageContext pageContext = null;
6. HttpSession session = null;
7. ServletContext application = null;
8. ServletConfig config = null;
9. JspWriter out = null;
10. Object page = this;
11. JspWriter _jspx_out = null;
12. PageContext _jspx_page_context = null;
13. ...
14. ...code de la page Jsp
15.

A partir de la ligne 14, on trouvera le code Java image de la page Jsp. Ce code va disposer d'un certain nombre d'objets initialisés
par la méthode _jspService, ligne 1 ci-dessus :

• ligne 1 : HttpServletRequest request : la requête en cours de traitement


• ligne 1 : HttpServletResponse response : la réponse qui va être envoyée au client
• ligne 7 : ServletContext application : un objet qui représente l'application web elle-même. Comme l'objet request, l'objet
application peut avoir des attributs. Ceux-ci sont partagés par toutes les requêtes de tous les clients. Ce sont en général des
attributs en lecture seule.
• ligne 6 : HttpSession session : représente la session du client. Comme les objets request et application, l'objet session peut
avoir des attributs. Ceux-ci sont partagés par toutes les requêtes d'un même client. Nous utiliserons cet objet pour y placer
le code langue de la page Jsp et des pages Jsf.
• ligne 9 : JspWriter out : un flux d'écriture vers le navigateur client. Cet objet est utile pour le débogage d'une page Jsp.
Tout ce qui est écrit via out.println(texte) sera affiché dans le navigateur client.

Lorsque dans la page Jsp, on écrit ${expression}, expression peut être la clé d'un attribut des objets request, session ou
application ci-dessus. L'attribut correspondant est cherché successivement dans ces trois objets. Ainsi ${clé} est évaluée de la
façon suivante :

1. request.getAttribute(clé)
2. session.getAttribute(clé)
3. application.getAttribute(clé)

Introdution à Java EE
297/334
Dès qu'une valeur non null est obtenue, l'évaluation de ${clé} est arrêtée. On peut vouloir être plus précis en indiquant le contexte
dans lequel l'attribut doit être cherché :

• ${requestScope['clé']} pour chercher l'attribut dans l'objet request


• ${sessionScope['clé']} pour chercher l'attribut dans l'objet session
• ${applicationScope['clé']} pour chercher l'attribut dans l'objet application

C'est ce qui a été fait dans la page [exception.jsp] page . Les attributs utilisés sont les suivants :

clé domaine valeur


codeLocale le code langue du client – est mis dans la session lorsque l'utilisateur utilise
session
les liens de choix de langue
javax.servlet.error.status_code cf paragraphe 1.5.4.3, page 294.
request
javax.servlet.error.message idem
idem
javax.servlet.error.exception_type idem
idem
javax.servlet.error.request_uri idem
idem
javax.servlet.error.servlet_name idem
idem

Pour terminer, on notera le début du code de la page [exception.jsp] :

1. ...
2. <%
3. // on change le code Http pour IE
4. response.setStatus(HttpServletResponse.SC_OK);
5. %>
6.
7. <fmt_rt:setLocale value="${sessionScope['codeLocale']}"/>
8. ...

Le navigateur Internet Explorer n'affiche pas les réponses HTML accompagnées d'un code erreur Http 500. Mozilla Firefox lui les
affiche. Afin que la page d'exception soit affichée par les deux navigateurs, on change le code erreur de la réponse Http en ligne 4.
Le code HttpServletResponse.SC_OK est le code 200 qui indique que la page demandée a été trouvée. Dans ce cas, les navigateurs
vont l'afficher.

1.5.5 Localisation des pages Jsf

Revenons sur la page d'exception affichée :

Lorsqu'on utilise les liens de [2] pour revenir sur l'une des pages [formx.jsp], on veut que celle-ci apparaisse dans la langue choisie
par l'utilisateur en [3]. Ce problème de l'internationalisation des pages Jsf a été traité dès les premiers exemples de ce document. La
solution alors imaginée ne fonctionne plus lorsque, de la page [exception.jsp] [2], on revient à l'une des pages Jsf [formx.jsp]. Il est
possible que lorsqu'une langue est choisie en [3], ce choix soit codé dans le ViewState de la page Jsf envoyée au client et récupéré au
POST qui suit, ce qui permet de le propager à la nouvelle page affichée. Ce n'est qu'une hypothèse non vérifiée. Dans ce cas, cela
expliquerait que ce choix soit perdu après affichage de la page Jsp [exception.jsp]. N'étant pas une page Jsf, la page n'a pas le champ
caché ViewState et donc le choix de la langue se perdrait à ce moment là.

Introdution à Java EE
298/334
Pour palier à ce problème de perte du choix de la langue, on décide de mémoriser le code langue choisi, dans la session du client.
Cela se fait dans les méthodes chargées de gérer les liens de choix de langue [3] :

1. package utils;
2.
3. import java.util.Locale;
4. import javax.faces.context.FacesContext;
5. import javax.servlet.http.HttpServletRequest;
6. import javax.servlet.http.HttpSession;
7.
8. public class ChangeLocale {
9.
10. /** Creates a new instance of ChangeLocale */
11. public ChangeLocale() {
12. }
13.
14. public String setFrenchLocale(){
15. changeLocale("fr");
16. return null;
17. }
18.
19. public String setEnglishLocale(){
20. changeLocale("en");
21. return null;
22. }
23.
24. private void changeLocale(String codeLocale){
25. // on mémorise la locale dans la session
26. HttpSession
session=((HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest()).
getSession();
27. session.setAttribute("codeLocale",codeLocale);
28. }
29. }

Lignes 24-28, la méthode privée changeLocale mémorise dans la session du client, un attribut de clé codeLocale et de valeur le
code langue choisi par l'utilisateur.

Nous avons déjà vu comment la page [exception.jsp] utilisait cet attribut :

<fmt_rt:setLocale value="${sessionScope['codeLocale']}"/>
<fmt_rt:setBundle basename="messages"/>

Les pages Jsf [formx.jsp] l'utilisent, elles, de la façon suivante :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3. <%@page import="java.util.Locale,javax.faces.context.FacesContext"%>
4. ...
5. <%
6. // on récupère la locale dans la session
7. String codeLocale=(String)session.getAttribute("codeLocale");
8. if(codeLocale==null){
9. codeLocale="fr";
10. }
11. // on fixe la locale
12. FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale(codeLocale));
13. %>
14.
15. <f:view>
16. <html>
17. ...

• lignes 5-13 : du code Java exécuté avant traitement de la vue <f:view> (ligne 15) de la page Jsf
• ligne 7 : on récupère l'attribut de clé codeLocale dans la session
• lignes 8-10 : si cet attribut n'existe pas, on fixe la langue au français
• ligne 12 : on fixe la langue de la vue Jsf qui va être affichée. On retrouve là le code qui auparavant était dans la méthode
changeLocale de la classe ChangeLocale.

1.5.6 Conclusion

Introdution à Java EE
299/334
La gestion des exceptions sur cet exemple s'est avérée être complexe du fait qu'elle n'a pas pu être traitée par une page Jsf. On
rappelle que cela est du au fait que, pour une cause non élucidée, la page Jsf d'une exception se produisant lors d'un POST n'est pas
affichée. Si ce problème est résolu dans l'avenir, la gestion des exceptions s'en trouvera simplifiée.

1.6 Exemple n° 6
Thème : Validation et conversion des saisies d'un formulaire

1.6.1 L'application

L'application présente un formulaire de saisies. A la validation de celui-ci, le même formulaire est renvoyé avec d'éventuels
messages d'erreurs si les saisies ont été trouvées incorrectes.

Introdution à Java EE
300/334
2

1.6.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

3
1

Le projet [intro-06] repose de nouveau sur une unique page [form.jsp] [1] et son modèle [Form.java] [2]. Il continue à utiliser des
messages tirés de [messages.properties] mais uniquement en français [3]. L'option de changement de langue n'est pas offerte.

1.6.3 La page [form.jsp] et son modèle [Form.java]

La page [form.jsp] est la suivante :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>
3.
4. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
5. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
6. <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
7.

Introdution à Java EE
301/334
8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
9. "http://www.w3.org/TR/html4/loose.dtd">
10.
11. <html>
12. <head>
13. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
14. <title>démo JSF</title>
15. <link href="styles.css" rel="stylesheet" type="text/css"/>
16. </head>
17. <body background="<c:url value="/ressources/standard.jpg"/>">
18. <f:view>
19. <h2><h:outputText value="#{msg['form.titre']}"/></h2>
20. <h:form id="formulaire">
21. <h:messages globalOnly="true"/>
22. <h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
23. <!-- headers -->
24. <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
25. <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
26. <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
27. <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
28. <!-- ligne 1 -->
29. <h:outputText value="#{msg['saisie1.prompt']}"/>
30. <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
31. <h:message for="saisie1" styleClass="error"/>
32. <h:outputText value="#{form.saisie1}"/>
33. <!-- ligne 2 -->
34. <h:outputText value="#{msg['saisie2.prompt']}" />
35. <h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
36. <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
37. <h:outputText value="#{form.saisie2}"/>
38. <!-- ligne 3 -->
39. <h:outputText value="#{msg['saisie3.prompt']}" />
40. <h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie" required="true"
requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
41. <h:message for="saisie3" styleClass="error"/>
42. <h:outputText value="#{form.saisie3}"/>
43. <!-- ligne 4 -->
44. <h:outputText value="#{msg['saisie4.prompt']}" />
45. <h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true"
requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"
validatorMessage="#{msg['saisie4.error']}">
46. <f:validateLongRange minimum="1" maximum="10" />
47. </h:inputText>
48. <h:message for="saisie4" styleClass="error"/>
49. <h:outputText value="#{form.saisie4}"/>
50. <!-- ligne 5 -->
51. ...
52. <!-- ligne 6 -->
53. ...
54. <!-- ligne 7 -->
55. ...
56. <!-- ligne 8 -->
57. ...
58. <!-- ligne 9 -->
59. ...
60. <!-- ligne 10 -->
61. ...
62. <!-- ligne 11 -->
63. ...
64. <!-- ligne 12 -->
65. ...
66. </h:panelGrid>
67. <!-- boutons de commande -->
68. <h:panelGrid columns="2">
69. <h:commandButton value="#{msg.submit}" action="#{form.submit}"/>
70. <h:commandButton value="#{msg.cancel}" immediate="true" action="#{form.cancel}"/>
71. </h:panelGrid>
72. </h:form>
73. </f:view>
74. </body>
75. </html>

La principale nouveauté vient de la présence de balises :


• pour afficher des messages d'erreurs <h:messages>(ligne 21), <h:message> (lignes 31, 36, ...)
• qui posent des contraintes de validité sur les saisies <f:validateLongRange> (ligne 46), <f:validateDoubleRange>,
<f:validateLength>
• qui définissent un convertisseur entre la saisie et son modèle comme <f:convertDateTime>.

Le modèle de cette page est la classe [Form.java] suivante :

Introdution à Java EE
302/334
1. package forms;
2.
3. ...
4. public class Form {
5.
6.
7. public Form() {
8. }
9.
10. // saisies
11. private Integer saisie1 = 0;
12. private Integer saisie2 = 0;
13. private Integer saisie3 = 0;
14. private Integer saisie4 = 0;
15. private Double saisie5 = 0.0;
16. private Double saisie6 = 0.0;
17. private Boolean saisie7 = true;
18. private Date saisie8 = new Date();
19. private String saisie9 = "";
20. private Integer saisie10 = 0;
21. private Integer saisie11 = 0;
22. private Integer saisie12 = 0;
23. private String errorSaisie11 = "";
24. private String errorSaisie12 = "";
25.
26. // actions
27. public String submit() {
28. ...
29. }
30.
31. public String cancel() {
32. ...
33. }
34.
35. // validateurs
36. public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
37. ....
38. }
39.
40.
41. // getters et setters
42. ...
43. }

La nouveauté ici est que les champs du modèle ne sont plus uniquement de type String mais de types divers.

1.6.4 L'environnement de l'application

Nous donnons ici la teneur des fichiers qui configurent l'application sans donner d'explications particulières. Ces fichiers permettent
de mieux comprendre ce qui suit.

[faces-config.xml]

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


2. <!-- =========== FULL CONFIGURATION FILE ================================== -->
3. <faces-config version="1.2" 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_1_2.xsd">
4. <application>
5. <resource-bundle>
6. <base-name>
7. messages
8. </base-name>
9. <var>msg</var>
10. </resource-bundle>
11. </application>
12. <managed-bean>
13. <managed-bean-name>form</managed-bean-name>
14. <managed-bean-class>forms.Form</managed-bean-class>
15. <managed-bean-scope>request</managed-bean-scope>
16. </managed-bean>
17. </faces-config>

[messages_fr_FR.properties]

1. form.titre=Jsf - validations et conversions

Introdution à Java EE
303/334
2. saisie1.prompt=1-Nombre entier de type int
3. saisie2.prompt=2-Nombre entier de type int
4. saisie3.prompt=3-Nombre entier de type int
5. data.required=Vous devez entrer une donnée
6. integer.required=Vous devez entrer un nombre entier
7. saisie4.prompt=4-Nombre entier de type int dans l'intervalle [1,10]
8. saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
9. saisie5.prompt=5-Nombre réel de type double
10. double.required=Vous devez entrer un nombre
11. saisie6.prompt=6-Nombre réel>=0 de type double
12. saisie6.error=6-Vous devez entrer un nombre >=0
13. saisie7.prompt=7-Booléen
14. saisie7.error=7-Vous devez entrer un booléen
15. saisie8.prompt=8-Date au format jj/mm/aaaa
16. saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
17. date.required=Vous devez entrer une date
18. saisie9.prompt=9-Chaîne de 4 caractères
19. saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement
20. submit=Valider
21. cancel=Annuler
22. saisie.type=Type de la saisie
23. saisie.champ=Champ de saisie
24. saisie.erreur=Erreur de saisie
25. bean.valeur=Valeurs du modèle du formulaire
26. saisie10.prompt=10-Nombre entier de type int <1 ou >7
27. saisie10.incorrecte=10-Saisie n° 10 incorrecte
28. saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
29. saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
30. saisies11et12.incorrectes_detail=La propriété saisie11+saisie12=10 n'est pas vérifiée
31. saisie11.prompt=11-Nombre entier de type int
32. saisie12.prompt=12-Nombre entier de type int
33. error.sign="!"
34. error.sign_detail="!"

1.6.5 Les différentes saisies du formulaire

Nous étudions maintenant successivement les différentes saisies du formulaire.

1.6.5.1 Saisies 1 à 4 : saisie d'un nombre entier


La page [form.jsp] présente la saisie 1 sous la forme suivante :

1. <!-- ligne 1 -->


2. <h:outputText value="#{msg['saisie1.prompt']}"/>
3. <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
4. <h:message for="saisie1" styleClass="error"/>
5. <h:outputText value="#{form.saisie1}"/>

Le modèle form.saisie1 est défini comme suit dans [Form.java] :

1.private Integer saisie1 = 0;

Sur un GET du navigateur , la page [form.jsp] associée à son modèle [Form.java] produit visuellement ce qui suit :

1 2 3 4

• la ligne 2 produit [1]


• la ligne 3 produit [2]
• la ligne 4 produit [3]
• la ligne 5 produit [4]

Supposons que la saisie suivante soit faite puis validée :

Introdution à Java EE
304/334
On obtient alors le résultat suivant dans le formulaire renvoyé par l'application :

1 2 3

• en [1], la saisie erronée


• en [2], le message d'erreur le signalant
• en [3], on voit que la valeur du champ Integer saisie1 du modèle n'a pas changé.

Expliquons ce qui s'est passé. Pour cela revenons au cycle de traitement d'une page Jsf :

A B C

D2

E D
F

Nous examinons ce cycle pour le composant :

<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>

et son modèle :

private Integer saisie1 = 0;

• en [A], la page [form.jsp] envoyée lors du GET du navigateur est restaurée. En [A], la page est telle que l'utilisateur l'a
reçue. Le composant id="saisie1" retrouve sa valeur initiale "0".
• en [B], les composants de la page reçoivent pour valeurs, les valeurs postées par le navigateur. En [B], la page est telle que
l'utilisateur l'a saisie et validée. Le composant id="saisie1" reçoit pour valeur, la valeur postée "x".
• en [C], si la page contient des validateurs et des convertisseurs explicites, ceux-ci sont exécutés. Des convertisseurs
implicites sont également exécutés si le type du champ associé au composant n'est pas de type String. C'est le cas ici, où le
champ form.saisie1 est de type Integer. Jsf va essayer de transformer la valeur "x" du composant id="saisie1" en un type
Integer. Ceci va provoquer une erreur qui va arrêter le cycle de traitement [A-F]. Cette erreur sera associée au composant
id="saisie1". Via [D2], on passe ensuite directement à la phase de rendu de la réponse. La même page [form.jsp] est
renvoyée.
• la phase [D] n'a lieu que si tous les composants d'une page ont passé la phase de conversion / validation. C'est dans cette
phase, que la valeur du composant id="saisie1" sera affectée à son modèle form.saisie1.

Si la phase [C] échoue, le code suivant est alors exécuté de nouveau :

1. <!-- ligne 1 -->


2. <h:outputText value="#{msg['saisie1.prompt']}"/>
3. <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
4. <h:message for="saisie1" styleClass="error"/>
5. <h:outputText value="#{form.saisie1}"/>

et affiche :

Introdution à Java EE
305/334
1 2 3

Le message affiché en [2] vient de la ligne 4 de [form.jsp]. La balise <h:message for="idComposant"/> affiche le message
d'erreur associé au composant désigné par l'attribut for, si erreur il y a. Le message affiché en [2] est standard et se trouve dans le
fichier [Messages.properties] de l'archive [jsf-impl.jar] :

En [2], on voit que le fichier des messages existe en plusieurs variantes. Leur examen montre cependant que toutes ces variantes
contiennent des messages en anglais. Examinons le contenu de [Messages.properties] :

1. ..
2. # ==============================================================================
3. # SPECIFICATION DEFINED MESSAGES
4. # ==============================================================================
5.
6.
7. # ==============================================================================
8. # Component Errors
9. # ==============================================================================
10.
11. javax.faces.component.UIInput.CONVERSION={0}: Conversion error occurred.
12. javax.faces.component.UIInput.REQUIRED={0}: Validation Error: Value is required.
13. ...
14.
15. # ==============================================================================
16. # Converter Errors
17. # ==============================================================================
18. javax.faces.converter.BigDecimalConverter.DECIMAL={2}: ''{0}'' must be a signed decimal number.
19. javax.faces.converter.BigDecimalConverter.DECIMAL_detail={2}: ''{0}'' must be a signed decimal
number consisting of zero or more digits, that may be followed by a decimal point and fraction.
Example: {1}
20. ...
21. javax.faces.converter.IntegerConverter.INTEGER={2}: ''{0}'' must be a number consisting of one or
more digits.
22. javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' must be a number between
-2147483648 and 2147483647 Example: {1}
23. ...
24.
25. # ==============================================================================
26. # Validator Errors
27. # ==============================================================================
28.
29. javax.faces.validator.NOT_IN_RANGE=Validation Error: Specified attribute is not between the
expected values of {0} and {1}.
30. javax.faces.validator.DoubleRangeValidator.MAXIMUM={1}: Validation Error: Value is greater than
allowable maximum of "{0}"
31. ...
32.
33. # ==============================================================================
34. # IMPLEMENTATION DEFINED MESSAGES
35. # ==============================================================================
36.

Introdution à Java EE
306/334
37. com.sun.faces.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK=ApplicationAssociate ctor not called in
same callstack as ConfigureListener.contextInitialized().
38. com.sun.faces.APPLICATION_ASSOCIATE_EXISTS=ApplicationAssociate already exists for this webapp.
39. ...

Le fichier contient environ 180 messages divisés en catégories :

• erreurs sur un composant, ligne 8


• erreurs de conversion entre un composant et son modèle, ligne 16
• erreurs de validation lorsque des validateurs sont présents dans la page, ligne 26
• erreurs générales, ligne 34

L'erreur qui s'est produite sur le composant id="saisie1" est de type erreur de conversion d'un type String vers un type Integer. Le message
d'erreur associé est celui de la ligne 22 du fichier des messages.

javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' must be a number between -2147483648


and 2147483647 Example: {1}

Le message d'erreur qui a été affiché est reproduit ci-dessous :

1 2 3

On voit que dans le message :

• le paramètre {2} a été remplacé par l'identifiant du composant pour lequel l'erreur de conversion s'est produite
• la paramètre {0} a été remplacé par la saisie faite en [1] pour le composant
• le paramètre {1} a été remplacé par le nombre 9346.

La plupart des messages liés aux composants ont deux versions : une version résumée (summary) et une version détaillée (detail).
C'est le cas lignes 21-22 :

1. javax.faces.converter.IntegerConverter.INTEGER={2}: ''{0}'' must be a number consisting of one or


more digits.
2. javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' must be a number between
-2147483648 and 2147483647 Example: {1}

Le message ayant la clé _detail (ligne 2) est le message dit détaillé. L'autre est le message dit résumé. La balise <h:message> affiche
par défaut le message détaillé. Ce comportement peut être changé par les attributs showSummary et showDetail. C'est ce qui est
fait pour le composant d'id saisie2 :

1. <!-- ligne 2 -->


2. <h:outputText value="#{msg['saisie2.prompt']}" />
3. <h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
4. <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
5. <h:outputText value="#{form.saisie2}"/>

Ligne 2, le composant saisie2 est lié au champ form.saisie2 suivant :

private Integer saisie2 = 0;

Le résultat obenu est le suivant :

• en [1], le message détaillé, en [2], le message résumé

Introdution à Java EE
307/334
La balise <h:messages> affiche sous la forme d'une liste tous les messages d'erreurs résumés de tous les composants ainsi que les
messages d'erreurs non liés à un composant. Là encore des attributs peuvent changer ce comportement par défaut :

• showDetail : true / false pour demander ou non les messages détaillés


• showSummary : true / false pour demander ou non les messages résumés
• globalOnly : true / false pour demander à ne voir ou non que les messages d'erreurs non liées à des composants. Un tel
message pourrait, par exemple, être créé par le développeur.

Le message d'erreur associé à une conversion peut être changé de diverses façons. Tout d'abord, on peut indiquer à l'application
d'utiliser un autre fichier de messages. Cette modification se fait dans [faces-config.xml] :

1. <faces-config ...">
2. <application>
3. <resource-bundle>
4. <base-name>
5. messages
6. </base-name>
7. <var>msg</var>
8. </resource-bundle>
9. <message-bundle>messages</message-bundle>
10. </application>
11. ...
12. </faces-config>

Les lignes 3-8 définissent un fichier des messages mais ce n'est pas celui-ci qui est utilisé par les balises <h:message> et
<h:messages>. Il faut utiliser la balise <message-bundle> de la ligne 9 pour le définir. La ligne 9 indique aux balises
<h:message(s)> que le fichier [messages.properties] doit être exploré avant le fichier [javax.faces.Messages.properties]. Ainsi si on
ajoute les lignes suivantes au fichier [messages_fr.properties] :

1. ...
2. # conversions
3. javax.faces.converter.IntegerConverter.INTEGER={2}: ''{0}'' doit être un nombre constitué d'un ou
plusieurs chiffres
4. javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' doit être un nombre entre
-2147483648 et 2147483647 Exemple: {1}

l'erreur renvoyée pour les composants saisie1 et saisie2 devient :

L'autre façon de modifier le message d'erreur de conversion est d'utiliser l'attribut converterMessage du composant comme ci-
dessous pour le composant saisie3 :

1. <!-- ligne 3 -->


2. <h:outputText value="#{msg['saisie3.prompt']}" />
3. <h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['integer.required']}"/>
4. <h:message for="saisie3" styleClass="error"/>
5. <h:outputText value="#{form.saisie3}"/>

Le composant saisie3 est lié au champ form.saisie3 suivant :

private Integer saisie3 = 0;

• ligne 3, l'attribut converterMessage fixe explicitement le message à afficher lors d'une erreur de conversion.
• ligne 3, l'attribut required="true" indique que la saisie est obligatoire. Le champ ne peut rester vide. Un champ est
considéré comme vide s'il ne contient aucun caractère ou s'il contient une suite d'espaces. Là encore, il existe dans
[javax.faces.Messages.properties] un message par défaut :

1. # ==============================================================================
2. # Component Errors

Introdution à Java EE
308/334
3. # ==============================================================================
4.
5. ...
6. javax.faces.component.UIInput.REQUIRED={0}: Validation Error: Value is required.

L'attribut requiredMessage permet de remplacer ce message par défaut. Si le fichier [messages.properties] contient les
messages suivants :

1. ...
2. data.required=Vous devez entrer une donnée
3. integer.required=Vous devez entrer un nombre entier
4. ...
5. # conversions
6. javax.faces.converter.IntegerConverter.INTEGER={2}: ''{0}'' doit être un nombre constitué d'un ou
plusieurs chiffres
7. javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' doit être un nombre entre
-2147483648 et 2147483647 Exemple: {1}

On pourra obtenir le résultat suivant :

ou encore celui-ci :

Vérifier qu'une saisie correspond bien à un nombre entier n'est pas toujours suffisant. Il faut parfois vérifier que le nombre saisi
appartient à un intervalle donné. On utilise alors un validateur. La saisie n° 4 en donne un exemple. Son code dans [form.jsp] est le
suivant :

1. <!-- ligne 4 -->


2. <h:outputText value="#{msg['saisie4.prompt']}" />
3. <h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
4. <f:validateLongRange minimum="1" maximum="10" />
5. </h:inputText>
6. <h:message for="saisie4" styleClass="error"/>

Ligne 3, le composant saisie4 est lié au modèle form.saisie4 suivant :

private Integer saisie4 = 0;

Lignes 3-5, la balise <h:inputText> a une balise enfant <f:validateLongRange> qui admet deux attributs facultatifs minimum
et maximum. Cette balise, appelée également validateur, permet d'ajouter une contrainte à la valeur de la saisie : ce doit être non
seulement un entier, mais un entier dans l'intervalle [minimum, maximum] si les deux attributs minimum et maximum sont présents,
supérieure ou égale à minimum si seul l'attribut minimum est présent, inférieure ou égale à maximum si seul l'attribut maximum est
présent. Le validateur <f:validateLongRange> a des messages d'erreur par défaut dans [javax.faces.Messages.properties] :

1. # ==============================================================================
2. # Validator Errors
3. # ==============================================================================

Introdution à Java EE
309/334
4.
5. ...
6. javax.faces.validator.LongRangeValidator.MAXIMUM={1}: Validation Error: Value is greater than
allowable maximum of ''{0}''
7. javax.faces.validator.LongRangeValidator.MINIMUM={1}: Validation Error: Value is less than
allowable minimum of ''{0}''
8. javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2}: Validation Error: Specified attribute
is not between the expected values of {0} and {1}.
9. javax.faces.validator.LongRangeValidator.TYPE={0}: Validation Error: Value is not of the correct
type.

De nouveau, il est possible de remplacer ces messages par d'autres. Il existe un attribut validatorMessage qui permet de définir un
message spécifique pour le composant. Ainsi avec le code Jsf suivant :

1. <h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie"


required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
2. <f:validateLongRange minimum="1" maximum="10" />
3. </h:inputText>

et le message suivant dans [messages.properties] :

saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]

on obtient le résultat suivant :

1.6.5.2 Saisies 5 et 6 : saisie d'un nombre réel


La saisie des nombres réels obéit à des règles similaires à celles de la saisie des nombres entiers. Le code Jsp des saisies 5 et 6 est le
suivant :

1. <!-- ligne 5 -->


2. <h:outputText value="#{msg['saisie5.prompt']}" />
3. <h:inputText id="saisie5" value="#{form.saisie5}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['double.required']}"/>
4. <h:message for="saisie5" styleClass="error"/>
5. <h:outputText value="#{form.saisie5}"/>
6. <!-- ligne 6 -->
7. <h:outputText value="#{msg['saisie6.prompt']}"/>
8. <h:inputText id="saisie6" value="#{form.saisie6}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['double.required']}" validatorMessage="#{msg['saisie6.error']}">
9. <f:validateDoubleRange minimum="0.0"/>
10. </h:inputText>
11. <h:message for="saisie6" styleClass="error"/>
12. <h:outputText value="#{form.saisie6}"/>

Les éléments du modèle [Form.java] liés aux composants saisie5 et saisie6 :

private Double saisie5 = 0.0;


private Double saisie6 = 0.0;

Les messages d'erreurs associés aux convertisseurs et validateurs des composants saisie5 et saisie6, dans [messages.properties] :

1. double.required=Vous devez entrer un nombre


2. saisie6.error=6-Vous devez entrer un nombre >=0

Introdution à Java EE
310/334
Voici un exemple d'exécution :

1.6.5.3 Saisie 7 : saisie d'un booléen


La saisie d'un booléen devrait être normalement faite avec une case à cocher. Si elle est faite avec un champ de saisie, la chaîne
"true" est convertie en booléen true et tout autre chaîne en booléen false.

Le code Jsp de l'exemple :

1. <!-- ligne 7 -->


2. <h:outputText value="#{msg['saisie7.prompt']}"/>
3. <h:inputText id="saisie7" value="#{form.saisie7}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"/>
4. <h:message for="saisie7" styleClass="error"/>
5. <h:outputText value="#{form.saisie7}"/>

Le modèle du composant saisie7 :

private Boolean saisie7 = true;

Voici un exemple de saisie et sa réponse :

2 3

En [1], la valeur saisie. Par conversion, cette chaîne "x" devient le booléen false. C'est ce que montre [2]. La valeur [3] du modèle
n'a pas changé. Elle ne change que lorsque toutes les conversions et validations de la page ont réussi. Ce n'était pas le cas dans cet
exemple.

1.6.5.4 Saisie 8 : saisie d'une date

La saisie d'une date est faite dans l'exemple avec le code Jsp suivant :

1. <!-- ligne 8 -->


2. <h:outputText value="#{msg['saisie8.prompt']}"/>
3. <h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie"
required="true" requiredMessage="#{msg['date.required']}"
converterMessage="#{msg['saisie8.error']}">
4. <f:convertDateTime pattern="dd/MM/yyyy"/>
5. </h:inputText>
6. <h:message for="saisie8" styleClass="error"/>
7. <h:outputText value="#{form.saisie8}">
8. <f:convertDateTime pattern="dd/MM/yyyy"/>
9. </h:outputText>

Le composant saisie8 de la ligne 3 utilise un convertisseur java.lang.String <--> java.util.Date. Le modèle form.saisie8 associé au
composant saisie8 est le suivant :

private Date saisie8 = new Date();

Le composant défini par les lignes 7-9 utilise également un convertisseur mais uniquement dans le sens java.util.Date -->
java.lang.String.

Le convertisseur <f:convertDateTime> admet divers attributs dont l'attribut pattern qui fixe la forme de la chaîne de caractères
qui doit être transformée en date où avec laquelle une date doit être affichée.

Introdution à Java EE
311/334
Lors de la demande initiale de la page [form.jsp], la ligne 8 précédente s'affiche comme suit :

1 2

Les champs [1] et [2] affichent tous deux la valeur du modèle form.saisie8 :

private Date saisie8 = new Date();

où saisie8 prend pour valeur la date du jour. Le convertisseur utilisé dans les deux cas pour l'affichage de la date est le suivant :

<f:convertDateTime pattern="dd/MM/yyyy"/>

où dd (day) désigne le n° du jour, MM (Month) le n° du mois et yyyy (year) l'année. En [1], le convertisseur est utilisé pour la
conversion inverse java.lang.String --> java.util.Date. La date saisie devra donc suivre le modèle "dd/MM/yyyy" pour être valide.

Il existe des messages par défaut pour les dates non valides dans [javax.faces.Messages.properties] :

1. # ==============================================================================
2. # Converter Errors
3. # ==============================================================================
4. ...
5. javax.faces.converter.DateTimeConverter.DATE={2}: ''{0}'' could not be understood as a date.
6. javax.faces.converter.DateTimeConverter.DATE_detail={2}: ''{0}'' could not be understood as a
date. Example: {1}
7. javax.faces.converter.DateTimeConverter.TIME={2}: ''{0}'' could not be understood as a time.
8. javax.faces.converter.DateTimeConverter.TIME_detail={2}: ''{0}'' could not be understood as a
time. Example: {1}
9. javax.faces.converter.DateTimeConverter.DATETIME={2}: ''{0}'' could not be understood as a date
and time.
10. javax.faces.converter.DateTimeConverter.DATETIME_detail={2}: ''{0}'' could not be understood as a
date and time. Example: {1}
11. javax.faces.converter.DateTimeConverter.PATTERN_TYPE={1}: A 'pattern' or 'type' attribute must be
specified to convert the value ''{0}''.

qu'on peut remplacer par ses propres messages. Ainsi dans l'exemple

1. <h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie"


required="true" requiredMessage="#{msg['date.required']}"
converterMessage="#{msg['saisie8.error']}">
2. <f:convertDateTime pattern="dd/MM/yyyy"/>
3. </h:inputText>

le message affiché en cas d'erreur de conversion sera le message de clé saisie8.error suivant :

saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa

Voici un exemple :

1.6.5.5 Saisie 9 : saisie d'une chaîne de longueur contrainte

La saisie 9 montre comment imposer à une chaîne saisie d'avoir un nombre de caractères compris dans un intervalle :

1. <!-- ligne 9 -->


2. <h:outputText value="#{msg['saisie9.prompt']}"/>
3. <h:inputText id="saisie9" value="#{form.saisie9}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
validatorMessage="#{msg['saisie9.error']}">
4. <f:validateLength minimum="4" maximum="4"/>
5. </h:inputText>
6. <h:message for="saisie9" styleClass="error"/>
7. <h:outputText value="#{form.saisie9}"/>

Introdution à Java EE
312/334
Ligne 4, la validateur <f:validateLength minimum="4" maximum="4"/> impose à la chaîne saisie d'avoir exactement 4
caractères. On peut n'utiliser que l'un des attributs : minimum pour un nombre minimal de caractères, maximum pour un nombre
maximal.

Le modèle form.saisie9 du composant saisie9 de la ligne 3 est le suivant :

private String saisie9 = "";

Il existe des messages d'erreur par défaut pour ce type de validation :

1. # ==============================================================================
2. # Validator Errors
3. # ==============================================================================
4.
5. ...
6. javax.faces.validator.LengthValidator.MAXIMUM={1}: Validation Error: Value is greater than
allowable maximum of ''{0}''
7. javax.faces.validator.LengthValidator.MINIMUM={1}: Validation Error: Value is less than allowable
minimum of ''{0}''

qu'on peut remplacer en utilisant l'attribut validatorMessage comme dans la ligne 3 ci-dessus. Le message de clé saisie9.error est le
suivant :

saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement

Voici un exemple d'exécution :

1.6.5.6 Saisie 10 : écrire une méthode de validation spécifique

Résumons : Jsf permet de vérifier parmi les valeurs saisies, la validité des nombres (entiers, réels), des dates et la longueur des
chaînes. C'est très peu. On peut être étonné de ne pas trouver par exemple un validateur de type "expression régulière" qui
vérifierait qu'une valeur saisie suit un modèle donné. Ce serait utile pour vérifier qu'une adresse électronique vérifie un format de
base. Jsf permet d'ajouter aux validateurs et convertisseurs existants ses propres validateurs et convertisseurs. Aussi est-il possible,
par exemple, de trouver le validateur de type "expression régulière" dans des bibliothèques tierces ainsi que d'autres validateurs et
convertisseurs. Il faut espérer que les versions futures de Jsf intègreront certians d'entre-eux afin d'avoir une base plus riche
qu'actuellement.

On pourra ici suivre le tutoriel Netbeans "Validating and Converting User Input With the JSF Framework"
[http://www.netbeans.org/kb/articles/jAstrologer-validate.html] pour découvrir comment construire ses propres validateurs et
convertisseurs.

Nous présentons ici une autre méthode : celle qui consiste à valider une donnée saisie par une méthode du modèle du formulaire.
C'est l'exemple suivant :

1. <!-- ligne 10 -->


2. <h:outputText value="#{msg['saisie10.prompt']}"/>
3. <h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
validator="#{form.validateSaisie10}"/>
4. <h:message for="saisie10" styleClass="error"/>
5. <h:outputText value="#{form.saisie10}"/>

Le modèle form.saisie10 associé au composant saisie10 de la ligne 3 est le suivant :

private Integer saisie10 = 0;

On veut que le nombre saisi soit <1 ou >7. On ne peut le vérifier avec les validateurs de base de Jsf. On écrit alors sa propre
méthode de validation du composant saisie10. On l'indique avec l'attribut validator du composant à valider :

<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true"


requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>

Introdution à Java EE
313/334
Le composant saisie10 est validé par la méthode form.validateSaisie10. Celle-ci est la suivante :

1. public void validateSaisie10(FacesContext context, UIComponent component, Object value) {


2. int saisie = (Integer) value;
3. if (!(saisie < 1 || saisie > 7)) {
4. FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
5. message.setSeverity(FacesMessage.SEVERITY_ERROR);
6. throw new ValidatorException(message);
7. }
8.}

La signature d'une méthode de validation est obligatoirement celle de la ligne 1 :

• FacesContext context : contexte d'exécution de la page - donne accès à diverses informations, notamment aux objets
HttpServletRequest request et HttpServletResponse response.
• UIComponent component : le composant qu'il faut valider. La balise <h:inputText> est représenté par un composant de
type UIInput dérivé de UIComponent. Ici c'est ce composant UIInput qui est reçu en second paramètre.
• Object value : la valeur saisie à vérifier transformée dans le type de son modèle. Il est important de comprendre ici que si
la conversion String -> type du modèle a échoué, alors la méthode de validation n'est pas exécutée. Lorsqu'on arrive dans la
méthode validateSaisie10, c'est que la conversion String --> Integer a réussi. Le 3ième paramètre est alors de type Integer value.

• ligne 2 : la valeur saisie est transformée en type int.


• ligne 3 : on vérifie que la valeur saisie est <1 ou >7. Si c'est le cas, la validation est terminée. Si ce n'est pas le cas, le
validateur doit signaler l'erreur en lançant une exception de type ValidatorException.

La classe ValidatorException a deux constructeurs :

• le constructeur [1] a pour paramètre un message d'erreur de type FacesMessage. Ce type de message est celui affiché par les
balises <h:messages> et <h:message>.
• le constructeur [2] permet de plus d'encapsuler la cause de type Throwable ou dérivé de l'erreur.

Il nous faut construire un message de type FacesMessage. Cette classe a divers constructeurs :

Le constructeur [1] définit les propriétés d'un objet FacesMessage :

• FacesMessage.Severity severity : un niveau de gravité pris dans l'énumération suivante : SEVERITY_ERROR,


SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN.
• String summary : la version résumée du message d'erreur - est affichée par les balises <h:message showSummary="true"> et
<h:messages>
• String detail : la version détaillée du message d'erreur - est affichée par les balises <h:message> et <h:messages
showDetail="true">

Introdution à Java EE
314/334
N'importe lequel des constructeurs peut-être utilisé, les paramètres manquants pouvant être fixés ultérieurement par des méthodes
set.

Le constructeur [1] ne permet pas de désigner un message qui serait dans un fichier de messages utilisé pour l'internationalisation
des pages. C'est évidemment dommage. David Geary et Cay Horstmann comblent cette lacune dans leur livre "Core JavaServer
Faces" avec la classe utilitaire com.corejsf.util.Messages. C'est cette classe qui est utilisée ligne 4 pour créer le message d'erreur. Elle ne
contient que des méthodes statiques dont la méthode getMessage utilisée ligne 4 :

public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)

La méthode getMessage admet trois paramètres :


• String bundleName : le nom d'un fichier de messages sans son suffixe .properties mais avec son nom de paquetage. Ici, notre
premier paramètre pourrait être messsages pour désigner le fichier [messages.properties]. Avant d'utiliser le fichier désigné
par le premier paramètre, getMessage essaie d'utiliser le fichier des messages de l'application, s'il y en a un. Ainsi si dans
[faces-config.xml] on a déclaré un fichier de messages avec la balise :

1. <application>
2....
3. <message-bundle>messages</message-bundle>
4.</application>

on peut passer null comme premier paramètre à la méthode getMessage.


• String resourceId : la clé du message à exploiter dans le fichier des messages. Nous avons vu qu'un message pouvait avoir à la
fois une version résumée et une version détaillée. resourceId est l'identifiant de la version résumée. La version détaillée sera
recherchée automatiquement avec la clé resourceId_detail. Ainsi, aurons-nous deux messages dans [messages.properties] pour
l'erreur sur la saisie n° 10 :

saisie10.incorrecte=10-Saisie n° 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7

Le message de type FacesMessage produit par la méthode Messages.getMessage inclut à la fois les versions résumée et détaillée
si elles ont été trouvées. Les deux versions doivent être présentes sinon on récupère une exception de type
[NullPointerException].

• Object[] params : les paramètres effectifs du message si celui-ci a des paramètres formels {0}, {1}, ... Ces paramètres
formels seront remplacés par les éléments du tableau params.

Revenons au code de la méthode de validation du composant saisie10 :

1. public void validateSaisie10(FacesContext context, UIComponent component, Object value) {


2. int saisie = (Integer) value;
3. if (!(saisie < 1 || saisie > 7)) {
4. FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
5. message.setSeverity(FacesMessage.SEVERITY_ERROR);
6. throw new ValidatorException(message);
7. }
8. }

• en [4], le message de type FacesMessage est créé à l'aide de la méthode statique Messages.getMessage
• en [5], on fixe le niveau de gravité du message
• en [6], on lance une exception de type ValidatorException avec le message construit précédemment. La méthode de
validation a été appelée par le code Jsp suivant :

1. <!-- ligne 10 -->


2. <h:outputText value="#{msg['saisie10.prompt']}"/>
3. <h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
validator="#{form.validateSaisie10}"/>
4. <h:message for="saisie10" styleClass="error"/>
5. <h:outputText value="#{form.saisie10}"/>

Ligne 3, la méthode de validation est exécutée pour le composant d'id saisie10. Aussi le message d'erreur produit par la
méthode validateSaisie10 est-il associé à ce composant et donc affiché par la ligne 4 (attribut for="saisie10"). C'est la
version détaillée qui est affichée par défaut par la balise <h:message>.

Voici un exemple d'exécution :

Introdution à Java EE
315/334
1.6.5.7 Saisies 11 et 12 : validation d'un groupe de composants

Jusqu'à maintenant, les méthodes de validation rencontrées ne validaient qu'un unique composant. Comment faire si la validation
souhaitée concerne plusieurs composants ? C'est ce que nous voyons maintenant. Dans le formulaire :

nous voulons que les saisies 11 et 12 soient deux nombres entiers dont la somme soit égale à 10.

Le code Jsf sera le suivant :

1. <!-- ligne 11 -->


2. <h:outputText value="#{msg['saisie11.prompt']}"/>
3. <h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['integer.required']}"/>
4. <h:panelGroup>
5. <h:message for="saisie11" styleClass="error"/>
6. <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
7. </h:panelGroup>
8. <h:outputText value="#{form.saisie11}"/>
9. <!-- ligne 12 -->
10. <h:outputText value="#{msg['saisie12.prompt']}"/>
11. <h:inputText id="saisie12" value="#{form.saisie12}" styleClass="saisie" required="true"
requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
12. <h:panelGroup>
13. <h:message for="saisie12" styleClass="error"/>
14. <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
15. </h:panelGroup>
16. <h:outputText value="#{form.saisie12}"/>
17. </h:panelGrid>

et le modèle associé :

a) private Integer saisie11 = 0;


b) private Integer saisie12 = 0;
c) private String errorSaisie11 = "";
d)private String errorSaisie12 = "";

Ligne 3 du code Jsf, on utilise les techniques déjà présentées pour vérifier que la valeur saisie pour le composant saisie11 est bien un
entier. Il en est de même, ligne 11, pour le composant saisie12. Pour vérifier que saisie11 + saisie12 =10, on pourrait construire un
validateur spécifique. Pour ce faire, on pourra s'inspirer du tutoriel Netbeans "Validating and Converting User Input With the JSF
Framework" cité page 313. Nous suivons ici une autre démarche.

La page [form.jsp] est validée par un bouton [Valider] dont le code Jsf est le suivant :

1. <!-- boutons de commande -->


2. <h:panelGrid columns="2">
3. <h:commandButton value="#{msg['submit']" action="#{form.submit}"/>
4. ...
5. </h:panelGrid>

où le message msg['submit'] est le suivant :

submit=Valider

On voit ligne 3, que la méthode form.submit va être exécutée pour traiter le clic sur le bouton [Valider]. Celle-ci est la suivante :

1. // actions
2. public String submit() {

Introdution à Java EE
316/334
3. // dernières validations
4. validateForm();
5. // on renvoie le même formulaire
6. return null;
7. }
8.
9. // validations globales
10. private void validateForm() {
11. if ((saisie11 + saisie12) != 10) {
12....
13.}

Il est important de comprendre que lorsque la méthode submit s'exécute,


• tous les validateurs et convertisseurs du formulaire ont été exécutés et réussis
• les champs du modèle [Form.java] ont reçu les valeurs postées par le client.

En effet, revenons au cycle de traitement d'un POST Jsf :

A B C

E D
F

La méthode submit est un gestionnaire d'événement. Elle gère l'événement clic sur le bouton [Valider]. Comme tous les gestionnaires
d'événement, elle s'exécute dans la phase [E], une fois que tous les validateurs et convertisseurs ont été exécutés et réussis [C] et que
le modèle a été mis à jour avec les valeurs postées [D]. Il n'est donc plus question ici de lancer des exceptions de type
[ValidatorException] comme nous l'avons fait précédemment. Nous nous contenterons de renvoyer le formulaire avec des
messages d'erreur :

En [1], nous alerterons l'utilisateur et en [2] et [3], nous mettrons un signe d'erreur. Dans le code Jsf, le message [1] sera obtenu de
la façon suivante :

1. <f:view>
2. <h2><h:outputText value="#{msg['form.titre']}"/></h2>
3. <h:form id="formulaire">
4. <h:messages globalOnly="true" />
5. ...

En ligne 4, la balise <h:messages> affiche par défaut la version résumée des messages d'erreurs de toutes les saisies erronées de
composants du formulaire ainsi que tous les messages d'erreur non liés à des composants. L'attribut globalOnly="true" limite
l'affichage à ces derniers.

Les messages [2] et [3] sont affichés avec de simples balises <h:outputText> :

Introdution à Java EE
317/334
1. <!-- ligne 11 -->
2. <h:outputText value="#{msg['saisie11.prompt']}"/>
3. <h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie"
required="true" requiredMessage="#{msg['data.required']}"
converterMessage="#{msg['integer.required']}"/>
4. <h:panelGroup>
5. <h:message for="saisie11" styleClass="error"/>
6. <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
7. </h:panelGroup>
8. <h:outputText value="#{form.saisie11}"/>
9. <!-- ligne 12 -->
10. ...
11. <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
12. ...
13. </h:panelGrid>

Lignes 4-7, le composant saisie11 a deux messages d'erreur possibles :


• celui indiquant une conversion erronée ou une absence de donnée. Ce message généré par Jsf lui-même sera contenu dans
un type FacesMessage et affiché par la balise <h:message> de la ligne 5.
• celui que nous allons générer si saisie11 + saisie12 n'est pas égal à 10. Il sera affiché par la ligne 6. Le message d'erreur sera
contenu dans le modèle form.errorSaisie11.

Les deux messages correspondent à des erreurs qui ne peuvent se produire en même temps. La vérification saisie11 + saisie12 = 10
est faite dans la méthode submit qui ne s'exécute que s'il ne reste aucune erreur dans le formulaire. Lorsqu'elle s'exécutera, le
composant saisie11 aura été vérifié et son modèle form.saise11 aura reçu sa valeur. Le message de la ligne 5 ne pourra plus être
affiché. Inversement, si le message de la ligne 5 est affiché, il reste alors au moins une erreur dans le formulaire et la méthode submit
ne s'exécutera pas. Le message de la ligne 6 ne sera pas affiché. Afin que les deux messages d'erreur possibles soient dans la même
colonne du tableau, ils ont été rassemblés dans une balise <h:panelGroup> (lignes 4 et 7).

La méthode submit est la suivante :

1. // actions
2. public String submit() {
3. // dernières validations
4. validateForm();
5. // on renvoie le même formulaire
6. return null;
7. }
8.
9. // validations globales
10. private void validateForm() {
11. if ((saisie11 + saisie12) != 10) {
12. // msg global
13. FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
14. message.setSeverity(FacesMessage.SEVERITY_ERROR);
15. FacesContext context = FacesContext.getCurrentInstance();
16. context.addMessage(null, message);
17. // msg liés aux champs
18. message = Messages.getMessage(null, "error.sign", null);
19. setErrorSaisie11(message.getSummary());
20. setErrorSaisie12(message.getSummary());
21. } else {
22. setErrorSaisie11("");
23. setErrorSaisie12("");
24. }
25.}

• ligne 4 : la méthode submit appelle la méthode validateForm pour faire les dernières validations.
• ligne 11 : on vérifie si saisie11+saisie12=10
• si ce n'est pas le cas, lignes 13-14, on crée un message de type FacesMessage avec le message d'id saisies11et12.incorrectes. Celui-
ci est le suivant :

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée

• le message ainsi construit est ajouté (lignes 15-16) à la liste des messages d'erreur de l'application. Ce message n'est pas lié à
un composant particulier. C'est un message global de l'application. Il sera affiché par la balise <h:messages
globalOnly="true"/> présentée plus haut.
• ligne 18 : on crée un nouveau message de type FacesMessage avec le message d'id error.sign. Celui-ci est le suivant :

error.sign="!"

Introdution à Java EE
318/334
Nous avons dit que la méthode statique [Messages.getMessage] construisait un message de type FacesMessage avec une
version résumée et une version détaillée si elles existaient. Ici, seule la version résumée du message error.sign existe. On
obtient la version résumée d'un message m, par m.getSummary(). Lignes 19 et 20, la version résumée du message error.sign est
mise dans les champs errorSaisie11 et errorSaisie12 du modèle. Ils seront affichés par les balises Jsf suivantes :

1. <h:outputText value="#{form.saisie11}"/>
2. ...
3. <h:outputText value="#{form.saisie12}"/>

• lignes 22-23 : si la propriété saisie11+saisie12=10 est vérifiée, alors les deux champs errorSaisie11 et errorSaisie12 du modèle
sont vidés afin qu'un éventuel message d'erreur précédent soit effacé. Il faut se rappeler ici que le modèle est conservé
entre les requêtes, dans la session du client.

Voici un exemple d'exécution :

On remarquera dans la colonne [1] que le modèle a reçu les valeurs postées, ce qui montre que toutes les opérations de validation et
de conversion entre les valeurs postées et le modèle ont réussi. Le gestionnaire d'événement form.submit qui gère le clic sur le bouton
[Valider] a pu ainsi s'exécuter. C'est lui qui a produit les messages affichés en [2] et [3]. On voit que le modèle a été mis à jour alors
même que le formulaire a été refusé et renvoyé au client. On pourrait vouloir que le modèle ne soit pas à mis à jour dans un tel cas.
En effet, en imaginant que l'utilisateur annule sa mise à jour avec le bouton [Annuler] [4], on ne pourra pas revenir au modèle initial
sauf à l'avoir mémorisé. La plupart du temps, ceci n'est pas réellement un problème.

1.6.5.8 POST d'un formulaire sans vérification des saisies

Considérons le formulaire ci-dessus et supposons que l'utilisateur ne comprenant pas ses erreurs veuille abandonner la saisie du
formulaire. Il va alors utiliser le bouton [Annuler] généré par le code Jsf suivant :

1. <!-- boutons de commande -->


2. <h:panelGrid columns="2">
3. <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
4. <h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>

Introdution à Java EE
319/334
5. </h:panelGrid>

Ligne 4, le message msg['cancel'] est le suivant :

cancel=Annuler

La méthode form.cancel associée au bouton [Annuler] ne sera exécutée que si le formulaire est valide. C'est ce que nous avons montré
pour la méthode form.submit associée au bouton [Valider]. Si l'utilisateur veut annuler la saisie du formulaire, il est bien sûr inutile de
vérifier la validité de ses saisies. Ce résultat est obtenu avec l'attribut immediate="true" qui indique à Jsf d'exécuter la méthode
form.cancel sans passer par la phase de validation et de conversion. Revenons au cycle de traitement du POST Jsf :

A B

C
D

Les événements des composants d'action <h:commandButton> et <h:commandLink> ayant l'attribut immediate="true" sont
traités dans la phase [C] puis le cycle Jsf passe directement à la phase [E] de rendu de la réponse.

La méthode form.cancel est la suivante :

1. public String cancel() {


2. saisie1 = 0;
3. saisie2 = 0;
4. saisie3 = 0;
5. saisie4 = 0;
6. saisie5 = 0.0;
7. saisie6 = 0.0;
8. saisie7 = true;
9. saisie8 = new Date();
10. saisie9 = "";
11. saisie10 = 0;
12. return null;
13.}

Si on utilise le bouton [Annuler] dans le formulaire précédent, on obtient en retour la page suivante :

Introdution à Java EE
320/334
3 1 2

• on obtient de nouveau le formulaire car le gestionnaire d'événement form.cancel rend la clé de navigation null. La page
[form.jsp] est donc renvoyée.
• le modèle [Form.java] a été modifié par la méthode form.cancel. Ceci est reflété par la colonne [2] qui affiche ce modèle.
• la colonne [3], elle, reflète la valeur postée pour les composants.

Revenons sur le code Jsf du composant saisie1 [4] ;

1. <!-- ligne 1 -->


2. <h:outputText value="#{msg['saisie1.prompt']}"/>
3. <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
4. <h:message for="saisie1" styleClass="error"/>
5. <h:outputText value="#{form.saisie1}"/>

Ligne 4, la valeur du composant saisie1 est liée au modèle form.saisie1. Cela entraîne plusieurs choses :

• lors d'un GET de [form.jsp], le composant saisie1 affichera la valeur du modèle form.saisie1.
• lors d'un POST de [form.jsp], la valeur postée pour le composant saisie1 n'est affectée au modèle form.saisie1 que si
l'ensemble des validations et conversions du formulaire réussissent. Que le modèle ait été mis à jour ou non par les valeurs
postées, si le formulaire est renvoyé à l'issue du POST, les composants affichent la valeur qui a été postée et non pas la
valeur du modèle qui leur est associé. C'est ce que montre la copie d'écran ci-dessus, où les colonnes [1] et [2] n'ont pas les
mêmes valeurs.

1.7 Exemple n° 7
Thème : Gestion des événements liés à des changements de valeur de composants de saisie.

Introdution à Java EE
321/334
1.7.1 L'application

L'application montre un exemple de POST réalisé sans l'aide d'un bouton ou d'un lien. Le formulaire est le suivant :

1
2

Le contenu de la liste combo2 [1] est liée à l'élément sélectionné dans le combo1 [1]. Lorsqu'on change la sélection dans [1], un POST
du formulaire est réalisé au cours duquel le contenu de combo2 est modifié pour refléter l'élément sélectionné dans [1], puis le
formulaire renvoyé. Au cours de ce POST, aucune validation n'est faite.

1.7.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

On a un unique formulaire [form.jsp] avec son modèle [Form.java].

1.7.3 Le formulaire [form.jsp]

Le formulaire [form.jsp] est le suivant :

1. ...
2. <html>
3. <f:view>
4. <head>
5. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6. <title><h:outputText value="#{msg['app.titre']}"/></title>
7. <link href="styles.css" rel="stylesheet" type="text/css"/>
8. </head>
9. <body background="<c:url value="/ressources/standard.jpg"/>">
10. <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
11. <h:form id="formulaire">
12. <h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
13. <!-- headers -->
14. <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
15. <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
16. <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>

Introdution à Java EE
322/334
17. <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
18. <!-- ligne 1 -->
19. <h:outputText value="#{msg['combo1.prompt']}"/>
20. <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" styleClass="combo"
onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}">
21. <f:selectItems value="#{form.combo1Items}"/>
22. </h:selectOneMenu>
23. <h:panelGroup></h:panelGroup>
24. <h:outputText value="#{form.combo1}"/>
25. <!-- ligne 2 -->
26. <h:outputText value="#{msg['combo2.prompt']}"/>
27. <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
28. <f:selectItems value="#{form.combo2Items}"/>
29. </h:selectOneMenu>
30. <h:panelGroup></h:panelGroup>
31. <h:outputText value="#{form.combo2}"/>
32. <!-- ligne 3 -->
33. <h:outputText value="#{msg['saisie1.prompt']}"/>
34. <h:inputText id="saisie1" value="#{form.saisie1}" required="true"
requiredMessage="#{msg['data.required']}" styleClass="saisie"
converterMessage="#{msg['integer.required']}"/>
35. <h:message for="saisie1" styleClass="error"/>
36. <h:outputText value="#{form.saisie1}"/>
37. </h:panelGrid>
38. <!-- boutons de commande -->
39. <h:panelGrid columns="2" border="0">
40. <h:commandButton value="#{msg['submit']}"/>
41. ...
42. </h:panelGrid>
43. </h:form>
44. </f:view>
45. </body>
46. </html>

La nouveauté réside dans le code de la liste combo1, lignes 20-22. De nouveaux attributs apparaissent :

• onchange : attribut Html - déclare une fonction ou du code Javascript qui doit être exécuté lorsque l'élément sélectionné
dans combo1 change. Ici, le code Javascript submit() poste le formulaire au serveur.
• valueChangeListener : attribut Jsf - déclare le nom de la méthode à exécuter côté serveur lorsque l'élément sélectionné
dans combo1 change. Au total, il y a deux méthodes exécutées : l'une côté client, l'autre côté serveur.
• immediate=true : attribut Jsf déjà rencontré - fixe le moment où doit être exécuté le gestionnaire d'événement côté
serveur : après que le formulaire ait été reconstitué comme l'utilisateur l'a saisi mais avant les contrôles de validité des
saisies. On veut ici, remplir la liste combo2 en fonction de l'élément sélectionné dans la liste combo1, même si par ailleurs
dans le formulaire il peut y avoir des saisies erronées. Voici un exemple :

• en [1], une première saisie


• en [2], on passe l'élément sélectionné de combo1 de A à B.

Le résultat obtenu est le suivant :

Introdution à Java EE
323/334
1
2

Le POST a eu lieu. Le contenu de combo2 [2] a été adapté à l'élément sélectionné dans combo1 [1] bien que la saisie [3] était
incorrecte. C'est l'attribut immediate=true qui a fait que la méthode form.combo1ChangeListener a été exécutée avant les contrôles de
validité. Sans cet attribut, elle n'aurait pas été exécutée car le cycle de traitement se serait arrêté aux contrôles de validité à cause de
l'erreur en [3].

Les messages associés au formulaire sont les suivants dans [messages.properties] :

1. app.titre=intro-07
2. app.titre2=JSF - Listeners
3. combo1.prompt=combo1
4. combo2.prompt=combo2
5. saisie1.prompt=Nombre entier de type int
6. submit=Valider
7. raz=Raz
8. data.required=Donnée requise
9. integer.required=Entrez un nombre entier
10. saisie.type=Type de la saisie
11. saisie.champ=Champ de saisie
12. saisie.erreur=Erreur de saisie
13. bean.valeur=Valeurs du modèle du formulaire

La durée de vie de [Form.java] est fixée à session :

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


2.
3. <!-- =========== FULL CONFIGURATION FILE ================================== -->
4.
5. <faces-config version="1.2"
6. xmlns="http://java.sun.com/xml/ns/javaee"
7. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
8. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
9.
10. <application>
11. <resource-bundle>
12. <base-name>
13. messages
14. </base-name>
15. <var>msg</var>
16. </resource-bundle>
17. </application>
18. <managed-bean>
19. <managed-bean-name>form</managed-bean-name>
20. <managed-bean-class>forms.Form</managed-bean-class>
21. <managed-bean-scope>session</managed-bean-scope>
22. </managed-bean>
23. </faces-config>

1.7.4 Le modèle [Form.java]

Le modèle [Form.java] est le suivant :

1. package forms;
2.
3. ...
4. public class Form {
5.
6. public Form() {
7. }
8.

Introdution à Java EE
324/334
9. // modèles du formulaire
10. private String combo1="A";
11. private String combo2="A1";
12. private Integer saisie1=0;
13.
14. // champs de travail
15. final private String[] combo1Labels={"A","B","C"};
16. private String combo1Label="A";
17.
18. // méthodes
19. public SelectItem[] getCombo1Items(){
20. // init combo1
21. SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
22. for(int i=0;i<combo1Labels.length;i++){
23. combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
24. }
25. return combo1Items;
26. }
27.
28. public SelectItem[] getCombo2Items(){
29. // init combo2 en fonction de combo1Label
30. SelectItem[] combo2Items=new SelectItem[5];
31. for(int i=1;i<=combo2Items.length;i++){
32. combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
33. }
34. return combo2Items;
35. }
36.
37. // listeners
38. public void combo1ChangeListener(ValueChangeEvent event){
39. // on mémorise la valeur postée pour combo1
40. combo1Label=(String)event.getNewValue();
41. // on rend la réponse car on veut court-circuiter les validations
42. FacesContext.getCurrentInstance().renderResponse();
43. }
44. ...
45. // getters - setters
46. ...
47.}

Relions le formulaire [form.jsp] à son modèle [Form.java] :

La liste combo1 est générée par le code Jsf suivant :

1. <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();"


valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
2. <f:selectItems value="#{form.combo1Items}"/>
3. </h:selectOneMenu>

Elle obtient ses éléments par la méthode getCombo1Items de son modèle (ligne 2). Celle-ci est définie lignes 19-26 du code Java. Elle
génère une liste de trois éléments {"A","B","C"}.

La liste combo2 est générée par le code Jsf suivant :

1. <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">


2. <f:selectItems value="#{form.combo2Items}"/>
3. </h:selectOneMenu>

Elle obtient ses éléments par la méthode getCombo2Items de son modèle (ligne 2). Celle-ci est définie lignes 28-35 du code Java. Elle
génère une liste de cinq éléments {"X1","X2","X3","X4","X5"} où X est l'élément combo1Label de la ligne 16. Donc lors de la
génération initiale du formulaire, la liste combo2 contient les éléments {"A1","A2","A3","A4","A5"}.

Lorsque l'utilisateur va changer l'élément sélectionné dans la liste combo1,


• l'événement onchange="submit();" va être traité par le navigateur client. Le formulaire va donc être posté au serveur.
• côté serveur, Jsf va détecter que le composant combo1 a changé de valeur. La méthode combo1ChangeListener des lignes 38-43
va être exécutée. Une méthode de type ValueChangeListener reçoit en paramètre un objet de type
javax.faces.event.ValueChangeEvent. Cet objet permet d'obtenir l'ancienne et la nouvelle valeur du composant qui a changé de
valeur avec les méthodes suivantes :

Introdution à Java EE
325/334
Ici le composant est la liste combo1 de type UISelectOne. Sa valeur est de type String.

• ligne 40 du modèle Java : la nouvelle valeur de combo1 est mémorisée dans combo1Label qui sert à générer les éléments de la
liste combo2.
• ligne 42 : on rend la réponse. Il faut se rappeler ici que le gestionnaire combo1ChangeListener est exécuté avec l'attribut
immediate="true". Il est donc exécuté après la phase où l'arbre des composants de la page a été mis à jour avec les valeurs
postées et avant le processus de validation des valeurs postées. Or on veut éviter ce processus de validation parce que la
liste combo2 doit être mise à jour même si par ailleurs dans le formulaire, il reste des saisies erronées. On demande donc à
ce que la réponse soit envoyée tout de suite sans passer par la phase de validation des saisies.
• le formulaire va être renvoyé tel qu'il a été saisi. Cependant, les éléments des listes combo1 et combo2 ne sont pas des valeurs
postées. Elles vont être générées de nouveau par appel aux méthodes getCombo1Items et getCombo2Items. Cette dernière
méthode va alors utiliser la nouvelle valeur de combo1Label fixée par combo1ChangeListener et les éléments de la liste combo2
vont changer.

1.7.5 Le bouton [Raz]

Nous voulons avec le bouton [Raz] remettre le formulaire dans un état initial comme montré ci-dessous :

En [1], le formulaire avant le POST du bouton [Raz], en [2] le résultat du POST.

Bien que fonctionnellement simple, la gestion de ce cas d'utilisation s'avère assez complexe. On peut essayer diverses solutions,
notamment celle utilisée pour le bouton [Annuler] de l'exemple précédent :

<h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>

où la méthode form.raz est la suivante :

1. public String raz(){


2. // raz du formulaire
3. combo1Label="A";
4. combo1="A";
5. combo2="A1";
6. saisie1=0;
7. return null;
8.}

Introdution à Java EE
326/334
Le résultat obtenu par le bouton [Raz] dans l'exemple précédent est alors le suivant :

La colonne [1] montre que la méthode form.raz a été exécutée. Cependant la colonne [1] continue à afficher les valeurs postées :
• pour combo1, la valeur postée était "B". Cet élément est donc sélectionné dans la liste.
• pour combo2, la valeur postée était "B5". Du fait de l'exécution de form.raz, les éléments {"B1", ..., "B5"} de combo2 ont été
changés en {"A1", ..., "A5"}. L'élément "B5" n'existe plus et ne peut donc être sélectionné. C'est alors le 1er élément de la
liste qui est affiché.
• pour saisie1, la valeur postée était 10.

Ceci est le fonctionnement normal avec l'attribut immediate="true". Pour avoir un résultat différent, il faut poster les valeurs
qu'on veut voir dans le nouveau formulaire même si l'utilisateur a lui saisi d'autres valeurs. On réalise cela avec un peu de code
Javascript côté client. Le formulaire devient le suivant :

1. <script language="javascript">
2. function raz(){
3. document.forms['formulaire'].elements['formulaire:combo1'].value="A";
4. document.forms['formulaire'].elements['formulaire:combo2'].value="A1";
5. document.forms['formulaire'].elements['formulaire:saisie1'].value=0;
6. //document.forms['formulaire'].submit();
7. }
8. </script>
9. ...
10. <h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>

• ligne 10, l'attribut onclick='raz()' indique d'exécuter la fonction Javascript raz lorsque l'utilisateur clique sur le bouton
[Raz].
• ligne 3 : on affecte la valeur "A" à l'élément Html de nom 'formulaire:combo1'. Les différents éléments de la ligne 3 sont
les suivants :
• document : page affichée par le navigateur
• document.forms : ensemble des formulaires du document
• document.forms['formulaire'] : le formulaire avec l'attribut name="formulaire"
• documents.forms['formulaire'].elements : ensemble des éléments du formulaire ayant l'attribut
name="formulaire"
• document.forms['formulaire'].elements['formulaire:combo1'] : élément du formulaire ayant l'attribut
name="formulaire:combo1"
• document.forms['formulaire'].elements['formulaire:combo1'].value : valeur qui sera postée par l'élément
du formulaire ayant l'attribut name="formulaire:combo1"

Pour connaître les attributs name des différents éléments de la page affichée par le navigateur, on pourra consulter son
code source ( ci-dessous avec IE7) :

Introdution à Java EE
327/334
<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>

Ceci expliqué, on peut comprendre que dans le code Javascript de la fonction raz :

• la ligne 3 fait que la valeur postée pour le composant combo1 sera la chaîne A
• la ligne 4 fait que la valeur postée pour le composant combo2 sera la chaîne A1
• la ligne 5 fait que la valeur postée pour le composant saisie1 sera la chaîne 0

Ceci fait, le POST du formulaire, associé à tout bouton de type <h:commandButton> (ligne 10) va se produire. La méthode
form.raz va être exécutée et le formulaire renvoyé tel qu'il a été posté. On obtient alors le résultat suivant :

Ce résultat cache beaucoup de choses. Les valeurs "A","A1","0" des composants combo1, combo2, saisie1 sont postées au serveur.
Supposons que la valeur précédente de combo1 était "B". Alors il y a un changement de valeur du composant combo1 et la méthode
form.combo1ChangeListener devrait être exécutée elle aussi. On a deux gestionnaires d'événements ayant l'attribut immediate="true".
Vont-ils être exécutés tous les deux ? Si oui, dans quel ordre ? L'un seulement ? Si oui lequel ?

Pour en savoir plus, nous allons créer des logs dans l'application. Ceux-ci viendront s'ajouter aux autres logs du serveur Sun. Le
modèle [Form.java] est transformé comme suit :

1. package forms;
2.
3. import java.util.logging.Logger;
4. ...
5. public class Form {
6.
7. ...
8. // champs du formulaire
9. private String combo1="A";
10. private String combo2="A1";
11. private Integer saisie1=0;
12.
13. // champs de travail
14. final private String[] combo1Labels={"A","B","C"};
15. private String combo1Label="A";
16. private static final Logger logger=Logger.getLogger("forms.Form");

Introdution à Java EE
328/334
17.
18. // listener
19. public void combo1ChangeListener(ValueChangeEvent event){
20. // suivi
21. logger.info("combo1ChangeListener");
22. // on récupère la valeur postée de combo1
23. combo1Label=(String)event.getNewValue();
24. // on rend la réponse car on veut court-circuiter les validations
25. FacesContext.getCurrentInstance().renderResponse();
26. }
27.
28. public String raz(){
29. // suivi
30. logger.info("raz");
31. // raz du formulaire
32. combo1Label="A";
33. combo1="A";
34. combo2="A1";
35. saisie1=0;
36. return null;
37. }
38. ...
39.}

• ligne 16 : un générateur de logs est créé. Le paramètre de getLogger permet de différencier les origines des logs. Ici, le
logueur s'appelle forms.Form.
• ligne 21 : on logue le passage dans la méthode combo1ChangeListener.
• ligne 30 : on logue le passage dans la méthode raz.

Les logs du serveur Sun peuvent être consultés de la façon suivante :

• en [1], dans Netbeans / Runtime, on demande à voir la console d'administration


• en [2], on s'identifie [admin, adminadmin]

3
4

• en [3], clic sur le lien [Application Server]


• en [4], on demande à voir les fichiers des logs

Introdution à Java EE
329/334
5 6

En [5], on peut voir les logs produits par le logueur [6].

Quels sont les logs produits par le bouton [Raz] ou le changement de valeur de combo1 ? Considérons divers cas :

• on utilise le bouton [Raz] alors que l'élément sélectionné dans combo1 est "A". "A" est donc la dernière valeur du
composant combo1. Nous avons vu que le bouton [Raz] exécutait une fonction Javascript qui postait la valeur "A" pour le
composant combo1. Le composant combo1 ne change donc pas de valeur. Les logs montrent alors que seule la méthode
form.raz est exécutée.
• on utilise le bouton [Raz] alors que l'élément sélectionné dans combo1 n'est pas "A". Le composant combo1 change donc de
valeur : sa dernière valeur n'était pas "A" et le bouton [Raz] va lui poster la valeur "A". Les logs montrent alors que trois
méthodes sont exécutées. Dans l'ordre : combo1ChangeListener, combo1ChangeListener, raz. Si on peut comprendre que la
méthode combo1ChangeListener soit exécutée, je ne sais pas expliquer pourquoi elle l'est deux fois.
• on change la valeur du combo1 sans utiliser le bouton [Raz]. Les logs montrent que seule la méthode combo1ChangeListener
est exécutée.

1.8 Compléments ...


On trouvera des compléments d'information aux endroits suivants :

• composant <h:dataTable> pour créer des tables de données : paragraphe 17.5.3, page 156.
• vues Jsf composées de sous-vues <f:subView> : paragraphe 17.4.1, page 146.
• applications Jsf à trois couches : paragraphe 17.7, page 163.
• création de validateurs et convertisseurs spécifiques : "Validating and Converting User Input with the JSF Framework"
[http://www.netbeans.org/kb/articles/jAstrologer-validate.html]

Introdution à Java EE
330/334
Table des matières
1ARCHITECTURE D'UNE APPLICATION JAVA EN COUCHES...................................................................................... 4
2PAM - VERSION 1...................................................................................................................................................................... 7
2.1LA BASE DE DONNÉES..................................................................................................................................................................... 7
2.2MODE DE CALCUL DU SALAIRE D'UNE ASSISTANTE MATERNELLE......................................................................................................... 8
2.3FONCTIONNEMENT DE L'APPLICATION CONSOLE.................................................................................................................................9
2.4FONCTIONNEMENT DE L'APPLICATION GRAPHIQUE........................................................................................................................... 10
3IMPLÉMENTATION JPA DE LA COUCHE DE PERSISTANCE DES DONNÉES....................................................... 11
3.1LES ENTITÉS JPA........................................................................................................................................................................11
3.2CONFIGURATION DE LA COUCHE JPA............................................................................................................................................ 11
4MISE EN OEUVRE DES TESTS DE LA COUCHE JPA..................................................................................................... 12
5LES INTERFACES DES COUCHES [METIER] ET [DAO]............................................................................................... 22
6LA CLASSE [PAMEXCEPTION]........................................................................................................................................... 27
7LA COUCHE [DAO] DE L'APPLICATION [PAM]............................................................................................................. 28
7.1IMPLÉMENTATION.........................................................................................................................................................................28
7.2CONFIGURATION...........................................................................................................................................................................29
7.3TESTS......................................................................................................................................................................................... 29
7.3.1TESTS 1....................................................................................................................................................................................30
7.3.1.1InitDB................................................................................................................................................................................. 30
7.3.1.2Mise en oeuvre des tests..................................................................................................................................................... 31
7.3.2TESTS 2....................................................................................................................................................................................33
7.3.2.1TestNGDao......................................................................................................................................................................... 34
7.3.2.2Mise en oeuvre des tests..................................................................................................................................................... 36
8LA COUCHE [METIER] DE L'APPLICATION [PAM]......................................................................................................38
8.1L'INTERFACE JAVA [IMETIER]..................................................................................................................................................... 38
8.2LA CLASSE [FEUILLESALAIRE]...................................................................................................................................................... 39
8.3LA CLASSE D'IMPLÉMENTATION [METIER] DE LA COUCHE [METIER]................................................................................................. 40
8.4TESTS DE LA COUCHE [METIER]..................................................................................................................................................... 41
9LA COUCHE [UI] DE L'APPLICATION [PAM] – VERSION CONSOLE....................................................................... 43
9.1.1LA CLASSE [UI.CONSOLE.MAIN]....................................................................................................................................................44
9.1.2EXÉCUTION................................................................................................................................................................................45
10LA COUCHE [UI] DE L'APPLICATION [PAM] – VERSION GRAPHIQUE................................................................ 46
10.1UN RAPIDE TUTORIEL..................................................................................................................................................................47
10.2L'INTERFACE GRAPHIQUE [PAMJFRAME].....................................................................................................................................49
10.3LES ÉVÉNEMENTS DE L'INTERFACE GRAPHIQUE............................................................................................................................. 51
10.4INITIALISATION DE L'INTERFACE GRAPHIQUE................................................................................................................................. 52
10.5GESTIONNAIRES D'ÉVÉNEMENTS...................................................................................................................................................54
10.6EXÉCUTION DE L'INTERFACE GRAPHIQUE...................................................................................................................................... 54
11IMPLÉMENTATION DE LA COUCHE JPA AVEC TOPLINK...................................................................................... 54
11.1LE PROJET NETBEANS................................................................................................................................................................ 54
11.2MISE EN OEUVRE DES TESTS........................................................................................................................................................56
11.3TESTS DE LA COUCHE [DAO]........................................................................................................................................................59
11.3.1INITDB.................................................................................................................................................................................. 59
11.3.2TESTNGDAO..........................................................................................................................................................................61
11.4LES AUTRES TESTS..................................................................................................................................................................... 63
12VERSION 2 - EJB3 / JPA DANS JBOSS.............................................................................................................................. 63
12.1LE PROJET NETBEANS / JBOSS EJB3 / JPA / HIBERNATE.............................................................................................................. 64
12.2LA COUCHE JPA / HIBERNATE.....................................................................................................................................................65
12.3LA COUCHE [DAO]......................................................................................................................................................................65
12.4TESTS DE LA COUCHE [DAO]........................................................................................................................................................69
12.5TESTS DE LA COUCHE [METIER]...................................................................................................................................................71
12.6TESTS DE LA COUCHE [UI]...........................................................................................................................................................72
13VERSION 3 – CLIENT / SERVEUR EJB............................................................................................................................. 73
13.1DÉCOUVERTE DU SERVEUR JAVA EE SUN AS 9........................................................................................................................... 73
13.1.1GÉRER LE SERVEUR SUN AVEC NETBEANS................................................................................................................................... 73
13.1.2GÉRER LE SERVEUR SUN VIA SON INTERFACE WEB......................................................................................................................... 77
13.1.3AJOUTER UN NOUVEAU SERVEUR D'APPLICATION............................................................................................................................ 82

Introdution à Java EE
331/334
13.2LA PARTIE SERVEUR DE L'APPLICATION CLIENT / SERVEUR PAM...................................................................................................85
13.2.1L'ARCHITECTURE DE L'APPLICATION............................................................................................................................................. 85
13.2.2LE PROJET NETBEANS............................................................................................................................................................... 86
13.2.3CONFIGURATION DE LA COUCHE DE PERSISTANCE........................................................................................................................... 87
13.2.4GÉNÉRATION DE LA SOURCE DE DONNÉES..................................................................................................................................... 90
13.2.5DÉPLOIEMENT DES COUCHES [JPA, DAO, METIER]........................................................................................................................... 91
13.3LA PARTIE CLIENT DE L'APPLICATION CLIENT / SERVEUR PAM..................................................................................................... 95
13.3.1LE PROJET NETBEANS DU CLIENT CONSOLE...................................................................................................................................95
13.3.2TESTS DU CLIENT CONSOLE.......................................................................................................................................................101
13.3.3CLIENT CONSOLE - VERSION 2.................................................................................................................................................. 103
13.3.4CLIENT CONSOLE - VERSION 3.................................................................................................................................................. 105
13.3.5LE CLIENT SWING...................................................................................................................................................................106
13.3.6LES TESTS TESTNG............................................................................................................................................................... 107
14VERSION 4 – CLIENT / SERVEUR DANS UNE ARCHITECTURE DE SERVICE WEB......................................... 109
14.1LA PARTIE SERVEUR DE L'APPLICATION CLIENT / SERVEUR DE TYPE " SERVICE WEB "....................................................................110
14.1.1LE PROJET NETBEANS............................................................................................................................................................. 110
14.2LA PARTIE CLIENT DE L'APPLICATION CLIENT / SERVEUR DE TYPE " SERVICE WEB "..................................................................... 112
14.2.1LE PROJET NETBEANS DU CLIENT CONSOLE.................................................................................................................................112
14.2.2LE CLIENT CONSOLE DU SERVICE WEB METIER............................................................................................................................ 115
14.2.3LE CLIENT SWING DU SERVICE WEB METIER................................................................................................................................117
15VERSION 5 - APPLICATION PAM DEUX COUCHES - INTERFACE WEB / JSF................................................... 117
15.1ARCHITECTURE DE L'APPLICATION.............................................................................................................................................117
15.2FONCTIONNEMENT DE L'APPLICATION.........................................................................................................................................119
15.3LE PROJET NETBEANS.............................................................................................................................................................. 121
15.3.1LES FICHIERS DE CONFIGURATION.............................................................................................................................................. 122
15.3.2LA FEUILLE DE STYLE..............................................................................................................................................................123
15.3.3LE FICHIER DES MESSAGES........................................................................................................................................................123
15.3.4LA COUCHE [MÉTIER]..............................................................................................................................................................124
15.4LE FORMULAIRE [FORM.JSP ] ET SON MODÈLE [FORM.JAVA].........................................................................................................125
15.4.1ÉTAPE 1................................................................................................................................................................................ 125
15.4.2ÉTAPE 2................................................................................................................................................................................ 126
15.4.3ÉTAPE 3................................................................................................................................................................................ 127
15.4.4ÉTAPE 4................................................................................................................................................................................ 127
16VERSION 6 - INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB.. 128
16.1ARCHITECTURE DE L'APPLICATION.............................................................................................................................................128
16.2LE PROJET NETBEANS DE LA COUCHE WEB.................................................................................................................................129
16.3LE PROJET NETBEANS DE L'APPLICATION D'ENTREPRISE.............................................................................................................. 131
17VERSION 7 - APPLICATION WEB PAM MULTI-VUES / MONO-PAGE.................................................................. 137
17.1LES VUES DE L'APPLICATION..................................................................................................................................................... 138
17.2LE PROJET NETBEANS DE LA COUCHE [WEB].............................................................................................................................. 140
17.2.1LES FICHIERS DE CONFIGURATION.............................................................................................................................................. 140
17.2.2LA FEUILLE DE STYLE..............................................................................................................................................................142
17.2.3LE FICHIER DES MESSAGES........................................................................................................................................................143
17.2.4LA COUCHE [MÉTIER]..............................................................................................................................................................144
17.3LE BEAN [APPLICATIONDATA].................................................................................................................................................. 144
17.4IMPLÉMENTATION DU MODÈLE MVC........................................................................................................................................ 146
17.4.1LE FORMULAIRE [FORM.JSP] ET SES VUES....................................................................................................................................146
17.4.2LE CONTRÔLEUR / MODÈLE [FORM.JAVA]...................................................................................................................................147
17.4.3LA VUE [VUEENTETE].............................................................................................................................................................149
17.4.4LA VUE [VUESAISIE]...............................................................................................................................................................150
17.5LES ACTIONS DU CONTRÔLEUR...................................................................................................................................................151
17.5.1L'ACTION [FAIRESIMULATION].................................................................................................................................................. 151
17.5.2L'ACTION [EFFACERSIMULATION].............................................................................................................................................. 152
17.5.3L'ACTION [ENREGISTRERSIMULATION]........................................................................................................................................ 153
17.5.4L'ACTION [RETOURSIMULATEUR]...............................................................................................................................................158
17.5.5L'ACTION [VOIRSIMULATIONS].................................................................................................................................................. 158
17.5.6L'ACTION [RETIRERSIMULATION]...............................................................................................................................................159
17.5.7L'ACTION [TERMINERSESSION].................................................................................................................................................. 160
17.6LA VUE [VUEERREUR].............................................................................................................................................................. 161
17.7INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB........................................................................163
18VERSION 8 - APPLICATION WEB PAM MULTI-VUES / MONO-PAGE - 2............................................................ 164
18.1LE PROJET NETBEANS.............................................................................................................................................................. 165
18.2LE FICHIER [FACES-CONFIG.XML].............................................................................................................................................. 166

Introdution à Java EE
332/334
18.3LE BEAN SESSIONDATA............................................................................................................................................................ 167
18.4LE BEAN FORM........................................................................................................................................................................172
18.5INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB........................................................................173
19VERSION 9 - APPLICATION WEB PAM MULTI-VUES / MULTI-PAGES............................................................... 174
19.1LE PROJET NETBEANS.............................................................................................................................................................. 176
19.2LA CONFIGURATION DE L'APPLICATION.......................................................................................................................................176
19.3LES VUES JSP ET LEURS BEANS MODÈLES / CONTRÔLEURS............................................................................................................ 178
19.3.1LA PAGE MAÎTRE [MASTERPAGE.JSP]......................................................................................................................................... 178
19.3.2LA PAGE [ENTETE.JSP].............................................................................................................................................................180
19.3.3LA PAGE [SAISIES.JSP]............................................................................................................................................................. 181
19.3.4LA PAGE [SIMULATION.JSP]...................................................................................................................................................... 184
19.3.5LA PAGE [SIMULATIONS.JSP]..................................................................................................................................................... 185
19.3.6LA PAGE [ERREUR.JSP]............................................................................................................................................................ 187
19.3.7LA PAGE [SIMULATIONSVIDES.JSP]............................................................................................................................................. 188
19.4LES ACTIONS............................................................................................................................................................................188
19.4.1L'ACTION [FAIRESIMULATION].................................................................................................................................................. 188
19.4.2L'ACTION [EFFACERSIMULATION].............................................................................................................................................. 189
19.4.3L'ACTION [ENREGISTRERSIMULATION]........................................................................................................................................ 189
19.4.4L'ACTION [RETOURSIMULATEUR]...............................................................................................................................................189
19.4.5L'ACTION [VOIRSIMULATIONS].................................................................................................................................................. 190
19.4.6L'ACTION [RETIRERSIMULATION]...............................................................................................................................................190
19.4.7L'ACTION [TERMINERSESSION].................................................................................................................................................. 191
19.5INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES AVEC JSF - EJB............................................................... 191
20VERSION 10 - INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES AVEC JSF
ET SPRING................................................................................................................................................................................ 192
20.1ARCHITECTURE JSF / SPRING.................................................................................................................................................... 192
20.2LE PROJET NETBANS JSF / SPRING / HIBERNATE........................................................................................................................193
20.3LE PROJET NETBANS JSF / SPRING / HIBERNATE - 2.................................................................................................................. 204
20.4LE PROJET NETBANS JSF / SPRING / TOPLINK............................................................................................................................205
1 JSF - JAVASERVER FACES............................................................................................................................................... 212
1.1EXEMPLE N° 1........................................................................................................................................................................... 212
1.1.1 GÉNÉRATION DU PROJET.......................................................................................................................................................... 212
1.1.2 CONFIGURATION D'UN PROJET JSF............................................................................................................................................ 214
1.1.2.1 web.xml...........................................................................................................................................................................214
1.1.2.2 faces-config.xml..............................................................................................................................................................217
1.1.3 EXÉCUTION DU PROJET............................................................................................................................................................ 218
1.1.4 LES PAGES [INDEX.JSP] ET [WELCOMEJSF.JSP]........................................................................................................................... 218
1.1.5 UTILISATION DE LA BIBLIOTHÈQUE DE BALISES JSTL................................................................................................................... 222
1.1.6 REDIRECTION DANS LA PAGE D'ACCUEIL......................................................................................................................................223
1.1.7 TUTORIELS NETBEANS.............................................................................................................................................................224
1.2 EXEMPLE N° 2......................................................................................................................................................................... 225
1.2.1 L'APPLICATION....................................................................................................................................................................... 225
1.2.2 LE PROJET NETBEANS............................................................................................................................................................. 226
1.2.3 LES PAGES JSF DU PROJET........................................................................................................................................................226
1.2.3.1 [index.jsp]....................................................................................................................................................................... 226
1.2.3.2 [welcomeJSF.jsp]............................................................................................................................................................226
1.2.3.3 [page1.jsp]...................................................................................................................................................................... 237
1.2.4 LE FICHIER DES MESSAGES DU PROJET........................................................................................................................................ 238
1.2.5 LES CLASSES JAVA DU PROJET.................................................................................................................................................. 240
1.2.6 LE FICHIER DE CONFIGURATION [FACES-CONFIG.XML]................................................................................................................... 241
1.2.7 EXÉCUTION DU PROJET............................................................................................................................................................ 244
1.2.8 CONCLUSION..........................................................................................................................................................................246
1.3 EXEMPLE N ° 3........................................................................................................................................................................ 246
1.3.1 L'APPLICATION....................................................................................................................................................................... 247
1.3.2 LE PROJET NETBEANS............................................................................................................................................................. 247
1.3.3 LE FICHIER [FACES-CONFIG]..................................................................................................................................................... 248
1.3.4 LE FICHIER DES MESSAGES [MESSAGES.PROPERTIES]......................................................................................................................248
1.3.5 LE MODÈLE [FORM.JAVA] DE LA PAGE [FORM.JSP].......................................................................................................................250
1.3.6 LA PAGE [FORM.JSP]............................................................................................................................................................... 255
1.3.6.1 La feuille de style du formulaire..................................................................................................................................... 258
1.3.6.2 Les deux cycles demande client / réponse serveur d'un formulaire................................................................................ 259
1.3.6.3 Balise <h:inputText>...................................................................................................................................................... 264
1.3.6.4 Balise <h:inputSecret>....................................................................................................................................................266

Introdution à Java EE
333/334
1.3.6.5 Balise <h:inputTextArea>...............................................................................................................................................267
1.3.6.6 Balise <h:selectOneListBox>......................................................................................................................................... 268
1.3.6.7 Balise <h:selectManyListBox>.......................................................................................................................................271
1.3.6.8 Balise <h:selectOneMenu>............................................................................................................................................. 274
1.3.6.9 Balise <h:selectManyMenu>.......................................................................................................................................... 274
1.3.6.10 Balise <h:inputHidden>................................................................................................................................................ 275
1.3.6.11 Balise <h:selectBooleanCheckBox>.............................................................................................................................276
1.3.6.12 Balise <h:selectManyCheckBox>.................................................................................................................................277
1.3.6.13 Balise <h:selectOneRadio>...........................................................................................................................................279
1.4 EXEMPLE N° 4......................................................................................................................................................................... 280
1.4.1 L'APPLICATION....................................................................................................................................................................... 280
1.4.2 LE PROJET NETBEANS............................................................................................................................................................. 281
1.4.3 LA PAGE [FORM.JSP] ET SON MODÈLE [FORM.JAVA]..................................................................................................................... 282
1.4.4 LE FICHIER DES MESSAGES........................................................................................................................................................285
1.4.5 TESTS...................................................................................................................................................................................285
1.5 EXEMPLE N° 5......................................................................................................................................................................... 285
1.5.1 L'APPLICATION....................................................................................................................................................................... 285
1.5.2 LE PROJET NETBEANS............................................................................................................................................................. 287
1.5.3 LES PAGES [FORM.JSP] ET LEUR MODÈLE [FORM.JAVA].................................................................................................................288
1.5.3.1 Le code des pages Jsp..................................................................................................................................................... 288
1.5.3.2 Durée de vie du modèle [Form.java] des pages [form*.jsp]........................................................................................... 290
1.5.4 GESTION DES EXCEPTIONS........................................................................................................................................................ 292
1.5.4.1 Configuration de l'application web pour la gestion des exceptions................................................................................ 292
1.5.4.2 La simulation de l'exception........................................................................................................................................... 293
1.5.4.3 Les informations liées à une exception........................................................................................................................... 294
1.5.4.4 La page d'erreur [exception.jsp]......................................................................................................................................294
1.5.5 LOCALISATION DES PAGES JSF...................................................................................................................................................298
1.5.6 CONCLUSION..........................................................................................................................................................................299
1.6 EXEMPLE N° 6......................................................................................................................................................................... 300
1.6.1 L'APPLICATION....................................................................................................................................................................... 300
1.6.2 LE PROJET NETBEANS............................................................................................................................................................. 301
1.6.3 LA PAGE [FORM.JSP] ET SON MODÈLE [FORM.JAVA]..................................................................................................................... 301
1.6.4 L'ENVIRONNEMENT DE L'APPLICATION........................................................................................................................................ 303
1.6.5 LES DIFFÉRENTES SAISIES DU FORMULAIRE.................................................................................................................................. 304
1.6.5.1 Saisies 1 à 4 : saisie d'un nombre entier..........................................................................................................................304
1.6.5.2 Saisies 5 et 6 : saisie d'un nombre réel............................................................................................................................310
1.6.5.3 Saisie 7 : saisie d'un booléen...........................................................................................................................................311
1.6.5.4 Saisie 8 : saisie d'une date............................................................................................................................................... 311
1.6.5.5 Saisie 9 : saisie d'une chaîne de longueur contrainte...................................................................................................... 312
1.6.5.6 Saisie 10 : écrire une méthode de validation spécifique................................................................................................. 313
1.6.5.7 Saisies 11 et 12 : validation d'un groupe de composants................................................................................................ 316
1.6.5.8 POST d'un formulaire sans vérification des saisies........................................................................................................ 319
1.7 EXEMPLE N° 7......................................................................................................................................................................... 321
1.7.1 L'APPLICATION....................................................................................................................................................................... 322
1.7.2 LE PROJET NETBEANS............................................................................................................................................................. 322
1.7.3 LE FORMULAIRE [FORM.JSP]..................................................................................................................................................... 322
1.7.4 LE MODÈLE [FORM.JAVA]........................................................................................................................................................324
1.7.5 LE BOUTON [RAZ]..................................................................................................................................................................326
1.8 COMPLÉMENTS ....................................................................................................................................................................... 330

Introdution à Java EE
334/334

S-ar putea să vă placă și