Sie sind auf Seite 1von 339

Introduction à Java EE 5

avec Netbeans 6.8


et le serveur d'applications Glassfish V3

serge.tahe at istia.univ-angers.fr
juin 2010

http://tahe.developpez.com/java/javaee
1/339
INTRODUCTION

Ce document reprend un précédent document écrit en 2007 et intitulé "Introduction à Java EE avec Netbeans 5.5.1". Celui-ci
intitulé "Introduction à Java EE 5 avec Netbeans 6.8 et le serveur Glassfish v3" adapte le document précédent à Netbeans 6.8. Les
copies d'écran ne convenaient plus et Netbeans 6.8 amène avec lui des assistants qui n'existaient pas avec Netbean 5.5.1. En-dehors
du changement d'IDE, les deux documents sont semblables.

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 a alors été utilisé pour désigner
l'ensemble des technologies qui concourent à créer une application d'entreprise avec la plate-forme Java. Au moment de la mise à
jour de ce document, la dernière version de Java EE est Java EE 6.

Les livres d'Antonio Goncalves :

• Java EE 5 aux éditions Eyrolles


• Beginning Java EE 6 Platform with Glassfish 3 aux éditions Apress

sont d'excellents livres pour découvrir les technologies de Java EE 5 et Java EE 6. Toutes les technologies importantes de Java EE
y sont passées en revue dans le contexte d'études de cas réalistes. L'auteur a un site [http://www.antoniogoncalves.org] que le
lecteur est invité à visiter.

Le document présent étudie certaines des technologies de Java EE 5. 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/EclipseLink : 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 les livres 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é en 5ième année de l'école d'ingénieurs ISTIA 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 6.8. Netbeans est un produit assez lourd : prévoir 1 Go de
Ram pour travailler confortablement. On peut le télécharger à l'url [http://www.netbeans.org/].

http://tahe.developpez.com/java/javaee
2/339
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)
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].

Serge Tahé, juin 2010.

http://tahe.developpez.com/java/javaee
3/339
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

http://tahe.developpez.com/java/javaee
4/339
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.

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 : Hibernate, Toplink,
EclipseLink, OpenJpa, .... Avec JPA, l'architecture précédente devient la suivante :

4
Couche d'accès aux Objets image Interface Implémentation JPA Couche Base de
données [dao] de la BD [JPA] [Hibernate / ...] [JDBC] Données
3 5 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/EclipseLink. Par ailleurs
nous utiliserons le framework Spring 2.8 pour lier ces couches entre-elles.

4
Couche Couche Objets image Interface Implémentation JPA Couche
[metier] [dao] de la BD [JPA] [EclipseLink [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)
:

http://tahe.developpez.com/java/javaee
5/339
4
Couche Couche Objets image Interface Implémentation JPA Couche
[metier] [dao] de la BD [JPA] [EclipseLink [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 ou OpenEJB.

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 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 6.8 et le serveur d'application Glassfish v3.

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, EclipseLink, 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.
• 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 JPA Couche
[web] [metier] [dao] de la BD [JPA] [EclipseLink [JDBC]
1 2 / Hibernate]
3 6
5
7 Spring ou Ejb3

http://tahe.developpez.com/java/javaee
6/339
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
CODEPOSTAL son code postal
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 : sécurité sociale, veuvage, vieillesse
RETRAITE pourcentage : retraite complémentaire + assurance chômage

Son contenu pourrait être le suivant :

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
ENTRETIENJOUR indemnité d’entretien en euro par jour de garde
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 :

http://tahe.developpez.com/java/javaee
7/339
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 :
Au final, le salaire net à payer à l'assistante [SALAIREBASE]- [salaire NET]=368,77
[COTISATIONSSOCIALES]+
maternelle est le suivant : [INDEMNITÉS]

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 :

http://tahe.developpez.com/java/javaee
8/339
1 2

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


• le dossier [lib] (2) contient les bibliothè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 pam-spring-ui-metier-dao-jpa-eclipselink.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

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 :

http://tahe.developpez.com/java/javaee
9/339
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Appel avec des données erronées :

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.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 pam-spring-ui-metier-dao-jpa-eclipselink.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

• 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 :

http://tahe.developpez.com/java/javaee
10/339
4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [EclipseLink/ [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 / EclipseLink. 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 [pam-jpa-hibernate-tools] suivant :

4
7
5
1 3 6

8
2

• 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-bd] contient les fichiers [persistence.xml] pour divers SGBD
• [6, 8] : le dossier [conf-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_hibernate, propriétaire de la connexion : dbpam,
mot de passe : dbpam].

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

http://tahe.developpez.com/java/javaee
11/339
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 6.8 (http://www.netbeans.org/). L'objectif de ces tests est de générer la base de données MySQL à partir de :

• la définition des entités JPA


• la définition du fichier [persistence.xml] qui définit l'implémentation JPA utilisée ainsi que l'url de la base de données dont
la couche JPA est l'image. Nous utiliserons deux implémentations JPA : Hibernate et EclipseLink.

4.1 Implémentation JPA / Hibernate


Nous allons utiliser l'architecture suivante pour créer la base de données à partir des entités JPA :

Script ant de Interface Implémentation Couche


génération de la [JPA] [Hibernate] [JDBC]
base de données

Les entités JPA et le fichier [persistence.xml] qui configure l'implémentation JPA et l'accès à la base de données sont ceux définis
précédemment. Pour créer l'environnement de test, on pourra procéder comme suit :

• créer le projet Netbeans :

3
2

http://tahe.developpez.com/java/javaee
12/339
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 divers
dossiers :

http://tahe.developpez.com/java/javaee
13/339
1 4

5 8
6
7

• en [1] : l'emplacement du dossier [lib] qui contient les classes externes nécessaires au projet. Il est au même niveau que les
dossiers des projets Netbeans qui vont être créés dans ce document.
• en [2] : les six sous-dossiers du dossier [lib]
• en [3] : le dossier [divers] contient les pilotes Jdbc des SGBD testés, l'archive log4j qui gère les logs de l'application
• en [4] : le dossier [hibernate-tools] contient les classes de l'implémentation JPA / Hibernate
• en [5] : le dossier [eclipselink] contient les classes de l'implémentation JPA / EclipseLink 2.0
• en [6] : le dossier [openejb] contient les classes du conteneur Ejb OpenEJB 3.1
• en [7] : le dossier [spring] contient les classes du framework Spring 2.8
• en [8] : le dossier [client-glassfish-v3] contient les classes nécessaires à la communication entre un client distant et un EJB
déployé sur un serveur Glassfish v3

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

3
1

4 6

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

http://tahe.developpez.com/java/javaee
14/339
• 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] : on précise que les bibliothèques du projet doivents être référencées par leur chemin relatif au chemin du projet
plutôt que par leur chemin absolu. Cela permet de déplacer le projet et ses bibliothèques ailleurs dans le système de
fichiers.
• en [5] : valider la sélection
• on fait ce processus pour les dossiers [divers, hibernate-tools]. [divers] nous apporte les pilotes Jdbc dont nous aurons
besoin pour travailler avec divers SGBD. [hibernate-tools] nous apporte les archives de l'implémentation JPA / Hibernate
ainsi que l'archive [hibernate-tools] dont nous aurons besoin dans un script ant.

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

• [2] : 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 :

2
1b 1

3a

3b 7
6

http://tahe.developpez.com/java/javaee
15/339
• [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 [3a,3b] porte le nom du projet.
• [6] 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.7.1
3. Created-By: 10.0-b23 (Sun Microsystems Inc.)
4. Main-Class: main.Main
5. Class-Path: lib/derbyclient.jar lib/hsqldb.jar lib/jaybird-full-2.1.1. jar lib/log4j-1.2.13.jar
lib/mysql-connector-java-5.0.5-bin.jar lib/o
6. jdbc14.jar lib/postgresql-8.2-505.jdbc3.jar lib/sqljdbc.jar lib/antlr -2.7.6.jar ...
7. 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>\pam-jpa-hibernate-tools.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.6] de [ref1].

Le script [ant-hibernate.xml] (8) va permettre de générer la base de données 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]. Pour cela, nous créons le dossier [ddl] [9].

Le script [ant-hibernate.xml] 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" />

http://tahe.developpez.com/java/javaee
16/339
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-10 :
• 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 jar 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

http://tahe.developpez.com/java/javaee
17/339
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_hibernate] ainsi qu'un utilisateur
[dbpam / dbpam] ayant tous les droits sur la base [dbpam_hibernate] (cf paragraphe 5.5 de [ref1])
3. compiler le projet Netbeans :

1
2

4. dans l'onglet [Files] de 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)
4. alter table employes
5. drop
6. foreign key FK4722E6BC73F24A67;
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. SECU double precision not null,
13. RETRAITE double precision not null,
14. CSGD double precision not null,
15. CSGRDS double precision not null,
16. VERSION integer not null,
17. primary key (ID)
18. ) ENGINE=InnoDB;
19. create table employes (
20. ID bigint not null auto_increment,
21. PRENOM varchar(20) not null,
22. SS varchar(15) not null,
23. ADRESSE varchar(50) not null,
24. CP varchar(5) not null,
25. VILLE varchar(30) not null,
26. NOM varchar(30) not null,
27. VERSION integer not null,
28. INDEMNITE_ID bigint not null,
29. primary key (ID),
30. unique (SS)
31. ) ENGINE=InnoDB;
32. create table indemnites (
33. ID bigint not null auto_increment,
34. ENTRETIEN_JOUR double precision not null,
35. REPAS_JOUR double precision not null,
36. INDICE integer not null,
37. INDEMNITES_CP double precision not null,
38. BASE_HEURE double precision not null,
39. VERSION integer not null,
40. primary key (ID),
41. unique (INDICE)
42. ) ENGINE=InnoDB;
43. alter table employes

http://tahe.developpez.com/java/javaee
18/339
44. add index FK4722E6BC73F24A67 (INDEMNITE_ID),
45. add constraint FK4722E6BC73F24A67
46. foreign key (INDEMNITE_ID)
47. references indemnites (ID);
48. BUILD SUCCESSFUL (total time: 3 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-31 : la table [EMPLOYES]
• lignes 32-42 : la table [INDEMNITES]
• lignes 43-47 : la clé étrangère (INDEMNITE_ID) de la table [EMPLOYES] sur la colonne ID de la table
[INDEMNITES].

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

• dans l'onglet [1] [Services], 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] [2], puis l'url de la base de données [3].

Netbeans 6.8 est livré avec le pilote Jdbc de MySQL déjà configuré dans la branche [Databases / Drivers]. Pour l'exemple, ajoutons
à cette branche le pilote Jdbc du SGBD Firebird. Celui-ci se trouve dans le dossier [lib / divers].

http://tahe.developpez.com/java/javaee
19/339
1

4
3
2

5
6

• 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 Firebird est ajouté à la liste des pilotes Jdbc disponibles :

Maintenant, créons une connexion vers la base MySQL [dbpam_hibernate] dans laquelle le script [ant-hibernate.xml] a du créer des
tables issues de la configuration JPA du projet :

2
1 3
4
5
6

• en [1] : cliquer droit sur le pilote JDBC MySQL et choisir [Connect Using...] pour créer la connexion à la base MySQL5 /
dbpam_hibernate.
• en [2] : le nom de la machine sur laquelle se trouve le SGBD MySQL
• en [3] : le port d'écoute du SGBD
• en [4] : le nom de la base de données à laquelle on veut se connecter
• en [5] : le propriétaire de la connexion

http://tahe.developpez.com/java/javaee
20/339
• en [6] : son mot de passe [dbpam]
• en [7] : l'url Jdbc de la base de données
• faire [OK]

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

1 5

3
2

• 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 :

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

4.2 Implémentation JPA / EclipseLink


L'architecture de notre application devient la suivante :

http://tahe.developpez.com/java/javaee
21/339
Programme Interface Implémentation Couche
principal [JPA] [EclipseLink] [JDBC]
[Main]

Nous créons un nouveau projet [pam-jpa-eclipselink] [1, 2, 3, 4] :

5 6
4

• En [5], nous ajoutons au projet les entités JPA du projet précédent. Des erreurs apparaissent parce que notre projet n'a pas
défini de bibliothèques pour l'implémentation JPA.
• en [6], nous ajoutons les bibliothèques nécessaires au projet :
• celles de l'implémentation JPA EclipseLink qu'on trouve dans le dossier [lib/eclipselink]
• celle du pilote Jdbc du SGBD MySQL qu'on trouve dans le dossier [lib/divers]

Le projet évolue alors de la façon suivante [7] :

http://tahe.developpez.com/java/javaee
22/339
7

Il ne présente plus d'erreurs. Rappelons qu'une couche JPA est définie par deux éléments :
• des entités JPA - nous venons de les définir
• un fichier [META-INF/persistence.xml] qui définit l'implémentation JPA utilisée et les propriétés de celle-ci. Parmi ces
dernières, il y a les paramètres de connexion à la base de données.

Netbeans peut nous aider à définir le fichier [persistence.xml] en procédant de la façon suivante :

• créer une base de données MySQL [dbpam_eclipselink] de propriétaire [dbpam] avec le mot de passe [dbpam]. On
procèdera comme il a été fait pour la base [dbpam_hibernate].

Netbeans permet de générer le fichier [persistence.xml] pour une base existante et une implémentation JPA donnée. Tout d'abord,
nous créons dans l'onglet [Services] de Netbeans, une connexion à la base MySQL [dbpam_eclipselink]. On procèdera comme il a
été vu page 20 pour la connexion à la base [dbpam_hibernate]. On obtient le résultat suivant :

Pour l'instant, la base [dbpam_eclipselink] est sans tables. Une fois la connexion à la base définie, il est possible de générer le fichier
[persistence.xml] :

• en [2], clic droit sur le projet / New / Other

http://tahe.developpez.com/java/javaee
23/339
5

6
7
4
8
3

• choisir la catégorie [Persistence] [3] et le type [Persistence Unit] [4] - faire [Next]
• en [5] le nom de l'unité de persistence. Ici, il est généré automatiquement par Netbeans. On peut le changer.
• en [6] choisir l'implémentation JPA désirée. Ici, on choisit EclipseLink
• en [7] désigner la connexion Netbeans reliée à la base de données cible de la couche JPA. Ici, nous désignons la connexion
à la base [dbpam_eclipselink] que nous venons de créer.
• en [8] indiquer la stratégie de génération des tables de la base de données lorsque la couche JPA est construite :
• None : rien n'est fait
• Drop and Create : la couche JPA supprime les tables existantes (Drop) et les recrée (Create)
• Create : la couche JPA crée les tables (Create). Si elles existent déjà, les ordres SQL CREATE échouent mais ne
provoquent pas de plantage.
Ici, nous choisissons le mode [Create].

Lorsqu'on termine l'assistant de création du fichier [persistence.xml], le projet évolue de la façon suivante :

• en [1], le fichier [persistence.xml] généré


• en [2], les bibliothèques d'EclipseLink ont été rajoutées. Elles sont désormais présentes deux fois. On peut supprimer l'une
des deux versions. Nous gardons celle référençant les bibliothèques du dossier [lib/eclipselink] [3].

Double-cliquons sur le fichier [persistence.xml]. Celui-ci peut être vu selon deux modes :

• [1] : mode texte [XML]


• [2] : mode [Design]

http://tahe.developpez.com/java/javaee
24/339
Prenons le mode [XML]. Le fichier [persistence.xml] est le suivant :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
4. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
5. <properties>
6. <property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
7. <property name="javax.persistence.jdbc.password" value="dbpam"/>
8. <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
9. <property name="javax.persistence.jdbc.user" value="dbpam"/>
10. <property name="eclipselink.ddl-generation" value="create-tables"/>
11. </properties>
12. </persistence-unit>
13. </persistence>

• ligne 3 : le nom de l'unité de persistance et son mode de transaction. Ici RESOURCE_LOCAL indique que c'est le code
de l'utilisateur qui a la charge de les gérer.
• ligne 4 : l'implémentation JPA utilisée, ici EclipseLink
• lignes 5-11 : les propriétés (paramètres) de l'implémentation JPA
• ligne 6 : l'url Jdbc de la base de données cible
• ligne 7 : le propriétaire des connexions qui seront faites sur la base
• ligne 9 : son mot de passe
• ligne 8 : le pilote Jdbc du SGBD cible MySQL
• ligne 10 : la stratégie de création des tables de la base lorsque la couche JPA est instanciée

En mode [Design], on a la vue suivante :

Pa défaut, la couche JPA gèrera toutes les classes ayant l'annotation @Entity [1]. On peut aussi déclarer explicitement ces classes
avec le bouton [2] :

http://tahe.developpez.com/java/javaee
25/339
4
3

• en [3], on désigne les entités JPA qui apparaissent alors en [4]

Le fichier [persistence.xml] évolue de la façon suivante dans la vue texte :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
4. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. <properties>
9. ...
10. </properties>
11. </persistence-unit>
12. </persistence>

Les entités JPA gérées par la couche JPA ont été définies aux lignes 5-7 par une balise <class>.

La couche JPA étant définie, nous allons l'instancier à partir de la classe principale du projet [main.Main] :

La classe [Main.java] sera la suivante :

1. package main;
2.
3. import javax.persistence.EntityManager;
4. import javax.persistence.EntityManagerFactory;
5. import javax.persistence.Persistence;
6.
7. public class Main {
8.
9. public static void main(String[] args){
10. // créer l'Entity Manager suffit à construire la couche JPA
11. EntityManagerFactory emf=Persistence.createEntityManagerFactory("pam-jpa-eclipselinkPU");
12. EntityManager em=emf.createEntityManager();
13. // libération des ressources
14. em.close();
15. emf.close();
16. // fin
17. System.out.println("terminé");
18. }
19. }

• ligne 11 : on crée un EntityManagerFactory à partir de l'unité de persistance [pam-jpa-eclipselinkPU] qui vient d'être créé.
Ce nom est celui de l'attribut name de la balise <persistence-unit>.

http://tahe.developpez.com/java/javaee
26/339
• ligne 12 : on crée l'objet EntityManager qui est le gestionnaire des entités JPA. La création de cet objet instancie la couche
JPA. Le fichier [persistence.xml] est exploité. Parce que nous avons écrit :

<property name="eclipselink.ddl-generation" value="create-tables"/>

la couche JPA va créer les tables images des entités JPA. Ainsi après exécution devrions-nous voir des tables dans la base
de données. Essayons.

2
3

• en [1], la classe [Main] est exécutée


• en [2], dans l'onglet [Services], nous rafraîchissons la base de données [dbpam_eclipselink]
• en [3], ce rafraîchissement fait apparaître 4 tables : une pour chacune des entités JPA et une quatrième appelée [sequence]
utilisée pour générer automatiquement les clés primaires des trois autres tables. On découvre ainsi, que selon
l'implémentation Hibernate ou EclipseLink, la base de données générée n'a pas été la même.

Travail pratique :

Refaire le travail précédent avec d'autres SGBD : Oracle XE, SQL Server Express, Firebird, Apache Derby, HSQL, ...

Revenons à l'architecture désirée pour le projet final :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [JDBC]
1 2 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].

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 :

1. dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.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

http://tahe.developpez.com/java/javaee
27/339
10. ...
11.
12. Informations Cotisations :
13. CSGRDS : 3.49 %
14. ...
15.
16. Informations Indemnités :
17. ...
18.
19. Informations Salaire :
20. Salaire de base : 362.25 euro
21. Cotisations sociales : 97.48 euro
22. Indemnités d'entretien : 42.0 euro
23. Indemnités de repas : 62.0 euro
24. 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]
• 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]

http://tahe.developpez.com/java/javaee
28/339
• 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
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] :

http://tahe.developpez.com/java/javaee
29/339
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.

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

http://tahe.developpez.com/java/javaee
30/339
• 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
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.

http://tahe.developpez.com/java/javaee
31/339
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. }

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

http://tahe.developpez.com/java/javaee
32/339
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;
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]

http://tahe.developpez.com/java/javaee
33/339
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] [Hibernate] [JDBC]
1 2 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] :

http://tahe.developpez.com/java/javaee
34/339
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.Par ailleurs, l'implémentation JPA utilisée sera Hibernate.

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] [Hibernate] [JDBC]
2 3 6
5
7 Spring

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.

1
3
2

http://tahe.developpez.com/java/javaee
35/339
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]. Ici la bibliothèque JUnit est présente par défaut en deux versions.

Les classes de tests utilisent l'outil de tests unitaires JUnit :


• [JUnitInitDB] ne fait aucun test. Elle remplit la base de données avec quelques enregistrements et affiche ensuite ceux-ci
sur la console.
• [JUnitDao] 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. @Before()
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 @Before). Elle vide la base de données.
• la méthode [initDB] est un test (annotation @Test). C'est le seul. Un test doit contenir des instructions d'assertion
Assert.assertCondition. 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

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

http://tahe.developpez.com/java/javaee
36/339
4
2

3
5 7
1

6
8

• les classes [1] et les fichiers de configuration [2, 3] de la couche [dao] sont mis en place
• en [4], on ajoute les bibliothèques suivantes au projet :
• celles d'Hibernate qu'on trouve dans [lib/hibernate-tools]
• celles de Spring qu'on trouve dans [lib/spring]
• celle du pilote Jdbc de MySQL qu'on trouve dans [lib/divers]
• en [5] les bibliothèques rajoutées au projet
• le SGBD MySQL5 est lancé. Il doit avoir la base [dbpam_hibernate] avec un utilisateur [dbpam/dbpam] ayant tous les
privilèges dessus.
• le projet est construit [6]
• la classe [JUnitInitDB] est exécutée [7]
• la fenêtre [Test Results] [8] dit que les tests ont été réussis. Ce message n'est pas significatif ici, car le programme
[JUnitInitDB] ne contient aucune instruction d'assertion Assert.assertCondition, 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] [9] contient les logs de l'exécution, ceux de Spring et ceux du test lui-même. Les affichages faits par la classe
JUnitInitDB sont les suivants :

1. ------------- Standard Output ---------------


2. Employés ----------------------
3. jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]
4. jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code postal=49014,indice=1]
5. Indemnités ----------------------
6. jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités
CP=12.0]
7. jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités
CP=15.0]
8. Cotisations ----------------------
9. jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
10. ------------- ---------------- ---------------

Les tables [EMPLOYES, INDEMNITES, COTISATIONS] ont été remplies. On peut le vérifier avec la connexion Netbeans à la
base [dbpam_hibernate] décrite construite page 20.

http://tahe.developpez.com/java/javaee
37/339
2

1 3

• en [1], dans l'onglet [services], on visualise les données de la table [employes] de la connexion [dbpam_hibernate] [2]
• en [3] le résultat

7.3.2 Tests 2

7.3.2.1 JUnitDao

Nous nous intéressons maintenant à une seconde classe de tests [JUnitDao] :

Le squelette de la classe sera le suivant :

1. package dao;
2.
3. import exception.PamException;
4. ...
5.
6. public class JUnitDao {
7.
8. // couches dao
9. static private IEmployeDao employeDao;
10. static private IIndemniteDao indemniteDao;
11. static private ICotisationDao cotisationDao;
12.
13. @BeforeClass
14. public static void init() {
15. // log
16. log("init");
17. // configuration de l'application
18. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
19. // couches dao
20. employeDao = (IEmployeDao) ctx.getBean("employeDao");
21. indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
22. cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
23. }
24.
25. @AfterClass
26. public static void terminate() {
27. }
28.
29. @Before()
30. public void clean() {
31. ...
32. }
33.

http://tahe.developpez.com/java/javaee
38/339
34. // logs
35. private static void log(String message) {
36. System.out.println("----------- " + message);
37. }
38.
39. // tests
40. @Test
41. public void test01() {
42. log("test01");
43. // liste des cotisations
44. List<Cotisation> cotisations = cotisationDao.findAll();
45. int nbCotisations = cotisations.size();
46. // on ajoute une cotisation
47. Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
48. // on la demande
49. cotisation = cotisationDao.find(cotisation.getId());
50. // vérification
51. Assert.assertNotNull(cotisation);
52. Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
53. Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
54. Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
55. Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
56. // on la modifie
57. cotisation.setCsgrds(-1);
58. cotisation.setCsgd(-1);
59. cotisation.setRetraite(-1);
60. cotisation.setSecu(-1);
61. Cotisation cotisation2 = cotisationDao.edit(cotisation);
62. // vérifications
63. Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
64. Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
65. Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
66. Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
67. Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
68. // on demande l'élément modifié
69. Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
70. // vérifications
71. Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
72. Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
73. Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
74. Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
75. Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
76. // on supprime l'élément
77. cotisationDao.destroy(cotisation3);
78. // vérifications
79. Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
80. Assert.assertNull(cotisation4);
81. cotisations = cotisationDao.findAll();
82. Assert.assertEquals(nbCotisations, cotisations.size());
83. }
84.
85.
86. @Test
87. public void test02(){
88. log("test02");
89. // on demande la liste des indemnités
90. ...
91. // on ajoute une Indemnite indemnite
92. ..
93. // on va chercher indemnite en base – on récupère indemnite1
94. ..
95. // on vérifie que indemnite1 = indemnite
96. ...
97. // on modifie l'indemnité obtenue et on persiste la modification en BD. On obtient indemnite2
98. ...
99. // on vérifie la version de indemnite2
100. ...
101. // on va chercher indemnite2 en base – on obtient indemnite3
102. ...
103. // on vérifie que indemnite3 = indemnite2
104. ...
105. // on supprime en base l'image de indemnite3
106. ...
107. // on va chercher indemnite3 en base
108. ...
109. // on vérifie qu'on a obtenu une référence null
110. ...
111. }
112.
113. @Test
114. public void test03(){
115. log("test03");
116. // on répète un test analogue aux précédents pour Employe

http://tahe.developpez.com/java/javaee
39/339
117. ...
118. }
119.
120. @Test
121. public void test04(){
122. log("test04");
123. // on teste la méthode [IEmployeDao].find(String SS)
124. // d'abord avec un employé existant
125. // puis avec un employé inexistant
126. ...
127. }
128.
129. @Test
130. public void test05(){
131. log("test05");
132. // on crée deux indemnités avec le même indice
133. // enfreint la contrainte d'unicité de l'indice
134. // on vérifie qu'une exception de type PamException se produit
135. // et qu'elle a le n° d'erreur attendu
136. ...
137. }
138.
139. @Test
140. public void test06(){
141. log("test06");
142. // on crée deux employés avec le même n° SS
143. // enfreint la contrainte d'unicité sur le n° SS
144. // on vérifie qu'une exception de type PamException se produit
145. // et qu'elle a le n° d'erreur attendu
146. ...
147.
148. }
149.
150. @Test
151. public void test07(){
152. log("test07");
153. // on crée deux employés avec le même n° SS, le 1er avec create, le 2ème avec edit
154. // enfreint la contrainte d'unicité sur le n° SS
155. // on vérifie qu'une exception de type PamException se produit
156. // et qu'elle a le n° d'erreur attendu
157. ...
158. }
159.
160. @Test
161. public void test08(){
162. log("test08");
163. // supprimer un employé qui n'existe pas ne provoque pas d'exception
164. // il est ajouté puis détruit – on le vérifie
165. ...
166. }
167.
168. @Test
169. public void test09(){
170. log("test09");
171. // modifier un employé sans avoir la bonne version doit provoquer une exception
172. // on le vérifie
173. ...
174. }
175.
176. @Test
177. public void test10(){
178. log("test10");
179. // supprimer un employé sans avoir la bonne version doit provoquer une exception
180. // on le vérifie
181. ...
182.
183. }
184.
185. // getters et setters
186. ...
187. }

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.

http://tahe.developpez.com/java/javaee
40/339
7.3.2.2 Mise en oeuvre des tests

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

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


• en [2], les résultats des tests dans la fenêtre [Test Results]

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 = cotisationDao.findAll();
6. int nbCotisations = cotisations.size();
7. // on ajoute une cotisation
8. Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
9. // on la demande
10. cotisation = cotisationDao.find(cotisation.getId());
11. // vérification
12. Assert.assertNotNull(cotisation);
13. Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
14. Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
15. Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
16. Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
17. // on la modifie
18. ....
19. }

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 :

• la page 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.

http://tahe.developpez.com/java/javaee
41/339
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] [Hibernate] [JDBC]
1 2 3 6
5
7 Spring

8.1 L'interface Java [IMetier]


Celle-ci a été décrite au paragraphe 5, page 29. 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] :

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 28. 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,

http://tahe.developpez.com/java/javaee
42/339
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
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

http://tahe.developpez.com/java/javaee
43/339
• 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.
• 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 [JUnitMetier_1] pourrait être la suivante :

1. package metier;
2.

http://tahe.developpez.com/java/javaee
44/339
3. ...
4.
5. public class JUnitMetier_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));
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.assertCondition 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. Testsuite: metier.JUnitMetier_1
2. ----------- init
3. ....
4. [jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code
postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88
],jpa.Indemnite[id=29,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]]
5. [jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code
postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88
],jpa.Indemnite[id=30,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]]
6. PamException[Code=101, message=L'employé de n°[xx] est introuvable]
7. Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec

• ligne 4 : la feuille de salaire de Justine Laverti

http://tahe.developpez.com/java/javaee
45/339
• ligne 5 : la feuille de salaire de Marie Jouveinal
• ligne 6 : l'exception due au fait que l'employé de n° SS 'xx' n'existe pas.

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

La classe [JUnitMetier_2] pourrait être la suivante :

1. package metier;
2.
3. ...
4. public class JUnitMetier_2 {
5.
6. // couche métier
7. private IMetier metier;
8.
9. @BeforeClass
10. public void init(){
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 [JUnitMetier_2] est une copie de la classe [JUnitMetier_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 [JUnitMetier_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] [Hibernate] [JDBC]
1 2 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 :

http://tahe.developpez.com/java/javaee
46/339
9.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 8. 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 peu demander la feuille de salaire
45. FeuilleSalaire feuilleSalaire = null;
46. try {
47. // instanciation couche [metier]
48. ...
49. // calcul de la feuille de salaire
50. ...
51. } catch (PamException ex) {
52. System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
53. return;

http://tahe.developpez.com/java/javaee
47/339
54. } catch (Exception ex) {
55. System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
56. return;
57. }
58.
59. // affichage détaillé
60. String output = "Valeurs saisies :\n";
61. output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
62. output += ajouteInfo("Nombre d'heures travaillées", args[1]);
63. output += ajouteInfo("Nombre de jours travaillés", args[2]);
64. output += ajouteInfo("\nInformations Employé", "");
65. output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
66. output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
67. output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
68. output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
69. output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
70. output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
71. output += ajouteInfo("\nInformations Cotisations", "");
72. output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
73. output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
74. output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
75. output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
76. output += ajouteInfo("\nInformations Indemnités", "");
77. output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getIndemnite().getBaseHeure() + "
euro");
78. output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getIndemnite().getEntretienJour() +
" euro");
79. output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getIndemnite().getRepasJour() + "
euro");
80. output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getIndemnite().getIndemnitesCP()+ "
%");
81. output += ajouteInfo("\nInformations Salaire", "");
82. output += ajouteInfo("Salaire de base", ""+
feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
83. output += ajouteInfo("Cotisations sociales", ""+
feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
84. output += ajouteInfo("Indemnités d'entretien", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
85. output += ajouteInfo("Indemnités de repas", ""+
feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
86. output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() +
" euro");
87.
88. System.out.println(output);
89. }
90.
91. static String ajouteInfo(String message, String valeur) {
92. return message + " : " + valeur + "\n";
93. }
94. }

Question : compléter le code ci-dessus.

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

http://tahe.developpez.com/java/javaee
48/339
2

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

http://tahe.developpez.com/java/javaee
49/339
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] [Hibernate] [JDBC]
1 2 3 6
5
7 Spring

http://tahe.developpez.com/java/javaee
50/339
• 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 :

1
2
3

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


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

9
7

• [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.

http://tahe.developpez.com/java/javaee
51/339
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].

15

14
13

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

17
18
16

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

http://tahe.developpez.com/java/javaee
52/339
19

20 21

• 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 :

http://tahe.developpez.com/java/javaee
53/339
1

• en [1], l'interface graphique


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

JLabel1

JPanel1

JPanel2

http://tahe.developpez.com/java/javaee
54/339
JPanel3

JPanel4

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 :

http://tahe.developpez.com/java/javaee
55/339
Le gestionnaire du clic sur le bouton [JButtonSalaire] est généré :

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


2. // TODO add your handling code here:
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.setText("Salaire");
2. jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
3. public void actionPerformed(java.awt.event.ActionEvent evt) {
4. jButtonSalaireActionPerformed(evt);
5. }
6. });

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

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 :

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 :

http://tahe.developpez.com/java/javaee
56/339
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 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.
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 :

http://tahe.developpez.com/java/javaee
57/339
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].

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 :

http://tahe.developpez.com/java/javaee
58/339
1

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

http://tahe.developpez.com/java/javaee
59/339
11 Implémentation de la couche JPA avec EclipseLink
Nous nous intéressons à l'architecture suivante où la couche JPA est désormais implémentée par EclipseLink :

4
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [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 :

4 2
3
5

http://tahe.developpez.com/java/javaee
60/339
• en [1] : après un clic droit sur le projet Hibernate, choisir Copy
• à 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

1
3

• en [1], le nouveau projet a été créé


• en [2], on en fait le projet principal

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

1. en [3], les fichiers de configuration de Spring doivent être modifiés. On y trouve en effet la configuration de la couche
JPA.
2. en [4], 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 :

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]. On gardera les bibliothèques nécessaires à l'interface graphique
[Absolute Layout, Swing Layout Extensions].

http://tahe.developpez.com/java/javaee
61/339
4
7

5
6

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


• en [5], sélectionner le .jar du pilote Jdbc de MySQL dans [divers] et tous les .jar des dossiers [spring, eclipselink]. Le
contenu de ces dossiers a été expliqué au paragraphe 4.1, page 13.
• en [6], la liste finale des .jar avec en [7], les 2 archives de l'implémentation Jpa / EclipseLink.

Les fichiers de configuration de Spring doivent être modifiés pour indiquer que l'implémentation JPA a changé. Dans les deux
fichiers, seule la section configurant la couche JPA change. Par exemple dans [spring-config-metier-dao.xml] on a :

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.
18. <!-- configuration JPA -->
19. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
20. <property name="dataSource" ref="dataSource" />
21. <property name="jpaVendorAdapter">
22. <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
23. <!--
24. <property name="showSql" value="true" />
25. -->
26. <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
27. <property name="generateDdl" value="true" />
28. <!--
29. <property name="generateDdl" value="true" />
30. -->
31. </bean>
32. </property>
33. <property name="loadTimeWeaver">
34. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
35. </property>
36. </bean>
37.
38. <!-- la source de donnéees DBCP -->
39. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
40. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
41. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
42. <property name="username" value="dbpam" />
43. <property name="password" value="dbpam" />
44. </bean>
45. ....
46. </beans>

Les lignes 19-36 configurent la couche JPA. L'implémentation JPA utilisée est Hibernate (ligne 22). Par ailleurs, la base de données
cible est [dbpam_hibernate] (ligne 41).

http://tahe.developpez.com/java/javaee
62/339
Pour passer à une implémentation JPA / EclipseLink, les lignes 19-35 ci-dessus sont remplacées par les lignes ci-dessous :

1. <!-- configuration JPA -->


2. <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
3. <property name="dataSource" ref="dataSource" />
4. <property name="jpaVendorAdapter">
5. <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
6. <!--
7. <property name="showSql" value="true" />
8. -->
9. <property name="databasePlatform"
value="org.eclipse.persistence.platform.database.MySQLPlatform" />
10. <!--
11. <property name="generateDdl" value="true" />
12. -->
13. </bean>
14. </property>
15. <property name="loadTimeWeaver">
16. <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
17. </property>
18. </bean>

• ligne 5 : l'implémentation JPA utilisée est EclipseLink


• ligne 9 : la propriété databasePlatform fixe le SGBD cible, ici MySQL
• ligne 11 : pour générer les tables de la base de données lorsque la couche JPA est instanciée. Ici, la propriété est en
commentaires.
• ligne 7 : pour visualiser sur la console les ordres SQL émis par la couche JPA. Ici, la propriété est en commentaires.

Par ailleurs, la base de données cible devient [dbpam_eclipselink] (ligne 4 ci-dessous) :

1. <!-- la source de donnéees DBCP -->


2. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
3. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
4. <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
5. <property name="username" value="dbpam" />
6. <property name="password" value="dbpam" />
7. </bean>

11.2 Mise en oeuvre des tests


Avant de tester l'application entière, il est bon de vérifier si les tests JUnit 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_eclipselink / MySQL5 comme expliqué au paragraphe 4.1, page 20. Une fois
connecté à la base dbpam_eclipselink / MySQL5, on pourra procéder à la suppression des tables comme montré ci-dessous :

• [1] : avant la suppression


• [2] : après la suppression

http://tahe.developpez.com/java/javaee
63/339
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, il faut s'assurer que dans la configuration Jpa / EclipseLink de Spring la ligne :

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

existe et n'est pas mise en commentaires.

Nous construisons le projet (Build) puis nous exécutons le test [JUnitInitDB] :

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

Caused by: 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.

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 / EclipseLink 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:\...\lib\spring\spring-agent.jar

[spring-agent.jar] est l'agent Java dont a besoin la Jvm pour gérer la configuration Spring / EclipeLink. Il a été placé dans
le dossier [lib / spring].

Lorsqu'on exécute un projet, il est possible de passer des arguments à la Jvm :

http://tahe.developpez.com/java/javaee
64/339
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

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 2 3

• en [1], le test a été réussi


• en [2], dans l'onglet [Services], on rafraîchit la connexion qu'a Netbeans avec la base [dbpam_eclipselink]
• en [3], quatre tables ont été créées

http://tahe.developpez.com/java/javaee
65/339
5

• en [5], on visualise le contenu de la table [employes]


• en [6], le résultat.

11.3.2 JUnitDao

L'exécution de la classe de tests [JUnitDao] 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
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.

http://tahe.developpez.com/java/javaee
66/339
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
2. 4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
3. ATTENTION: SQL Error: 1062, SQLState: 23000
4. 4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
5. GRAVE: Duplicate entry '1' for key 2
6. Chaîne des exceptions --------------------------------------
7. exception.PamException
8. javax.persistence.EntityExistsException
9. org.hibernate.exception.ConstraintViolationException

http://tahe.developpez.com/java/javaee
67/339
10. com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Le test passe, c.a.d. que les assertions sont vérifiées et 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 / EclipseLink sont les suivants :

1. ----------- test05
2. [EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Exception [EclipseLink-4002] (Eclipse
Persistence Services - 2.0.0.v20091127-r5931):
org.eclipse.persistence.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, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE,
VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
6. bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
7. Query: InsertObjectQuery(jpa.Indemnite[id=108,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.TransactionSystemException
10. javax.persistence.RollbackException
11. org.eclipse.persistence.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 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 EclipseLink à 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 / EclipseLink lui, lance une exception de type [TransactionSystemException], 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.

http://tahe.developpez.com/java/javaee
68/339
12 Portage d'une architecture Spring / Jpa vers une architecture
OpenEJB / Jpa

12.1 Introduction aux principes du portage


Nous présentons ici les principes qui vont gouverner le portage d'une application Jpa / Spring / Hibernate vers une application
Jpa / OpenEJB / EclipseLink. La mise en oeuvre pratique du portage est décrite au paragraphe 12.2, page 74.

12.1.1 Les deux architectures

L'implémentation actuelle avec Spring / Hibernate

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
2

7 1 Spring

L'implémentation à construire avec OpenEjb / Eclipselink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [JDBC]
2

1 OpenEjb

12.1.2 Les bibliothèques des projets

• les couches [dao] et [metier] ne sont plus instanciées par Spring. Elles le sont par le conteneur OpenEjb.
• les bibliothèques du conteneur Spring et sa configuration disparaissent au profit des bibliothèques du conteneur OpenEjb
et sa configuration.
• les bibliothèques de la couche JPA / Hibernate sont remplacées par celles de la couche JPA / EclipseLink

12.1.3 Configuration de la couche JPA / EclipseLink / OpenEjb

• le fichier [META-INF/persistence.xml] configurant la couche JPA devient le suivant :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
4. <!-- entités Jpa -->
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. <!-- le fournisseur JPA est EclipseLink -->
9. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
10. <!-- propriétés provider -->
11. <properties>
12. <property name="eclipselink.ddl-generation" value="create-tables"/>

http://tahe.developpez.com/java/javaee
69/339
13. </properties>
14. </persistence-unit>
15. </persistence>

• ligne 3 : les transactions dans un conteneur Ejb sont de type JTA (Java Transaction Api). Elles étaient de type
RESOURCE_LOCAL avec Spring.
• ligne 9 : l'implémentation JPA utilisée est EclipseLink
• lignes 5-7 : les entités gérées par la couche Jpa
• lignes 11-13 : propriétés du provider Eclipselink
• ligne 12 : à chaque exécution, les tables seront créées

Les caractéristiques Jdbc de la source de données JTA utilisée par le conteneur OpenEjb seront précisées par le fichier de
configuration [conf/openejb.conf] suivant :

1. <?xml version="1.0"?>
2. <openejb>
3. <Connector id="Default JDBC Database">
4. JdbcDriver com.mysql.jdbc.Driver
5. JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
6. UserName dbpam
7. Password dbpam
8. </Connector>
9. </openejb>

• ligne 3 : on utilise l'id Default JDBC Database lorsqu'on travaille avec un conteneur OpenEjb embarqué (embedded)
dans l'application elle-même.
• ligne 5 : nous utilisons une base MySQL [dbpam_eclipselink]

12.1.4 Implémentation de la couche [dao] par des Ejb

• Les classes implémentant la couche [dao] devient des Ejb. Prenons l'exemple de la classe [CotisationDao] :

L'interface [ICotisationDao] dans la version Spring était 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. Cotisation create(Cotisation cotisation);
9. // modifier une cotisation existante
10. Cotisation edit(Cotisation cotisation);
11. // supprimer une cotisation existante
12. void destroy(Cotisation cotisation);
13. // chercher une cotisation particulière
14. Cotisation find(Long id);
15. // obtenir tous les objets Cotisation
16. List<Cotisation> findAll();
17.
18. }

L'Ejb va implémenter cette même interface sous deux formes différentes : une locale et une distante. L'interface locale
peut être utilisée par un client s'exécutant dans la même JVM, l'interface distante par un client s'exécutant dans une autre
JVM.

L'interface locale :

1. package dao;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface ICotisationDaoLocal extends ICotisationDao{
7. }

• ligne 6 : l'interface [ICotisationDaoLocal] hérite de l'interface [ICotisationDao] pour en reprendre toutes les méthodes.
Elle n'en ajoute pas de nouvelles.
• ligne 5 : l'annotation @Local en fait une interface locale pour l'Ejb qui l'implémentera.

http://tahe.developpez.com/java/javaee
70/339
L'interface distante :

1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }

• ligne 6 : l'interface [ICotisationDaoRemote] hérite de l'interface [ICotisationDao] pour en reprendre toutes les méthodes.
Elle n'en ajoute pas de nouvelles.
• ligne 5 : l'annotation @Remote en fait une interface distante pour l'Ejb qui l'implémentera.

La couche [dao] est implémentée par un Ejb implémentant les deux interfaces (ce n'est pas obligatoire) :

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

• ligne 1 : l'annotation @Stateless qui fait de la classe un Ejb


• ligne 2 : l'annotation @TransactionAttribute qui fait que chaque méthode de la classe s'exécutera au sein d'une
transaction.
• ligne 5 : l'annotation @PersistenceContext qui injecte dans la classe [CotisationDao] l'EntityManager de la couche JPA.
Elle est identique à ce qu'on avait dans la version Spring.

Lorsque l'interface locale de la couche [dao] est utilisée, le client de cette interface s'exécute 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

Ci-dessus, les couches [metier] et [dao] échangent des objets par référence. Lorsqu'une couche change l'objet partagé,
l'autre couche voit ce changement.

Lorsque l'interface distante de la couche [dao] est utilisée, le client de cette interface s'exécute habituellement dans une
autre JVM.

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, les couches [metier] et [dao] échangent des objets par valeur (sérialisation de l'objet échangé). Lorsqu'une
couche change un objet partagé, l'autre couche ne voit ce changement que si l'objet modifié lui est renvoyé.

12.1.5 Implémentation de la couche [metier] par un Ejb

• La classe implémentant la couche [metier] devient elle aussi un Ejb implémentant une interface locale et distante.
L'interface initiale [IMetier] était la suivante :

1. package metier;
2.

http://tahe.developpez.com/java/javaee
71/339
3. import java.util.List;
4. import jpa.Employe;
5.
6. public interface IMetier {
7. // obtenir la feuille de salaire
8. FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int
nbJoursTravaillés );
9. // liste des employés
10. List<Employe> findAllEmployes();
11. }

On crée une interface locale et une interface distante à partir de l'interface précédente :

1. package metier;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface IMetierLocal extends IMetier{
7. }

1. package metier;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface IMetierRemote extends IMetier{
7. }

L'Ejb de la couche [metier] implémente ces deux interfaces :

1. @Stateless()
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class Metier implements IMetierLocal, IMetierRemote {
4.
5. // référence sur les couches [dao] locales
6. @EJB
7. private ICotisationDaoLocal cotisationDao = null;
8. @EJB
9. private IEmployeDaoLocal employeDao = null;
10. @EJB
11. private IIndemniteDaoLocal indemniteDao = null;

• lignes 1-2 : définissent un Ejb dont chaque méthode s'exécute dans une transaction.
• ligne 7 : une référence sur l'interface locale de l'Ejb [CotisationDao].
• ligne 6 : l'annotation @EJB demande à ce que le conteneur Ejb injecte une référence sur l'interface locale de l'Ejb
[CotisationDao].
• lignes 8-11 : on refait la même chose pour les interfaces locales des Ejb [EmployeDao] et [IndemniteDao].

Au final, lorsque l'Ejb [Metier] sera instancié, les champs des lignes 7, 9 et 11 seront initialisés avec des références sur les
interfaces locales des trois Ejb de la couche [dao]. On fait donc ici l'hypothèse que les couches [metier] et [dao]
s'exécuteront 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

12.1.6 Les clients des Ejb

http://tahe.developpez.com/java/javaee
72/339
1

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 le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface
distante de l'Ejb de la couche [metier].

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


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

JVM

Dans le schéma ci-dessus, pour communiquer avec la couche [metier], la couche [ui] doit obtenir une référence sur l'interface
locale de l'Ejb de la couche [metier]. La méthode pour obtenir ces références diffère d'un conteneur à l'autre. Pour le conteneur
OpenEjb, on pourra procéder comme suit :

Référence sur l'interface locale :

1. // on configure le conteneur Open Ejb embarqué


2. Properties properties = new Properties();
3. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
4. // initialisation du contexte JNDI avec les propriétés précédentes
5. InitialContext initialContext = new InitialContext(properties);
6. // instanciation couches dao
7. employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
8. cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
9. indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");

• lignes 2-5 : le conteneur OpenEjb est initialisé.


• ligne 5 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'obtenir des références sur les Ejb.
Chaque Ejb est désigné par un nom JNDI :
• pour l'interface locale on ajoute Local au nom de l'Ejb (lignes 7-9)
• pour l'interface distante on ajoute Remote au nom de l'Ejb
Avec Java EE 5, ces règles changent selon le conteneur Ejb. C'est une difficulté. Java EE 6 a introduit une
notation JNDI portable sur tous les serveurs d'applications.

Ci-dessous, le code pour avoir une référence sur l'interface distante de l'Ejb [Metier] :

1. // on configure le conteneur Open Ejb embarqué


2. Properties properties = new Properties();
3. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
4. // initialisation du contexte JNDI du conteneur Ejb
5. InitialContext initialContext = new InitialContext(properties);
6.
7. // instanciation couche métier distante
8. metier = (IMetierRemote) initialContext.lookup("MetierRemote");

http://tahe.developpez.com/java/javaee
73/339
12.2 Travail pratique
On se propose de porter l'application Netbeans Spring / Hibernate vers une architecture OpenEjb / EclipseLink.

L'implémentation actuelle avec Spring / Hibernate

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]
2

1 Spring

L'implémentation à construire avec OpenEjb / Eclipselink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [JDBC]

OpenEjb

12.2.1 Mise en place de la base de données [dbpam_eclipselink]

Si elle n'existe pas, créez la base MySQL [dbpam_eclipselink]. Si elle existe, supprimez toutes ses tables. Créez une connexion
Netbeans vers cette base comme il a été décrit page 74.

12.2.2 Configuration initiale du projet Netbeans

• charger le projet [pam-spring-ui-metier-dao-jpa-hibernate]


• créer un nouveau projet Java [pam-openejb-ui-metier-dao-jpa-eclipselink]
• décocher la propriété [compile on save] [1] dans les propriétés du projet. Si cette propriété est cochée, le conteneur
OpenEjb trouve deux contextes de persistence au lieu d'un et il refuse de se lancer.

2
3
1

• dans l'onglet [Files] [2], créer un dossier [conf] [3] sous la racine du projet
• placer dans ce dossier, le fichier [openejb.conf] [4] suivant :

1. <?xml version="1.0"?>
2. <openejb>
3. <Connector id="Default JDBC Database">

http://tahe.developpez.com/java/javaee
74/339
4. JdbcDriver com.mysql.jdbc.Driver
5. JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
6. UserName dbpam
7. Password dbpam
8. </Connector>
9. </openejb>

5 6
4

• dans [Source Packages], créer le dossier [META-INF] [5]


• y mettre le fichier [persistence.xml] [6] suivant :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
4. <!-- entités Jpa -->
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. <!-- le fournisseur JPA est EclipseLink -->
9. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
10. <!-- propriétés provider -->
11. <properties>
12. <property name="eclipselink.ddl-generation" value="create-tables"/>
13. </properties>
14. </persistence-unit>
15. </persistence>

• ajouter les bibliothèques OpenEjb, EclipseLink au projet, le pilote Jdbc de MySQL, la bibliothèque log4j.jar.

12.2.3 Portage de la couche [dao]

Nous allons faire le portage de la couche [dao] par copie de packages du projet [pam-spring-ui-metier-dao-jpa-hibernate] vers le
projet [pam-openejb-ui-metier-dao-jpa-eclipselink].

• copier les packages [dao, exception, jpa]

http://tahe.developpez.com/java/javaee
75/339
Les erreurs signalées ci-dessus viennent du fait que la couche [dao] copiée utilise Spring et que les bibliothèques Spring ne font plus
partie du projet.

12.2.3.1 L'Ejb [CotisationDao]

Nous créons les interfaces locale et distante du futur Ejb [CotisationDao] :

L'interface locale ICotisationDaoLocal :

1. package dao;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface ICotisationDaoLocal extends ICotisationDao{
7. }

Pour avoir les bons import de paquetages, faire [clic droit sur le code / Fix Imports].

L'interface distante ICotisationDaoRemote :

1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }

Puis nous modifions la classe [CotisationDao] pour en faire un Ejb :

1. ...
2. import javax.persistence.PersistenceContext;
3. import jpa.Cotisation;
4.
5. @Stateless
6. @TransactionAttribute(TransactionAttributeType.REQUIRED)
7. public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {
8.
9. @PersistenceContext
10. private EntityManager em;
11. ...

L'import que faisait cette classe sur le framework Spring disparaît. Faire un [Clean and Build] du projet :

http://tahe.developpez.com/java/javaee
76/339
1

En [1], il n'y a plus d'erreurs sur la classe [CotisationDao].

12.2.3.2 Les Ejb [EmployeDao] et [IndemniteDao]

On répète la même démarche pour les autres éléments de la couche [dao] :

• interfaces IEmployeDaoLocal, IEmployeDaoRemote dérivées de IEmployeDao


• Ejb EmployeDao implémentant ces deux interfaces
• interfaces IIndemniteDaoLocal, IIndemniteDaoRemote dérivées de IIndemniteDao
• Ejb IndemniteDao implémentant ces deux interfaces

Ceci fait, il n'y a plus d'erreurs dans le projet [2].

12.2.3.3 La classe [PamException]

La classe [PamException] reste ce qu'elle était à un détail près :

1. package exception;
2.
3. import javax.ejb.ApplicationException;
4.
5. @ApplicationException(rollback=true)
6. public class PamException extends RuntimeException {
7.
8. // code d'erreur
9. private int code;
10. ...

La ligne 5 est ajoutée. Pour avoir les bons import faire [Fix imports].

Pour comprendre l'annotation de la ligne 5, il faut se rappeler que chaque méthode des Ejb de notre couche [dao] :
• s'exécute dans une transaction démarrée et terminée par le conteneur Ejb
• lance une exception de type [PamException] dès que quelque chose se passe mal

Couche Couche Proxy Couche d'accès aux Données


utilisateur
interface métier Ejb données [dao]
utilisateur [ui] [metier]

Lorsque la couche [metier] appelle une méthode M de la couche [dao], cet appel est intercepté par le conteneur Ejb. Tout se passe
comme s'il y avait une classe intermédiaire entre la couche [metier] et la couche [dao], ici appelée [Proxy Ejb], interceptant tous les

http://tahe.developpez.com/java/javaee
77/339
appels vers la couche [dao]. Lorsque l'appel à la méthode M de la couche [dao] est interceptée, le Proxy Ejb démarre une
transaction puis passe la main à la méthode M de la couche [dao] qui s'exécute alors dans cette transaction. La méthode M se
termine avec ou sans exception.

• si la méthode M se termine sans exception, l'exécution revient au proxy Ejb qui termine la transaction en la validant par un
commit. Le flux d'exécution est ensuite rendu à la méthode appelante de la couche [metier]
• si la méthode M se termine avec exception, l'exécution revient au proxy Ejb qui termine la transaction en l'invalidant par
un rollback. De plus il encapsule cette exception dans un type EjbException. Le flux d'exécution est ensuite rendu à la
méthode appelante de la couche [metier] qui reçoit donc une EjbException. L'annotation de la ligne 5 ci-dessus empêche
cette encapsulation. La couche [metier] recevra donc une PamException. De plus, l'attribut rollback=true indique au
proxy Ejb que lorsqu'il reçoit une PamException, il doit invalider la transaction.

12.2.3.4 Test de la couche [dao]

Notre couche [dao] implémentée par des Ejb peut être testée. Nous commençons par copier le package [dao] de [Test Packages] du
projet [pam-spring-ui-metier-dao-jpa-hibernate] dans le projet en cours de construction [1] :

1 2

Nous ne conservons que le test [JUnitInitDB] qui initialise la base avec quelques données [2]. Nous renommons la classe
[ JUnitInitDbLocal] [3]. La classe [JUnitInitDBLocal] utilisera l'interface locale des Ejb de la couche [dao].

Nous modifions tout d'abord la classe [JUnitInitDBLocal] de la façon suivante :

1. public class JUnitInitDBLocal {


2.
3. static private IEmployeDaoLocal employeDao = null;
4. static private ICotisationDaoLocal cotisationDao = null;
5. static private IIndemniteDaoLocal indemniteDao = null;
6.
7. @BeforeClass
8. public static void init() throws Exception {
9. // on configure le conteneur Open Ejb embarqué
10. Properties properties = new Properties();
11. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
12. // initialisation du contexte JNDI avec les propriétés précédentes
13. InitialContext initialContext = new InitialContext(properties);
14. // instanciation couches dao locales
15. employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
16. cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
17. indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
18. }
1.
2. ...

• lignes 3-5 : des références sur les interfaces locales des Ejb de la couche [dao]
• ligne 7 : @BeforeClass annote la méthode exécutée au démarrage du test JUnit
• lignes 10-13 : initialisation du conteneur OpenEjb. Cette initialisation est propriétaire et change avec chaque conteneur
Ejb.
• ligne 13 : on a un contexte JNDI (Java Naming and Directory Interface) qui permet d'accéder aux Ejb via des noms. Avec
OpenEjb, l'interface locale d'un Ejb E est désignée par ELocal et l'interface distante par ERemote.
• lignes 15-17 : on demande au contexte JNDI, une référence sur les interfaces locales des Ejb [EmployeDao,
CotisationDao, IndemniteDao].

http://tahe.developpez.com/java/javaee
78/339
Ceci fait, on peut compiler la classe de test [JUnitInitDBLocal] : clic droit sur la classe / compile

2
1

En [1], le projet ne présente pas d'erreur. En [2], dans l'onglet [Files], on place dans le dossier [conf] du projet, un fichier
[logging.properties] qui permet d'avoir des logs sur la console. Ce fichier est le suivant :

1. ## ---------------------------------------------------
2. ## Nice alternate configuration for embedded testing
3. ##
4. ## Output is slightly more terse and sent to System.out
5. ##
6. ## Simply comment out the above declarations and
7. ## uncomment the configuration below.
8. ##
9. #
10. log4j.rootLogger = fatal,C
11. log4j.category.OpenEJB = warn
12. log4j.category.OpenEJB.server = info
13. log4j.category.OpenEJB.startup = info
14. log4j.category.OpenEJB.startup.service = warn
15. log4j.category.OpenEJB.startup.config = info
16. log4j.category.OpenEJB.hsql = info
17. log4j.category.CORBA-Adapter = info
18. log4j.category.Transaction = warn
19. log4j.category.org.apache.activemq = error
20. log4j.category.org.apache.geronimo = error
21. log4j.category.openjpa = error
22.
23. log4j.appender.C = org.apache.log4j.ConsoleAppender
24. log4j.appender.C.layout = org.apache.log4j.SimpleLayout

Ce fichier utilise la bibliothèque [log4j.jar]. Celle-ci doit être présente dans les bibliothèques du projet.

Lorsque tout ceci a été fait, on construit le projet (Build), on lance le serveur MySQL si besoin est, on exécute le test
JUnitInitDBLocal. On rappelle que le fichier [persistence.xml] a été configuré pour recréer les tables à chaque exécution. Avant
l'exécution du test, il est préférable de supprimer les éventuelles tables de la base MySQL [dbpam_eclipselink].

2 3

1 4

• en [1], dans l'onglet [Services], on supprime les tables de la connexion Netbeans établie au paragraphe 12.2.1.
• en [2], la base [dbpam_eclipselink] n'a plus de tables
• en [3], le projet est construit
• en [4], les test JUnitInitDBLocal est exécuté

http://tahe.developpez.com/java/javaee
79/339
5 6 7

• en [5], le test a été réussi


• en [6], on rafraîchit la connexion Netbeans
• en [7], on voit les 4 tables créées par la couche JPA. Le test avait pour but de les remplir. On visualise le contenu de l'une
d'entre-elles

• en [8], le contenu de la table [EMPLOYES]

Grâce au fichier [logging.properties], le conteneur OpenEjb a affiché des logs dans la console :

1. Apache OpenEJB 3.1 build: 20081009-03:31


2. http://openejb.apache.org/
3. INFO - openejb.home = C:\temp\pam\02\pam-openejb-ui-metier-dao-jpa-eclipselink
4. INFO - openejb.base = C:\temp\pam\02\pam-openejb-ui-metier-dao-jpa-eclipselink
5. INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default
Security Service)
6. INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-
id=Default Transaction Manager)
7. INFO - Configuring Service(id=Default JDBC Database, type=Resource, provider-id=Default JDBC
Database)
8. INFO - Found PersistenceModule in classpath: C:\temp\pam\02\pam-openejb-ui-metier-dao-jpa-
eclipselink\build\classes
9. INFO - Beginning load: C:\temp\pam\02\pam-openejb-ui-metier-dao-jpa-eclipselink\build\classes
10. INFO - Configuring enterprise application: classpath.ear
11. INFO - Configuring Service(id=Default Stateless Container, type=Container, provider-id=Default
Stateless Container)
12. INFO - Auto-creating a container for bean CotisationDao: Container(type=STATELESS, id=Default
Stateless Container)
13. INFO - Configuring PersistenceUnit(name=pam-openejb-ui-metier-dao-jpa-eclipselinkPU,
provider=org.eclipse.persistence.jpa.PersistenceProvider)
14. INFO - Auto-creating a Resource with id 'Default JDBC DatabaseNonJta' of type 'DataSource for
'pam-openejb-ui-metier-dao-jpa-eclipselinkPU'.
15. INFO - Configuring Service(id=Default JDBC DatabaseNonJta, type=Resource, provider-id=Default JDBC
Database)
16. INFO - Adjusting pam-openejb-ui-metier-dao-jpa-eclipselinkPU <jta-data-source> to 'Default JDBC
Database'
17. INFO - Adjusting pam-openejb-ui-metier-dao-jpa-eclipselinkPU <non-jta-data-source> to 'Default
JDBC DatabaseNonJta'
18. INFO - Enterprise application "classpath.ear" loaded.
19. INFO - Assembling app: classpath.ear
20. INFO - PersistenceUnit(name=pam-openejb-ui-metier-dao-jpa-eclipselinkPU,
provider=org.eclipse.persistence.jpa.PersistenceProvider)
21. ERROR - JAVA AGENT NOT INSTALLED. The JPA Persistence Provider requested installation of a
ClassFileTransformer which requires a JavaAgent. See http://openejb.apache.org/3.0/javaagent.html
22. INFO - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
23. INFO - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
24. INFO - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
25. INFO - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
26. INFO - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
27. INFO - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
28. INFO - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default
Stateless Container)
29. INFO - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless
Container)
30. INFO - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless
Container)
31. INFO - Deployed Application(path=classpath.ear)
32. [EL Info]: 2010-06-04 18:04:00.562--ServerSession(5263041)--EclipseLink, version: Eclipse
Persistence Services - 2.0.0.v20091127-r5931

http://tahe.developpez.com/java/javaee
80/339
33. [EL Info]: 2010-06-04 18:04:01.078--ServerSession(5263041)--file:/C:/temp/pam/02/pam-openejb-ui-
metier-dao-jpa-eclipselink/build/classes/_pam-openejb-ui-metier-dao-jpa-eclipselinkPU login
successful
34. [EL Warning]: 2010-06-04 18:04:01.125--ServerSession(5263041)--Exception [EclipseLink-4002]
(Eclipse Persistence Services - 2.0.0.v20091127-r5931):
org.eclipse.persistence.exceptions.DatabaseException
35. Internal Exception: com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Table 'employes' already
exists
36. Error Code: 1050
37. Call: CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, PRENOM VARCHAR(20) NOT NULL, SS VARCHAR(15) NOT
NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, VILLE VARCHAR(30) NOT NULL, NOM
VARCHAR(30) NOT NULL, VERSION INTEGER NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
38. ....
39. Employés ----------------------
40. jpa.Employe[id=353,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des
oiseaux,ville=St Corentin,code postal=49203,indice=2]
41. jpa.Employe[id=354,version=1,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La
brûlerie,ville=St Marcel,code postal=49014,indice=1]
42. Indemnités ----------------------
43. jpa.Indemnite[id=351,version=1,indice=1,base heure=1.93,entretien jour2.0,repas
jour=3.0,indemnités CP=12.0]
44. jpa.Indemnite[id=352,version=1,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités
CP=15.0]
45. Cotisations ----------------------
46. jpa.Cotisation[id=355,version=1,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
47. Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,813 sec

• lignes 22-27 : les noms JNDI des six interfaces présentées par les trois Ejb.
• ligne 32 : débutent les logs d'EclipseLink
• ligne 33 : EclipseLink a réussi à se connecter à la base cible
• lignes 34-37 : une exception se produit car Eclipselink essaie de créer la table [EMPLOYES] qui existe déjà. L'erreur est
signalée dans les logs mais ce n'est pas une erreur fatale. Le code continue à s'exécuter.
• lignes 39-46 : les affichages du test JUnitInitDBLocal

Nous refaisons le même test, en utilisant cette fois-ci l'interface distante des Ejb.

En [1], la classe [JUnitInitDbLocal] a été dupliquée (copy / paste) dans [JUnitInitDBRemote]. Dans cette classe, nous remplaçons
les interfaces locales par les interfaces distantes :

1. public class JUnitInitDBRemote {


2.
3. static private IEmployeDaoRemote employeDao = null;
4. static private ICotisationDaoRemote cotisationDao = null;
5. static private IIndemniteDaoRemote indemniteDao = null;
6.
7. @BeforeClass
8. public static void init() throws Exception {
9. // on configure le conteneur Open Ejb embarqué
10. Properties properties = new Properties();
11. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
12. // initialisation du contexte JNDI avec les propriétés précédentes
13. InitialContext initialContext = new InitialContext(properties);
14. // instanciation couches dao distantes
15. employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
16. cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
17. indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
18. }

http://tahe.developpez.com/java/javaee
81/339
Ceci fait, la nouvelle classe de test peut être exécutée. Auparavant, avec la connexion Netbeans [dbpam_eclipselink], supprimez les
tables de la base [dbpam_eclipselink].

Avec la connexion Netbeans [dbpam_eclipselink], vérifiez que la base a été remplie.

12.2.4 Portage de la couche [metier]

Nous allons faire le portage de la couche [metier] par copie de packages du projet [pam-spring-ui-metier-dao-jpa-hibernate] vers le
projet [pam-openejb-ui-metier-dao-jpa-eclipselink]..

2
3

Les erreurs signalées ci-dessus [1] viennent du fait que la couche [metier] copiée utilise Spring et que les bibliothèques Spring ne
font plus partie du projet.

12.2.4.1 L'Ejb [Metier]

Nous suivons la même démarche que celle décrite pour l'Ejb [CotisationDao]. Nous créons tout d'abord en [2] les interfaces locale
et distante du futur Ejb [Metier]. Elles dérivent toutes deux de l'interface initiale [IMetier].

1. package metier;
2.
3. import javax.ejb.Local;
4.
5. @Local
6. public interface IMetierLocal extends IMetier{
7.
8. }

1. package metier;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface IMetierRemote extends IMetier{
7.
8. }

Ceci fait, en [3] nous modifions la classe [Metier] afin qu'elle devienne un Ejb :

1. @Stateless
2. @TransactionAttribute(TransactionAttributeType.REQUIRED)
3. public class Metier implements IMetierLocal, IMetierRemote {
4.
5. // références sur la couche [dao] locale
6. @EJB

http://tahe.developpez.com/java/javaee
82/339
7. private ICotisationDaoLocal cotisationDao = null;
8. @EJB
9. private IEmployeDaoLocal employeDao = null;
10. @EJB
11. private IIndemniteDaoLocal indemniteDao = null;
12.
13. // obtenir la feuille de salaire
14. public FeuilleSalaire calculerFeuilleSalaire(String SS,
15. double nbHeuresTravaillées, int nbJoursTravaillés) {
16. // on récupère les informations liées à l'employé
17. ...

• ligne 1 : l'annotation @Stateless fait de la classe un Ejb


• ligne 2 : chaque méthode de la classe s'exécutera dans une transaction
• ligne 3 : l'Ejb [Metier] implémente les deux interfaces locale et distante que nous venons de définir
• ligne 7 : l'Ejb [Metier] va utiliser l'Ejb [CotisationDao] via l'interface locale de celui-ci. Cela signifie que les couches
[metier] et [dao] doivent s'exécuter dans la même Jvm.
• ligne 6 : l'annotation @EJB fait en sorte que le conteneur Ejb injecte lui-même la référence sur l'interface locale de l'Ejb
[CotisationDao]. L'autre façon que nous avons rencontrée est d'utiliser un contexte JNDI.
• lignes 8-11 : le même mécanisme est utilisé pour les deux autres Ejb de la couche [dao].

12.2.4.2 Test de la couche [metier]

Notre couche [metier] implémentée par un Ejb peut être testée. Nous commençons par copier le package [metier] de [Test
Packages] du projet [pam-spring-ui-metier-dao-jpa-hibernate] dans le projet en cours de construction [1] :

2
3

• en [1], le résultat de la copie


• en [2], on supprime le 1er test
• en [3], le test restant est renommé [JUnitMetierLocal]

La classe [JUnitMetierLocal] devient la suivante :


1. public class JUnitMetierLocal {
2.
3. // couche métier locale
4. static private IMetierLocal metier;
5.
6. @BeforeClass
7. public static void init() throws NamingException {
8. // on configure le conteneur Open Ejb embarqué
9. Properties properties = new Properties();
10. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
11. // initialisation du contexte JNDI avec les propriétés précédentes
12. InitialContext initialContext = new InitialContext(properties);
13.
14. // instanciation couches dao locales
15. IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
16. ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
17. IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
18. // instanciation couche métier locale
19. metier = (IMetierLocal) initialContext.lookup("MetierLocal");
20.
21. // on vide la base
22. ...
23. }

• ligne 4 : une référence sur l'interface locale de l'Ejb [Metier]


• lignes 8-12 : configuration du conteneur OpenEjb identique à celle faite dans le test de la couche [dao]

http://tahe.developpez.com/java/javaee
83/339
• lignes 15-19 : on demande au contexte JNDI de la ligne 12, des références sur les 3 Ejb de la couche [dao] et sur l'Ejb de
la couche [metier]. Les Ejb de la couche [dao] vont servir à initialiser la base, l'Ejb de la couche [metier] à faire des tests de
calculs de salaire.

L'exécution du test [JUnitMetierLocal] donne le résultat suivant [1] :

1 2

En [2], on duplique [JUnitMetierLocal] en [JUnitMetierRemote] pour tester cette fois-ci l'interface distante de l'Ejb [Metier]. Le
code de [JUnitMetierRemote] est modifié pour utiliser cette interface distante. Le reste ne change pas.
1. public class JUnitMetierRemote {
2.
3. // couche métier distante
4. static private IMetierRemote metier;
5.
6. @BeforeClass
7. public static void init() throws NamingException {
8. // on configure le conteneur Open Ejb embarqué
9. Properties properties = new Properties();
10. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
11. // initialisation du contexte JNDI avec les propriétés précédentes
12. InitialContext initialContext = new InitialContext(properties);
13.
14. // instanciation couches dao distantes
15. IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
16. ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
17. IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
18. // instanciation couche métier distante
19. metier = (IMetierRemote) initialContext.lookup("MetierRemote");
20.
21. // on vide la base
22. for(Employe employe:employeDao.findAll()){
23. employeDao.destroy(employe);
24. }
25. for(Cotisation cotisation:cotisationDao.findAll()){
26. cotisationDao.destroy(cotisation);
27. }
28. for(Indemnite indemnite : indemniteDao.findAll()){
29. indemniteDao.destroy(indemnite);
30. }
31. // on la remplit
32. Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
33. Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
34. indemnite1=indemniteDao.create(indemnite1);
35. indemnite2=indemniteDao.create(indemnite2);
36. employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St
Corentin","49203",indemnite2));
37. employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St
Marcel","49014",indemnite1));
38. cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
39. }
40. }

• lignes 4 et 19 : on utilise l'interface distante de l'Ejb [Metier].


• lignes 15-17 : on utilise les interfaces distantes de la couche [dao]
• lignes 34-35 : parce qu'avec les interfaces distantes, les objets échangés entre le client et le serveur le sont par valeur, il faut
récupérer le résultat rendu par la méthode create(Indemnite i). Ce n'était pas obligatoire avec les interfaces locales où là les
objets sont passés par référence.

Ceci fait, le projet peut être construit et le test [JUnitMetierRemote] exécuté :

http://tahe.developpez.com/java/javaee
84/339
12.2.5 Portage de la couche [console]

Nous allons faire le portage de la couche [console] par copie de packages du projet [pam-spring-ui-metier-dao-jpa-hibernate] vers le
projet [pam-openejb-ui-metier-dao-jpa-eclipselink].

2
3

Les erreurs signalées ci-dessus [1] viennent du fait que la couche [metier] copiée utilise Spring et que les bibliothèques Spring ne
font plus partie du projet. En [2], la classe [Main] est renommée [MainLocal]. Elle utilisera l'interface locale de l'Ejb [Metier].

Le code de la classe [MainLocal] évolue de la façon suivante :

1. public static void main(String[] args) {


2. // données locales
3. final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
4. ...
5. // des erreurs ?
6. if (erreurs.size() != 0) {
7. for (int i = 0; i < erreurs.size(); i++) {
8. System.err.println(erreurs.get(i));
9. }
10. return;
11. }
12. // c'est bon - on peut demander la feuille de salaire à la couche [métier]
13. IMetierLocal metier = null;
14. FeuilleSalaire feuilleSalaire = null;
15. try {
16. // on configure le conteneur Open Ejb embarqué
17. Properties properties = new Properties();
18. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
19. // initialisation du contexte JNDI avec les propriétés précédentes
20. InitialContext initialContext = new InitialContext(properties);
21. // instanciation couche métier locale
22. metier = (IMetierLocal) initialContext.lookup("MetierLocal");
23. // calcul de la feuille de salaire
24. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
25. } catch (PamException ex) {
26. System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
27. return;
28. } catch (Exception ex) {
29. System.err.println("L'erreur suivante s'est produite : " + ex.toString());
30. return;
31. }
32. // affichage détaillé
33. String output = "Valeurs saisies :\n";
34. output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
35. ....

Les modifications se situent dans les lignes 13-25. C'est la façon d'avoir une référence sur la couche [metier] qui change (lignes 17-
22). Nous n'expliquons pas le nouveau code qui a déjà été vu dans des exemples précédents. Une fois ces modifications faites, le
projet ne présente plus d'erreur (cf [3]).

http://tahe.developpez.com/java/javaee
85/339
Nous configurons le projet pour qu'il soit exécuté avec des arguments [1] :

Pour que l'application console s'exécute normalement, il faut qu'il y ait des données dans la base. Pour cela, il faut modifier le fichier
[META-INF/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="jpa" transaction-type="JTA">
4. <!-- le fournisseur JPA est EclipseLink -->
5. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
6. <!-- entités Jpa -->
7. <class>jpa.Cotisation</class>
8. <class>jpa.Employe</class>
9. <class>jpa.Indemnite</class>
10. <!-- propriétés provider -->
11. <properties>
12. <!--
13. <property name="eclipselink.ddl-generation" value="create-tables"/>
14. -->
15. </properties>
16. </persistence-unit>
17. </persistence>

La ligne 13 qui faisait que les tables de la base de données étaient recréées à chaque exécution est mise en commentaires. Le projet
doit être reconstruit (Clean and Build) pour que cette modification soit prise en compte. Ceci fait, on peut exécuter le programme.
Si tout va bien, on obtient un affichage console analogue au suivant :

1. .......
2. INFO - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless
Container)
3. INFO - Deployed Application(path=classpath.ear)
4. [EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse
Persistence Services - 1.1.2.v20090612-r4475
5. [EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-
metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
6. Valeurs saisies :
7. N° de sécurité sociale de l'employé : 254104940426058
8. Nombre d'heures travaillées : 150
9. Nombre de jours travaillés : 20
10.
11. Informations Employé :
12. Nom : Jouveinal
13. Prénom : Marie
14. Adresse : 5 rue des oiseaux
15. Ville : St Corentin
16. Code Postal : 49203
17. Indice : 2
18.
19. Informations Cotisations :
20. CSGRDS : 3.49 %
21. CSGD : 6.15 %
22. Retraite : 7.88 %
23. Sécurité sociale : 9.39 %
24.
25. Informations Indemnités :
26. Salaire horaire : 2.1 euro
27. Entretien/jour : 2.1 euro
28. Repas/jour : 3.1 euro

http://tahe.developpez.com/java/javaee
86/339
29. Congés Payés : 15.0 %
30.
31. Informations Salaire :
32. Salaire de base : 362.25 euro
33. Cotisations sociales : 97.48 euro
34. Indemnités d'entretien : 42.0 euro
35. Indemnités de repas : 62.0 euro
36. Salaire net : 368.77 euro
37.
38. BUILD SUCCESSFUL (total time: 4 seconds)

Nous avons utilisé ici l'interface locale de la couche [metier]. Nous utilisons maintenant son interface distante dans une seconde
classe console :

En [1], la classe [MainLocal] a été dupliquée dans [MainRemote]. Le code de [MainRemote] est modifié pour utiliser l'interface
distante de la couche [metier] :

1. // c'est bon - on peut demander la feuille de salaire à la couche [metier]


2. IMetierRemote metier = null;
3. FeuilleSalaire feuilleSalaire = null;
4. try {
5. // on configure le conteneur Open Ejb embarqué
6. ...
7. // instanciation couche métier distante
8. metier = (IMetierRemote) initialContext.lookup("MetierRemote");
9. // calcul de la feuille de salaire
10. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
11. } catch (PamException ex) {
12. ...
13. } catch (Exception ex) {
14. ...
15. }

Les modifications sont faites aux lignes 2 et 8. Le projet est configuré [2] pour exécuter la classe [MainRemote]. Son exécution
donne les mêmes résultats que précédemment.

12.3 Conclusion
Nous avons montré comment porter une architecture Spring / Hibernate vers une architecture OpenEjb / EclipseLink.

L'architecture Spring / Hibernate

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [Hibernate] [JDBC]

Spring

L'architecture OpenEjb / Eclipselink

http://tahe.developpez.com/java/javaee
87/339
Couche Couche Couche Objets image Interface Implémentation Couche
[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [JDBC]
2

1 OpenEjb

Le portage a pu se faire sans trop de difficultés parce que l'application initiale avait été structurée en couches. Ce point est
important à comprendre.

http://tahe.developpez.com/java/javaee
88/339
13 Portage de l'application PAM sur un serveur d'applications Glassfish
On se propose de placer les Ejb des couches [metier] et [dao] de l'architecture OpenEjb / Eclipselink dans le conteneur d'un
serveur d'applications Glassfish.

L'implémentation actuelle avec OpenEjb / Eclipselink

Couche Couche Couche Objets image Interface Implémentation Couche


[ui] [metier] [dao] de la BD [JPA] [EclipseLink] [JDBC]

OpenEjb

Ci-dessus, la couche [ui] utilise l'interface distante de la couche [metier].

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 Glassfish. Ce serveur est livré avec
Netbeans.

L'implémentation à construire avec le serveur Glassfish

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

Jvm1 - Java SE 7 Jvm2 – Java EE - serveur Glassfish v3

• 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 Glassfish
v3
• 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 Glassfish sera EclipseLink.

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

13.1.1 L'architecture de l'application

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

http://tahe.developpez.com/java/javaee
89/339
Conteneur
Client Jpa / Toplink
Ejb3 Données
Java SE
serveur Java EE

Il s'agit de faire un portage vers le serveur Glassfish de ce qui a déjà été fait et testé avec le conteneur OpenEjb. C'est là l'intérêt de
OpenEjb et en général des conteneurs Ejb embarqués : 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 Glassfish.

13.1.1.1 Le projet Netbeans

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

3
2

• en [1], nouveau projet


• en [2], choisir la catégorie Java EE 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 Glassfish.

5
4b 6
4a

• 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], ici Glassfish v3.
• 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. Glassfish v3 supporte Java EE 6 mais dans ce document cette version de Java EE n'est
pas développée.

http://tahe.developpez.com/java/javaee
90/339
1

4 5

• en [1], le nouveau projet. Il diffère d'un projet Java classique par quelques points :
• une branche [Configuration Files] [2] 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.
• une branche [Server Resources] [3]. Cette branche permet de créer des ressources sur le serveur. Nous aurons à
l'utiliser pour créer une source de données Jdbc.
• une branche [Libraries] [4] dans laquelle on trouve les bibliothèques du serveur Glassfish [5] :

13.1.1.2 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
Glassfish.

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

Java SE Java EE - serveur Glassfish

On pourra procéder comme suit. Tout d'abord, dans l'onglet [Runtime / Databases], on créera [1] une connexion sur la base
MySQL5 / dbpam_eclipselink :

Ceci fait, on peut passer à la création de la ressource Jdbc utilisée par le module Ejb :

http://tahe.developpez.com/java/javaee
91/339
2

4
1

• en [1], créer un nouveau fichier – 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 [Glassfish]
• en [4], on veut créer une ressource Jdbc

• en [5], indiquer que la ressource Jdbc va utiliser un nouveau pool de connexions. On rappelle qu'un pool de connexions
est un pool de connexions ouvertes qui sert à accélérer les échanges de l'application avec la base de données.
• en [6], donner un nom JNDI à la ressource Jdbc créée. Ce nom peut être quelconque mais il a souvent la forme jdbc/nom.
Ce nom JNDI sera utilisé dans le fichier [persistence.xml] pour désigner la source de données que l'implémentation Jpa
doit utiliser.
• en [7], donner un nom qui peut être quelconque au pool de connexions qui va être créé
• dans la liste déroulante [8], choisir la connexion Jdbc créée précédemment sur la base MySQL / dbpam_eclipselink.
• en [9], un récapitulatif des propriétés du pool de connexions - on ne touche à rien

http://tahe.developpez.com/java/javaee
92/339
11

10

• en [10], on peut préciser plusieurs des propriétés du pool de connexions - on laisse les valeurs par défaut
• en [11], à l'issue de l'assistant de création d'une ressource Jdbc pour le module Ejb, un fichier [sun-resources.xml] a été
créée dans la branche [Server Resources]. Le contenu de ce fichier est le suivant :

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


2. <!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource
Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
3. <resources>
4. <jdbc-resource enabled="true" jndi-name="jdbc/dbpam_eclipselink" object-type="user" pool-
name="dbpamEclipseLinkConnectionPool">
5. <description/>
6. </jdbc-resource>
7. <jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false"
connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10"
connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-
method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" fail-
all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false"
is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-
enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32"
max-wait-time-in-millis="60000" name="dbpamEclipseLinkConnectionPool" non-transactional-
connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-
seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-
objects="false">
8. <property name="URL" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
9. <property name="User" value="dbpam"/>
10. <property name="Password" value="dbpam"/>
11. </jdbc-connection-pool>
12. </resources>

Le fichier [sun-resources.xml] est un fichier Xml qui reprend toutes les données collectées par l'assistant. Il va être utilisé par
Netbeans pour, lors du déploiement du module Ejb sur le serveur Glassfish, demander la création de la ressource Jdbc dont a
besoin ce module.

On peut désormais créer le fichier [persistence.xml] qui va configurer la couche JPA du module Ejb :

http://tahe.developpez.com/java/javaee
93/339
2

4
1
3

• en [1], créer un nouveau fichier – 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

5
10
6
7
8
9

• en [5], donner un nom à l'unité de persistance


• en [6], plusieurs implémentations Jpa sont proposées. On choisira ici [EclipseLink]. D'autres implémentations sont
utilisables à condition de mettre les bibliothèques qui les implémentent avec celles du serveur Glassfish.
• dans la liste déroulante [7], choisir la source de données Jdbc [jdbc/dbpam_eclipselink] qui vient d'être créée.
• en [8], indiquer que les transactions sont gérées par le conteneur Ejb
• en [9], indiquer qu'aucune opération ne doit être faite sur la source de données lors du déploiement du module Ejb sur le
serveur. En effet, le module Ejb va utiliser une base [dbpam_eclipselink] déjà créée.
• à la fin de l'assistant, un fichier [persistence.xml] a été créé [10]. Son contenu est le suivant :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-serveur-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
4. <jta-data-source>jdbc/dbpam_eclipselink</jta-data-source>
5. <exclude-unlisted-classes>false</exclude-unlisted-classes>
6. <properties/>
7. </persistence-unit>
8. </persistence>

• ligne 3 : le nom de l'unité de persistance (pam-serveur-metier-dao-jpa-eclipselinkPU) et le type de transactions (JTA pour


un conteneur Ejb)
• ligne 5 : le nom JNDI de la source de données utilisée par la couche de persistance : jdbc/dbpam_eclipselink
• ligne 6 : les entités Jpa ne sont pas précisées. Elles seront cherchées dans le Classpath du module Ejb.
• le nom de l'implémentation JPA (Hibernate, EclipseLink, ...) utilisée n'est pas indiquée. Dans ce cas, Glassfish v3 utilise
par défaut EclipseLink.

13.1.1.3 Insertion des couches [jpa, dao, metier]

Maintenant que le fichier [persistence.xml] a été défini, nous pouvons passer à l'insertion dans le projet des couches [metier, dao,
jpa] de l'application d'entreprise [pam] :

http://tahe.developpez.com/java/javaee
94/339
Couche Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
EclipseLink]

Java SE Java EE - serveur Glassfish

Ces trois couches sont identiques à ce qu'elles étaient avec OpenEjb. On peut procéder à un simple copier / coller entre les deux
projets. C'est ce que nous faisons maintenant :

1 2

• en [1], le résultat de la copie des paquetages [jpa, dao, metier, exception] du projet [pam-openejb-ui-metier-dao-jpa-
eclipselink] dans le module Ejb [pam-serveur-metier-dao-jpa-eclipselink]
• en [2], dans la branche [Enterprise Beans], les trois Ejb de la couche [dao] et l'Ejb de la couche [metier] ont été reconnus.

13.1.1.4 Configuration du serveur Glassfish

Il nous reste à configurer le serveur Glassfish sur deux points :

• la couche Jpa est implémentée par EclipseLink. Il faut nous assurer que le serveur Glassfish a les bibliothèques de cette
implémentation Jpa.
• la source de données est une base MySQL. Il faut nous assurer que le serveur Glassfish a le pilote Jdbc de ce SGBD.

On peut découvrir l'absence de ces bibliothèques lors du déploiement du module Ejb. Voici une façon de procéder parmi d'autres
pour ajouter des bibliothèques manquantes au serveur Glassfish :

http://tahe.developpez.com/java/javaee
95/339
• en [1], visualiser les propriétés du serveur Glassfish
• en [2], noter le dossier des domaines du serveur. Nous le notons par la suite <domains>
• dans le dossier <domains>\domain1\lib, mettre les bibliothèques manquantes. Dans l'exemple, les bibliothèques
d'Hibernate (lib / hibernate-tools) et le pilote Jdbc de MySQL (lib / divers) ont été rajoutés. Par défaut, le serveur
Glassfish a les bibliothèques d'EclipseLink.

1 2

• en [1], dans l'onglet [Services], nous lançons le serveur Glassfish v3


• en [2], il est actif

13.1.1.5 Déploiement du module Ejb

Nous déployons maintenant le module Ejb sur le serveur Glassfish :

1
4

• en [1], le module Ejb est déployé


• en [2], l'arborescence du serveur Glassfish est rafraîchie
• en [3], après déploiement, le module Ejb apparaît dans la branche [Applications] du serveur Glassfish
• en [4], la ressource Jdbc [jdbc / dbpam_eclipselink] a été créée sur le serveur Glassfish. On rappelle que nous l'avions
définie page 91.

Lors du déploiement, le serveur Glassfish logue dans la console des informations intéressantes :

1. INFO: Realm admin-realm of classtype com.sun.enterprise.security.auth.realm.file.FileRealm


successfully created.
2. ....
3. INFO: Portable JNDI names for EJB IndemniteDao : [java:global/pam-serveur-metier-dao-jpa-
eclipselink/IndemniteDao!dao.IIndemniteDaoLocal, java:global/pam-serveur-metier-dao-jpa-
eclipselink/IndemniteDao!dao.IIndemniteDaoRemote]
4. INFO: Glassfish-specific (Non-portable) JNDI names for EJB IndemniteDao :
[dao.IIndemniteDaoRemote#dao.IIndemniteDaoRemote, dao.IIndemniteDaoRemote]
5. ...
6. INFO: Portable JNDI names for EJB CotisationDao : [java:global/pam-serveur-metier-dao-jpa-
eclipselink/CotisationDao!dao.ICotisationDaoLocal, java:global/pam-serveur-metier-dao-jpa-
eclipselink/CotisationDao!dao.ICotisationDaoRemote]
7. INFO: Glassfish-specific (Non-portable) JNDI names for EJB CotisationDao :
[dao.ICotisationDaoRemote, dao.ICotisationDaoRemote#dao.ICotisationDaoRemote]
8. INFO: Portable JNDI names for EJB Metier : [java:global/pam-serveur-metier-dao-jpa-
eclipselink/Metier!metier.IMetierRemote, java:global/pam-serveur-metier-dao-jpa-
eclipselink/Metier!metier.IMetierLocal]
9. INFO: Glassfish-specific (Non-portable) JNDI names for EJB Metier :
[metier.IMetierRemote#metier.IMetierRemote, metier.IMetierRemote]
10. ...

http://tahe.developpez.com/java/javaee
96/339
11. INFO: Portable JNDI names for EJB EmployeDao : [java:global/pam-serveur-metier-dao-jpa-
eclipselink/EmployeDao!dao.IEmployeDaoLocal, java:global/pam-serveur-metier-dao-jpa-
eclipselink/EmployeDao!dao.IEmployeDaoRemote]
12. INFO: Glassfish-specific (Non-portable) JNDI names for EJB EmployeDao :
[dao.IEmployeDaoRemote#dao.IEmployeDaoRemote, dao.IEmployeDaoRemote]
13. INFO: pam-serveur-metier-dao-jpa-eclipselink was successfully deployed in 12 891 milliseconds.

On notera aux lignes


• 3, 6, 8 et 11 les noms portables JNDI des Ejb déployés. Java EE 6 a introduit la notion de nom JNDI portable. Cela
dénote un nom JNDI reconnu par tous les serveurs Java EE 6. Avec Java EE 5, les noms JNDI sont spécifiques au
serveur utilisé.
• 4, 7, 9, 12 : les noms JNDI des Ejb déployés sous une forme spécifique à Glassfish v3.

Ces noms seront utiles à l'application console que nous allons écrire pour utiliser le module Ejb déployé.

13.2 Client console - version 1


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

Couche Couche Couche Couche Couche


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

Java SE Java EE - serveur Glassfish

13.2.1 Le projet du client

Nous créons un nouveau projet Netbeans de type [Java Application] nommé [pam-client-metier-dao-jpa-eclipselink-mysql] :

1
2

• en [1], le projet du client


• en [2], les paquetages [ui.console, metier, jpa, exception] copiés à partir du projet [pam-openejb-ui-metier-dao-jpa-
eclipselink]. Le client va échanger avec le serveur des entités Jpa ainsi que des objets de la couche [metier] (FeuilleSalaire,
ElementsSalaire). Aussi a-t-on besoin des paquetages [jpa] et [metier]. Par ailleurs, le module Ejb peut lancer des
exceptions de type [PamException] que le client va gérer. Aussi a-t-on besoin du paquetage [exception]. Les erreurs
signalées viennent du fait que les classes ajoutées ont besoin de bibliothèques qui n'ont pas encore été intégrées au projet.
• en [3], le client ne peut être ici qu'un client distant : il va utiliser une couche [metier] qui s'exécute dans une autre Jvm
(celle du serveur Glassfish) que celle du client. On supprime la classe [MainLocal].

http://tahe.developpez.com/java/javaee
97/339
• en [4], le client n'a pas besoin de l'interface locale [IMetierLocal] de la couche [metier], ni de l'implémentation [Metier] de
cette dernière. On supprime ces deux classes.

13.2.2 Suppression des annotations Java de persistence et d'EJB

Parmi les classes copiées, nous trouvons


• des annotations JPA dans le package [jpa]
• des annotations EJB dans le pacake [metier] et [Exception]

Les classes copiées existent côté serveur avec les notations JPA et EJB adéquates. Côté client, nous n'avons pas besoin de ces
annotations. On pourrait les garder mais cela nous oblige alors à intégrer au projet les bibliothèques définissant ces annotations.
Nous décidons de les enlever. Les nouvelles classes sont les suivantes :

Notations EJB

Interface IMetierRemote

1. package metier;
2.
3. public interface IMetierRemote extends IMetier{
4.
5. }

En enlevant les notations EJB de l'interface IMetierRemote, cette interface devient identique à l'interface IMetier dont elle dérive. On
pourrait donc supprimer cette interface pour ne garder que l'interface IMetier.

Classe PamException

1. package exception;
2.
3. public class PamException extends RuntimeException {
4.
5. // code d'erreur
6. private int code;
7.
8. public PamException(int code) {
9. super();
10. this.code = code;
11. }
12. ...

Dans la classe PamException, nous enlevons l'annotation :

@ApplicationException(rollback = true)

Notations JPA

Classe Employe

1. package jpa;
2.
3. import java.io.Serializable;
4.
5. public class Employe implements Serializable {
6.
7. private Long id;
8. private int version;
9. private String SS;
10. private String nom;
11. private String prenom;
12. private String adresse;

http://tahe.developpez.com/java/javaee
98/339
13. private String ville;
14. private String codePostal;
15. private Indemnite indemnite;
16. ...

La classe [Employe] a perdu toutes ses annotations JPA (@Entity, @Column, ...). Nous faisons de même pour les classes
[Cotisation] et [Indemnite].

13.2.3 Les bibliothèques du client


Le client du module Ejb a besoin de certaines bibliothèques pour communiquer avec le serveur Glassfish. Celles-ci se trouvent dans
les bibliothèques du serveur Glassfish lui-même :

2 3

• en [1,2], on détermine l'emplacement d'installation du serveur Glassfish. Nous l'appelerons <glassfish> et ci-dessus, c'est
le dossier [C:\devjava\glassfish-v3\sges-v3\glassfish].
• en [3], on clique droit sur la branche [Libraries] du projet pour ajouter des archives .jar

2
4

• dans <glassfish>/modules [1], on trouve une archive à mettre dans le Classpath du client : gf-client.jar [2,3]. Cette
archive fait elle-même référence à un nombre important d'autres archives du dossier <glassfish>/modules. La liste de
celles-ci peut être trouvée dans le fichier [MANIFEST.MF] [4] :

1. Manifest-Version: 1.0
2. Ant-Version: Apache Ant 1.6.5
3. Created-By: Apache Maven
4. Archiver-Version: Plexus Archiver
5. Built-By: java_re
6. Build-Jdk: 1.6.0_10
7. Package: org.glassfish.appclient.client.acc

http://tahe.developpez.com/java/javaee
99/339
8. Main-Class: org.glassfish.appclient.client.AppClientFacade
9. PreMain-Class: org.glassfish.appclient.client.acc.agent.AppClientConta inerAgent
10. Class-Path: ../modules/woodstox-osgi.jar ../modules/tools.jar ../modul es/glassfish-corba-asm.jar ../modules/glassfish-
corba-codegen.jar ../ modules/glassfish-corba-csiv2-idl.jar ../modules/glassfish-corba-newt imer.jar ../modules/glassfish-
corba-omgapi.jar ../modules/glassfish-c orba-orb.jar ../modules/glassfish-corba-orbgeneric.jar ../modules/aut o-
depends.jar ../modules/config.jar ../modules/config-types.jar ../mo dules/hk2.jar ../modules/hk2-core.jar
../modules/osgi-adapter.jar ../ modules/tiger-types-osgi.jar ../modules/grizzly-comet.jar ../modules/ grizzly-config.jar ../
modules/grizzly-framework.jar ../modules/grizzl y-http.jar ../modules/grizzly-http-servlet.jar ../modules/grizzly-por
tunif.jar ../modules/grizzly-rcm.jar ../modules/grizzly-utils.jar ../ modules/pkg-client.jar ../modules/jaxb-osgi.jar
../modules/webservice s-osgi.jar ../modules/activation.jar ../modules/el-api.jar ../modules .....................................................

Plusieurs dizaines d'archives sont référencées ligne 10 et seront automatiquement intégrées dans le ClassPath du client. Pour la
plupart, elles se trouvent dans le dossier [modules] de Glassfish.

• en [5], le pilote Jdbc du SGBD MySQL a été ajouté aux bibliothèques du client. On peut s'étonner de sa présence côté
client. En effet, toute la persistance se fait côté serveur et non côté client. Cependant lorsque la couche de persistance côté
serveur rencontre des problèmes, un serveur MySQL non lancé par exemple, elle lance une exception appartenant aux
bibliothèques d'EclipseLink, exception encapsulant l'exception lancée par le pilote Jdbc de MySQL. Si côté client, on veut
avoir accès aux messages de ces exceptions, il faut que le client ait accès aux classes de celles-ci. D'où la nécessité des
archives d'EclipseLink et de celle du pilote Jdbc de MySQL. La bibliothèque [gf-client.jar] référence les bibliothèques
d'EclipseLink donc celles-ci seront intégrées automatiquement au projet.

La classe [MainRemote] doit obtenir une référence sur l'Ejb de la couche [metier]. Le code de la classe [MainRemote] évolue de la
façon suivante :

1. ....
2. // c'est bon - on peut demander la feuille de salaire
3. FeuilleSalaire feuilleSalaire = null;
4. IMetierRemote metier=null;
5. try {
6. // contexte JNDI du serveur Glassfish
7. InitialContext initialContext = new InitialContext();
8. // instanciation couche métier
9. metier = (IMetierRemote) initialContext.lookup("java:global/pam-serveur-metier-dao-jpa-
eclipselink/Metier!metier.IMetierRemote");
10. // calcul de la feuille de salaire
11. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
12. } catch (PamException ex) {
13. System.err.println("L'erreur suivante s'est produite : "
14. + ex.getMessage());
15. return;
16. } catch (Exception ex) {
17. System.err.println("L'erreur suivante s'est produite : "
18. + ex.toString());
19. return;
20. }
21. ....

• ligne 7 : initialisation du contexte JNDI du serveur Glassfish.


• ligne 9 : on demande à ce contexte JNDI une référence sur l'interface distante de la couche [metier]. D'après les logs de
Glassfish, on sait que l'interface distante de la couche [metier] a deux noms possibles :

1. INFO: Portable JNDI names for EJB Metier : [java:global/pam-serveur-metier-dao-jpa-eclipselink/Metier!


metier.IMetierRemote, java:global/pam-serveur-metier-dao-jpa-eclipselink/Metier!metier.IMetierLocal]
2. INFO: Glassfish-specific (Non-portable) JNDI names for EJB Metier : [metier.IMetierRemote#metier.IMetierRemote,
metier.IMetierRemote]

http://tahe.developpez.com/java/javaee
100/339
Ligne 1, le nom JNDI utilisable avec tout serveur d'applications JAVA EE 6. Ligne 2, le nom JNDI spécifique à Glassfish.
Dans le code, ligne 9, nous utilisons le nom JNDI portable.

• le reste du code ne change pas

En [1], on configure le projet pour qu'il exécute la classe [MainRemote] avec des arguments. Si tout va bien, l'exécution du projet
donne le résultat suivant :

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

Si dans les propriétés, on met un n° de sécurité sociale incorrect, on obtient le résultat suivant :

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

Lorsqu'on construit le projet, Netbeans produit une archive exécutable. Celle-ci est placée dans le sous-dossier [dist] du dossier du
projet :

http://tahe.developpez.com/java/javaee
101/339
Ci-dessus, l'archive exécutable est [pam-client-metier-dao-jpa-eclipselink.jar]. On notera que dossier [dist / lib] contient les
bibliothèques faisant partie de la branche [Libraries] du projet. Netbeans indique dans les logs de la console comment exécuter
l'archive exécutable [pam-client-metier-dao-jpa-eclipselink.jar] :

1. To run this application from the command line without Ant, try:
2. java -jar "C:\temp\pam\02\pam-client-metier-dao-jpa-eclipselink\dist\pam-client-metier-dao-jpa-
eclipselink.jar"

La ligne 2 indique la commande à exécuter dans une fenêtre Dos pour exécuter le jar créé par Netbeans. Ouvrons une fenêtre Dos
et tapons la commande indiquée plus haut en lui ajoutant les trois paramètres attendus par la classe principale :

Ci-dessus, la commande a échoué. Cela vient du fait que les bibliothèques référencées par l'archive [gf-client.jar] n'ont pas été
trouvées. En effet, celles-ci sont référencées dans le fichier [MANIFEST.MF] de [gf-client.jar] de la façon suivante :

11. Class-Path: ../modules/woodstox-osgi.jar ../modules/tools.jar ../modules/glassfish-corba-asm.jar ../modules/glassfish-


corba-codegen.jar ../ modules/glassfish-corba-csiv2-idl.jar ../modules/glassfish-corba-newt imer.jar ../modules/glassfish-
corba-omgapi.jar ../modules/glassfish-c orba-orb.jar ../modules/glassfish-corba-orbgeneric.jar ../modules/auto-
depends.jar ../modules/config.jar ../modules/config-types.jar ../modules/hk2.jar ../modules/hk2-core.jar
../modules/osgi-adapter.jar ../ modules/tiger-types-osgi.jar ../modules/grizzly-comet.jar ../modules/grizzly-config.jar ../
modules/grizzly-framework.jar ../modules/grizzl y-http.jar ../modules/grizzly-http-servlet.jar ../modules/grizzly-por
tunif.jar ../modules/grizzly-rcm.jar ../modules/grizzly-utils.jar ../ modules/pkg-client.jar ../modules/jaxb-osgi.jar
../modules/webservice s-osgi.jar ../modules/activation.jar ../modules/el-api.jar ../modules .....................................................

Or nous avons vu que l'archive [gf-client.jar] se trouvait dans le dossier [dist / lib] du projet. Les archives ci-dessus sont alors
cherchées dans un dossier [dist / modules] qui n'existe pas.

13.3 Client console - version 2


Nous créons un nouveau projet Java par copie du précédent [pam-client-metier-dao-jpa-eclipselink] :

12
3

http://tahe.developpez.com/java/javaee
102/339
• en [1], le nouveau projet
• en [2], on enlève du projet la bibliothèque [gf-link.jar]
• en [3], on ajoute de nouvelles bibliothèques au projet

5 6

• en [4,5], on prend toutes archives du dossier [lib / client-glassfish-v3]. Ce dossier a été créé par copie du dossier
[<glassfish>/modules]. Il contient donc toutes les archives du dossier [<glassfish>/modules].
• en [6], les nouvelles bibliothèques du projet.

Après avoir construit le nouveau projet (Clean and Build), le dossier [dist] a le contenu suivant :

1 2

• en [1], le dossier [dist] du projet


• en [2], le contenu du dossier [dist / lib]. Les archives qui y sont contenues font automatiquement partie du Classpath du
projet.

Dans les logs issus du Build, Netbeans indique comment exécuter l'application dans une fenêtre DOS :

1. To run this application from the command line without Ant, try:
2. java -jar "C:\temp\pam\02\pam-client-metier-dao-jpa-eclipselink-v2\dist\pam-client-metier-dao-jpa-
eclipselink-v2.jar"

Ouvrons une fenêtre Dos et tapons la commande indiquée plus haut en lui ajoutant les trois paramètres attendus par la classe
principale :

Cette fois-ci l'application fonctionne et elle fonctionne en-dehors de Netbeans.

http://tahe.developpez.com/java/javaee
103/339
13.4 Client console - version 3
Dans les versions précédentes, l'environnement JNDI du serveur Glassfish était configuré à partir d'un fichier [jndi.properties]
trouvé quelque part dans les archives du projet. Son contenu par défaut 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. Ce fichier ne permet 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]. Pour montrer cette technique, nous dupliquons la première version du client console
[pam-client-metier-dao-jpa-eclipselink] en un nouveau projet Netbeans [pam-openejb-ui-metier-dao-jpa-eclipselink-v3] :

1
2

Dans l'onglet [Files], nous créons le fichier [jndi.properties - 1] dans le dossier [src] du projet avec le contenu décrit précédemment.
Lorsque le projet est compilé, ce fichier est recopié [2] dans le jar [3] produit par le build du projet dans le dossier dist [4]. Il existe
maintenant deux fichiers [jndi.properties] :
• celui qui était utilisé auparavant et qui se trouve dans l'une des archives du dossier [lib]
• celui dans l'archive [dist/pam-openejb-ui-metier-dao-jpa-eclipselink-v3.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] [5]. Ce fichier est généré à partir du fichier [manifest.mf] [6] qu'on trouve à la racine du
dossier du projet Netbeans.

5
6

http://tahe.developpez.com/java/javaee
104/339
Regardons le contenu du fichier [manifest.mf] [6] :

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

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

1. Manifest-Version: 1.0
2. Ant-Version: Apache Ant 1.7.1
3. Created-By: 10.0-b23 (Sun Microsystems Inc.)
4. Main-Class: ui.console.MainRemote
5. Class-Path: lib/gf-client.jar lib/mysql-connector-java-5.1.6-bin.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. Il reprend les éléments de la branche [Libraries] du projet. Si on laisse le ClassPath tel quel, le fichier
[jndi.properties] sera trouvé dans l'une des archives référencées par l'archive [gf-client.jar] (ligne 5). Le fichier [jndi.properties] qui se
trouve dans l'archive [dist/pam-client-metier-dao-jpa-eclipselink-v3.jar] ne sera pas exploité.

Une solution est d'inclure le dossier courant dans le ClassPath. Le Classpath du fichier [manifest.mf] est défini comme suit :

1. Manifest-Version: 1.0
2. X-COMMENT: Main-Class will be added automatically by build
3. Class-Path: . lib/gf-client.jar lib/mysql-connector-java-5.1.6-bin.jar

Ligne 3, on définit explicitement le ClassPath. Dans celui-ci, comme premier élément, on met le dossier courant, noté .

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

Lorsqu'on exécute l'application, elle fonctionne mais on peut avoir des doutes sur le fichier [jndi.properties] réellement utilisé, le
nôtre ou celui contenu dans l'une des archives du dossier [lib]. 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. L'erreur suivante s'est produite : javax.naming.NamingException: Lookup failed for


'java:global/pam-serveur-metier-dao-jpa-eclipselink/Metier!metier.IMetierRemote' in SerialContext
targetHost=localhost,targetPort=3701,orb'sInitialHost=localhost,orb'sInitialPort=3700 [Root
exception is javax.naming.NamingException: Unable to acquire SerialContextProvider for
SerialContext
targetHost=localhost,targetPort=3701,orb'sInitialHost=localhost,orb'sInitialPort=3700 [Root
exception is org.omg.CORBA.COMM_FAILURE: vmcid: SUN minor code: 201 completed: No]]

Le message d'erreur indique que le port 3701 a été utilisé (targetPort). C'est donc bien notre fichier [jndi.properties] qui a été utilisé.
On remettra le port à 3700 dans [jndi.properties].

13.5 Client console - version 4


Dans les versions précédentes, l'environnement JNDI du serveur é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 du serveur.

Nous commençons par créer un nouveau projet à partir du projet [pam-client-metier-dao-jpa-eclipselink] initial.

http://tahe.developpez.com/java/javaee
105/339
3
1
2

• en [1], le nouveau projet


• en [2], les trois archives nécessaires à Spring (lib / spring) ont été ajoutées au projet
• en [3], le fichier [spring-config-ui.xml] de configuration de Spring. Son contenu est le suivant :

Le fichier de configuration de Spring est le suivant :

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:jee="http://www.springframework.org/schema/jee"
4. xsi:schemaLocation="
5. http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7. http://www.springframework.org/schema/jee
8. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
9.
10. <!-- métier -->
11. <jee:jndi-lookup id="metier" jndi-name="java:global/pam-serveur-metier-dao-jpa-
eclipselink/Metier!metier.IMetierRemote">
12. <jee:environment>
13. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
14. java.naming.factory.url.pkgs=com.sun.enterprise.naming
15. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
16. org.omg.CORBA.ORBInitialHost=localhost
17. org.omg.CORBA.ORBInitialPort=3700
18. </jee:environment>
19. </jee:jndi-lookup>
20. </beans>

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

• ligne 11 : 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 associée à l'EJB [Metier]. Le nom JNDI utilisé ici est le nom portable (Java
EE 6) de l'EJB.
• le contenu du fichier [jndi.properties] utilisé dans les deux versions précédentes devient le contenu de la balise
<jee:environment> (ligne 12) qui sert à définir les paramètres de connexion au service JNDI.

La classe principale [MainRemote] évolue de la façon suivante :

1. ...
2. // c'est bon - on peut demander la feuille de salaire
3. FeuilleSalaire feuilleSalaire = null;
4. IMetierRemote metier=null;
5. try {
6. // instanciation couche [metier]
7. ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-ui.xml");
8. metier = (IMetierRemote) ctx.getBean("metier");
9. // calcul de la feuille de salaire
10. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
11. } catch (PamException ex) {
12. System.err.println("L'erreur suivante s'est produite : "
13. + ex.getMessage());
14. return;
15. } catch (Exception ex) {
16. System.err.println("L'erreur suivante s'est produite : "
17. + ex.toString());
18. return;
19. }
20. ...

http://tahe.developpez.com/java/javaee
106/339
Lignes 7-8, la référence de type [IMetierRemote] sur la couche [metier] est demandée à Spring. Cette solution amène de la souplesse
dans 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
[MainRemote], 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 au paragraphe 9.

Le lecteur est invité à tester cette nouvelle version.

13.6 Un client console de type [Enterprise Client Application]


Nous décrivons maintenant un nouveau client qui nécessite ce qu'on appelle un ACC (Application Client Container) pour être
exécuté. Un client exécuté dans ce contexte peut se voir injecter des EJB via l'annotation @EJB ce qui évite les appels JNDI faits au
serveur d'applications.

Nous créons un nouveau projet (File / New Project) :

4
3
1
2

• en [1], choisir la catégorie [Java EE]


• en [2], choisir un projet de type [Enterprise Application Client]
• en [3], choisir le dossier parent du projet
• en [4], donner un nom au projet

5
7

8
6

• en [5], indiquer que l'application sera une application client du serveur Glassfish v3
• en [6], donner un nom à la classe principale
• en [7], le projet Netbeans ainsi créé
• en [8], on peut voir que Netbeans a adjoint automatiquement au projet un certain nombre d'archives liées au serveur
Glassfish. Elles vont permettre au client de communiquer avec le serveur.

Nous allons modifier le projet généré de la façon suivante :

http://tahe.developpez.com/java/javaee
107/339
1
2
3

En [1], nous supprimons les packages existants de [Source Packages] pour les remplacer en [2] par les packages copiés à partir client
initial [pam-client-metier-dao-jpa-eclipselink]. En [3], nous configurons l'environnement d'exécution du projet comme pour les
versions précédentes.

Puis nous modifions le code de la classe principale [MainRemote] de la façon suivante :

1. ....
2. public class MainRemote {
3.
4. // EJB distant
5. @EJB
6. static IMetierRemote metier;
7.
8. public static void main(String[] args) {
9. ....
10. // c'est bon - on peut demander la feuille de salaire à la couche [metier]
11. FeuilleSalaire feuilleSalaire = null;
12. try {
13. // calcul de la feuille de salaire
14. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
15. } catch (PamException ex) {
16. ...
17. } catch (Exception ex) {
18. ...
19. }
20. // affichage détaillé
21. ...

Parce que le client est exécuté dans un environnement managé, l'Application Client Container (ACC), il est possible d'injecter l'EJB
[Metier] distant en lignes 5-6. C'est le conteneur ACC qui fait cette injection. Ligne 14, l'EJB de la couche [metier] est utilisé pour
calculer le salaire.

Le lecteur est invité à exécuter ce nouveau client. On rappelle que le serveur EJB doit être actif, c.a.d. que l'application [pam-
serveur-metier-dao-jpa-eclipselink] doit être déployée sur le serveur Glassfish v3.

La construction du projet produit plusieurs archives jar [1] dans le dossier [dist] du projet (onglet Files). Par ailleurs, l'application
cliente est déployée sur le serveur d'applications [2].

L'archive [pam-acc-client-metier-dao-jpa-eclipselink.jar] ci-dessus [1] peut être exécutée en-dehors de Netbeans à l'aide d'un script
fourni avec le serveur Glassfish.

Appelons <glassfish> le dossier d'installation du serveur Glassfish. Ce dossier [1] peut être trouvé dans les propriétés du serveur
(onglet Services, clic droit sur le serveur Glassfish, option Propriétés) :

http://tahe.developpez.com/java/javaee
108/339
2

Dans <glassfish>/bin, on trouve le script [appclient.bat] qui permet d'exécuter l'archive [pam-acc-client-metier-dao-jpa-
eclipselink.jar] selon la syntaxe suivante :

dos>"<glassfish>\bin\appclient.bat" -client <chemin>/pam-acc-client-metier-dao-jpa-eclipselink.jar


254104940426058 150 20

La commande précédente est exécutée dans une fenêtre Dos. Il faut mettre le chemin du .jar à exécuter ou se placer dans le dossier
de l'archive à exécuter. Le résultat obtenu est le suivant :

Au final, comme dans le cas de la version 2 du client console, on a bien un client capable de s'exécuter en-dehors de Netbeans.

13.7 Le client Swing


Nous construisons maintenant le client swing de notre application client / serveur Ejb. Pour cela nous créons un nouveau projet.

2
4

• en [1], le nouveau projet [pam-clientswing-metier-dao-jpa-eclipselink] est obtenue par recopie du projet console [pam-
client-metier-dao-jpa-eclipselink-v4].
• en [2], le package [ui.swing] est obtenu par recopie du package du même nom du projet [pam-spring-ui-metier-dao-jpa-
hibernate]. Des erreurs apparaissent parce qu'il manque des bibliothèques au projet.

http://tahe.developpez.com/java/javaee
109/339
• on ajoute aux bibliothèques du projet [3], celles nécessaires aux interfaces swing [4], comme montré ci-dessous :

Après l'ajout de ces bibliothèques, il n'y a plus d'erreur dans le projet :

Ci-dessus, la classe [PamJFrame] avait été écrite initialement pour s'exécuter dans un environnement Spring / JPA :

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

Maintenant cette classe doit devenir le client distant d'un EJB déployé sur le serveur Glassfish.

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

Jvm1 - Java SE 7 Jvm2 – Java EE - serveur Glassfish v3

Travail pratique : en suivant l'exemple du client console [ui.console.MainRemote] du projet, modifier la façon utilisée par la
méthode [doMyInit] (cf page 58) de la classe [PamJFrame] pour acquérir une référence sur la couche [metier] qui est maintenant
distante.

13.8 Les tests unitaires JUnit

http://tahe.developpez.com/java/javaee
110/339
Nous souhaitons refaire les tests JUnit distants réalisés avec le conteneur OpenEJB. Nous voulons vérifier l'interchangeabilité des
conteneurs Ejb. S'ils sont réellement interchangeables, les tests déjà réalisés avec OpenEJB devraient réussir ici également. Nous
créons un nouveau projet par recopie du projet client console [pam-client-metier-dao-jpa-eclipselink-v4] :

1 2 3

• en [1], le nouveau projet obtenu par recopie du projet client console [pam-client-metier-dao-jpa-eclipselink-v4]
• en [2], dans la branche [Test Packages], on copie les tests JUnit du projet OpenEJB [pam-openejb-ui-metier-dao-jpa-
eclipselink]. On ne garde que les tests des interfaces distantes des Ejb. Des erreurs apparaissent parce que ces tests
référencent des classes d'un package [dao] qui n'existe pas dans le projet [1].
• en [3], dans la branche [Source Packages], on copie le package [dao] du projet OpenEJB [pam-openejb-ui-metier-dao-jpa-
eclipselink]. On ne garde que les interfaces distantes des Ejb. Des erreurs apparaissent parce que ces interfaces utilisent des
notations propres aux Ejb et que la bibliothèque gérant ces annotations n'a pas été incluse dans le projet. Nous faisons en
sorte que les interfaces distantes n'utilisent plus d'annotations EJB :

package dao;

public interface ICotisationDaoRemote extends ICotisationDao{


}

package dao;

public interface IEmployeDaoRemote extends IEmployeDao{


}

package dao;

public interface IIndemniteDaoRemote extends IIndemniteDao{


}

Ceci fait, le projet ne présente plus d'erreurs.

http://tahe.developpez.com/java/javaee
111/339
Les classes JUnit 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 EclipseLink] 6
7

Java SE Java EE 5 - serveur Glassfish

Tests de la couche [dao]

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

Java SE Java EE 5 - serveur Glassfish

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

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:jee="http://www.springframework.org/schema/jee"
4. xsi:schemaLocation="
5. http://www.springframework.org/schema/beans
6. http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7. http://www.springframework.org/schema/jee
8. http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
9.
10. <!-- dao -->
11. <jee:jndi-lookup id="employeDao" jndi-name="java:global/pam-serveur-metier-dao-jpa-
eclipselink/EmployeDao!dao.IEmployeDaoRemote">
12. <jee:environment>
13. java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
14. java.naming.factory.url.pkgs=com.sun.enterprise.naming

http://tahe.developpez.com/java/javaee
112/339
15. java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl
16. org.omg.CORBA.ORBInitialHost=localhost
17. org.omg.CORBA.ORBInitialPort=3700
18. </jee:environment>
19. </jee:jndi-lookup>
20.
21. <jee:jndi-lookup id="indemniteDao" jndi-name="java:global/pam-serveur-metier-dao-jpa-
eclipselink/IndemniteDao!dao.IIndemniteDaoRemote">
22. <jee:environment>
23. ...
24. </jee:environment>
25. </jee:jndi-lookup>
26.
27. <jee:jndi-lookup id="cotisationDao" jndi-name="java:global/pam-serveur-metier-dao-jpa-
eclipselink/CotisationDao!dao.ICotisationDaoRemote">
28. <jee:environment>
29. ...
30. </jee:environment>
31. </jee:jndi-lookup>
32.
33. <!-- métier -->
34. <jee:jndi-lookup id="metier" jndi-name="java:global/pam-serveur-metier-dao-jpa-
eclipselink/Metier!metier.IMetierRemote">
35. <jee:environment>
36. ...
37. </jee:environment>
38. </jee:jndi-lookup>
39. </beans>

L'environnement JNDI est celui déjà utilisé, par exemple dans le projet [pam-client-metier-dao-jpa-eclipselink-v4]. Il est peut-être
possible d'éviter sa répétition (lignes 12-18, 22-24, 28-30, 35-37).

Travail pratique : modifier la méthode d'initialisation (méthode init) 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.xml] précédent et exécuter les tests.

Nous ajoutons maintenant un nouveau test de la couche [dao] :

2
1

• en [1], la classe [JUnitDao] peut être trouvée dans le projet Spring / Jpa [pam-spring-ui-metier-dao-jpa-hibernate].
• en [2], cette classe est renommée [JUnitDaoRemote]

Travail pratique :
- modifier la méthode d'initialisation (méthode init) du test afin qu'elle acquière des références sur les Ejb distants [*Dao] via le
fichier [spring-config-ui.xml]
- si besoin est modifier les méthodes de test afin qu'elles utilisent les Ejb distants

Pour comprendre le second point, il faut se souvenir que dans le projet Spring / Jpa, la classe de test et la couche [dao] s'exécutaient
dans la même Jvm. Donc, les paramètres passés aux méthodes de la couche [dao] l'étaient par référence. Ici, la classe de test et la
couche [dao] s'exécutent dans deux Jvm différentes. Aussi, les paramètres passés aux méthodes de la couche [dao] le sont par
valeur.

http://tahe.developpez.com/java/javaee
113/339
Soit la méthode de test suivante :

1. @Test
2. public void test06() {
3. log("test06");
4. // on crée deux employés avec le même n° SS
5. // enfreint la contrainte d'unicité sur le n° SS
6. boolean erreur = true;
7. Employe employe1 = null;
8. Employe employe2 = null;
9. Indemnite indemnite1 = null;
10. Indemnite indemnite2 = null;
11. Throwable th = null;
12. try {
13. indemnite1 = new Indemnite(1, 1.93, 2, 3, 12);
14. indemnite2 = new Indemnite(2, 2.1, 2.1, 3.1, 15);
15. indemniteDao.create(indemnite1);
16. indemniteDao.create(indemnite2);
17. employeDao.create(employe1 = new Employe("254104940426058", "Jouveinal", "Marie", "5 rue des
oiseaux", "St Corentin", "49203", indemnite1));
18. employeDao.create(employe2 = new Employe("254104940426058", "Jouveinal", "Marie", "5 rue des
oiseaux", "St Corentin", "49203", indemnite2));
19. erreur = false;
20. } catch (PamException ex) {
21. th = ex;
22. // vérifications
23. Assert.assertEquals(21, ex.getCode());
24. } catch (Throwable th1) {
25. th = th1;
26. }
27. // vérifications
28. Assert.assertTrue(erreur);
29. // chaîne des exceptions
30. System.out.println("Chaîne des exceptions --------------------------------------");
31. System.out.println(th.getClass().getName());
32. while (th.getCause() != null) {
33. th = th.getCause();
34. System.out.println(th.getClass().getName());
35. }
36. // le 1er employé a du être persisté
37. Employe employe = employeDao.find(employe1.getId());
38. // vérification
39. Assert.assertNotNull(employe);
40. Assert.assertEquals("254104940426058", employe.getSS());
41. Assert.assertEquals("Jouveinal", employe.getNom());
42. Assert.assertEquals("Marie", employe.getPrenom());
43. Assert.assertEquals("5 rue des oiseaux", employe.getAdresse());
44. Assert.assertEquals("St Corentin", employe.getVille());
45. Assert.assertEquals("49203", employe.getCodePostal());
46. Assert.assertEquals(indemnite1, employe.getIndemnite());
47. // le 2ème employé n'a pas du être persisté
48. List<Employe> employes = employeDao.findAll();
49. int nbEmployes = employes.size();
50. Assert.assertEquals(1, nbEmployes);
51. }

Ci-dessus, la ligne 15

indemniteDao.create(indemnite1);

crée un objet Indemnite. Après cette création, l'objet créé indemnite1 a une clé primaire Id qu'il n'avait pas avant sa création. Si le
programme de test et la couche [dao] s'exécutent dans la même Jvm, l'objet indemnite1 a été passé par référence et est partagé par la
méthode appelante et la méthode appelée. La classe de test voit donc bien l'objet indemnite1 avec sa clé primaire. Si le programme
de test et la couche [dao] s'exécutent dans deux Jvm, l'objet indemnite1 a été passé par valeur et n'est pas partagé par la méthode
appelante et la méthode appelée. La classe de test ne voit donc pas l'objet indemnite1 avec sa clé primaire. Seule la couche [dao] le
voit. Il faut alors écrire :

indemnite1=indemniteDao.create(indemnite1);

c'est à dire utiliser le résultat renvoyé par la méthode create. Le test précédent doit alors être réécrit comme suit :

1. @Test
2. public void test06() {
3. log("test06");
4. // on crée deux employés avec le même n° SS
5. // enfreint la contrainte d'unicité sur le n° SS
6. boolean erreur = true;

http://tahe.developpez.com/java/javaee
114/339
7. Employe employe1 = null;
8. Employe employe2 = null;
9. Indemnite indemnite1 = null;
10. Indemnite indemnite2 = null;
11. Throwable th = null;
12. try {
13. indemnite1 = new Indemnite(1, 1.93, 2, 3, 12);
14. indemnite2 = new Indemnite(2, 2.1, 2.1, 3.1, 15);
15. indemnite1=indemniteDao.create(indemnite1);
16. indemnite2=indemniteDao.create(indemnite2);
17. employe1=employeDao.create(employe1 = new Employe("254104940426058", "Jouveinal", "Marie",
"5 rue des oiseaux", "St Corentin", "49203", indemnite1));
18. employe2=employeDao.create(employe2 = new Employe("254104940426058", "Jouveinal", "Marie",
"5 rue des oiseaux", "St Corentin", "49203", indemnite2));
19. erreur = false;
20. } catch (PamException ex) {
21. th = ex;
22. // vérifications
23. Assert.assertEquals(21, ex.getCode());
24. } catch (Throwable th1) {
25. th = th1;
26. }
27. // vérifications
28. Assert.assertTrue(erreur);
29. // chaîne des exceptions
30. System.out.println("Chaîne des exceptions --------------------------------------");
31. System.out.println(th.getClass().getName());
32. while (th.getCause() != null) {
33. th = th.getCause();
34. System.out.println(th.getClass().getName());
35. }
36. // le 1er employé a du être persisté
37. Employe employe = employeDao.find(employe1.getId());
38. // vérification
39. Assert.assertNotNull(employe);
40. Assert.assertEquals("254104940426058", employe.getSS());
41. Assert.assertEquals("Jouveinal", employe.getNom());
42. Assert.assertEquals("Marie", employe.getPrenom());
43. Assert.assertEquals("5 rue des oiseaux", employe.getAdresse());
44. Assert.assertEquals("St Corentin", employe.getVille());
45. Assert.assertEquals("49203", employe.getCodePostal());
46. Assert.assertEquals(indemnite1, employe.getIndemnite());
47. // le 2ème employé n'a pas du être persisté
48. List<Employe> employes = employeDao.findAll();
49. int nbEmployes = employes.size();
50. Assert.assertEquals(1, nbEmployes);
51. }

Ceci fait, les tests réussissent :

http://tahe.developpez.com/java/javaee
115/339
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 EclipseLink]

Java SE Java EE - serveur Glassfish

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 EclipseLink]
1 HTTP /
SOAP
Java SE Java EE - serveur Glassfish

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 Glassfish alors que le client lui, pourrait être un client .NET ou PHP.

Nous allons développer cette architecture selon trois modes différents :

1. le service web sera assuré par l'EJB [Metier]


2. le service web sera assuré par une application web utilisant l'EJB [Metier]
3. le service web sera assuré par une application web utilisant Spring

Un service web peut être implémenté de diverses façons au sein d'un serveur Java EE :

• 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

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

Nous començons par cette dernière architecture.

http://tahe.developpez.com/java/javaee
116/339
14.1 Service web implémenté par un EJB

14.1.1 La partie serveur

14.1.1.1 Le projet Netbeans

Commençons par créer un nouveau projet Netbeans copie du projet EJB [pam-serveur-metier-dao-jpa-eclipselink] :

Dans l'architecture suivante :

Couche C S Couche Couche Couche Couche


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

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
20. public FeuilleSalaire calculerFeuilleSalaire(String SS,
21. ...
22. }
23.
24. // liste des employés
25. @WebMethod
26. public List<Employe> findAllEmployes() {
27. ...
28. }
29. ...

http://tahe.developpez.com/java/javaee
117/339
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]. Le serveur MySQL soit être lancé et sa base [dbpam_eclipselink] exister et être remplie.
Il peut être nécessaire auparavant de supprimer [3] les Ejb du projet client / serveur EJB étudié précédemment 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 Glassfish. Une fois le service web déployé, il peut être testé :

• en [1], dans le projet courant, nous testons le service web [Metier]


• le service web est accessible via différentes URL. L'URL [2] permet de tester le service web

http://tahe.developpez.com/java/javaee
118/339
• en [3], un lien sur le fichier XML définissant le service web. Les clients du service web ont besoin de connaître l'url de ce
fichier. C'est à partir de lui qu'est générée la couche cliente (stubs) du service web.
• en [4,5], un formulaire permettant de tester les méthodes exposées par le service web. Celles-ci sont présentées avec leurs
paramètres que l'utilisateur peut définir.

Par exemple, testons la méthode [findAllEmployes] qui n'a besoin d'aucun paramètre :

Ci-dessus, nous testons la méthode. Nous recevons alors la réponse ci-dessous (vue partielle). Nous y retrouvons bien les deux
employés avec leurs indemnités. Le lecteur est invité à tester de la même façon la méthode [4] en lui passant les trois paramètres
qu'elle attend.

14.1.2 La partie cliente

http://tahe.developpez.com/java/javaee
119/339
14.1.2.1 Le projet Netbeans du client console

Nous créons maintenant un projet Java de type [Java Application] pour la partie client de l'application. Une fois le projet créé, nous
indiquons qu'il sera client du service web que nous venons de déployer sur le serveur Glassfish :

3
1 2

• en [1], le projet du client du service web


• en [2], nous sélectionnons le nouveau projet et activons le bouton [New File]
• en [3], nous indiquons que nous voulons créer un client de service web

3 5

• avec [4], nous allons désigner le projet Netbeans du service web


• dans la fenêtre [5], sont listés tous les projets ayant une branche [Web Services], ici uniquement le projet [pam-serveurws-
metier-dao-jpa-eclipselink].
• un projet peut déployer plusieurs services web. En [6], nous désignons le service web auquel on veut se connecter.

http://tahe.developpez.com/java/javaee
120/339
• en [7], est affichée l'Url de définition du service web. Cette url est utilisée par les outils logiciels qui génèrent la couche
cliente qui va s'interfacer avec le service web.

Couche C S Couche Couche Couche Couche


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

• 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 [8].
• en [9], on indique le type de client souhaité. On garde la valeur par défaut [JAX-WS].
• 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 [Generated Sources] qui contient les classes de la couche [C] qui permettent au
client [3] de communiquer avec le service web. Cette couche permet au client [3] de communiquer avec la couche [metier]
[4] comme si elle était locale et non distante.
• en [9], apparaît une arborescence [Web Service References] qui liste les services web pour lesquels une couche cliente a été
générée.

On notera que dans la couche [C] [9] générée, 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 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.1.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 [pam-client-
metier-dao-jpa-eclipselink] qui était un client d'un serveur Ejb, dans le nouveau projet et nous la renommons [MainClientWs].

http://tahe.developpez.com/java/javaee
121/339
1

2
3

• en [1], la classe du client du service web. La classe [MainClientWs] présente des erreurs. Pour les corriger, on commencera
par supprimer toutes les instructions [import] existantes dans la classe et on les régènerera par l'option [Fix Imports]. En
effet, certaines des classes utilisées par la classe [MainClientWs] font désormais partie du package [clientws.metier] [2]
généré.
• en [3], le morceau de code où la couche [metier] est instanciée [3]. Elle est avec du code JNDI pour obtenir une référence
sur un Ejb distant.

Nous faisons évoluer le code de la façon suivante :

• le code JNDI est supprimé


• la classe [PamException] n'existant pas côté client, nous supprimons le catch qui lui est associé.

• en [4], il nous reste à obtenir une référence sur la service web distant [Metier] afin de pouvoir appeler sa méthode
[calculerFeuilleSalaire].
• en [5], avec la souris, nous tirons (drag) la méthode [calculerFeuilleSalaire] du service web [Metier] pour la déposer (drop)
en [4]. Du code est généré [6]. Ce code générique peut être ensuite adapté par le développeur.

http://tahe.developpez.com/java/javaee
122/339
6

• ligne 71, on voit que [calculerFeuilleSalaire] est une méthode de la classe [clientws.metier.Metier] (ligne 65). Maintenant
que nous savons comment obtenir la couche [metier], le code précédent peut être réécrit de la façon suivante :

1. ...
2. // c'est bon - on peut demander la feuille de salaire
3. FeuilleSalaire feuilleSalaire = null;
4. Metier metier = null;
5. try {
6. // instanciation couche [metier]
7. metier = new MetierService().getMetierPort();
8. // calcul de la feuille de salaire
9. feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées,
nbJoursTravaillés);
10. } catch (Throwable th) {
11. // chaîne des exceptions
12. System.out.println("Chaîne des exceptions --------------------------------------");
13. System.out.println(th.getClass().getName() + ":" + th.getMessage());
14. while (th.getCause() != null) {
15. th = th.getCause();
16. System.out.println(th.getClass().getName() + ":" + th.getMessage());
17. }
18. System.exit(1);
19. }
20. // affichage rapide
21. ...

La ligne 7 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 [Exception] qui est gérée mais le type plus général Throwable, la classe parent de la
classe Exception. S'il y a exception, nous affichons toutes les causes imbriquées de celle-ci jusqu'à la cause originelle.

Nous sommes prêts pour les tests :

• s'assurer que le SGBD MySQL5 est lancé, que la base dbpam_eclipselink est créée et initialisée
• s'assurer que le service web est déployé sur le serveur Glassfish
• construire le client (Clean and Build)
• configurer l'exécution du client

http://tahe.developpez.com/java/javaee
123/339
• exécuter le client

Les résultats dans la console sont les suivants :

1. ...
2. Valeurs saisies :
3. N° de sécurité sociale de l'employé : 254104940426058
4. Nombre d'heures travaillées : 150
5. Nombre de jours travaillés : 20
6.
7. Informations Employé :
8. Nom : Jouveinal
9. Prénom : Marie
10. Adresse : 5 rue des oiseaux
11. ...

Avec la configuration suivante :

on obtient les résultats suivants :

1. Chaîne des exceptions --------------------------------------


2. javax.xml.ws.soap.SOAPFaultException:L'employé de n°[xx] est introuvable
3. com.sun.xml.internal.ws.developer.ServerSideException:L'employé de n°[xx] est introuvable
4. Java Result: 1

On notera qu'alors que le service web [Metier] envoie une exception de type [PamException] l'exception reçue par le client de type
[SOAPFaultException]. Même dans la chaîne des exceptions, on ne voit pas apparaître le type [PamException].

14.1.3 Le client swing du service web Metier

Travail à faire : porter le client swing du projet [pam-clientswing-metier-dao-jpa-eclipselink] dans le nouveau projet afin que lui
aussi soit client du service web déployé sur le serveur Glassfish.

14.2 Service web implémenté par une application web


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

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

http://tahe.developpez.com/java/javaee
124/339
Le service web est assuré par une application web exécutée au sein du conteneur web du serveur Glassfish. Ce service web va
s'appuyer sur l'Ejb [Metier] déployé lui dans le conteneur Ejb3.

14.2.1 La partie serveur

Nous créons une application web :

3
1
2

• en [1], nous créons un nouveau projet


• en [2], ce projet est de type [Web Application]
• en [3], nous lui donnons le nom [pam-webservice-metier-dao-jpa-eclipselink]

5
4

• en [4], nous choisissons la version Java EE 5


• en [5], nous n'utilisons aucun des frameworks proposés
• en [6], le projet créé

Sur le schéma ci-dessous, l'application web créée va s'exécuter dans le conteneur web. Elle va utiliser l'Ejb [Metier] qui lui sera
déployé dans le conteneur Ejb du serveur.

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

Pour que l'application web créée ait accès aux classes associées à l'Ejb [Metier], nous ajoutons aux bibliothèques de l'application web
[pam-webservice-metier-dao-jpa-eclipselink], l'archive jar du serveur Ejb [pam-serveur-metier-dao-jpa-eclipselink] déjà étudié.

http://tahe.developpez.com/java/javaee
125/339
1

4
2

5
3

• en [1], on ajoute un projet aux bibliothèques du projet web


• en [2,3], on sélectionne le projet [pam-serveur-metier-dao-jpa-eclipselink]
• en [4], c'est l'archive .jar du projet qui sera ajouté aux bibliothèques du projet web
• en [5], le jar du projet Ejb a été ajouté aux bibliothèques du projet web.

Pour créer le même service web que précédemment, il nous faut :

• créer une classe taguée @Webservice


• avec deux méthodes calculerFeuilleSalaire et findAllEmployes taguées @WebMethod

Nous créons une classe [PamWsEjbMetier] dans un package [pam.ws] :

La classe [PamWsEjbMetier] est la suivante :

1. package pam.ws;
2.
3. import java.util.List;
4. import javax.ejb.EJB;
5. import javax.jws.WebMethod;
6. import javax.jws.WebService;
7. import jpa.Employe;
8. import metier.FeuilleSalaire;
9. import metier.IMetier;
10. import metier.IMetierLocal;
11.
12. @WebService
13. public class PamWsEjbMetier implements IMetier{
14.

http://tahe.developpez.com/java/javaee
126/339
15. @EJB
16. private IMetierLocal metier;
17.
18. @WebMethod
19. public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés) {
20. return metier.calculerFeuilleSalaire(SS, nbHeuresTravaillées, nbJoursTravaillés);
21. }
22.
23. @WebMethod
24. public List<Employe> findAllEmployes() {
25. return metier.findAllEmployes();
26. }
27.
28. }

• lignes 7-10 : la classe importe des classes du module Ejb [pam-serveurws-metier-dao-jpa-eclipselink] dont l'archive .jar a
été ajoutée aux bibliothèques du projet.
• ligne 12 : la classe est un service web
• ligne 13 : elle implémente l'interface IMetier définie dans le module Ejb
• lignes 18-19 : la méthode calculerFeuilleSalaire est exposée comme méthode du service web
• lignes 23-24 : la méthode findAllEmployes est exposée comme méthode du service web
• lignes 15-16 : l'interface locale de l'Ejb [Metier] est injectée dans le champ de la ligne 16. Nous utilisons l'interface locale
car l'application web et le module Ejb s'exécutent dans la même Jvm.
• lignes 20 et 25 : les méthodes calculerFeuilleSalaire et findAllEmployes délèguent leur traitement aux méthodes de même nom
de l'Ejb [Metier]. La classe ne sert donc qu'à exposer à des clients distants les méthodes de l'Ejb [Metier] comme des
méthodes d'un service web.

Dans Netbeans, l'application web est reconnue comme exposant un service web :

2
1 3

Pour déployer le service web sur le serveur Glassfish, il nous faut à la fois déployer :

• le module web dans le conteneur web du serveur


• le module Ejb dans le conteneur Ejb du serveur

Pour cela, nous avons besoin de créer une application de type [Enterprise Application] qui va déployer les deux modules en même
temps. Pour ce faire, il faut que les deux projets soient chargés dans Netbeans [2]. Ceci fait, nous créons un nouveau projet [3].

http://tahe.developpez.com/java/javaee
127/339
5

• en [4], nous choisissons un projet de type [Enterprise Application].


• en [5], nous donnons un nom au projet

6 7

• en [6], nous configurons le projet. La version de Java EE sera Java EE 5. Un projet d'entreprise peut être créé avec trois
modules : un module Ejb, un module Web, un module Client d'Application. Ici, le projet d'entreprise va encapsuler le
module Web et le module Ejb déjà créés et chargés dans Netbeans. Donc nous ne demandons pas la création de nouveaux
modules.
• en [7], le projet d'entreprise ainsi créé
• en [8], nous ajoutons des modules au projet d'entreprise

9 10

• en [9], les modules Web et Ejb chargés dans Netbeans sont présentés. Nous sélectionnons les deux modules ci-dessus.
• en [10], le projet d'entreprise inclut désormais les modules Web et Ejb que nous devons déployer sur le serveur Glassfish.

Nous construisons le projet d'entreprise par un Clean and Build. Nous sommes quasiment prêts à le déployer sur le serveur Glassfish.
Auparavant il peut être nécessaire de décharger les applications déjà chargées sur le serveur afin d'éviter d'éventuels conflits de
noms d'Ejb [11] :

http://tahe.developpez.com/java/javaee
128/339
11
13

12

Le serveur MySQL doit être lancé et la base [dbpam_eclipselink] disponible et remplie. Ceci fait, l'application d'entreprise peut être
déployée [12]. En [13], on peut voir qu'elle a bien été déployée sur le serveur Glassfish.

Nous pouvons tester le service web qui vient d'être déployé :

• en [1], nous demandons à tester le service web [PamWsEjbMetier]


• en [2], la page de test. Nous laissons au lecteur le soin de conduire les tests.

14.2.2 La partie cliente

Travail à faire : en suivant la démarche décrite au paragraphe 14.1.2.1, page 120, construire un client console du service web
précédent.

http://tahe.developpez.com/java/javaee
129/339
http://tahe.developpez.com/java/javaee
130/339
15 Version 5 - Application PAM 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 222, 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] [EclipseLink] [JDBC]
jsf] [metier] [dao]

7 Serveur Glassfish v3

Dans cette version, le serveur Glassfish 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ébergées par le conteneur Ejb3 du serveur (2 ci-dessous)

Conteneur web Conteneur 2 Jpa 3


Navigateur [web / jsf] Ejb3 [metier, dao] EclipseLink 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.1, page 89 et dont l'architecture était la suivante :

client serveur
Couche Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC]
EclipseLink]

Jvm1 - Java SE Jvm2 – Java EE - serveur Glassfish

Les couches [metier, dao] s'exécutaient dans le conteneur Ejb3 du serveur Glassfish et la couche [ui] dans une application console
ou swing sur une autre machine :

client serveur

Client Conteneur Ejb3 Jpa / EclipseLink Données


Java SE serveur Java EE

Dans l'architecture de la nouvelle application :

http://tahe.developpez.com/java/javaee
131/339
Conteneur web Conteneur 2 Jpa 3
Navigateur [web / jsf] Ejb3 [metier, dao] EclipseLink 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

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 [jsf-api.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.

http://tahe.developpez.com/java/javaee
132/339
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 :

On obtient le résultat suivant :

http://tahe.developpez.com/java/javaee
133/339
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 :

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.1, page 89 :

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 :

http://tahe.developpez.com/java/javaee
134/339
4
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.

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. <servlet>
4. <servlet-name>Faces Servlet</servlet-name>
5. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
6. <load-on-startup>1</load-on-startup>
7. </servlet>
8. <servlet-mapping>
9. <servlet-name>Faces Servlet</servlet-name>
10. <url-pattern>/faces/*</url-pattern>
11. </servlet-mapping>
12. <session-config>
13. <session-timeout>
14. 30
15. </session-timeout>
16. </session-config>
17. <welcome-file-list>
18. <welcome-file>faces/form.jsp</welcome-file>
19. </welcome-file-list>
20. <error-page>
21. <error-code>500</error-code>
22. <location>/faces/exception.jsp</location>
23. </error-page>
24. <error-page>
25. <exception-type>java.lang.Exception</exception-type>
26. <location>/faces/exception.jsp</location>
27. </error-page>
28.
29. </web-app>

• ligne 18 : [form.jsp] est la page d'accueil de l'application


• lignes 20-27 : configuration de la page d'exception

http://tahe.developpez.com/java/javaee
135/339
La page [exception.jsp] reprend la technique développée dans l'exemple du paragraphe 21.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 prefix="f" uri="http://java.sun.com/jsf/core"%>
4. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
5. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
6. "http://www.w3.org/TR/html4/loose.dtd">
7.
8. <%
9. // on change le code Http de la page pour certaines versions d'IE
10. response.setStatus(HttpServletResponse.SC_OK);
11. %>
12.
13.
14. <f:view>
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><h:outputText value="#{msg['exception.header']}"/></h3>
24. <h:outputText value="#{requestScope['javax.servlet.error.message']}"/>
25. </body>
26. </html>
27. </f:view>

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

http://tahe.developpez.com/java/javaee
136/339
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 :

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

http://tahe.developpez.com/java/javaee
137/339
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.1, page 89.

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

http://tahe.developpez.com/java/javaee
138/339
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 251, pour la listes des balises utilisables dans un formulaire
• exemple n° 4 (intro-04), page 285, pour les listes déroulantes remplies par le modèle
• exemple n° 6 (intro-06), page 303, pour la validation des saisies
• exemple n° 7 (intro-07), page 325, 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

• 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

http://tahe.developpez.com/java/javaee
139/339
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.

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 :

http://tahe.developpez.com/java/javaee
140/339
1

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 21.7.5, page 330.

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 :

Ce résultat peut être obtenu avec une balise <f:subview> utilisée de la façon suivante :

http://tahe.developpez.com/java/javaee
141/339
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.

http://tahe.developpez.com/java/javaee
142/339
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.1, page
89 :

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 :

http://tahe.developpez.com/java/javaee
143/339
2

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.1, page 89. 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, le projet du serveur Ejb construit au paragraphe 13.1, page 89.

• en [1], ajout d'un projet aux bibliothèques du projet courant


• en [2], choix du projet Ejb [pam-serveur-metier-dao-jpa-eclipselink]

http://tahe.developpez.com/java/javaee
144/339
• 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 [pam-serveur-metier-dao-ejb-jpa-eclipselink.jar] [3] :

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.

16.3 Le projet Netbeans de l'application d'entreprise


Une application d'entreprise permet le déploiement simultané sur un serveur d'applications, 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 :

http://tahe.developpez.com/java/javaee
145/339
4
3

1 2

• 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

7 8

• en [5], nous choisissons Java EE 5


• en [6], 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 [6], nous créons un projet d'entreprise sans modules. Nous allons lui ajouter ses modules web et ejb ultérieurement.
• en [7], le projet d'eentreprise. Sa branche [Java EE modules] est vide.
• en [8], nous chargeons dans Netbeans le module Ejb [pam-serveur-metier-dao-jpa-eclipselink] qui va être intégré au
module d'application d'entreprise.

Nous ajoutons le module web et le module Ejb au projet d'entreprise :

3
1
2

• en [1], ajout d'un nouveau module


• en [2], choix du projet web [pam-jsf-ejb]et du projet Ejb [pam-serveur-metier-dao-jpa-eclipselink]
• en [3], le projet d'entreprise a deux modules

Ceci-fait, construisons le binaire de l'application d'entreprise :

http://tahe.developpez.com/java/javaee
146/339
2

1 4

• en [1], construction du projet


• en [2], dans l'onglet [Files], le dossier [dist] contient l'archive [pam-ea-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 [pam-serveur-metier-dao-jpa-eclipselink.jar].
• en [4] : le fichier [persistence.xml] qui configure la couche de persistance du module Ejb. Son contenu est le suivant :

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


2. <persistence version="2.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_2_0.xsd">
3. <persistence-unit name="pam-serveur-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
4. <jta-data-source>jdbc/dbpam_eclipselink</jta-data-source>
5. <exclude-unlisted-classes>false</exclude-unlisted-classes>
6. <properties/>
7. </persistence-unit>
8. </persistence>

Avant le déploiement de l'application d'entreprise [pam-ea-jsf-ejb], on s'assurera que la base MySQL [dbpam_eclipselink] existe et
est remplie.

Ceci fait, nous pouvons déployer l'application d'entreprise [pam-ea-jsf-ejb] :

3
1

• en [1], l'application d'entreprise est déployée


• en [2], l'application d'entreprise [pam-ea-jsf-ejb] a bien été déployée.
• en [3], elle est exécutée

Si l'application est exécutée sans que le serveur MySQL soit lancé, on obtient la page d'exception :

http://tahe.developpez.com/java/javaee
147/339
Si nous lançons MySQL et réexécutons l'application, on obtient la page suivante :

• 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 :

http://tahe.developpez.com/java/javaee
148/339
http://tahe.developpez.com/java/javaee
149/339
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
• 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 :

http://tahe.developpez.com/java/javaee
150/339
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

- la vue [VueSimulation] utilisée pour afficher le résultat détaillé de la simulation :

http://tahe.developpez.com/java/javaee
151/339
- 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 :

– la vue [VueErreur] qui indique une ou plusieurs erreurs :

http://tahe.developpez.com/java/javaee
152/339
17.2 Le projet Netbeans de la couche [web]
Le projet Netbeans de cette version sera le suivant :

1
4
6
5

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

http://tahe.developpez.com/java/javaee
153/339
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. // les vues
10....

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

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>

http://tahe.developpez.com/java/javaee
154/339
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;
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.

http://tahe.developpez.com/java/javaee
155/339
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°
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 :

http://tahe.developpez.com/java/javaee
156/339
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]


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.

http://tahe.developpez.com/java/javaee
157/339
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()
• à 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.

http://tahe.developpez.com/java/javaee
158/339
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

• lignes 31-33 : la vue [vueSaisies]

http://tahe.developpez.com/java/javaee
159/339
• 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
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;

http://tahe.developpez.com/java/javaee
160/339
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
• 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.

http://tahe.developpez.com/java/javaee
161/339
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 159.

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(){
20. ...
21. }
22.
23. public String effacerSimulation(){
24. ...

http://tahe.developpez.com/java/javaee
162/339
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}">
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']}">

http://tahe.developpez.com/java/javaee
163/339
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 :

http://tahe.developpez.com/java/javaee
164/339
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

http://tahe.developpez.com/java/javaee
165/339
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;

http://tahe.developpez.com/java/javaee
166/339
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 :

http://tahe.developpez.com/java/javaee
167/339
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 :

http://tahe.developpez.com/java/javaee
168/339
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}">

http://tahe.developpez.com/java/javaee
169/339
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
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 166.

http://tahe.developpez.com/java/javaee
170/339
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 :

http://tahe.developpez.com/java/javaee
171/339
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 :

http://tahe.developpez.com/java/javaee
172/339
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 :

http://tahe.developpez.com/java/javaee
173/339
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 {

http://tahe.developpez.com/java/javaee
174/339
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é :

http://tahe.developpez.com/java/javaee
175/339
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.1, page
89 :

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

http://tahe.developpez.com/java/javaee
176/339
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
4
6
5

• 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

http://tahe.developpez.com/java/javaee
177/339
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]
:

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 :

http://tahe.developpez.com/java/javaee
178/339
5

1 6
4
6
3 5

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

http://tahe.developpez.com/java/javaee
179/339
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

http://tahe.developpez.com/java/javaee
180/339
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 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 :

http://tahe.developpez.com/java/javaee
181/339
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>

http://tahe.developpez.com/java/javaee
182/339
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 :

http://tahe.developpez.com/java/javaee
183/339
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

http://tahe.developpez.com/java/javaee
184/339
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;

http://tahe.developpez.com/java/javaee
185/339
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 :

http://tahe.developpez.com/java/javaee
186/339
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.1, page
89 :

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

http://tahe.developpez.com/java/javaee
187/339
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 quelques 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.
•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 :

http://tahe.developpez.com/java/javaee
188/339
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]
• 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 :

http://tahe.developpez.com/java/javaee
189/339
4
1
5 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>
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>

http://tahe.developpez.com/java/javaee
190/339
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.

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

http://tahe.developpez.com/java/javaee
191/339
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 :

http://tahe.developpez.com/java/javaee
192/339
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 190). La mémorisation de l'état des menus est faite par les champs des lignes 11-16.
• 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] :

http://tahe.developpez.com/java/javaee
193/339
• 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";
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}"/>

http://tahe.developpez.com/java/javaee
194/339
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 :

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

http://tahe.developpez.com/java/javaee
195/339
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 = "";
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(...);

http://tahe.developpez.com/java/javaee
196/339
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>
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>

http://tahe.developpez.com/java/javaee
197/339
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"/>
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 -->

http://tahe.developpez.com/java/javaee
198/339
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 :

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

http://tahe.developpez.com/java/javaee
199/339
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. }

Rappelons que ce bean est de portée session (cf configuration page 190).

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

http://tahe.developpez.com/java/javaee
200/339
• 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;
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 :

http://tahe.developpez.com/java/javaee
201/339
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 :

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

Question : écrire la méthode [faireSimulation] du bean #{beanSimulation} présenté page 196. 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 :

http://tahe.developpez.com/java/javaee
202/339
<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 165.

Question : écrire la méthode [effacerSimulation] du bean #{beanSimulation} présenté page 196. 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 166.

Question : écrire la méthode [enregistrerSimulation] du bean #{beanSimulations} présenté page 200. 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 171.

Question : écrire la méthode [retourSimulateur] du bean #{beanMasterPage} présenté page 193. 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>
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>

http://tahe.developpez.com/java/javaee
203/339
• 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 171.

Question : écrire la méthode [voirSimulations] du bean #{beanMasterPage} présenté page 193. 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 :

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

Question : écrire la méthode [retirerSimulation] du bean #{beanSimulations} présenté page 200. 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}"/>

http://tahe.developpez.com/java/javaee
204/339
L'action [terminerSession] a été présentée page 173.

Question : écrire la méthode [terminerSession] du bean #{beanMasterPage} présenté page 193. 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.1, page
89 :

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

Travail pratique : réaliser l'intégration des couches Jsf et Ejb en suivant la méthodologie du paragraphe 16, page 143.

http://tahe.developpez.com/java/javaee
205/339
20 Intégration Spring / Glassfish
Nous nous intéressons ici à l'intégration de Spring et Glassfish. Les couches [metier] et [dao] ne seront plus implémentées par des
Ejb. Ce seront des classes normales (POJO) liées ensemble par un fichier de configuration Spring. Nous allons porter deux
applications développées avec des Ejb :

• le service web développé avec des Ejb page 124.


• la version 9 de l'application web [pam] page 188.

Nous commençons par le service web.

20.1 Service web implémenté par une application web Spring / Glassfish
Nous nous plaçons dans le cadre de l'architecture suivante :

Couche C S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Spring et Serveur Glassfish

Le service web est assuré par une application web exécutée au sein du conteneur web du serveur Glassfish. La couche [metier] sera
comme précédemment exposée comme un service web. L'intégration des couches [metier], [dao] et [jpa] sera assurée par un
conteneur Spring. La gestions des transactions sera dévolue au gestionnaire de transactions du serveur Glassfish.

L'application qui va être décrite est propre au serveur Glassfish. Ses fondements ont été trouvés dans l'article "How to use Glassfish
Managed JPA EntityManager in Spring" [http://java.dzone.com/tips/how-use-glassfish-managed-jpa]. Certains points resteront
vagues car à vrai dire, je n'ai pas tout compris dans l'article. Néanmoins comme il y a peu d'exemples d'intégration Spring /
Glassfish sur le web, il m'a semblé intéressant d'inclure cet exemple dans ce document.

20.1.1 La partie serveur

Nous créons une application web. Pour cela, nous suivons la procédure décrite page 124.

3
1
2

• en [1], nous créons un nouveau projet


• en [2], ce projet est de type [Web Application]
• en [3], nous lui donnons le nom [pam-springws-metier-dao-jpa-eclipselink]

http://tahe.developpez.com/java/javaee
206/339
6

5
4

• en [4], nous choisissons la version Java EE 5


• en [5], nous n'utilisons aucun des frameworks proposés
• en [6], le projet créé

Comme nous allons utiliser Spring, nous ajoutons ses bibliothèques (lib / spring) au projet. Nous ajoutons également l'archive du
pilote Jdbc de MySQL :

1
2

• en [1], les bibliothèques actuelles du projet


• en [2], on en ajoute de nouvelles
• en [3], les nouvelles bibliothèques

Revenons à l'architecture de notre projet :

Couche C S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Spring et Serveur Glassfish

Les couches [metier], [dao] et [jpa] sont celles qui ont été utilisées dans le projet initial Spring / Jpa [pam-spring-ui-metier-dao-jpa-
hibernate]. Nous chargeons ce projet dans Netbeans [1] et nous en copions les packages [META-INF, jpa, dao, exception, metier]
dans le nouveau projet [2] :

http://tahe.developpez.com/java/javaee
207/339
3

2
1

http://tahe.developpez.com/java/javaee
208/339
En [3], le fichier [persistence.xml] configure une couche JPA gérée par Spring :

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="pam-spring-ui-metier-dao-jpa-eclipselinkPU" transaction-
type="RESOURCE_LOCAL">
4. <class>jpa.Cotisation</class>
5. <class>jpa.Employe</class>
6. <class>jpa.Indemnite</class>
7. </persistence-unit>
8. </persistence>

Ligne 3, l'attribut transaction-type="RESOURCE_LOCAL" indique que la source de données et les transactions qui sont faites
dessus sont gérées par le code utilisateur (Spring) et non par un conteneur Ejb. Dans le nouveau projet, nous voulons utiliser la
ressource Jdbc [jdbc/dbpam_eclipselink] du serveur Glassfish et le gestionnaire de transactions de Glassfish. Le fichier
[persistence.xml] évolue alors comme suit :

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="pam-spring-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
4. <jta-data-source>jdbc/dbpam_eclipelink</jta-data-source>
5. <class>jpa.Cotisation</class>
6. <class>jpa.Employe</class>
7. <class>jpa.Indemnite</class>
8. </persistence-unit>
9. </persistence>

• ligne 3, l'attribut transaction-type="RESOURCE_LOCAL" indique que la source de données et les transactions qui sont
faites dessus sont gérées par le serveur Java EE Glassfish.
• ligne 4 : le nom JNDI de la source de données. C'est la source de données déjà utilisée dans les exemples précédents
d'applications déployées sur Glassfish.

Il nous faut maintenant configurer Spring. Dans l'application Spring / Jpa [pam-spring-ui-metier-dao-jpa-hibernate], le fichier de
configuration de Spring était lu au démarrage de l'application par une méthode main de la couche [ui]. Ici, nous sommes dans une
application web où une telle méthode main n'existe pas. Il est possible de configurer l'application web pour que le fichier de
configuration de spring soit lu au chargement de l'application web. Cela se fait dans le fichier [web.xml] qui configure l'application
web [1] :

Le fichier [web.xml] sera 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. <listener>
4. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
5. </listener>
6. <persistence-unit-ref>
7. <persistence-unit-ref-name>persistence/pam-springws-metier-dao-jpa-eclipselinkPU</persistence-
unit-ref-name>
8. <persistence-unit-name>pam-springws-metier-dao-jpa-eclipselinkPU</persistence-unit-name>
9. </persistence-unit-ref>
10. </web-app>

• lignes 3-5 : définissent un listener. Un listener est une classe chargée au démarrage de l'application web.
• ligne 4 : le listener chargé est un listener fourni par Spring. Il a pour fonction d'exploiter le fichier de configuration de
Spring et de créer avec un objet de type WebApplicationContext. Cet objet contiendra les beans définis dans le fichier de

http://tahe.developpez.com/java/javaee
209/339
configuration de Spring. Il est accessible à partir de chaque requête faite à l'application web. Lorsque le nom du fichier de
configuration n'est pas indiqué, c'est le fichier [WEB-INF/applicationContext.xml] qui est utilisé. Il faut alors le créer.

• lignes 6-9 : définissent les unités de persistance que l'on trouve dans le fichier [META-INF/persistence.xml]. Il peut y en
avoir plusieurs.
• ligne 8 : le nom de l'unité de persistence. C'est l'attribut name de la balise <persistence-unit> du fichier [META-
INF/persistence.xml] :
<persistence-unit name="pam-springws-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
• ligne 7 : le nom JNDI de l'unité de persistance. Pour le serveur Glassfish, ce nom est de la forme
persistence/nom_unite_de_persistance. Ce nom JNDI change avec chaque serveur Java EE.

Une fois, le fichier [web.xml] défini, nous pouvons écrire le fichier [applicationContext.xml] [1] qui configure Spring. Son contenu
sera le suivant :

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.
12. <!-- persistence -->
13. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >
14. <property name="persistenceUnits">
15. <map>
16. <entry key="pam-springws-metier-dao-jpa-eclipselinkPU" value="persistence/pam-springws-
metier-dao-jpa-eclipselinkPU"/>
17. </map>
18. </property>
19. </bean>
20.
21. <!-- le gestionnaire de transactions -->
22. <tx:annotation-driven transaction-manager="txManager" />
23.
24. <!-- gestionnaire de transactions JTA -->
25. <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
26.
27. </beans>

• lignes 8-10 : les beans de la couche [dao]


• ligne 13 : la classe Spring qui gère les annotations de persistance, par exemple @PersistenceContext.
• lignes 14-18 : définissent toutes les unités de persistance du fichier [persistence.xml]. On retrouve là les mêmes définitions
que celles trouvées pour les unités de persistance du fichier [web.xml]
• ligne 22 : définit le bean chargé de gérer les annotations de transaction telle l'annotation @Transactional.
• ligne 25 : le gestionnaire de transactions est un gestionnaire JTA. Ce sera le gestionnaire de transactions du serveur sur
lequel la couche JPA sera déployée.

Revenons à l'architecture de notre application :

http://tahe.developpez.com/java/javaee
210/339
Couche C S Couche Couche Couche Couche
[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Spring et Serveur Glassfish

Pour l'instant, nous avons configuré l'intégration Spring / Glassfish pour gérer la couche Jpa [persistence.xml, web.xml,
applicationContext.xml]. La couche [dao] évolue légèrement.

Prenons par exemple la classe [CotisationDao] ci-dessus. Elle pourrait être écrite comme suit :

1. package dao;
2.
3. import exception.PamException;
4. import java.util.List;
5. import javax.persistence.EntityManager;
6. import javax.persistence.PersistenceContext;
7. import jpa.Cotisation;
8. import org.springframework.transaction.annotation.Transactional;
9.
10. @Transactional
11. public class CotisationDao implements ICotisationDao {
12.
13. @PersistenceContext
14. private EntityManager em;
15.
16. // constructeur
17. public CotisationDao() {
18. }
19. ........

• ligne 10 : l'annotation Spring qui fait que toutes les méthodes vont être exécutées dans une transaction. Nous avons vu que
par configuration, ce sera une transaction JTA du serveur Glassfish.
• ligne 13 : l'annotation qui injecte un EntityManager pour gérer la couche Jpa. Cette annotation est traitée à la fois par Spring
et le serveur Glassfish. Il peut y avoir conflit. Avec la configuration qui a été faite, Spring utilisera le même EntityManager
que Glassfish. Dans le fichier [applicationContext.xml] qui configure Spring, nous avons écrit :

1. <!-- persistence -->


2. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >
3. <property name="persistenceUnits">
4. <map>
5. <entry key="pam-springws-metier-dao-jpa-eclipselinkPU" value="persistence/pam-springws-
metier-dao-jpa-eclipselinkPU"/>
6. </map>
7. </property>
8. </bean>

En ligne 5, le nom d'une unité de persistance (key) est associé à son nom JNDI (value) sur le serveur Glassfish. C'est via
ce nom JNDI que Spring utilisera le même contexte de persistance que Glassfish. Pour cela, tout contexte de persistance

http://tahe.developpez.com/java/javaee
211/339
doit être identifié par son nom (key) lorsqu'il est utilisé dans une classe. La classe [CotisationDao] doit donc être réécrite
comme suit :

1. package dao;
2.
3. import exception.PamException;
4. import java.util.List;
5. import javax.persistence.EntityManager;
6. import javax.persistence.PersistenceContext;
7. import jpa.Cotisation;
8. import org.springframework.transaction.annotation.Transactional;
9.
10. @Transactional
11. public class CotisationDao implements ICotisationDao {
12.
13. @PersistenceContext(unitName="pam-springws-metier-dao-jpa-eclipselinkPU")
14. private EntityManager em;
15.
16. // constructeur
17. public CotisationDao() {
18. }
19. ...

En ligne 13, on précise l'unité de persistance à utiliser. On fait de même pour les deux autres classes de la couche [dao],
[EmployeDao] et [IndemniteDao].

Revenons de nouveau sur l'architecture de l'application :

Couche C S Couche Couche Couche Couche


[ui] [metier] [dao] [JPA / [JDBC] SGBD BD
2 EclipseLink]
1 HTTP /
SOAP
Java SE Spring et Serveur Glassfish

Ci-dessus, la couche [metier] va être exposée comme un service web. La couche [metier] est implémentée par la classe [Metier] ci-
dessous.

Pour exposer la classe [Metier] ci-dessus comme un service web, nous suivons une démarche qui présente des analogies avec celle
utilisée pour exposer un Ejb comme un service web. Le code de la classe [Metier] évolue comme suit :

1. package metier;
2.
3. ...
4. import org.springframework.transaction.annotation.Transactional;
5. import org.springframework.web.context.support.WebApplicationContextUtils;
6.
7. @WebService
8. @Transactional
9. public class Metier implements IMetier {
10.
11. // références sur la couche [dao]
12. private ICotisationDao cotisationDao = null;

http://tahe.developpez.com/java/javaee
212/339
13. private IEmployeDao employeDao = null;
14. private IIndemniteDao indemniteDao = null;
15.
16. // contexte du service web
17. @Resource()
18. private WebServiceContext wsContext;
19.
20. // accès aux beans de la couche [dao]
21. private void getDao() {
22. ....
23. }
24.
25. // obtenir la feuille de salaire
26. @WebMethod
27. public FeuilleSalaire calculerFeuilleSalaire(String SS,
28. double nbHeuresTravaillées, int nbJoursTravaillés) {
29. // couche [dao]
30. getDao();
31. // on récupère les informations liées à l'employé
32. Employe employe = employeDao.find(SS);
33. ...
34. }
35.
36. // liste des employés
37. @WebMethod
38. public List<Employe> findAllEmployes() {
39. // couche [dao]
40. getDao();
41. // résultat
42. return employeDao.findAll();
43. }
44. }

• ligne 7 : la classe est taguée @Webservice. C'est donc un service web.


• ligne 8 : chaque méthode de la classe s'exécutera au sein d'une transaction Spring. Nous savons que Spring va déléguer la
gestion de la transaction au serveur Glassfish.
• lignes 12-14 : des références sur la couche [dao]. Ces références seront initialisées par la méthode privée getDao() des
lignes 21-23.
• lignes 26 et 37 : l'annotation @WebMethod désigne les méthodes exposées par le service web.

Pour comprendre le reste du code, il faut savoir qu'un service web est instancié à chaque requête. Ainsi la classe [Metier] ci-dessus
est-elle instanciée de façon répétée par le conteneur web du serveur Glassfish. C'est la raison pour laquelle, on ne trouve pas la
définition de la classe [Metier] dans le fichier de configuration de Spring [applicationContext.xml] :

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


2. <beans xmlns="http://www.springframework.org/schema/beans" ...>
3.
4. <!-- couches applicatives -->
5. <!-- dao -->
6. <bean id="employeDao" class="dao.EmployeDao" />
7. <bean id="indemniteDao" class="dao.IndemniteDao" />
8. <bean id="cotisationDao" class="dao.CotisationDao" />
9.
10. <!-- persistence -->
11. ...
12.
13. </beans>

• lignes 6-8 : les beans de la couche [dao] seront instanciés lorsque Spring exploitera son fichier de configuration. D'après le
fichier [web.xml] que nous avons écrit, ce sera dès le chargement de l'application web dans le conteneur web de Glassfish.
Le bean de la couche [Metier] ne peut pas, lui, être instancié par Spring. Il doit l'être par le conteneur web lorsqu'une
requête pour le service web [Metier] arrive.

La classe [Metier] a besoin de connaître les beans de la couche [dao] pour traiter la requête du client :

1. // obtenir la feuille de salaire


2. @WebMethod
3. public FeuilleSalaire calculerFeuilleSalaire(String SS,
4. double nbHeuresTravaillées, int nbJoursTravaillés) {
5. // couche [dao]
6. getDao();
7. // on récupère les informations liées à l'employé
8. Employe employe = employeDao.find(SS);
9. ...
10. }
11.
12. // liste des employés

http://tahe.developpez.com/java/javaee
213/339
13. @WebMethod
14. public List<Employe> findAllEmployes() {
15. // couche [dao]
16. getDao();
17. // résultat
18. return employeDao.findAll();
19. }

Lignes 8 et 18 ci-dessus, les méthodes du service web [Metier] utilisent des éléments de la couche [dao]. Ces derniers ont été
instanciés par Spring au chargement initial de l'application. La question est donc de retrouver les références des beans instanciés par
Spring. Ci-dessus, celles-ci sont rerouvées par l'appel à la méthode getDao(). Celle-ci est la suivante :
1. @WebService
2. @Transactional
3. public class Metier implements IMetier {
4.
5. // références sur la couche [dao]
6. private ICotisationDao cotisationDao = null;
7. private IEmployeDao employeDao = null;
8. private IIndemniteDao indemniteDao = null;
9.
10. // contexte du service web
11. @Resource()
12. private WebServiceContext wsContext;
13.
14. // accès aux beans de la couche [dao]
15. private void getDao() {
16. // on récupère le contexte de l'application
17. ServletContext servletContext = (ServletContext) wsContext.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
18. // on récupère les beans de la couche [dao]
19. cotisationDao = (ICotisationDao) WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("cotisationDao");
20. employeDao = (IEmployeDao) WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("employeDao");
21. indemniteDao = (IIndemniteDao) WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("indemniteDao");
22. }
23. .......

Un service web est une application web. Une application web est exécutée par une classe spéciale appelée Servlet. Les informations
liées à cette servlet forment le contexte de la servlet ou le contexte de l'application web. Via ce contexte, on a notamment accès aux
informations de configuration de l'application. Les lignes de code ci-dessus reposent sur l'accès à ce contexte représenté par le type
ServletContext (ligne 17).

• lignes 11-12 : demande au serveur Java EE d'injecter dans la variable wsContext (elle doit s'appeler comme ça) un objet
de type WebServiceContext. C'est un objet qui va nous donner accès au contexte de l'application web. A chaque
instanciation du service web (donc à chaque requête) ce champ sera initialisé par le conteneur web du serveur.
• ligne 17 : la méthode getMessageContext() donne accès à diverses informations liées à la requête courante au service
web. Ces informations sont disponibles sous la forme d'un dictionnaire. Ici, nous demandons l'objet de type ServletContext
lié à la requête courante.
• une fois l'objet servletContext obtenu, nous avons accès aux beans instanciés par Spring au chargement de l'application par
l'instruction suivante : WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("idDuBean").
• lignes 19-21 : instancient les champs des lignes 6-8. En conclusion, après exécution de la méthode privée getDao, les
références des lignes 6-8 sur la couche [dao] sont initialisées. Les méthodes du service web [Metier] peuvent alors les
utiliser.

Nous sommes désormais prêts à déployer notre service web. Nous le construisons (Clean and Build) puis le déployons. Il faut que
le serveur MySQL soit lancé et la base [dbpam_eclipselink] créée et remplie.

• en [1], le service web a été déployé sur le serveur Glassfish v3

http://tahe.developpez.com/java/javaee
214/339
• en [2], nous le testons

• en [3], la page de tests. Nous invitons le lecteur à s'assurer que le service web qu'il a déployé fonctionne correctement.

Le service web étant déployé, il nous reste à construire un client.

20.1.2 La partie cliente

Travail à faire : en suivant la démarche décrite au paragraphe 14.1.2.1, page 120, construire un client console du service web
précédent.

20.2 Portage de la version 9 de l'application web [pam]

20.2.1 Architecture Jsf / Spring

L'architecture de la version 9 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 :

http://tahe.developpez.com/java/javaee
215/339
Application web
couche [web]
2a 2b
1 MC1
Faces Servlet
3
V1 couches
[V] MC2
4 [métier,dao,jpa/eclipselink]
V2 JSP
masterpage MCn
Vn

Conteneur de servlets / Spring / Glassfish

• L'intégration des couches [metier], [dao] et [jpa] sera assurée par un conteneur Spring. La gestions des transactions sera
dévolue au gestionnaire de transactions du serveur Glassfish.

20.2.2 Le projet Netbeans Jsf / Spring

Le projet [pam-spring-jsf-metier-dao-jpa-eclipselink-mysql] utilise des éléments de deux projets déjà construits :

• le service web précédent


• l'application web multivues / multipages décrite au paragraphe 19, page 188

Nous chargeons ces deux projets dans Netbeans.

Le projet [pam-spring-jsf-metier-dao-jpa-eclipselink-mysql] 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
[META6INF, exception, jpa, dao, metier] du projet précédemment étudié [pam-springws-metier-dao-jpa-eclipselink].

http://tahe.developpez.com/java/javaee
216/339
1 2

• en [1], nous avons des erreurs dus au fait que les paquetages copiés font référence à Spring
• en [2], nous ajoutons les bibliothèques de Spring au projet [2] ainsi que le pilote Jdbc de MySQL

A ce stage, seule une classe présente des erreurs, la classe [ApplicationData] [3] :

20.2.3 Fichiers de configuration

L'application en cours de construction est configurée par divers fichiers :


• META-INF / persistence.xml qui configure la couche de persistance qui sera gérée par le serveur Glassfish comme
dans le service web étudié précédemment - déjà présent - issu du service web - reste inchangé
• WEB-INF / faces-config.xml qui configure la couche web / JSF - déjà présent - issu de l'application web version 9 -
reste inchangé
• WEB-INF / web.xml qui configure l'application web - déjà présent - issu de l'application web version 9 - doit évoluer
• WEB-INF / applicationContext.xml qui configure Spring comme il a été fait dans le service web précédent - à inclure
dans le projet - doit évoluer

Nous copions maintenant le fichier [WEB-INF / applicationContext.xml] à partir du projet [pam-springws-metier-dao-jpa-


eclipselink] :

Ce fichier est actuellement le suivant :

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

http://tahe.developpez.com/java/javaee
217/339
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.
12. <!-- persistence -->
13. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >
14. <property name="persistenceUnits">
15. <map>
16. <entry key="pam-springws-metier-dao-jpa-eclipselinkPU" value="persistence/pam-springws-
metier-dao-jpa-eclipselinkPU"/>
17. </map>
18. </property>
19. </bean>
20.
21. <!-- le gestionnaire de transactions -->
22. <tx:annotation-driven transaction-manager="txManager" />
23.
24. <!-- gestionnaire de transactions JTA -->
25. <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
26.
27. </beans>

Il avait été écrit pour une application où la couche [metier] était un service web. La classe [Metier] qui l'implémentait était instanciée
à chaque appel fait au service web. Ici, la classe [Metier], comme les classes de la couche [dao] doit être instanciée une seule fois, au
démarrage de l'application. Aussi le fichier [applicationContext.xml] évolue-t-il comme suit :

1. <!-- couches applicatives -->


2. <!-- dao -->
3. <bean id="employeDao" class="dao.EmployeDao" />
4. <bean id="indemniteDao" class="dao.IndemniteDao" />
5. <bean id="cotisationDao" class="dao.CotisationDao" />
6. <!-- métier -->
7. <bean id="metier" class="metier.Metier">
8. <property name="employeDao" ref="employeDao"/>
9. <property name="indemniteDao" ref="indemniteDao"/>
10. <property name="cotisationDao" ref="cotisationDao"/>
11. </bean>
12.
13. <!-- persistence -->
14. <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >
15. <property name="persistenceUnits">
16. <map>
17. <entry key="pam-springws-metier-dao-jpa-eclipselinkPU" value="persistence/pam-springws-
metier-dao-jpa-eclipselinkPU"/>
18. </map>
19. </property>
20. </bean>
21. ............

• lignes 7-11 : définition du bean "metier" de la couche [metier]

Le fichier [WEB-INF / web.xml] qui configure l'application web fusionne les informations des fichiers [web.xml] du service web et
l'application web :

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. <!-- configuration Spring -->
5. <listener>
6. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
7. </listener>
8. <persistence-unit-ref>
9. <persistence-unit-ref-name>persistence/pam-springws-metier-dao-jpa-eclipselinkPU</persistence-
unit-ref-name>
10. <persistence-unit-name>pam-springws-metier-dao-jpa-eclipselinkPU</persistence-unit-name>
11. </persistence-unit-ref>
12.
13. <!-- configuration Jsf -->

http://tahe.developpez.com/java/javaee
218/339
14. <servlet>
15. <servlet-name>Faces Servlet</servlet-name>
16. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
17. <load-on-startup>1</load-on-startup>
18. </servlet>
19. <servlet-mapping>
20. <servlet-name>Faces Servlet</servlet-name>
21. <url-pattern>/faces/*</url-pattern>
22. </servlet-mapping>
23. <session-config>
24. <session-timeout>
25. 30
26. </session-timeout>
27. </session-config>
28. <welcome-file-list>
29. <welcome-file>faces/masterPage.jsp</welcome-file>
30. </welcome-file-list>
31. <error-page>
32. <error-code>500</error-code>
33. <location>/faces/exception.jsp</location>
34. </error-page>
35. <error-page>
36. <exception-type>java.lang.Exception</exception-type>
37. <location>/faces/exception.jsp</location>
38. </error-page>
39.
40. </web-app>

20.2.4 Mise à jour du code Java

La classe [ApplicationData] actuelle est issue de l'application web version 9 :

1. package web.beans.application;
2.
3. ...
4. public class ApplicationData {
5.
6. /** Creates a new instance of ApplicationData */
7. public ApplicationData() {
8. }
9.
10. // couche métier
11. private IMetierLocal metier=new Metier();
12. ...

Dans la version 9 de l'application web, nous utilisions une couche [metier] simulée implémentée par une classe [Metier]
implémentant l'interface [IMetierLocal]. Ci-dessous, nous pouvons voir que cette interface n'existe plus. Dans la version Spring,
l'interface s'appelle [IMetier] :

Par ailleurs, la classe [Metier] issue du projet [pam-springws-metier-dao-jpa-eclipselink] est un service web. Pour en faire une classe
normale gérée par Spring, nous enlevons les annotations et le code qui en faisaient un service web :

1. package metier;
2.
3. ...
4.
5. @Transactional
6. public class Metier implements IMetier {
7.
8. // références sur la couche [dao]
9. private ICotisationDao cotisationDao = null;
10. private IEmployeDao employeDao = null;
11. private IIndemniteDao indemniteDao = null;
12.
13. // obtenir la feuille de salaire

http://tahe.developpez.com/java/javaee
219/339
14. public FeuilleSalaire calculerFeuilleSalaire(String SS,
15. double nbHeuresTravaillées, int nbJoursTravaillés) {
16. // on récupère les informations liées à l'employé
17. Employe employe = employeDao.find(SS);
18. ....
19. // on rend le résultat
20. return new FeuilleSalaire(employe, cotisation, indemnite, elementsSalaire);
21. }
22.
23. // liste des employés
24. public List<Employe> findAllEmployes() {
25. // résultat
26. return employeDao.findAll();
27. }
28. }

Le code de la classe [ApplicationData] évolue comme suit :


1. public class ApplicationData {
2.
3. /** Creates a new instance of ApplicationData */
4. public ApplicationData() {
5. }
6.
7. // couche métier
8. private IMetier metier;
9. ...
10.
11. @PostConstruct
12. private void init(){
13. // on récupère le contexte Spring à partir de JSF
14. ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
15. // instanciation couche [metier]
16. metier = (IMetier) ctx.getBean("metier");
17. // la liste des employés est demandée à la couche métier
18. List<Employe>employes=getMetier().findAllEmployes();
19. ....
20. }
21.
22. // getters et setters
23. .....

• ligne 8 : la couche [metier] initialisée par la méthode [init] de la ligne 12


• ligne 14 : on récupère le contexte Spring issu de l'exploitation du fichier de configuration [applicationContext.xml]. La
classes [FacesContextUtils] est fournie par Spring. Elle permet de récupérer un contexte Spring dans une application Jsf.
• ligne 16 : initilaisation du champ de la ligne 8 avec le bean Spring "metier"

Ces modifications faites, le nouveau projet [pam-spring-jsf-metier-dao-jpa-eclipselink-mysql] doit se compiler sans erreurs. Avant de
le déployer, on procèdera aux préliminaires suivants :

• on lancera le SGBD MySQL5


• on s'assurera que la base de données [dbpam_eclipselink) existe et est remplie.

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 :

http://tahe.developpez.com/java/javaee
220/339
Le lecteur est invité à faire des tests supplémentaires.

http://tahe.developpez.com/java/javaee
221/339
JavaServer Faces (JSF)
par l'exemple

avec Netbeans 6.8


et le serveur Java EE Glassfish V3

http://tahe.developpez.com/java/javaee
222/339
21 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] [EclipseLink] [JDBC]
1

Nous utiliserons l'IDE Netbeans 6.8 pour construire les exemples et le serveur Glassfish v3 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. 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 Glassfish. Nous présenterons quelques application avec les couches [web, metier, dao, jpa] et où les couches
[metier, dao] seront implémentées avec des Ejb. Dans ce cas, l'application s'exécutera dans le contexte 2.

21.1 Exemple n° 1

21.1.1 Génération du projet

http://tahe.developpez.com/java/javaee
223/339
1 2

• en [1], créer un nouveau projet


• en [2], choisir la catégorie [web] et le type de projet [Web Application].

4
3 6 8

7
5

• 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 choisit le serveur Glassfish v3
• en [7], on choisit la version Java EE 5
• en [8]on demande le support du framework JavaServer Faces, version 1.2 [9]. Cela va générer les fichiers de
configuration nécessaires aux projets de type JSF 1.2.

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 :


• [Web Pages] : contiendra les pages web (.html, .jsp), les ressources (images, documents divers), la configuration
de la couche web.
• [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]
• [Configuration Files] : reprend les fichiers de configuration de la couche [web] déjà présents dans la branche
[Web Pages].

http://tahe.developpez.com/java/javaee
224/339
• en [2], on trouve des pages JSP (Java Server Pages) et des fichiers de configuration :
• [welcomeJSF.jsp] : une page JSP générée automatiquement par Netbeans. Nous la 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 Glassfish - est propre à celui-ci et sera remplacé
par un autre si on change de serveur.
• en [3] : les bibliothèques du projet.

21.1.2 Configuration d'un projet JSF

21.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. <servlet>
4. <servlet-name>Faces Servlet</servlet-name>
5. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
6. <load-on-startup>1</load-on-startup>
7. </servlet>
8. <servlet-mapping>
9. <servlet-name>Faces Servlet</servlet-name>
10. <url-pattern>/faces/*</url-pattern>
11. </servlet-mapping>
12. <session-config>
13. <session-timeout>
14. 30
15. </session-timeout>
16. </session-config>
17. <welcome-file-list>
18. <welcome-file>faces/welcomeJSF.jsp</welcome-file>
19. </welcome-file-list>
20. </web-app>

• les lignes 3-7 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

http://tahe.developpez.com/java/javaee
225/339
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. <servlet>
4. <servlet-name>Faces Servlet</servlet-name>
5. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
6. <load-on-startup>1</load-on-startup>
7. </servlet>
8. <servlet-mapping>
9. <servlet-name>Faces Servlet</servlet-name>
10. <url-pattern>/faces/*</url-pattern>
11. </servlet-mapping>
12. <session-config>
13. <session-timeout>
14. 30
15. </session-timeout>
16. </session-config>
17. <welcome-file-list>
18. <welcome-file>faces/welcomeJSF.jsp</welcome-file>
19. </welcome-file-list>
20. </web-app>

• lignes 8-11 : 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 3-7. 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 3-7 : 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 6 : 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.

http://tahe.developpez.com/java/javaee
226/339
• lignes 13-15 : 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 17-19 : 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/welcomeJSF.jsp] qui lui sera servie.

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

http://tahe.developpez.com/java/javaee
227/339
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.

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

http://tahe.developpez.com/java/javaee
228/339
7

• en [6], on exécute le projet. Ceci va avoir pour effet de lancer le serveur Glassfish, si ce n'était fait [7]
• en [8], l'application [intro-01] apparaît dans la branche [Applications] du serveur Glassfish

L'exécution ouvre un navigateur et demande la page d'accueil de l'application [http://localhost:8080/intro-01/] [9] :

10

• en [10], la page [welcomeJSF.jsp]. En [9], on a demandé le contexte /intro-01/ sans mentionner de page. C'est donc la
page [http://localhost:8080/intro-01/welcomeJSF.jsp] qui a été servie (cf <welcome-file-list> dans [web.xml]).

21.1.4 La page [welcomeJSF.jsp]

Examinons la page [welcomeJSF.jsp] générée par l'assistant de création du projet web :

1. <%@page contentType="text/html" pageEncoding="UTF-8"%>


2.
3. <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
4. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
5.
6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
7. "http://www.w3.org/TR/html4/loose.dtd">
8.
9. <%--
10. This file is an entry point for JavaServer Faces application.
11. --%>
12. <f:view>
13. <html>
14. <head>
15. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
16. <title>JSP Page</title>
17. </head>
18. <body>
19. <h1><h:outputText value="JavaServer Faces"/></h1>
20. </body>
21. </html>
22. </f:view>

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 12) : 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 19) : 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 :

http://tahe.developpez.com/java/javaee
229/339
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 3 et 4 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] [1,2] :

3
2

Examinons [3] le fichier [jsf_core.tld] :

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>

• en ligne 15, l'uri de la bibliothèque de balises


• en ligne 14, son nom court
• 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. ...

http://tahe.developpez.com/java/javaee
230/339
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>

1. <tag>
2. <description>
3. Container for all JavaServer Faces core and custom component actions used on a page.
4. </description>
5. <name>view</name>
6. <tag-class>com.sun.faces.taglib.jsf_core.ViewTag</tag-class>
7. <tei-class>com.sun.faces.taglib.FacesTagExtraInfo</tei-class>
8. <body-content>JSP</body-content>
9. ....
10. </tag>

• lignes 1, 10 : chaque balise JSF fait l'objet d'une définition à l'intérieur d'une balise <tag>
• ligne 5 : nom de la balise
• lignes 6-7 : les classes capables de gérer la balise. Ces classes sont recherchées dans le Classpath de l'application. Elles seront
trouvées, là encore, dans l'archive [jsf-impl.jar] :

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

21.1.5 Tutoriels Netbeans

Il existe de nombreux tutoriels Java pour Netbeans et notamment pour le développement web. Pour y accéder, on procédera ainsi :

2 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 [Java EE & Java 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.

http://tahe.developpez.com/java/javaee
231/339
21.2 Exemple n° 2
Mots clés : gestionnaire d'événements, internationalisation, navigation entre pages.

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

1
2

• sur la page d'accueil [1], les liens [2] permettent de changer de langue
• en [3], la page d'accueil en anglais

21.2.2 Le projet Netbeans

On génèrera un nouveau projet Jsf comme expliqué au paragraphe 21.1.1, page 223. On le nommera intro-02 :

http://tahe.developpez.com/java/javaee
232/339
1 2

• en [1], le projet généré


• en [2], le projet final. Il comportera deux pages JSF [3], des fichiers de messages pour internationaliser l'application [4] et
du code Java [5] pour gérer certains événements de la page [welcomeJSF].

21.2.3 Les pages Jsf du projet

21.2.3.1 [welcomeJSF.jsp]

Le nouveau fichier [welcomeJSF.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['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>

http://tahe.developpez.com/java/javaee
233/339
26. </html>
27. </f:view>

• 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 :

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çais
3. welcome.langue2=Anglais
4. welcome.page1=Page 1
5. page1.titre=page1
6. page1.entete=Page 1
7. page1.welcome=Page d'accueil

Revenons au fichier [faces-config.xml] qui déclare le fichier des messages :

http://tahe.developpez.com/java/javaee
234/339
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>

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>

http://tahe.developpez.com/java/javaee
235/339
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 :

<h:commandLink value="Français" action="#{locale.setFrenchLocale}"/>

La traduction Html de cette balise Jsf va être la suivante :

<a href="#" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),


{'formulaire:j_id_id21':'formulaire:j_id_id21'},'');return false">Français</a></td>

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

http://tahe.developpez.com/java/javaee
236/339
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 :

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>

http://tahe.developpez.com/java/javaee
237/339
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] :

• 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 :

http://tahe.developpez.com/java/javaee
238/339
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
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. }

http://tahe.developpez.com/java/javaee
239/339
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.
• 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 234.

http://tahe.developpez.com/java/javaee
240/339
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 :

Application web
couche [web]
1 2a 2b
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 :

http://tahe.developpez.com/java/javaee
241/339
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"/>

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 :

http://tahe.developpez.com/java/javaee
242/339
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] :

21.2.3.2 [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>

http://tahe.developpez.com/java/javaee
243/339
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>
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).

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

http://tahe.developpez.com/java/javaee
244/339
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].

Pour notre projet, nous construirons trois fichiers de messages avec les contenus suivants :

[messages_fr.properties]

welcome.titre=Tutoriel JSF (JavaServer Faces)


welcome.langue1=Fran9ais
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].

21.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] :

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

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

http://tahe.developpez.com/java/javaee
245/339
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 239, 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.

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 :

http://tahe.developpez.com/java/javaee
246/339
1

2
3

Dans le code source de [faces-config.xml] en [1], on clique droit pour prendre l'option |Insert] [2], puis l'option [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>
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 :

http://tahe.developpez.com/java/javaee
247/339
3 5
6 4
1

• dans le code de [faces-config.xml], cliquer droit pour choisir l'option [Insert] [2] puis l'option [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 :

1 2
3 4
5
6
7
8
9

• en [1], cliquer droit sur le code de la règle de navigation

http://tahe.developpez.com/java/javaee
248/339
• 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>

21.2.7 Exécution du projet

Notre projet est désormais complet. Nous pouvons le construire (Clean and Build) :

1
2

7
3

• dans le dossier [dist] [1] est placée l'archive [intro-02.war] [2] du projet. C'est cette archive qui est déployée sur le serveur.

http://tahe.developpez.com/java/javaee
249/339
• dans [WEB-INF / classes], on trouve les classes compilées du dossier [Source Packages] du projet ainsi que les autres
fichiers qui se trouvaient dans ce même dossier, ici les fichiers de messages.
• dans [WEB-INF / lib] [4], on trouve les bibliothèques du projet
• à la racine de [WEB-INF] [5], on trouve les fichiers de configuration du projet
• à la racine de l'archive [intro-02.war] [6], on trouve les pages JSF qui étaient dans la branche [Web Pages] du projet
• une fois le projet construit, il peut être exécuté [7]. Il va être exécuté selon sa configuration d'exécution [8].
• le serveur Glassfish va être lancé s'il n'était pas déjà lancé
• l'archive [intro-02.war] va être chargée sur le serveur. On appelle cela le déploiement du projet sur le serveur d'application.
• l'application va être exécutée 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 225), c'est le fichier
[faces/welcomeJSF.jsp] qui va être servi au navigateur client. Puisque l'url est de la forme [/faces/*], elle va être traitée par
le contrôleur [Faces Servlet] (cf [web.xml] page 225). Celui-ci va traiter la page et envoyer le flux Html suivant :

• le contrôleur [Faces Servlet] traitera les événements qui vont se produire à partir de cette page.

21.2.8 Conclusion

Revenons sur le projet Netbeans que nous avons écrit :

http://tahe.developpez.com/java/javaee
250/339
D

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

21.3 Exemple n ° 3
Mots clés : formulaire de saisie – composants Jsf

21.3.1 L'application

L'application a une unique page :

http://tahe.developpez.com/java/javaee
251/339
5

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.

21.3.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

http://tahe.developpez.com/java/javaee
252/339
1
5

2
3
4

• en [1], les fichiers de configuration du projet Jsf.


• en [2], l'unique page Jsp du projet : form.jsp
• en [3], une feuille de style [styles.css] 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.

21.3.3 Le fichier [web.xml]

Le fichier [web.xml] a été configuré pour que la page [form.jsp] soit la page d'accueil du projet :

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. <servlet>
4. <servlet-name>Faces Servlet</servlet-name>
5. <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
6. <load-on-startup>1</load-on-startup>
7. </servlet>
8. <servlet-mapping>
9. <servlet-name>Faces Servlet</servlet-name>
10. <url-pattern>/faces/*</url-pattern>
11. </servlet-mapping>
12. <session-config>
13. <session-timeout>
14. 30
15. </session-timeout>
16. </session-config>
17. <welcome-file-list>
18. <welcome-file>faces/form.jsp</welcome-file>
19. </welcome-file-list>
20. </web-app>

21.3.4 Le fichier [faces-config.xml]

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>

http://tahe.developpez.com/java/javaee
253/339
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.

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

1. form.langue1=Français
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 :

http://tahe.developpez.com/java/javaee
254/339
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
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

21.3.6 Le modèle [Form.java] de la page [form.jsp]

http://tahe.developpez.com/java/javaee
255/339
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>

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 :

http://tahe.developpez.com/java/javaee
256/339
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(){
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 :

http://tahe.developpez.com/java/javaee
257/339
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 peut être 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

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 :

http://tahe.developpez.com/java/javaee
258/339
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 :

1. package forms;
2.
3. public class Form {
4.
5. /** Creates a new instance of Form */
6. public Form() {
7. }
8.

http://tahe.developpez.com/java/javaee
259/339
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 :

10 10
11 11

12 12

13 13

14 14

15 15

16 16
17 17

18
19 19
20
20

21
21

21.3.7 La page [form.jsp]

http://tahe.developpez.com/java/javaee
260/339
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">
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"/>

http://tahe.developpez.com/java/javaee
261/339
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}" >
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>

http://tahe.developpez.com/java/javaee
262/339
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>

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

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

http://tahe.developpez.com/java/javaee
263/339
<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"/>

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.

http://tahe.developpez.com/java/javaee
264/339
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.

21.3.7.2 Les deux cycles demande client / réponse serveur d'un formulaire

Revnons sur ce qui a déjà été expliqué page 256 dans un cas général et appliquons-le au formulaire étudié. Celui-ci sera testé dans
l'environnement Jsf classique :

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 :

http://tahe.developpez.com/java/javaee
265/339
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

http://tahe.developpez.com/java/javaee
266/339
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 :

http://tahe.developpez.com/java/javaee
267/339
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&formulaire%3AinputSecret=mdp&formulaire%3AinputTextArea=Tutoriel+JSF%0D
%0Apartie+1%0D%0A&formulaire%3AselectOneListBox1=3&formulaire%3AselectOneListBox2=5&formulaire
%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire
%3AselectManyListBox=5&formulaire%3AselectOneMenu=4&formulaire%3AselectManyMenu=4&formulaire
%3AselectManyMenu=5&formulaire%3AinputHidden=initial&formulaire%3AselectManyCheckbox=2&formulaire
%3AselectManyCheckbox=4&formulaire%3AselectOneRadio=4&formulaire%3Asubmit=Valider

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 :

http://tahe.developpez.com/java/javaee
268/339
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.

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

http://tahe.developpez.com/java/javaee
269/339
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"/>

http://tahe.developpez.com/java/javaee
270/339
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 :

21.3.7.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] :

http://tahe.developpez.com/java/javaee
271/339
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].

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

http://tahe.developpez.com/java/javaee
272/339
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].

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

http://tahe.developpez.com/java/javaee
273/339
<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].

http://tahe.developpez.com/java/javaee
274/339
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] :

http://tahe.developpez.com/java/javaee
275/339
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].

21.3.7.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"};

http://tahe.developpez.com/java/javaee
276/339
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

http://tahe.developpez.com/java/javaee
277/339
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].

http://tahe.developpez.com/java/javaee
278/339
21.3.7.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

21.3.7.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"/>

http://tahe.developpez.com/java/javaee
279/339
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).

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

http://tahe.developpez.com/java/javaee
280/339
Jsf de reconstituer la page telle qu'elle avait été envoyée initialement. Ce mécanisme a été expliqué au paragraphe 21.3.6
page 255.

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

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

http://tahe.developpez.com/java/javaee
281/339
• 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;

21.3.7.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"/>

http://tahe.developpez.com/java/javaee
282/339
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 :

http://tahe.developpez.com/java/javaee
283/339
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

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

http://tahe.developpez.com/java/javaee
284/339
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

21.4 Exemple n° 4
Thèmes : formulaires dynamiques

21.4.1 L'application

L'application est la même que précédemment :

http://tahe.developpez.com/java/javaee
285/339
1

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.

21.4.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

1 2

http://tahe.developpez.com/java/javaee
286/339
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é

21.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"/>

http://tahe.developpez.com/java/javaee
287/339
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.

http://tahe.developpez.com/java/javaee
288/339
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;

http://tahe.developpez.com/java/javaee
289/339
35.}

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

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

21.5 Exemple n° 5
Thèmes : navigation, session, pages Jsp, gestion des exceptions

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

http://tahe.developpez.com/java/javaee
290/339
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]

http://tahe.developpez.com/java/javaee
291/339
6

• en [6], la page obtenue par le lien 4 de [2]. Elle récapitule les saisies faites dans les pages 1 à 3.

21.5.2 Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

3 2

http://tahe.developpez.com/java/javaee
292/339
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 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 présente
quelques difficultés.

21.5.3 Les pages [form.jsp] et leur modèle [Form.java]

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

http://tahe.developpez.com/java/javaee
293/339
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>

http://tahe.developpez.com/java/javaee
294/339
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].

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

http://tahe.developpez.com/java/javaee
295/339
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>

http://tahe.developpez.com/java/javaee
296/339
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.

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

21.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>/faces/exception.jsp</location>
18. </error-page>
19. <error-page>
20. <exception-type>java.lang.Exception</exception-type>
21. <location>/faces/exception.jsp</location>

http://tahe.developpez.com/java/javaee
297/339
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 [/faces/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.

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

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
[/faces/exception.jsp].

21.5.4.3 Les informations liées à une exception

http://tahe.developpez.com/java/javaee
298/339
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.

21.5.4.4 La page d'erreur [exception.jsp]

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 prefix="f" uri="http://java.sun.com/jsf/core"%>
4. <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
5. <%@page import="java.util.Locale,javax.faces.context.FacesContext"%>
6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
7. "http://www.w3.org/TR/html4/loose.dtd">
8.
9. <%
10. // on récupère la locale dans la session
11. String codeLocale = (String) session.getAttribute("codeLocale");
12. if (codeLocale == null) {
13. codeLocale = "fr";
14. }
15. // on fixe la locale
16. FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale(codeLocale));
17. // on change le code Http de la page pour certaines versions d'IE
18. response.setStatus(HttpServletResponse.SC_OK);
19. %>
20.
21.

http://tahe.developpez.com/java/javaee
299/339
22. <f:view>
23. <html>
24. <head>
25. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
26. <title>JSF</title>
27. <link href="<c:url value="/styles.css"/>" rel="stylesheet" type="text/css"/>
28. </head>
29. <body background="<c:url value="/ressources/standard.jpg"/>">
30.
31. <h3><h:outputText value="#{msg['exception.header']}"/></h3>
32. <table border="1">
33. <tr>
34. <td><h:outputText value="#{msg['exception.httpCode']}"/></td>
35. <td><h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/></td>
36. </tr>
37. <tr>
38. <td><h:outputText value="#{msg['exception.message']}"/></td>
39. <td><h:outputText value="#{requestScope['javax.servlet.error.message']}"/></td>
40. </tr>
41. <tr>
42. <td><h:outputText value="#{msg['exception.exceptionClassName']}"/></td>
43. <td><h:outputText value="#{requestScope['javax.servlet.error.exception_type']}"/></td>
44. </tr>
45. <tr>
46. <td><h:outputText value="#{msg['exception.requestUri']}"/></td>
47. <td><h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/></td>
48. </tr>
49. <tr>
50. <td><h:outputText value="#{msg['exception.servletName']}"/></td>
51. <td><h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/></td>
52. </tr>
53. </table>
54. <!-- liens -->
55. <table>
56. <tr>
57. <td><a href="<c:url value="/faces/form1.jsp"/>"/>1</td>
58. <td><a href="<c:url value="/faces/form2.jsp"/>"/>2</td>
59. <td><a href="<c:url value="/faces/form3.jsp"/>"/>3</td>
60. <td><a href="<c:url value="/faces/form4.jsp"/>"/>4</td>
61. </tr>
62. </table>
63. </body>
64. </html>
65. </f:view>

21.5.4.4.1 Les expressions de la page d'exception

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;

http://tahe.developpez.com/java/javaee
300/339
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 Jsf, 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é)

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 299. 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 21.5.4.3, page 299.
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. ....
4. // on change le code Http de la page pour certaines versions d'IE
5. response.setStatus(HttpServletResponse.SC_OK);
6. %>
7.
8. <fmt_rt:setLocale value="${sessionScope['codeLocale']}"/>
9. ...

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.

http://tahe.developpez.com/java/javaee
301/339
21.5.4.4.2 Localisation de la page d'exception et des autres pages Jsf

Nous souhaitons avoir une page d'erreur localisée, c.a.d. capable de s'afficher en plusieurs langues. Les tests montrent que si on garde
le mécanisme de localisation utilisé dans les exemples précédents, la page [exception.jsp] s'affiche toujours en français. Ci-dessous, la
page [form1] est affichée en anglais [1]. Si on utilise le lien [Throw an exception] pour lancer une exception, alors la page
d'exception n'apparaît pas en anglais mais en français [2].

Si dans la page [2] on utilise l'un des liens [3], la page demandée sera affichée en français. La raison de la perte de la locale des pages
n'a pas été trouvée. Pour palier à ce problème, 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.

Les pages Jsf [formi] retrouveront alors leur locale d'affichage de la façon suivante :

1. <%@page contentType="text/html"%>
2. <%@page pageEncoding="UTF-8"%>

http://tahe.developpez.com/java/javaee
302/339
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.
• ligne 3 : directive d'import des classes utilisées dans le code des lignes 5-13

Les différents messages nécessaires à la page Jsf [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ée lors de l'erreur
exception.servletName=Nom de la servlet demandée 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
exception.requestUri=Url requested when error occurred
exception.servletName=Servlet requested when error occurred

21.5.4.4.3 Internet Explorer

Pour terminer, on notera le début du code de la page [exception.jsp] :

10. ...
11. <%
12. .....
13. // on change le code Http pour certaines versions d'IE
14. response.setStatus(HttpServletResponse.SC_OK);
15. %>
16. ...

Certaines versions d'Internet Explorer n'affiche pas les réponses HTML qui sont 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 5. 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.

21.6 Exemple n° 6
Thème : Validation et conversion des saisies d'un formulaire

21.6.1 L'application

http://tahe.developpez.com/java/javaee
303/339
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.

http://tahe.developpez.com/java/javaee
304/339
21.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.

21.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.
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']}"/>

http://tahe.developpez.com/java/javaee
305/339
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 :

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

http://tahe.developpez.com/java/javaee
306/339
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.

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


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

http://tahe.developpez.com/java/javaee
307/339
21.6.5 Les différentes saisies du formulaire

Nous étudions maintenant successivement les différentes saisies du formulaire.

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

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 :

http://tahe.developpez.com/java/javaee
308/339
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 :

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

http://tahe.developpez.com/java/javaee
309/339
1

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

http://tahe.developpez.com/java/javaee
310/339
• 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 des 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é

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.

http://tahe.developpez.com/java/javaee
311/339
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
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. ...

http://tahe.developpez.com/java/javaee
312/339
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. # ==============================================================================
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.

http://tahe.developpez.com/java/javaee
313/339
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 :

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

Voici un exemple d'exécution :

http://tahe.developpez.com/java/javaee
314/339
21.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.

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

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 :

http://tahe.developpez.com/java/javaee
315/339
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 :

21.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}"/>

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 :

http://tahe.developpez.com/java/javaee
316/339
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 :

21.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}"/>

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

http://tahe.developpez.com/java/javaee
317/339
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">

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 :

http://tahe.developpez.com/java/javaee
318/339
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 :

http://tahe.developpez.com/java/javaee
319/339
21.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 317. 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() {
3. // dernières validations
4. validateForm();
5. // on renvoie le même formulaire
6. return null;
7. }
8.
9. // validations globales

http://tahe.developpez.com/java/javaee
320/339
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> :

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>

http://tahe.developpez.com/java/javaee
321/339
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="!"

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

http://tahe.developpez.com/java/javaee
322/339
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.

21.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}"/>
5. </h:panelGrid>

Ligne 4, le message msg['cancel'] est le suivant :

cancel=Annuler

http://tahe.developpez.com/java/javaee
323/339
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 :

http://tahe.developpez.com/java/javaee
324/339
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.

21.7 Exemple n° 7
Thème : Gestion des événements liés à des changements de valeur de composants de saisie.

21.7.1 L'application

http://tahe.developpez.com/java/javaee
325/339
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.

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

21.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"/>
17. <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
18. <!-- ligne 1 -->
19. <h:outputText value="#{msg['combo1.prompt']}"/>

http://tahe.developpez.com/java/javaee
326/339
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 :

http://tahe.developpez.com/java/javaee
327/339
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>

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

http://tahe.developpez.com/java/javaee
328/339
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 :

http://tahe.developpez.com/java/javaee
329/339
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.

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

http://tahe.developpez.com/java/javaee
330/339
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) :

http://tahe.developpez.com/java/javaee
331/339
<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 Glassfish.
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");
17.
18. // listener
19. public void combo1ChangeListener(ValueChangeEvent event){

http://tahe.developpez.com/java/javaee
332/339
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 suit le lien [Enterprise Server]

• en [3], on demande à voir les fichiers des logs

http://tahe.developpez.com/java/javaee
333/339
• en [4,5], on peut demander à voir les logs les plus récents. On obtient alors quelque chose comme suit :

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 deux
méthodes sont exécutées. Dans l'ordre : combo1ChangeListener, raz.
• on change la valeur du combo1 sans utiliser le bouton [Raz]. Les logs montrent que seule la méthode combo1ChangeListener
est exécutée.

21.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 169.
• vues Jsf composées de sous-vues <f:subView> : paragraphe 17.4.1, page 159.
• applications Jsf à trois couches : paragraphe 17.7, page 176.
• 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]

http://tahe.developpez.com/java/javaee
334/339
Table des matières
1 ARCHITECTURE D'UNE APPLICATION JAVA EN COUCHES....................................................................................4
2 PAM - VERSION 1....................................................................................................................................................................7
2.1 LA BASE DE DONNÉES...................................................................................................................................................................7
2.2 MODE DE CALCUL DU SALAIRE D'UNE ASSISTANTE MATERNELLE.......................................................................................................8
2.3 FONCTIONNEMENT DE L'APPLICATION CONSOLE...............................................................................................................................8
2.4 FONCTIONNEMENT DE L'APPLICATION GRAPHIQUE.........................................................................................................................10
3 IMPLÉMENTATION JPA DE LA COUCHE DE PERSISTANCE DES DONNÉES.....................................................10
3.1 LES ENTITÉS JPA......................................................................................................................................................................10
3.2 CONFIGURATION DE LA COUCHE JPA..........................................................................................................................................11
4 MISE EN OEUVRE DES TESTS DE LA COUCHE JPA...................................................................................................12
4.1 IMPLÉMENTATION JPA / HIBERNATE..........................................................................................................................................12
4.2 IMPLÉMENTATION JPA / ECLIPSELINK.......................................................................................................................................21
5 LES INTERFACES DES COUCHES [METIER] ET [DAO].............................................................................................27
6 LA CLASSE [PAMEXCEPTION].........................................................................................................................................33
7 LA COUCHE [DAO] DE L'APPLICATION [PAM]...........................................................................................................34
7.1 IMPLÉMENTATION.......................................................................................................................................................................34
7.2 CONFIGURATION........................................................................................................................................................................35
7.3 TESTS.......................................................................................................................................................................................35
7.3.1 TESTS 1..................................................................................................................................................................................35
7.3.1.1 InitDB...............................................................................................................................................................................35
7.3.1.2 Mise en oeuvre des tests...................................................................................................................................................36
7.3.2 TESTS 2..................................................................................................................................................................................38
7.3.2.1 JUnitDao...........................................................................................................................................................................38
7.3.2.2 Mise en oeuvre des tests...................................................................................................................................................41
8 LA COUCHE [METIER] DE L'APPLICATION [PAM]....................................................................................................42
8.1 L'INTERFACE JAVA [IMETIER]...................................................................................................................................................42
8.2 LA CLASSE [FEUILLESALAIRE]....................................................................................................................................................42
8.3 LA CLASSE D'IMPLÉMENTATION [METIER] DE LA COUCHE [METIER]...............................................................................................44
8.4 TESTS DE LA COUCHE [METIER]...................................................................................................................................................44
9 LA COUCHE [UI] DE L'APPLICATION [PAM] – VERSION CONSOLE.....................................................................46
9.1 LA CLASSE [UI.CONSOLE.MAIN]..................................................................................................................................................47
9.2 EXÉCUTION................................................................................................................................................................................48
10 LA COUCHE [UI] DE L'APPLICATION [PAM] – VERSION GRAPHIQUE..............................................................50
10.1 UN RAPIDE TUTORIEL................................................................................................................................................................51
10.2 L'INTERFACE GRAPHIQUE [PAMJFRAME]...................................................................................................................................53
10.3 LES ÉVÉNEMENTS DE L'INTERFACE GRAPHIQUE...........................................................................................................................55
10.4 INITIALISATION DE L'INTERFACE GRAPHIQUE...............................................................................................................................56
10.5 GESTIONNAIRES D'ÉVÉNEMENTS.................................................................................................................................................58
10.6 EXÉCUTION DE L'INTERFACE GRAPHIQUE....................................................................................................................................58
11 IMPLÉMENTATION DE LA COUCHE JPA AVEC ECLIPSELINK...........................................................................60
11.1 LE PROJET NETBEANS..............................................................................................................................................................60
11.2 MISE EN OEUVRE DES TESTS......................................................................................................................................................63
11.3 TESTS DE LA COUCHE [DAO]......................................................................................................................................................65
11.3.1 INITDB................................................................................................................................................................................65
11.3.2 JUNITDAO............................................................................................................................................................................66
11.4 LES AUTRES TESTS...................................................................................................................................................................68
12 PORTAGE D'UNE ARCHITECTURE SPRING / JPA VERS UNE ARCHITECTURE OPENEJB / JPA................69
12.1 INTRODUCTION AUX PRINCIPES DU PORTAGE................................................................................................................................69
12.1.1 LES DEUX ARCHITECTURES.......................................................................................................................................................69
12.1.2 LES BIBLIOTHÈQUES DES PROJETS..............................................................................................................................................69
12.1.3 CONFIGURATION DE LA COUCHE JPA / ECLIPSELINK / OPENEJB...................................................................................................69
12.1.4 IMPLÉMENTATION DE LA COUCHE [DAO] PAR DES EJB...................................................................................................................70
12.1.5 IMPLÉMENTATION DE LA COUCHE [METIER] PAR UN EJB................................................................................................................71
12.1.6 LES CLIENTS DES EJB..............................................................................................................................................................72
12.2 TRAVAIL PRATIQUE..................................................................................................................................................................74
12.2.1 MISE EN PLACE DE LA BASE DE DONNÉES [DBPAM_ECLIPSELINK]....................................................................................................74

http://tahe.developpez.com/java/javaee
335/339
12.2.2 CONFIGURATION INITIALE DU PROJET NETBEANS..........................................................................................................................74
12.2.3 PORTAGE DE LA COUCHE [DAO]................................................................................................................................................75
12.2.3.1 L'Ejb [CotisationDao].....................................................................................................................................................76
12.2.3.2 Les Ejb [EmployeDao] et [IndemniteDao].....................................................................................................................77
12.2.3.3 La classe [PamException]...............................................................................................................................................77
12.2.3.4 Test de la couche [dao]...................................................................................................................................................78
12.2.4 PORTAGE DE LA COUCHE [METIER]............................................................................................................................................82
12.2.4.1 L'Ejb [Metier].................................................................................................................................................................82
12.2.4.2 Test de la couche [metier]...............................................................................................................................................83
12.2.5 PORTAGE DE LA COUCHE [CONSOLE]..........................................................................................................................................85
12.3 CONCLUSION............................................................................................................................................................................87
13 PORTAGE DE L'APPLICATION PAM SUR UN SERVEUR D'APPLICATIONS GLASSFISH..............................89
13.1 LA PARTIE SERVEUR DE L'APPLICATION CLIENT / SERVEUR PAM.................................................................................................89
13.1.1 L'ARCHITECTURE DE L'APPLICATION...........................................................................................................................................89
13.1.1.1 Le projet Netbeans..........................................................................................................................................................90
13.1.1.2 Configuration de la couche de persistance......................................................................................................................91
13.1.1.3 Insertion des couches [jpa, dao, metier]..........................................................................................................................94
13.1.1.4 Configuration du serveur Glassfish.................................................................................................................................95
13.1.1.5 Déploiement du module Ejb...........................................................................................................................................96
13.2 CLIENT CONSOLE - VERSION 1...................................................................................................................................................97
13.2.1 LE PROJET DU CLIENT..............................................................................................................................................................97
13.2.2 SUPPRESSION DES ANNOTATIONS JAVA DE PERSISTENCE ET D'EJB..................................................................................................98
13.2.3 LES BIBLIOTHÈQUES DU CLIENT.................................................................................................................................................99
13.3 CLIENT CONSOLE - VERSION 2.................................................................................................................................................102
13.4 CLIENT CONSOLE - VERSION 3.................................................................................................................................................104
13.5 CLIENT CONSOLE - VERSION 4.................................................................................................................................................105
13.6 UN CLIENT CONSOLE DE TYPE [ENTERPRISE CLIENT APPLICATION]............................................................................................107
13.7 LE CLIENT SWING..................................................................................................................................................................109
13.8 LES TESTS UNITAIRES JUNIT...................................................................................................................................................110
14 VERSION 4 – CLIENT / SERVEUR DANS UNE ARCHITECTURE DE SERVICE WEB.......................................116
14.1 SERVICE WEB IMPLÉMENTÉ PAR UN EJB..................................................................................................................................117
14.1.1 LA PARTIE SERVEUR..............................................................................................................................................................117
14.1.1.1 Le projet Netbeans........................................................................................................................................................117
14.1.2 LA PARTIE CLIENTE...............................................................................................................................................................119
14.1.2.1 Le projet Netbeans du client console............................................................................................................................120
14.1.2.2 Le client console du service web Metier.......................................................................................................................121
14.1.3 LE CLIENT SWING DU SERVICE WEB METIER..............................................................................................................................124
14.2 SERVICE WEB IMPLÉMENTÉ PAR UNE APPLICATION WEB.............................................................................................................124
14.2.1 LA PARTIE SERVEUR..............................................................................................................................................................125
14.2.2 LA PARTIE CLIENTE...............................................................................................................................................................129
15 VERSION 5 - APPLICATION PAM WEB / JSF.............................................................................................................131
15.1 ARCHITECTURE DE L'APPLICATION...........................................................................................................................................131
15.2 FONCTIONNEMENT DE L'APPLICATION.......................................................................................................................................133
15.3 LE PROJET NETBEANS............................................................................................................................................................134
15.3.1 LES FICHIERS DE CONFIGURATION............................................................................................................................................135
15.3.2 LA FEUILLE DE STYLE............................................................................................................................................................137
15.3.3 LE FICHIER DES MESSAGES......................................................................................................................................................137
15.3.4 LA COUCHE [MÉTIER]............................................................................................................................................................138
15.4 LE FORMULAIRE [FORM.JSP] ET SON MODÈLE [FORM.JAVA].......................................................................................................139
15.4.1 ÉTAPE 1..............................................................................................................................................................................139
15.4.2 ÉTAPE 2..............................................................................................................................................................................139
15.4.3 ÉTAPE 3..............................................................................................................................................................................140
15.4.4 ÉTAPE 4..............................................................................................................................................................................141
16 VERSION 6 - INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB 143
16.1 ARCHITECTURE DE L'APPLICATION...........................................................................................................................................143
16.2 LE PROJET NETBEANS DE LA COUCHE WEB...............................................................................................................................143
16.3 LE PROJET NETBEANS DE L'APPLICATION D'ENTREPRISE............................................................................................................145
17 VERSION 7 - APPLICATION WEB PAM MULTI-VUES / MONO-PAGE................................................................150
17.1 LES VUES DE L'APPLICATION...................................................................................................................................................151
17.2 LE PROJET NETBEANS DE LA COUCHE [WEB]............................................................................................................................153
17.2.1 LES FICHIERS DE CONFIGURATION............................................................................................................................................153
17.2.2 LA FEUILLE DE STYLE............................................................................................................................................................155
17.2.3 LE FICHIER DES MESSAGES......................................................................................................................................................156

http://tahe.developpez.com/java/javaee
336/339
17.2.4 LA COUCHE [MÉTIER]............................................................................................................................................................156
17.3 LE BEAN [APPLICATIONDATA]................................................................................................................................................157
17.4 IMPLÉMENTATION DU MODÈLE MVC......................................................................................................................................159
17.4.1 LE FORMULAIRE [FORM.JSP] ET SES VUES.................................................................................................................................159
17.4.2 LE CONTRÔLEUR / MODÈLE [FORM.JAVA].................................................................................................................................160
17.4.3 LA VUE [VUEENTETE]...........................................................................................................................................................162
17.4.4 LA VUE [VUESAISIE].............................................................................................................................................................163
17.5 LES ACTIONS DU CONTRÔLEUR.................................................................................................................................................164
17.5.1 L'ACTION [FAIRESIMULATION]................................................................................................................................................164
17.5.2 L'ACTION [EFFACERSIMULATION]............................................................................................................................................165
17.5.3 L'ACTION [ENREGISTRERSIMULATION]......................................................................................................................................166
17.5.4 L'ACTION [RETOURSIMULATEUR].............................................................................................................................................171
17.5.5 L'ACTION [VOIRSIMULATIONS]................................................................................................................................................171
17.5.6 L'ACTION [RETIRERSIMULATION].............................................................................................................................................172
17.5.7 L'ACTION [TERMINERSESSION]................................................................................................................................................173
17.6 LA VUE [VUEERREUR]............................................................................................................................................................174
17.7 INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB......................................................................176
18 VERSION 8 - APPLICATION WEB PAM MULTI-VUES / MONO-PAGE - 2..........................................................177
18.1 LE PROJET NETBEANS............................................................................................................................................................178
18.2 LE FICHIER [FACES-CONFIG.XML]............................................................................................................................................179
18.3 LE BEAN SESSIONDATA..........................................................................................................................................................180
18.4 LE BEAN FORM......................................................................................................................................................................185
18.5 INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES JSF / EJB......................................................................186
19 VERSION 9 - APPLICATION WEB PAM MULTI-VUES / MULTI-PAGES.............................................................188
19.1 LE PROJET NETBEANS............................................................................................................................................................189
19.2 LA CONFIGURATION DE L'APPLICATION.....................................................................................................................................190
19.3 LES VUES JSP ET LEURS BEANS MODÈLES / CONTRÔLEURS..........................................................................................................191
19.3.1 LA PAGE MAÎTRE [MASTERPAGE.JSP].......................................................................................................................................192
19.3.2 LA PAGE [ENTETE.JSP]...........................................................................................................................................................194
19.3.3 LA PAGE [SAISIES.JSP]...........................................................................................................................................................195
19.3.4 LA PAGE [SIMULATION.JSP]....................................................................................................................................................198
19.3.5 LA PAGE [SIMULATIONS.JSP]...................................................................................................................................................199
19.3.6 LA PAGE [ERREUR.JSP]..........................................................................................................................................................201
19.3.7 LA PAGE [SIMULATIONSVIDES.JSP]...........................................................................................................................................201
19.4 LES ACTIONS..........................................................................................................................................................................202
19.4.1 L'ACTION [FAIRESIMULATION]................................................................................................................................................202
19.4.2 L'ACTION [EFFACERSIMULATION]............................................................................................................................................202
19.4.3 L'ACTION [ENREGISTRERSIMULATION]......................................................................................................................................203
19.4.4 L'ACTION [RETOURSIMULATEUR].............................................................................................................................................203
19.4.5 L'ACTION [VOIRSIMULATIONS]................................................................................................................................................204
19.4.6 L'ACTION [RETIRERSIMULATION].............................................................................................................................................204
19.4.7 L'ACTION [TERMINERSESSION]................................................................................................................................................204
19.5 INTÉGRATION DE LA COUCHE WEB DANS UNE ARCHITECTURE 3 COUCHES AVEC JSF - EJB.............................................................205
20 INTÉGRATION SPRING / GLASSFISH.........................................................................................................................206
20.1 SERVICE WEB IMPLÉMENTÉ PAR UNE APPLICATION WEB SPRING / GLASSFISH...............................................................................206
20.1.1 LA PARTIE SERVEUR..............................................................................................................................................................206
20.1.2 LA PARTIE CLIENTE...............................................................................................................................................................215
20.2 PORTAGE DE LA VERSION 9 DE L'APPLICATION WEB [PAM].........................................................................................................215
20.2.1 ARCHITECTURE JSF / SPRING..................................................................................................................................................215
20.2.2 LE PROJET NETBEANS JSF / SPRING........................................................................................................................................216
20.2.3 FICHIERS DE CONFIGURATION..................................................................................................................................................217
20.2.4 MISE À JOUR DU CODE JAVA..................................................................................................................................................219
21 JSF - JAVASERVER FACES.............................................................................................................................................223
21.1 EXEMPLE N° 1.......................................................................................................................................................................223
21.1.1 GÉNÉRATION DU PROJET........................................................................................................................................................223
21.1.2 CONFIGURATION D'UN PROJET JSF..........................................................................................................................................225
21.1.2.1 web.xml.........................................................................................................................................................................225
21.1.2.2 faces-config.xml............................................................................................................................................................227
21.1.3 EXÉCUTION DU PROJET..........................................................................................................................................................228
21.1.4 LA PAGE [WELCOMEJSF.JSP].................................................................................................................................................229
21.1.5 TUTORIELS NETBEANS...........................................................................................................................................................231
21.2 EXEMPLE N° 2.......................................................................................................................................................................232
21.2.1 L'APPLICATION.....................................................................................................................................................................232

http://tahe.developpez.com/java/javaee
337/339
21.2.2 LE PROJET NETBEANS...........................................................................................................................................................232
21.2.3 LES PAGES JSF DU PROJET......................................................................................................................................................233
21.2.3.1 [welcomeJSF.jsp]..........................................................................................................................................................233
21.2.3.2 [page1.jsp]....................................................................................................................................................................243
21.2.4 LE FICHIER DES MESSAGES DU PROJET......................................................................................................................................244
21.2.5 LES CLASSES JAVA DU PROJET................................................................................................................................................245
21.2.6 LE FICHIER DE CONFIGURATION [FACES-CONFIG.XML].................................................................................................................245
21.2.7 EXÉCUTION DU PROJET..........................................................................................................................................................249
21.2.8 CONCLUSION........................................................................................................................................................................250
21.3 EXEMPLE N ° 3......................................................................................................................................................................251
21.3.1 L'APPLICATION.....................................................................................................................................................................251
21.3.2 LE PROJET NETBEANS...........................................................................................................................................................252
21.3.3 LE FICHIER [WEB.XML]..........................................................................................................................................................253
21.3.4 LE FICHIER [FACES-CONFIG.XML]............................................................................................................................................253
21.3.5 LE FICHIER DES MESSAGES [MESSAGES.PROPERTIES]....................................................................................................................254
21.3.6 LE MODÈLE [FORM.JAVA] DE LA PAGE [FORM.JSP].....................................................................................................................255
21.3.7 LA PAGE [FORM.JSP].............................................................................................................................................................260
21.3.7.1 La feuille de style du formulaire...................................................................................................................................263
21.3.7.2 Les deux cycles demande client / réponse serveur d'un formulaire..............................................................................265
21.3.7.3 Balise <h:inputText>....................................................................................................................................................269
21.3.7.4 Balise <h:inputSecret>..................................................................................................................................................271
21.3.7.5 Balise <h:inputTextArea>.............................................................................................................................................272
21.3.7.6 Balise <h:selectOneListBox>.......................................................................................................................................273
21.3.7.7 Balise <h:selectManyListBox>.....................................................................................................................................276
21.3.7.8 Balise <h:selectOneMenu>...........................................................................................................................................279
21.3.7.9 Balise <h:selectManyMenu>........................................................................................................................................279
21.3.7.10 Balise <h:inputHidden>..............................................................................................................................................280
21.3.7.11 Balise <h:selectBooleanCheckBox>...........................................................................................................................281
21.3.7.12 Balise <h:selectManyCheckBox>...............................................................................................................................282
21.3.7.13 Balise <h:selectOneRadio>.........................................................................................................................................284
21.4 EXEMPLE N° 4.......................................................................................................................................................................285
21.4.1 L'APPLICATION.....................................................................................................................................................................285
21.4.2 LE PROJET NETBEANS...........................................................................................................................................................286
21.4.3 LA PAGE [FORM.JSP] ET SON MODÈLE [FORM.JAVA]...................................................................................................................287
21.4.4 LE FICHIER DES MESSAGES......................................................................................................................................................290
21.4.5 TESTS.................................................................................................................................................................................290
21.5 EXEMPLE N° 5.......................................................................................................................................................................290
21.5.1 L'APPLICATION.....................................................................................................................................................................290
21.5.2 LE PROJET NETBEANS...........................................................................................................................................................292
21.5.3 LES PAGES [FORM.JSP] ET LEUR MODÈLE [FORM.JAVA]...............................................................................................................293
21.5.3.1 Le code des pages Jsp...................................................................................................................................................293
21.5.3.2 Durée de vie du modèle [Form.java] des pages [form*.jsp].........................................................................................295
21.5.4 GESTION DES EXCEPTIONS......................................................................................................................................................297
21.5.4.1 Configuration de l'application web pour la gestion des exceptions..............................................................................297
21.5.4.2 La simulation de l'exception.........................................................................................................................................298
21.5.4.3 Les informations liées à une exception.........................................................................................................................298
21.5.4.4 La page d'erreur [exception.jsp]....................................................................................................................................299
21.6 EXEMPLE N° 6.......................................................................................................................................................................303
21.6.1 L'APPLICATION.....................................................................................................................................................................303
21.6.2 LE PROJET NETBEANS...........................................................................................................................................................305
21.6.3 LA PAGE [FORM.JSP] ET SON MODÈLE [FORM.JAVA]...................................................................................................................305
21.6.4 L'ENVIRONNEMENT DE L'APPLICATION......................................................................................................................................307
21.6.5 LES DIFFÉRENTES SAISIES DU FORMULAIRE................................................................................................................................308
21.6.5.1 Saisies 1 à 4 : saisie d'un nombre entier........................................................................................................................308
21.6.5.2 Saisies 5 et 6 : saisie d'un nombre réel..........................................................................................................................314
21.6.5.3 Saisie 7 : saisie d'un booléen.........................................................................................................................................315
21.6.5.4 Saisie 8 : saisie d'une date.............................................................................................................................................315
21.6.5.5 Saisie 9 : saisie d'une chaîne de longueur contrainte....................................................................................................316
21.6.5.6 Saisie 10 : écrire une méthode de validation spécifique...............................................................................................317
21.6.5.7 Saisies 11 et 12 : validation d'un groupe de composants..............................................................................................320
21.6.5.8 POST d'un formulaire sans vérification des saisies......................................................................................................323
21.7 EXEMPLE N° 7.......................................................................................................................................................................325
21.7.1 L'APPLICATION.....................................................................................................................................................................325

http://tahe.developpez.com/java/javaee
338/339
21.7.2 LE PROJET NETBEANS...........................................................................................................................................................326
21.7.3 LE FORMULAIRE [FORM.JSP]...................................................................................................................................................326
21.7.4 LE MODÈLE [FORM.JAVA]......................................................................................................................................................328
21.7.5 LE BOUTON [RAZ]................................................................................................................................................................330
21.8 COMPLÉMENTS .....................................................................................................................................................................334

http://tahe.developpez.com/java/javaee
339/339

Das könnte Ihnen auch gefallen