Beruflich Dokumente
Kultur Dokumente
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.
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 :
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.
• 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
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 :
• 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].
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 :
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.
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.
Table EMPLOYES : rassemble des informations sur les différentes assistantes maternelles
Structure :
Table COTISATIONS : rassemble des pourcentages nécessaires au calcul des cotisations sociales
Structure :
Les taux des cotisations sociales sont indépendants du salarié. La table précédente n'a qu'une ligne.
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).
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
http://tahe.developpez.com/java/javaee
8/339
1 2
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
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
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.
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.
4
7
5
1 3 6
8
2
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].
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 :
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 :
3
2
http://tahe.developpez.com/java/javaee
12/339
8 7
11
6
9
10
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.
• [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 :
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].
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
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] :
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]
1 5
3
2
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.
http://tahe.developpez.com/java/javaee
21/339
Programme Interface Implémentation Couche
principal [JPA] [EclipseLink] [JDBC]
[Main]
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]
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] :
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 :
Double-cliquons sur le fichier [persistence.xml]. Celui-ci peut être vu selon deux modes :
http://tahe.developpez.com/java/javaee
24/339
Prenons le mode [XML]. Le fichier [persistence.xml] est le suivant :
• 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
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
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] :
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 :
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
Travail pratique :
Refaire le travail précédent avec d'autres SGBD : Oracle XE, SQL Server Express, Firebird, Apache Derby, HSQL, ...
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].
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
A partir de ces information et d'autres enregistrées dans des fichiers de configuration, l'application affiche les informations suivantes
:
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]
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
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 :
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 :
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 :
Ci-dessus, la couche [metier] s'exécute dans la JVM 1 et la couche [dao] dans la JVM 2 sur deux machines différentes. Les deux
couches ne communiquent pas directement. Entre-elles s'intercale une couche qu'on appellera couche de communication [1]. Celle-
ci est composée d'une couche d'émission [2] et d'une couche de réception [3]. Le développeur n'a en général pas à écrire ces
couches de communication. Elles sont générées automatiquement par des outils logiciels. La couche [metier] est écrite comme si
elle s'exécutait dans la même Jvm que la couche [dao]. Il n'y a donc aucune modification de code.
• la couche [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] :
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 :
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] :
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. }
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.
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.
• 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.
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.
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.
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.
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 :
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
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.
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 :
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
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.
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
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
2
3
Les classes de tests [3] sont créés dans un paquetage [metier] [2] de la branche [Test Packages] [1] du projet.
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
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].
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.
Lors de l'exécution de la classe [JUnitMetier_2], on obtient les résultats suivants si tout va bien :
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. }
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
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
1
2
3
9
7
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
17
18
16
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é.
http://tahe.developpez.com/java/javaee
53/339
1
JLabel1
JPanel1
JPanel2
http://tahe.developpez.com/java/javaee
54/339
JPanel3
JPanel4
JPanel5
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é :
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 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).
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 :
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. }
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 : é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].
http://tahe.developpez.com/java/javaee
58/339
1
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
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
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.
1
2
3
http://tahe.developpez.com/java/javaee
61/339
4
7
5
6
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 :
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 :
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 :
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].
http://tahe.developpez.com/java/javaee
64/339
2
3
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
http://tahe.developpez.com/java/javaee
65/339
5
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.
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. }
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.
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 : de ces deux exemples, que peut-on conclure de l'interchangeabilité des implémentations Jpa ? Est-elle totale ici ?
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
7 1 Spring
1 OpenEjb
• 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
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]
• Les classes implémentant la couche [dao] devient des Ejb. Prenons l'exemple de la classe [CotisationDao] :
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;
Lorsque l'interface locale de la couche [dao] est utilisée, le client de cette interface s'exécute dans la même JVM.
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.
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é.
• 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. }
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.
http://tahe.developpez.com/java/javaee
72/339
1
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].
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 :
Ci-dessous, le code pour avoir une référence sur l'interface distante de l'Ejb [Metier] :
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.
1 Spring
OpenEjb
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.
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
• ajouter les bibliothèques OpenEjb, EclipseLink au projet, le pilote Jdbc de MySQL, la bibliothèque log4j.jar.
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].
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.
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].
1. package dao;
2.
3. import javax.ejb.Remote;
4.
5. @Remote
6. public interface ICotisationDaoRemote extends ICotisationDao{
7. }
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
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
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.
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].
• 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
Grâce au fichier [logging.properties], le conteneur OpenEjb a affiché des logs dans la console :
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 :
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].
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.
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. ...
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
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.
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. }
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].
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] :
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] :
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.
Spring
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.
OpenEjb
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.
Couche C
Couche Couche Couche Couche
[ui] [dao] 4
[metier] [JPA / [JDBC]
1 3 EclipseLink]
2 6
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.
3
2
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] :
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
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 :
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
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]
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.
• 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
1
4
Lors du déploiement, le serveur Glassfish logue dans la console des informations intéressantes :
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.
Ces noms seront utiles à l'application console que nous allons écrire pour utiliser le module Ejb déployé.
Nous créons un nouveau projet Netbeans de type [Java Application] nommé [pam-client-metier-dao-jpa-eclipselink-mysql] :
1
2
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.
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. ...
@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].
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. ....
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.
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 :
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.
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
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 :
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 :
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
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é .
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 :
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].
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
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.
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.
4
3
1
2
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.
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.
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 :
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.
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 :
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
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.
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;
package dao;
package dao;
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 :
Couche C Couche 4
Couche Couche Couche
[tests] [metier] [dao] [JPA / [JDBC] SGBD BD
1 2 3 EclipseLink] 6
7
Couche C 4
Couche Couche Couche
[tests] [dao] [JPA / [JDBC] SGBD BD
1 3 EclipseLink] 6
7
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.
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. }
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 :
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] :
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.
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
Client Conteneur
Jpa
du Ejb3 Données
service web serveur Java EE
http://tahe.developpez.com/java/javaee
116/339
14.1 Service web implémenté par un EJB
Commençons par créer un nouveau projet Netbeans copie du projet EJB [pam-serveur-metier-dao-jpa-eclipselink] :
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é :
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.
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
3 5
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.
• 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.
• 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.
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.
• 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.
• 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
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. ...
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].
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.
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.
3
1
2
5
4
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
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 :
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
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.
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.
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)
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]
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
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 :
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 :
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].
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
http://tahe.developpez.com/java/javaee
134/339
4
1
6
Le fichier [web.xml] est celui généré par défaut par Netbeans avec de plus la configuration d'une 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 :
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 :
• 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
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. }
<h:outputText value="#{msg['form.infos.employé']}"
styleClass="titreInfos"/>
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].
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
• 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 :
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}"/>
[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.
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 :
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
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.
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
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
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.
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] :
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 :
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
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.
http://tahe.developpez.com/java/javaee
145/339
4
3
1 2
7 8
3
1
2
http://tahe.developpez.com/java/javaee
146/339
2
1 4
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.
3
1
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 :
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
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.
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
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 :
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
Le fichier [web.xml] est celui des versions précédentes. Le fichier [faces-config.xml] évolue de la façon suivante :
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>
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. }
Vue Simulations
Vue Erreur
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é
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. }
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. }
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
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>
http://tahe.developpez.com/java/javaee
159/339
• lignes 35-37 : la vue [vueSimulation] (vue partielle ci-dessous)
Nous examinerons ces vues les unes après les autres en liaison avec le contrôleur / modèle [Form.java].
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]
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.
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.
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 ?
L'action [faireSimulation] calcule une feuille de salaire. Elle provoque les échanges client / serveur suivants :
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.
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] :
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.
L'action [enregistrerSimulation] associée au lien permet d'enregistrer la simulation courante dans une liste de simulations maintenue
dans la classe [Form] :
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. }
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 :
• 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 :
Dans l'exemple ci-dessus, le n° de la dernière simulation faite est associée à la clé numDerniereSimulation.
• 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;
http://tahe.developpez.com/java/javaee
168/339
Le tableau des simulations peut être affiché avec une balise <h:dataTable> :
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 :
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>
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].
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.
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 :
On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vueSimulationsVides] :
http://tahe.developpez.com/java/javaee
172/339
Si ci-dessus, on retire la dernière simulation, on obtiendra le résultat suivant :
• 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] :
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.
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.
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.}
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.
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 :
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
1
4
6
5
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]
:
• 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)
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.
http://tahe.developpez.com/java/javaee
178/339
5
1 6
4
6
3 5
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>
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 :
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 :
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.
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. 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.
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 :
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
http://tahe.developpez.com/java/javaee
189/339
4
1
5 6
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 :
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.
http://tahe.developpez.com/java/javaee
191/339
19.3.1 La page maître [masterPage.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. <%@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>
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. }
• 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].
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.
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.
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 :
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).
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 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):
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]
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. }
La page [simulations.jsp] est celle qui génère la vue présentant la liste des simulations :
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 :
Rappelons que ce bean est de portée session (cf configuration page 190).
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>
La page [erreur.jsp] est celle qui génère la vue présentant une erreur :
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. }
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>
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.
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.
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()"/>
Question : écrire la méthode [effacerSimulation] du bean #{beanSimulation} présenté page 196. On s'inspirera du code de la
version précédente.
Question : écrire la méthode [enregistrerSimulation] du bean #{beanSimulations} présenté page 200. On s'inspirera du code de la
version précédente.
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} :
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 :
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 :
Question : écrire la méthode [voirSimulations] du bean #{beanMasterPage} présenté page 193. On s'inspirera du code de la
version précédente.
Question : écrire la méthode [retirerSimulation] du bean #{beanSimulations} présenté page 200. On s'inspirera du code de la
version précédente.
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.
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 :
20.1 Service web implémenté par une application web Spring / Glassfish
Nous nous plaçons dans le cadre de l'architecture suivante :
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.
Nous créons une application web. Pour cela, nous suivons la procédure décrite page 124.
3
1
2
http://tahe.developpez.com/java/javaee
206/339
6
5
4
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
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 :
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 :
• 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] :
• 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 :
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 :
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].
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. }
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] :
• 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 :
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.
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.
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.
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
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
• 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.
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] :
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 :
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 :
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>
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. }
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 :
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
http://tahe.developpez.com/java/javaee
222/339
21 JSF - JavaServer Faces
Objectif : Introduction à JSF via des exemples Netbeans.
Pour approfondir :
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 :
• 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
http://tahe.developpez.com/java/javaee
223/339
1 2
4
3 6 8
7
5
2
1
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.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].
• 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, ...
• 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
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
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.
3
1
4
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
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]).
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.
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] :
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
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>
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.
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
4
1
2
5
1
2
• sur la page d'accueil [1], les liens [2] permettent de changer de langue
• en [3], la page d'accueil en anglais
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
21.2.3.1 [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>
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 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 :
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 :
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']} :
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.
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.
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 :
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 :
• 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
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] :
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 :
• 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] :
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.
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é :
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 :
• 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].
Dans la balise
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
clé valeur
formulaire formulaire
javax.faces.ViewState ...
formulaire:j_id_id21 formulaire:j_id_id21
Dans la balise
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);
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 :
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.
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.
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 :
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 :
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]
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 :
nécessite une règle de navigation qui est ajoutée au fichier de configuration [faces-config.xml] :
• 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).
Rappelons comment est déclaré le fichier des messages du projet Jsf dans [faces-config.xml] :
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]
[messages_en.properties]
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.
Nous avons décrit les différentes éléments du fichier de configuration du projet Jsf [faces-config.xml]. Son code est le suivant :
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 :
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
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.
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
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.
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>
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>
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 :
• le contrôleur [Faces Servlet] traitera les événements qui vont se produire à partir de cette page.
21.2.8 Conclusion
http://tahe.developpez.com/java/javaee
250/339
D
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
21.3 Exemple n ° 3
Mots clés : formulaire de saisie – composants Jsf
21.3.1 L'application
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.
http://tahe.developpez.com/java/javaee
252/339
1
5
2
3
4
Le fichier [web.xml] a été configuré pour que la page [form.jsp] soit la page d'accueil du projet :
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>
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
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
[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
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] :
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.
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.
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.
<h:outputText value="#{form.inputText}"/>
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 :
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.
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].
• 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.
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
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, ...).
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"/>
<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 :
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 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.
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 :
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.
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 :
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.
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.
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
1 3 4
2
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
formulaire%3AinputText=nouveau+texte
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 :
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 :
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 :
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 *.
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
1 3 4
2
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
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].
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.
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
1 4
3
2
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] :
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].
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.
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
4
1 2 3
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.
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
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 :
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]
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> :
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
2 3
4
1
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
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 :
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
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].
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.
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
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.
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
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 :
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 :
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
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 :
formulaire%3AselectOneMenu=4
La balise <h:selectManyMenu> est identique à la balise <h:selectManyListBox size= "1 ">. Le code Jsf exécuté dans
l'exemple est le suivant :
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"/>
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).
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 :
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.
1 2
• la ligne 2 génère [1], la ligne 4 [2]. La ligne 3 ne génère aucun élément visuel.
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
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.
formulaire%3AinputHidden=initial
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].
1. <tr>
2. <td class="col1"><span class="info">selectBooleanCheckbox</span></td>
3. <td class="col2"><span class="prompt">marié(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é :
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.
http://tahe.developpez.com/java/javaee
282/339
10. </h:selectManyCheckbox>
11. </h:panelGroup>
12. <h:outputText value="#{form.selectManyCheckboxValue}"/>
Lorsque la page [form.jsp] est demandée la première fois, la page obtenue est la suivante :
2
4
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. <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
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
La balise <h:selectOneRadio> génère un groupe de boutons radio exclusifs les uns des autres.
Lorsque la page [form.jsp] est demandée la première fois, la vue obtenue est la suivante :
2
4
1
3
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].
1.<tr>
2.<td class="col1"><span class="info">selectOneRadio</span></td>
3.<td class="col2">moyen de transport préféré : <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é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 :
formulaire%3AselectOneRadio=4
21.4 Exemple n° 4
Thèmes : formulaires dynamiques
21.4.1 L'application
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.
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é
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 :
on a désormais celui-ci :
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 :
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
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 :
http://tahe.developpez.com/java/javaee
289/339
35.}
[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 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
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
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.
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.
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>
http://tahe.developpez.com/java/javaee
293/339
1
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 :
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].
2
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.
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].
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>
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.
La configuration d'une application web pour la gestion des exceptions se fait dans son fichier [web.xml] :
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.
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 :
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 :
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].
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.
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>
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 :
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 :
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é :
C'est ce qui a été fait dans la page [exception.jsp] page 299. Les attributs utilisés sont les suivants :
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]
[messages_en.properties]
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
3
1
Le projet [intro-06] repose de nouveau sur une unique page [form.jsp] [1] et son modèle [Form.java] [2]. Il continue à utiliser des
messages tirés de [messages.properties] mais uniquement en français [3]. L'option de changement de langue n'est pas offerte.
1. <%@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>
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.
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]
[messages_fr_FR.properties]
http://tahe.developpez.com/java/javaee
307/339
21.6.5 Les différentes saisies du formulaire
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
1 2 3
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
et son modèle :
• 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.
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. ...
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.
1 2 3
• 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 :
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 :
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 :
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'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 :
• 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}
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 :
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 :
Les messages d'erreurs associés aux convertisseurs et validateurs des composants saisie5 et saisie6, dans [messages.properties] :
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.
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.
La saisie d'une date est faite dans l'exemple avec le code Jsp suivant :
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 :
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
le message affiché en cas d'erreur de conversion sera le message de clé saisie8.error suivant :
Voici un exemple :
La saisie 9 montre comment imposer à une chaîne saisie d'avoir un nombre de caractères compris dans un intervalle :
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.
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 :
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 :
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 :
Le composant saisie10 est validé par la méthode form.validateSaisie10. Celle-ci est la suivante :
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.
• 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 :
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)
1. <application>
2....
3. <message-bundle>messages</message-bundle>
4.</application>
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.
• 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 :
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>.
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.
et le modèle associé :
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 :
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.}
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> :
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>
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).
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 :
• 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.
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.
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 :
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.
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.
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.
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 :
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].
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
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.}
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"}.
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"}.
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.
Nous voulons avec le bouton [Raz] remettre le formulaire dans un état initial comme montré ci-dessous :
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 :
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.
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.
• 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