Tous Les TD
Tous Les TD
Tous Les TD
J.-B. Fasquel
1 Objectifs
Le travail à faire est de se familiariser avec JPA et d'implémenter des requêtes JPQL (Java Persistence Query Language –
langage de requêtes de la couche JPA) indiquées en fin de document, dans le cas de la base [dbrdvmedecins] fournie (script
sql). Ce travail correspond à la problématique d'interaction avec une base de données en manipulant des objets en Java. Dans
l'architecture en couche, ceci est requis par la couche DAO, qui dialogue avec l'implémentation de la spécifique JPA
(hibernate dans notre cas). Cette implémentation accède à la base en utilisant JDBC (Java DataBase Connectivity).
2 Projet « mv_rdvmedecins »
Fichier « persistance.xml »
• Ajouter la dépendance au pilote JDBC de MySQL : « bouton droit sur dependencies », de manière à compléter le fichier
maven « pom.xml » avec la dépendance suivante (vous pouvez aussi éditer « manuellement » ce « pom.xml »):
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
« pom.xml » avec la dépendance au pilote JDBC [<property name = "javax. Persistence. jdbc.driver" value="com.mysql.jdbc.Driver"/>]
dans le fichier « persistance.xml ». Attention à bien respecter la version car la plus récente est incompatible avec MySQL5 !
• Créer enfin les entités JPA : classes images des tables de la base, générées automatiquement depuis Netbeans : « bouton
droit sur le projet » → « New / Persistance / entity classes from database », avec la configuration suivante :
• renommer les classes « au singulier » (raison pratique de lisibilité)
• décocher « related tables » et « Generate ... » (« tous les generate »)
• choisir java.util.List pour les collections,
• choisir un nom de package pertinent dans lequel les classes vont être générées : par exemple « jpa »
Page 1
JEE – ISTIA SAGI – TD1 – Environ 3 séances
J.-B. Fasquel
(https://docs.oracle.com/javaee/7/api/javax/persistence/package-summary.html )
6. @Entity
7. @Table(name = "medecins")
8. public class Medecin implements Serializable
…
11. @Id
12. @GeneratedValue(strategy = GenerationType.IDENTITY)
13. @Column(name = "ID")
14. private Long id;
15.
16. @Column(name = "TITRE")
17. private String titre;
18.
19. @Column(name = "NOM")
20. private String nom;
...
• @Entity fait de la classe [Medecin], une entité JPA, i.e.. une classe liée à une table de BD via l'API JPA
• Les classes générées sont « Serializables » (i.e. implémentent [Serializable]) : nécessaire dans les applications client /
serveur, pour échanger les entités sous forme de « chaîne de caractères »
• @Id indique que le champ annoté est associé à la clé primaire de la table. La couche [JPA] va générer la clé primaire
des lignes qu'elle insèrera dans la table [Medecins]. Ici la stratégie GenerationType.IDENTITY indique que la couche
JPA va utiliser le mode auto_increment de la table MySQL (valeur unique générée automatiquement à chaque nouvelle
insertion dans une table)
• @Column(nam = ''…'') associent les attributs aux colonnes des tables
• Voir ci-dessous : la table [creneaux] a une clé étrangère sur la table [medecins]. Un créneau appartient à un médecin.
Inversement, un médecin a plusieurs créneaux qui lui sont associés. On a donc une relation un (médecin) à plusieurs
(créneaux), une relation qualifiée par l'annotation @OneToMany par JPA (ligne 28). Le champ de la ligne 26 contiendra
Page 2
JEE – ISTIA SAGI – TD1 – Environ 3 séances
J.-B. Fasquel
tous les créneaux du médecin. Ceci sans programmation. Pour comprendre totalement la ligne 28, il nous faudra
présenter la classe [Creneau] ; la table [CRENEAUX] avait une clé étrangère vers la table [MEDECINS] : un créneau
est associé à un médecin. Plusieurs créneaux peuvent être associés au même médecin. On a une relation de la table
[CRENEAUX] vers la table [MEDECINS] qui est qualifiée de plusieurs (créneaux) à un (médecin). C'est l'annotation
@ManyToOne de la ligne 35 qui sert à qualifier la clé étrangère, la ligne 34 avec l'annotation @JoinColumn précise la
relation de clé étrangère : la colonne [ID_MEDECIN] de la table [CRENEAUX] est clé étrangère sur la colonne [ID] de
la table [MEDECINS]. Dans l'entité [Medecin], l'attribut cascade=CascadeType.ALL fixe le comportement de l'entité
[Medecin] vis à vis de l'entité [Creneau] :
◦ si on insère une nouvelle entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent
être insérées elles-aussi,
◦ si on modifie une entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent être
modifiées elles-aussi,
◦ si on supprime une entité [Medecin] dans la base, alors les entités [Creneau] du champ de la ligne 2 doivent être
supprimées elles-aussi.
//Extrait de la classe [Medecin]
28. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
29. private List<Creneau> creneauList;
//Extrait de la classe [Creneau]
31. @OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
32. private List<Rv> rvList;
33.
34. @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
35. @ManyToOne(optional = false)
36. private Medecin idMedecin;
Page 3
JEE – ISTIA SAGI – TD1 – Environ 3 séances
J.-B. Fasquel
3 Projet « pam_jpa_hibernate »
Extrait du main
List< Object > os = em.createQuery("select e from Employe e").getResultList();
for(Object o : os) { System.out.println(o); }
Avec la méthode « toString() » suivante pour [Employe] :
@Override
public String toString() { return "jpa.Employe[ nom=" + getNom() + " indemnite=" + getIndemniteId()+ " ]"; }
Structure du code Java de test dont l'exécution produit :
jpa.Employe[ nom=Jouveinal indemnite=jpa.Indemnite[ id=2Base heures=2.1 ] ]
jpa.Employe[ nom=Laverti indemnite=jpa.Indemnite[ id=1Base heures=1.93 ] ]
Quelques commentaires et ajustements du code:
• pour simplifier, penser à éliminer les relations @OneToMany
• on souhaite que l'attribut « securité social » [ss] de [Employe] soit non nul et unique : ceci se traduit par
@Column(name = "SS",nullable=false,unique=true). Remarque : on pourra consulter en ligne toutes les options
possibles (https://docs.oracle.com/javaee/7/api/javax/persistence/Column.html)
@ManyToOne(optional = false,fetch=...)
private Indemnite indemniteId;
Page 4
JEE – ISTIA SAGI – TD1 – Environ 3 séances
J.-B. Fasquel
L'attribut fetch définit la stratégie de recherche du champ [Indemnite indemniteId] de la classe [Employe] :
• FetchType.LAZY : lorsqu'un employé est cherché, l'indemnité qui lui correspond n'est pas ramenée. Elle le sera lorsque
le champ [Employe.indemniteId] sera référencé pour la première fois. Dans notre cas, ceci se fait lors du
System.println(…) qui invoque [Employe.getIndemniteId()]. Avec Hibernate, ce n'est possible que si le contexte de
persistance est ouvert. L'intérêt de cette stratégie est d'éviter de ramener trop de données de la base à un moment donné.
• FetchType.EAGER : lorsqu'un employé est cherché, l'indemnité qui lui correspond est ramenée. C'est le mode par défaut
lorsqu'aucun mode n'est précisé.
Travail à faire : vérifier le comportement en modifiant en utilisant le débugger et en consultant l'attribut « indemnite » des
employés récupérés par une requête JPQL : "select e from Employe e". Dans le cas d'une configuration EAGER : l'indemnité
est bien de type « Indemnite ». Dans le cas d'une configuration LAZY : l'attribut « indemnité » est de type « Indemnite_$
$_.. ». Cet objet n'est pas l'indemnité mais un objet « cachant » l'accès ultérieur à l'indemnité
Page 5
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
Objectif : Application « PAM » Architecture en couche utilisant les frameworks spring, hibernate.
1. Objectif
Cette architecture sera implémentée pas à pas, en commençant par les entités associées à l'interface JPA, puis la couche DAO,
ensuite la couche métier et enfin les deux variantes de la couche UI.
Le diagramme UML ci-dessous fournit une vue générale de cette architecture en terme de packages, classes et dépendances :
chaque couche communique avec les autres (adjacentes) au travers d'interface.
Page 1
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
Quelques commentaires :
• Le fichier pom.xml gère toutes les dépendances aux librairies [hibernate], [spring…], [swing],… etc
• L'exception [PamException] permet de gérer les exceptions comme schématisé par la figure suivante (voir chapitre
4.7.4 de [JEETahe]). Définir sa propre exception de type [RuntimeException] permet d'éviter que le compilateur
nous oblige à la déclarer dans la signature des méthodes (ce qui pose des problèmes avec les « beans »).
• Chaque couche correspond à un package spécifique ([metier] et [dao] dans les diagrammes UML), les interfaces
étant décrites ci-après.
• Le fichier [spring-config- .xml] (2 versions fournies) configure le projet, incluant notamment l'instanciation de
certains objets (les beans liés aux classes), la configuration JPA. A noter que le fichier [persistance.xml] reste
nécessaire mais limité en particulier à la déclaration des classes [employe,cotisation,indemnite] associés aux tables
de la base de données. Ce fichier [persistance.xml] ne configure plus la connexion avec la base de données (ceci
étant intégré au fichier de configuration [spring…-.xml]).
• Le package [jpa] avec les classes [employe,cotisation,indemnite] est fournie, configurées en mode [LAZY]. Nous
n'aurons pas à y toucher.
Page 2
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
La couche DAO permet de cacher les détails de l'accès à la base de données (par exemple les requêtes JPQL).
Objectif (Lisez attentivement les explications ci-après avant de commencer !!!): L'objectif est d'é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. Pensez à tester toutes les méthodes depuis le
« Main », incluant également la création d'objet et leur sauvegarde dans la base de données (par exemple, la création d'une
nouvelle cotisation, qui doit se traduire par l'ajout d'une nouvelle ligne à la table [Cotisations], visible avec WAMP).
Attention : n'éditer pas les champs des entités JPA annotés [@Version] → non éditables car permettent le suivi des
modifications (géré au niveau de la base – un peu au sens des outils de versionning de type svn git).
Test préliminaire : vérifier que vous parvenez à exécuter le programme principal [MainDao] fourni (vérifier par exemple
que le mot de passe déclaré dans les fichiers de configuration [spring.[..].xml] est conforme):
//Extrait de [MainDao]
...
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
// CotisationDao
ICotisationDao cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
// Recuperation des cotisations en utilisant la couche DAO
List<Cotisation> cotisations = cotisationDao.findAll();
// Affichage des cotisations
for ( Cotisation c : cotisations) { System.out.println("Cotisation: "+c); }
…
//Exemple de sortie console attendue
Cotisation: Cotisations=[id=1, version=1, csgrds=3.49, csgd=6.15, secu=9.39, retraite=7.88]
Extrait de [MainDao] et exemple de sortie console attendu
Page 3
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
Remarque : on constate que les méthodes [create] et [edit] retournent des objets. A priori ceci n'est pas nécessaire car le code
qui invoque (code « appelant » - voir ci-dessous) cette API fournit les entités en paramètre, et n'a donc, a priori pas besoin de
les récupérer car ce code « appelant » a déjà une référence sur les objets : si l'objet est modifié lors de l'écriture en base de
données (e.g. affectation de clé primaire ou modification de la version), le code appelant verra cette modification car il a une
référence sur l'objet, et il n'est pas nécessaire de « changer de référence ». Ceci est néanmoins une bonne pratique dans le cas
d'un échange de données entre 2 JVM ou par le réseaux : la référence ne sera pas affectée lors de la mise à jour, il est donc
requis que la variable ([c] dans notre cas) fasse référence à l'objet retourné (e.g. [c=dao.create(c)]):
Code et annotations : prenons l'exemple de la classe CotisationDao est fournie, avec seulement une méthode implémentée
([findAll()]), utilisée dans le [MainDao] :
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class CotisationDao implements ICotisationDao
{
@PersistenceContext
private EntityManager em;
// constructeur
public CotisationDao() {}
// créer une cotisation
public List<Employe> findAll()
{
try {
List<Cotisation> l = em.createQuery("select c from Cotisation c").getResultList();
return l;
}
catch (Throwable th) { throw new PamException(th,11); }
}
....
}
Extrait de la classe [CotisationDao] founie (partiellement implémentée)
• l'annotation Spring @Transactional indique à Spring que chaque méthode doit se dérouler dans une transaction ;
• l'annotation Spring @PersistenceContext injecte dans le champ [em] le gestionnaire du contexte de persistance de la
couche JPA. On utilise l'EntityManager pour faire des opérations de persistance (persist, merge, remove, createQuery),
depuis les méthodes [create], [edit], [find(..)], [findAll]… Parce que la méthode se déroule dans une transaction, on est
assuré qu'à la fin de la méthode, les modifications apportées au contexte de persistance seront synchronisées avec la base
de données ; Pour implémenter les interfaces Dao, pensez à lire la documentation :
[http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html]).
• on fera en sorte que le code de chaque méthode soit entouré d'un try / catch arrêtant toute éventuelle exception. On
encapsulera celle-ci dans un type [PamException].
Fichier de configuration « spring[…].xml » :
• L'instance « cotisationDao » est une implémentation de l'interface « ICotisationDao », dont le [Main] ne dépend pas :
[import dao. ICotisationDao ;] et non [import dao. CotisationDao ;]. L'implémentation utilisée est déclarée dans le
fichier de configuration [spring-….xml] : [<bean id="cotisationDao" class="dao.CotisationDao"/>].
• L'idée de ce type d'architecture est qu'une couche dépend d'une autre couche à travers l'interface et non l'implémentation :
le choix de l'implémentation est définie dans un fichier de configuration et non dans du code « en dur ».
Page 4
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
Travail à faire (2) : Implémenter [IndemniteDao], en vous inspirant de CotisationDao, sans la tester. Remarque : attention à
la colonne [INDICE] qui est non nulle et unique (erreur si vous essayer de créer une nouvelle indemnité d'indice déjà
existant).
Travail à faire (3) : Implémenter [EmployeDao]. Dans ce cas, on remarque une nouvelle méthode à implémenter : [find(ss)]
qui retourne l'employé dont le numéro de sécurité social est [ss]. A noter le cas particulier du mode [LAZY] (passer en mode
« LAZY » : [@ManyToOne(optional = false,fetch=FetchType.LAZY)] à l'attribut [private Indemnite indemnite] de
[Employe]:
• la méthode [employeDao::findAll()] retourne les employés sans les indemnités : dans notre cas, ceci permet de consulter
la liste des employés de la table [EMPLOYES] sans rapatrier toutes les informations « annexes » (indemnité de la table
[INDEMNITES]), et ainsi optimiser l'application.
• les méthodes [employeDao::find(String ss)] et [employeDao::find(Long id)] retourne les employés avec les indemnités
(requête JPQL avec jointure) : ceci permettra de calculer la feuille de salaire, qui requiert en particulier les indemnités.
L'objectif est d'implémenter l'interface de [IMetier] : il s'agit de la classe [Metier] dont la structure est la suivante (voir
également le diagramme UML « métier »):
@Transactional
public class Metier implements IMetier {
// références sur la couche [dao]: par configuration (properties) du bean "metier" dans spring-...-config.xml
private ICotisationDao cotisationDao = null;
private IEmployeDao employeDao = null;
// obtenir la feuille de salaire
@Override
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillees, int nbJoursTravailles)
{
try { // à coder }
catch (Throwable th) { throw new PamException("Employe de numero SS "+ SS + " not found",101); }
}
// liste des employés
@Override
public List<Employe> findAllEmployes() { // à coder }
// getters et setters (requis pour renseigner « automatiquement » les attributs privés)
public void setCotisationDao(ICotisationDao c) {this.cotisationDao=c;}
public void setEmployeDao(IEmployeDao e) {this.employeDao=e;}
...
}
Structure de la classe [Metier] à implémenter
Page 5
JEE – ISTIA SAGI – TD2 – 4 séances
J.-B. Fasquel
Remarque : les attributs [cotisationDao] et [employeDao] sont assignés de manière « transparente » (« automatiquement ») à
l'instanciation de l'objet de type [Metier], car cette classe est déclarée dans le fichier [spring….xml] sous forme d'une « bean »
avec les propriétés « property » appropriée :
Travail à faire : implémenter la classe [ui.swing.PamSwing] de manière à obtenir (au moins) l'interface graphique (voir
section 1.2). Remarque : un caneva de départ est fourni (obtenu, depuis netbeans, en faisant : [NewFile]→[Swing GUI
Form]-→ [JFrame Form]). Le diagramme UML ci-après donne une vue partielle de la classe : vous devez, en particulier
implémenter la méthode [doMyInit], qui vous permettra d'initialiser les éléments graphiques (e.g. ComboxBox), qui auront
été placé « graphiquement » depuis netbeans. [doMyInit] initialisera également l'application (« xml » invoqué depuis le
[MainMetier]).
Documentation : chapitre 4.11 de [JEETahe] & https://netbeans.org/kb/docs/java/quickstart-gui.html
Page 6
JEE – ISTIA SAGI – TD3 – 2 Séances
J.-B. Fasquel
1. Objectif
Il s'agit de porter l'application précédente [spring, JPA/Hibernate] vers [openEJB,JPA/EclipseLink]. Au terme du portage, les
« codes » suivants devront fonctionner (après une éventuelle adaptation) : ui.console.Main et ui.swing.PamSwing
L'implémentation actuelle est :
Version 1 : cas d'une couche [ui] qui est un client local de [metier]
Fichier de configuration [conf/conf/openejb.conf] : 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 suivant :
<?xml version="1.0"?>
<openejb>
<Resource id="Default JDBC Database">
JdbcDriver com.mysql.jdbc.Driver
JdbcUrl jdbc:mysql://localhost:3306/dbpam
UserName root
Password root
</Resource>
</openejb>
Fichier « openejb.conf »
Page 1
JEE – ISTIA SAGI – TD3 – 2 Séances
J.-B. Fasquel
Contrairement à [spring], les « beans » ne sont pas déclarées dans un fichier de configuration (c'était le cas avec spring, à
travers le fichier [spring-config.xml] : avec openEJB, tout se fait au niveau du code
@ApplicationException(rollback=true)
public class PamException extends RuntimeException implements Serializable
Modification à apporter à la classe [PamException]
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], appelée ici[Proxy EJB],
interceptant tous les 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 @ApplicationException
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 ;
• l'annotation @Local déclare une interface locale pour l'EJB qui l'implémentera
• l'annotation @Remote déclare une interface distante pour l'EJB qui l'implémentera.
• l'annotation @Stateless fait de la classe [CotisationDao] un EJB (à la fois local et distant) et l''annotation
@TransactionAttribute qui fait que chaque méthode de la classe s'exécutera au sein d'une transaction.
Les EJB peuvent être récupérés depuis avec un code du type (voir [MainExemple] fourni) :
Page 2
JEE – ISTIA SAGI – TD3 – 2 Séances
J.-B. Fasquel
Remarque : En exécutant ce code, on peut voir dans la sortie console les informations suivantes :
INFOS - Jndi(name=ExempleALocal) --> Ejb(deployment-id=ExempleA)
INFOS - Jndi(name=global/classpath.ear/pam_opene/Exemple!exemple.IExempleALocal) --> Ejb(deployment-id=Exemple)
Le JNDI (Java Naming and Directory Interface) : Cette API fournit une interface unique pour utiliser différents services
de nommages ou d'annuaires et définit une API standard pour permettre l'accès à ces services. Dans notre cas, cela permet
d'instancier (« récupérer ») les classes embarquées dans notre « EJB » à partir d'une chaîne de caractère (sorte d'annuaire qui
associe un nom à une classe). Pour plus d'informations, on pourra se référer à :
https://www.jmdoudoux.fr/java/dej/chap-jndi.htm ou http://docs.oracle.com/javase/jndi/tutorial/ ou
http://www.oracle.com/technetwork/java/overview-142035.html
Remarque : on peut également implémenter les deux interfaces [Local] et [Remote] comme illustré par l'exemple fourni pour
[ExampleLR], par héritage en diamant.
Au final, lorsque l'EJB [Metier] sera instancié, les attributs seront initialisés avec des références sur les interfaces locales des
deux EJB de la couche [DAO]. On fait donc l'hypothèse que les couches [metier] et [DAO] s'exécuteront dans la même JVM.
Travail à faire : Implémenter la couche metier. Pour cela, commencer par copier le package [metier] de la version « spring »
de l'application, puis adapter les classes.
Page 3
JEE – ISTIA SAGI – TD4 – 2 séances
J.-B. Fasquel
1. Objectif
On se propose de placer les EJB des couches [metier] et [DAO] dans le conteneur d'un serveur d'applications Glassfish. Dans
l'application actuelle (voir figure), la couche [ui] utilise l'interface distante de la couche [metier] (nous avons également testé
la version « locale »).
Architecture attendue
• la couche [ui] s'exécutera dans un environnement Java SE (Standard Edition)
• les couches [metier, DAO, JPA] s'exécuteront dans un environnement Java EE, sur un serveur Glassfish ;
• le client communiquera avec le serveur via un réseau tcp-ip avec le protocole RMI (Remote Method Invocation),
utilisable uniquement entre deux applications Java. Le client et le serveur s'échangent des objets sérialisés pour
communiquer et non des références d'objets.
la partie serveur qui sera hébergée par le conteneur EJB3 du serveur Glassfish :
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 : ils nous permettent 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
L'objectif sera de :
• Porter l'application PAM précédente : cela consiste en un nouveau projet type « EJB », le « copié-collé » des
package [metier] [dao] et [jpa] du projet PAM du TD précédent.
• Implémenter le « client » dans un autre projet (2 types de client dans notre cas)
Page 1
JEE – ISTIA SAGI – TD4 – 2 séances
J.-B. Fasquel
3. Partie serveur
Page 2
JEE – ISTIA SAGI – TD4 – 2 séances
J.-B. Fasquel
Extrait de la sortie console : on observe que la couche métier est accessible. Note : les noms portables JNDI des EJB
déployés sont reconnu par tous les serveurs Java EE (supérieur à la version 5).
Le [throws NamingException] permet de laisser remonter une exception de type [NamingException] sans l'intercepter,
permettant de se focaliser sur d'autres exceptions, plus « claires ». En cas de problème, pensez à notamment vérifier que vos
classes sont [Serializable] et pensez à regarder la sortie de log du serveur GlassFish.
• Travail à faire : implémenter dans ce projet le client graphique « swing », en adaptant la classe [PamSwing] codée lors
d'un TD précédent. Pour cela, n'oubliez pas d'ajouter la dépendance à « swing » dans votre « pom.xml »
• POM.xml : ajoutez la dépendance au module EJB [pam-ejb-glassfish] (comme pour le premier client)
• Adapter le programme console pour calculer le salaire d'une assistante maternelle.
Travail à faire : implémenter dans ce projet le client graphique « swing », en adaptant la classe [PamSwing] codée lors d'un
TD précédent. Pour cela, n'oubliez pas d'ajouter la dépendance à « swing » dans votre « pom.xml ».
Page 3
JEE – ISTIA SAGI – TD5 – 2 séances
J.-B. Fasquel
Objectif : Application « PAM » : client serveur dans un architecture de service web SOAP (chapitres 7.1 et 7.2 de [JEETahe])
1. Objectif
Nous allons maintenant remplacer la couche de communication [C, RMI, S] 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. Le client lui, pourrait être un client .NET ou PHP. Nous allons
développer cette architecture selon le modes suivant : le service web sera assuré par une application web utilisant l'EJB
[Metier] ; Dans ce cas un service web peut être implémenté par une classe annotée @WebService qui utilise un EJB :
Page 1
JEE – ISTIA SAGI – TD5 – 2 séances
J.-B. Fasquel
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
• Ajouter la seule classe du projet [PamWsEjbMetier] (en faisant [add web service]), qui sera associée au webservice :
[Packages de sources / pam.ws / PamWsEjbMetier]. Elle fait le lien entre le webservice et la couche [Metier] de l'EJB.
Vous pouvez vous inspirer du webservice de l'exemple fourni. Il suffit que cette classe soit taguée @Webservice,
avec deux méthodes [calculerFeuilleSalaire] et [findAllEmployes] ;
@WebService
public class PamWsEjbMetier {
@EJB
private IMetier metier;
Classe PamWsEjbMetier
L'annotation @WebService fait de la classe [Metier] un service web. Un service web expose des méthodes à ses clients.
Celles-ci peuvent être annotées par l'attribut @WebMethod, mais c'est facultatif. Par défaut, les méthodes publiques d'une
classe annotée par [@WebService] deviennent automatiquement des méthodes du service web. Il est important que les
getters et setters soient supprimés sinon ils seront exposés dans le service web et cela cause des erreurs de sécurité : on
pourrait obtenir/modifier les accès à la couche [dao] et donc aux données.
Démarrer le serveur « glassfish », compiler [pam-webapp-ear] et exécuter-le (entraîne de déploiement) : votre navigateur doit
afficher la page [http://localhost:8080/pam-web-ws-ejb-glassfish/] avec « Hello world ». Remarque : en cas de problème,
vous pouvez forcer la compilation des projets « war » et « ejb ».
Page 2
JEE – ISTIA SAGI – TD5 – 2 séances
J.-B. Fasquel
Problème du type « [2] EJBs ..etc. » : cela peut venir des dépendances : Attention à ce que le projet « webservice » ait une
dépendance de type « provided » et non « compile » vers pam-ejb-glassfish, sinon vous aurez un « double » déploiement du
« pam-ejb-glassfish ».
4. Test du webservice
Test 1: Après avoir déployé de le projet [pam-webapp-ear], vous pouvez tester votre webservice : projet [pam-webservice-
glassfish] / bouton droit sur [Web Services / PamWsEjbMetier ] / Tester webservice. Le navigateur doit afficher la page :
[http://localhost:8080/MetierService/Metier?Tester]
Test 2 : en cliquant sur [findAllEmployes], vous devez récupérer les informations affichées (table [Employes] de la base
MySQL) au format « xml » directement dans la page du navigateur. De même, vous pouvez calculer la feuille de salaire en
entrant les bons paramètres. Si ces tests échouent, la suite ne fonctionnera pas. Cause possible de problème : il faut avoir,
avoir les getters/setters sur la classe [FeuilleSalaire].
@WebEndpoint(name = "PamWsEjbMetierPort")
public PamWsEjbMetier getPamWsEjbMetierPort() {
return super.getPort(new QName("http://ws.pam/", "PamWsEjbMetierPort"), PamWsEjbMetier.class);
}
Extrait de la classe [MetierService] automatiquement générée
• En s'inspirant de l'exemple fourni, créer le programme console [MainRemote] dans le package [ui.console], où l'on
récupérera l'instance [Metier] de la manière suivante (voir exemple fourni) :
• En vous inspirant du cas « console » et des TDs précédents pour l'utilisation de l'interface graphique avec swing, créer
une interface graphique [PamSwing] dans le package [ui.swing]
Page 3
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
1 Généralités
Documentation en ligne : API : https://javaserverfaces.java.net/ ; Tutoriels : https://www.tutorialspoint.com/jsf/
http://www.coreservlets.com/JSF-Tutorial/jsf2/
Page 1
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
<exception-type>java.lang.Exception</exception-type> <location>/faces/exception.xhtml</location>
</error-page>
Fichier web.xml
• La classe « servlet » [FacesServlet] est le contrôleur C du modèle MVC. 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]. Les URL des clients traitées par la servlet [Faces Servlet] auront donc
la forme [http://machine:port/nom_projet/faces/*].
• <session-config … /> : durée d'une session. Ici, 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. Au prochain accès, une nouvelle session démarrera.
• La page d'accueil. Lorsque le client demande l'URL [http://machine:port/nom_projet], c'est l'URL
[http://machine:port/nom_projet/index.xhtml] qui lui sera servie.
• Le fichier [exception.xhtml] gère les erreurs. Pour tester la levée d'exception : « corrompre votre fichier index.html »
(par exemple en enlevant un « > » à une balise), compiler et exécuter le projet → votre navigateur doit instantanément
afficher le message d'erreur (code 500) décrit dans exception.xhtml.
• Les balises préfixées par h sont des balises HTML et celles préfixées par f sont des balises propres à JSF . La balise
<f:view> sert à délimiter le code que le moteur JSF doit traiter, celui où apparaîssent les balises <f:xx>. L a balise
<h:form > introduit un formulaire, généralement constitué de balises de champs de saisie (texte, boutons radio, cases à
cocher, listes d'éléments, ...) et balises de validation du formulaire (boutons, liens).
• La feuille de style (dossier [resources]) est définie à l'intérieur de la balise HTML <head >, par <h:outputStylesheet .../>
Exemple d'utilisation (voir code): <h:panelGrid columnClasses="col1,col2" columns="2" > (tableau à trois colonnes, où
le style de colonnes 1 et 2 seront col1 et col2). L'image de fond est définie par <h:body style="background-image: ….>
• L'extrait du fichier index.xhtml donne des exemples de composants graphiques en lien avec le « monde » Java :
<h:inputText > pour la saisie, <h:command... /> pour l'exécution de commandes,… interagissant avec la classe [Form].
• La classe [Form] doit avoir les propriétés suivantes :
Page 2
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
◦ @ManagedBean fait de la classe [Form] un bean reconnu par JSF. Son nom peut être fixé par
@ManagedBean(name= "xx ") . Par défaut, le nom de la classe est utilisé (premier caractère en minuscule):
e.g. #{form...}. ManagedBean appartient à javax.faces.bean.. et non javax.annotations....
◦ L'annotation SessionScoped fixe la portée du bean. Il y en a plusieurs, dont :
▪ RequestScoped : la durée de vie du bean est celle du cycle demande navigateur / réponse serveur. L'objet sera
à nouveau instancié (donc les données perdues!) pour traiter une nouvelle requête,
▪ SessionScoped : la durée de vie du bean est celle de la session d'un client particulier. Le bean est créé
initialement lors d'une des requêtes du client, et mémorise en général des données propres à un client donné.
▪ ApplicationScoped : la durée de vie du bean est celle de l'application elle-même, souvent partagé par tous les
clients de l'application. Il est en général initialisé au début de l'application.
◦ [Form] implémente l'interface [Serializable] : obligatoire pour la portée Session (e.g. sérialisation dans des fichiers)
◦ La classe [Form] doit avoir des getters/setters pour les attributs utilisés par la page JSF, de la forme [get]/[set] suivi
du nom de l'attribut avec la première lettre en majuscule (voir exemple)
• Lien entre le fichier JSP et la classe Java, par l'exemple:
◦ L'instruction [<h:inputText id="saisie1" value="#{form.myAttrib}"] signifie que la saisie sera affectée (lors du
POST) à l'attribut [myAttrib] d'un objet de type [Form]. Ici, on impose qu'une valeur soit saisie avec l'option
[required=''true''] (sinon un message d'erreur est affiché). On personnalise également le message associé à une
erreur de conversion (attribut attendu de type [Integer] ici). L'affichage de ces messsages est effectué dans une
balise [<h:message />]. Remarque : on peut contraindre davantage la saisie (e.g. intervalle de valeur autorisée :
<f:validateLongRange minimum="1" maximum="10" />). Bien que non illustré ici, on peut également valider des
contraintes complexes avec du code Java côté serveur (e.g. concernant simultanéement plusieurs champs).
◦ [<h:outputText value="#{form.myAttrib}"/>] permet d'afficher la valeur de l'attribut [myAttrib] (getter requis)
• Remarque générale : En fonction de la requête, le contrôleur analyse la page JSP à afficher (e.g. mise à jour en fonction
du code Java associé), avant de finalement retourner une page web « classique » purement html/javascript. Vous pouvez
consulter le code source de la page depuis le navigateur pour constater la différence entre la page finale et la page JSP.
• Certaines instructions dans le fichier JSP peuvent ne pas avoir de lien avec des classes java.
◦ Par exemple, dans le fichier index.xhtml, l'instruction [<h:commandLink value="#{msg['welcome.page1']}"
action="page1"/>] permet de tout de suite passer à la page [page1.xhtml].
◦ Par exemple, on peut déclarer, dans la page JSP du code javascript à exécuter, par exemple :
<script type="text/javascript">
function clean(){
document.forms['formulaire'].elements['formulaire:saisie1'].value=""; document.forms['formulaire'].submit(); }
</script>
….
<h:inputText id="saisie1" value="#{form.myAttrib}" … />
….
<h:commandButton value="INCREMENT" onclick='clean()' immediate='true' action="#{form.increment}"/>
Exemple d'usage du javascript : sur ''onclick'', la fonction [clean] est appelée pour reinitialiser (vider) le champ de saisie
[saisie1]. A noter que la méthode [increment] sera également invoquée. L'instruction [immediate='true'] permet d'éviter le
processus de validation qui échouerait car on vide le champ [saisie1] alors que celui est requis [required=''true''].
• La langue est associé la variable [locale], dont la valeur est géré par la classe [ChangeLocale]. En fonction cette valeur,
les textes affichés seront différents (en anglais, en français), ceci étant géré par des fichiers (un fichier par langue) et la
variable [msg] (voir l'exemple <h:outputText /> avec #{msg['welcome.titre']}). [msg] est associé à un fichier de
messages. Ce dernier doit être déclaré dans le fichier de configuration [faces-config.xml]:
Page 3
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
11. <application>
12. <resource-bundle>
13. <base-name>messages</base-name> <var>msg</var>
17. </resource-bundle>
18. </application>
Fichier « faces-config », où <application > sert à configurer l'application JSF
• Dans le cas de [#{msg['welcome.titre']}], le système ira chercher la chaine de caractère associée à ''welcome.titre'' dans
le fichier ''messages[_CodeLangue][_CodePays].properties'', où [_CodeLangue][_CodePays] est défini par la variable
[locale] (« fr » ou « en » dans notre cas). Il peut exister plusieurs fichiers de messages (typiquement placés dans [src /
main / resources] du projet) : messages_fr.properties (français), messages_en.properties (anglais) et messages.properties
(par défaut). Ici, si [locale] vaut ''fr'' : "#{msg['welcome.titre']}" vaudra "Tutoriel JSF (JavaServer Faces)"
Balise <h:selectManyListBox> :
2. <h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
3. <h:panelGroup>
4. <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
5. <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}"
size="3">
6. <f:selectItem itemValue="1" itemLabel="un"/>
….
10. <f:selectItem itemValue="5" itemLabel="cinq"/>
11. </h:selectManyListbox>
12. <p><input type="button" value="…" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
Cet élément est associé au tableau de chaine de caractère de [Form] : la méthode invoquée ([getSelectManyListBoxValue()])
ne retourne pas un tableau, mais une seule chaine qui concatène toutes les chaines du tableau.
Lorsqu'on clique sur le bouton [Raz], le code Javascript de l'attribut [onclick] s'exécute. Il permet de modifier directement
dans la page côté navigateur (sans « POST » au serveur) l'état du « ManyListBox » : 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.
Balise <h:selectOneMenu> avec <f:selectItems> (listes dynamiques): Les éléments de la liste sont ici générés
dynamiquement par du code Java, et non « en dur » comme précédemment
<h:panelGroup>
<h:outputText value="..."/>
<h:selectOneMenu id="selectOneMenuDynamic" value="#{form.selectOneMenuDynamic}">
<f:selectItems value="#{form.selectOneMenuDynamicItems}"/>
</h:selectOneMenu>
</h:panelGroup>
<h:outputText value="#{form.selectOneMenuDynamic}"/>
import javax.faces.model.SelectItem;
...
Page 4
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
Balise <h:dataTable>:
1. <h:dataTable value="#{table.personnes}" var="personne" … >
2. <h:column>
3. <f:facet name="header"> <h:outputText value="Id"/> </f:facet>
6. <h:outputText value="#{personne.id}"/>
7. </h:column>
8. …
4. <h:commandLink value="Retirer" action="#{table .retirerPersonne}">
5. <f:setPropertyActionListener target="#{table .personneId}" value="#{personne.id}"/>
6. </h:commandLink>
9. </h:dataTable>
• Le bean [Table] comprend une liste de personne [table.personnes] que nous affichons ligne par ligne : l'attribut
var="personne" fixe le nom de la variable représentant la personne courante à l'intérieur de la balise <h:datatable >.
• Lorsque le lien [Retirer] est cliqué, la méthode [Form].retirerPersonne : la personne associée à l'identifiant [personne.id]
va être retiré, en affectant préalablement la valeur [personne.id] (« value ») à l'attribut [personneId] de [Table]
(« target »). Cette affectation préalable est géré par le [<f:setPropertyActionListener … />] va être exécutée. A noter que
[Table] est de portée « session » pour que cette liste de personne vive au fil des requêtes
@SessionScoped.
11. public class Table {
14. private List<Personne> personnes;
15. private int personneId;
18. public Form() { // initialisation de la liste des personnes }
26. public String retirerPersonne() { /* On retire la personne dont l'identifiant vaut [personneId] */ return null}
43. }
4 Objectif
L'objectif est d'implémenter de l'application web « PAM » avec JSF (voir copies
d'écran attendues)
Note: un projet « JSF » peut être créé depuis netbeans: « Maven / Web Application »
[mv-pam-jsf2-simulation], avec le framework JavaServer Pages [Properties →
Framework → Add JavaServer Pages]. Après compilation et exécution, consulter, avec
votre navigateur, la page : http://localhost:8080/mv-pam-jsf2-simulation/ pour vérifier
que tout fonctionne. Dans notre cas, un caneva est fourni, que l'on peut
directement compiler et exécuter (déploiement sur serveur glassfish fourni).
Page 5
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
• Les objets affichés par le combo auront pour attribut itemValue , le n° SS de l'employé et pour attribut itemLabel, une
chaîne constituée du prénom et du nom de l'employé ;
• Les boutons [Salaire] et [Raz] seront pour l'instant inactifs
• La validité des saisies sera vérifiée
Pensez à utiliser le « logger » ([java.utils.logging.logger]) pour
suivre les échanges entre le navigateur et le serveur
5.4 Travail à faire : Etape 4 : Gérer le bouton [Raz], ramenant le formulaire dans l'état initial
• Réinitialiser les champs de saisie avec un code javascript (onclick=''raz'')
• Cacher la partie « salaire » en mettant l'attribut [viewInfosIsRendered] à « False » ( méthode [Form::raz])
6 [Optionnel] Intégration de la couche « ejb » réelle (couche métier réelle – non simulée)
Nous commençons par adapter la couche web, en intégrant de la couche « ejb ». Pensez à travailler sur une copie du projet
web (e.g. [pam-jsf2] et supprimer les paquetages [exception, metier, JPA] « simulés » et ajouter ensuite aux dépendances du
projet web le projet du serveur EJB construit précédemment (TD5 : pam-ejb-glassfish): « Add dependancy » → dépendance
de type « ejb » avec le scope « provided » (car fourni ultérieurement au projet web par son environnement). Adapter
également la classe [Form] pour référencer la couche [metier] réelle (« EJB local » car exécution dans la même JVM). Note :
pour éviter les erreurs de compilation, pensez à « encadrer », dans la classe [Form], les appels aux méthodes de [metier] par
un [try {} catch(Exception ex) {}].
<dependency> ...
<groupId>${project.groupId}</groupId> public class Form {
<artifactId>pam-ejb-glassfish</artifactId> @EJB
<version>${project.version}</version> private IMetierLocal metier; // couche métier réelle
Page 6
JEE – ISTIA SAGI – TD6 – 4 séances
J.-B. Fasquel
<scope>provided</scope> …
<type>ejb</type>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
Forme du fichier « pom.xml » Adaptation du bean [Form.java]
Comme pour le TD avec le « webservice », créez un nouveau projet Netbeans de type « Maven / Entreprise Application »
[pam-webapp], en décochant les options « create web..» et « create ejb » (fournis ensuite) :
• Deux projets Maven ont été créés. Le projet d'entreprise est celui qui a le suffixe ear. L'autre projet est un projet parent.
• Nous ajoutons le module web et le module EJB au projet d'entreprise (ajout de dépendance):
◦ ajout du projet EJB [pam-ejb-glassfish], de type ejb (scope : compile)
◦ ajout du projet web [pam-jsf2], de type war (scope : compile)
Vérifiez que la base MySQL [dbpam] existe et est remplie, et déployez l'application d'entreprise [pam-webapp-ear] :
• faire un [Clean and Build] sur les projets EJB [pam-ejb-glassfish], Web [pam-jsf2] et EAR [pam-webapp-ear] ;
• Exécuter le projet d'entreprise (projet « ear ») : la page [http://localhost:8080/pam-jsf2/faces/index.xhtml] doit
s'afficher.
Page 7
JEE – ISTIA SAGI – TD7 – 2 séances
J.-B. Fasquel
Objectif : Application web et utilisation du framework JEE Java Server Pages : application multi-pages avec « sessionScoped »
1 Objectif
L'objectif est de développer d'étendre l'application en ajoutant l'historique des salaires calculés. On souhaite également que
cet historique soit éditable (on souhaite pouvoir retirer des éléments de l'historique).
Vue principale pour le calcul des salaires Vue secondaire pour l'historique
Affichage de l'historique après 3 calculs de feuilles de salaire Après avoir retiré la seconde ligne
• On ajoute la classe [HistoryElement] au même package que [Form] : il stockera les attributs [rank, name, jours, heures et
salaireNet] requis pour l'historisation. Rank correspond au rang de l'élément dans la liste (0, 1, 2, ...)
• Form aura un nouvel attribut [List<HistoryElement> history= new ArrayList<>() ;] avec le getter associé. History se
verra ajouter un nouvel 'HistoryElement' à chaque calcul de salaire (méthode [calculerSalaire]).
• Changer l'annotation « scope » de [Form] en [@SessionScoped] En effet, celui-ci ne doit pas être de type
[@RequestScoped], sinon l'objet de type [Form] sera instancié à chaque requête, perdant ainsi l'historique. Attention à
bien utiliser le [SessionScoped] du package [import javax.faces.bean.SessionScoped]
Page 1
JEE – ISTIA SAGI – TD7 – 2 séances
J.-B. Fasquel
<h:column>
<h:commandLink value="Retirer" action="#{form.removeElement}">
<f:setPropertyActionListener target="#{form.attribut}" value="#{value}"/>
</h:commandLink>
</h:column>
Cette [commandLink] permet d'exécuter l'action (méthode) [form.removeElement]. Cette méthode n'aura pas de
paramètre. Cependant, on a besoin de passer à [form] l'identifiant de l'objet à retirer de l'historique : ici, ce sera le [rank].
C'est là que le [f:setPropertyActionListener] intervient. On va considérer un nouvel attribut ([form.attribut] dans
l'exemple ci-dessus) de [form] que [form.removeElement] utilisera. La valeur de cet attribut correspondra à
[value="#{value}"], où « value » est la valeur que l'on souhaite affecter au nouvel attribut. Dans notre cas, on pourra
utiliser le [rank] associé [HistoryElement].
On souhaite que le calcul de feuille de salaire et l'affichage de l'historique se fassent dans deux pages distinctes (voir ci-
dessous). Il s'agit simplement d'ajouter deux liens ([<h:commandLink />]) permettant de naviguer entre les deux pages (voir
exemple [mv-jsf01])
Page 2
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Objectif : Application web et utilisation du framework JEE Java Server Pages : application multi-pages / multi-vues
1 Objectif
L'objectif est de développer un simulateur de calcul de paie, impliquant plusieurs vues. Les différentes vues présentées à
l'utilisateur seront les suivantes :
• la vue [accueil.xhtml] qui présente le formulaire de simulation :
• la vue [simulations.xhtml] (simulationS avec un « S » !) qui donne la liste des simulations faites par le client
• la vue [simulationsVides.xhtml] qui indique que le client n'a pas ou plus de simulations :
2 Architecture
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.
Page 1
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Page 2
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
La page d'accueil est la page [accueil.xhtml]: cette page s'affiche à l'intérieur de la page [layout.xhtml] la place du fragment
nommé part1. Dans ce fragment, on affiche la page [saisie.xhtml].
L'entête [entete.xhtml] est intégrée par le « template » [layout.xhtml] : elle affiche un titre général ainsi que les différents
liens (en haut à droite) correspondant aux différentes actions. A noter que ces liens (<h:commandLink >) sont affichés
(« rendered=... ») selon la vue rendue : tous les liens possibles sont déclarés mais seulement certains sont affichés, ceci étant
géré par le bean « SessionData » (voir ci-après). Plus précisément :
• les six liens correspondant aux six actions que peut faire l'utilisateur. Ces liens sont contrôlés (attribut rendered )
par des booléens du bean SessionData.
• Un clic sur le lien [Effacer la simulation] provoque l'exécution de la fonction Javascript raz . Celle-ci a été définie
dans le modèle [layout.xhtml],
• l'attribut « immediate=true » fait que la validité des données n'est pas vérifiée avant exécution de la méthode (par
exemple [Form.enregistrerSimulation] de l'entête « entete.xhtml »). C'est voulu. On peut vouloir enregistrer la
dernière simulation faite il y a une minute, même si les données saisies depuis dans le formulaire (mais pas encore
validées) ne sont pas valides. Il est fait de même pour les actions [Voir les simulations], [Effacer la simulation],
[Retour au simulateur] et [Terminer la session].
Page 3
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
4 Travail à faire
Travail à faire : Compléter le programme pour que la liste des éléments du combo des employés soit fournie par une
méthode du bean [Form]. Les éléments affichés dans le combo auront leur propriété itemValue égale au n° SS d'un employé
et la propriété itemLabel sera une chaîne formée du prénom et du nom de celui-ci.
Travail à faire : Initialisez correctement le bean [SessionData] pour que, lors de la requête GET initiale faite au formulaire,
le menu de l'entête soit celui montré ci-dessus.
Page 4
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Page d'erreur : l'employé sélectionné (à gauche) n'existe pas, ce qui conduit à l'affichage d'une page d'erreur (à droite)
Travail à faire : Compléter la méthode [faireSimulation] afin que lors d'une exception, elle fasse afficher la vue [vueErreur]
(voir figure).
Démarche : afin de tester la levée d'erreur, sélectionnez un employé « erroné » (XYZ) ajouté en dur dans la méthode
« findAllEmployes » de la classe Metier.
La structure de la méthode [faireSimulation] intégrant l'erreur est la suivante :
// action du menu
2. public String faireSimulation(){
3. try{
4. // on calcule la feuille de salaire
5. feuilleSalaire= ...
6. // on met . jour le menu
7. ...
8. // on rend la vue simulation
9. return "simulation";
10. }catch(Throwable th){
11. // on vide la liste des erreurs pr.c.dentes
12. ...
13. // on cree la nouvelle liste des erreurs
14. ...
15. // on affiche la vue vueErreur
16. ...
17. // on met . jour le menu
18. ...
19. // on affiche la vue erreur
20. return "erreurs";
21. }
22.}
La liste des erreurs est mémorisée dans la classe [Form] par l'attribut private List<Erreur> erreurs=new ArrayList<Erreur>();
Cette liste d'erreur est construite en ajoutant des objets de type [Erreur] (classe fournie) à la liste : chaque erreur est construite
avec le nom de la classe de l'exception (e.g. th.getClass().getName()) et le message associé (e.g. th.getMessage()). On ajoute
itérativement les causes de l'exception (e.g. [cause.getCause()]) jusqu'à ce que la cause soit nulle (i.e. [cause.getCause() ==
null]).
Note: documentation sur [Throwable] en ligne : [https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html].
Page 5
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Travail à faire : écrire la méthode [retourSimulateur] de la classe [Form]. Le formulaire de saisie présenté doit être vide
comme ci-dessus.
Un clic sur le lien [EffacerSimulation] provoque d'abord l'appel de la fonction Javascript raz(). Cette méthode est définie dans
la page [layout.xhtml] :
• les valeurs postées sont des valeurs valides, c.a.d. qu'elles passeront les tests de validation des champs de saisie
heuresTravaillees et joursTravailles ;
• la fonction raz ne poste pas le formulaire. En effet, celui-ci va être posté par le lien cmdEffacerSimulation . Ce post
se fera après exécution de la fonction Javascript raz ;
Au cours du post , les valeurs postées vont suivre un cheminement normal : validation puis affectation aux champs du
modèle. Ceux-ci sont les suivants dans la classe [Form] :
// le mod.le des vues
private String comboEmployesValue;
private String heuresTravaill.es;
private String joursTravaill.s;
Ces trois champs vont recevoir les trois valeurs postées {"0","0","0"}. Une fois cette affectation opérée, la méthode
effacerSimulation va être exécutée.
Travail à effectuer : écrire la méthode [effacerSimulation] de la classe [Form]. On fera en sorte que :
• seule la zone des saisies soit affichée,
• le combo soit positionné sur son 1er élément,
• les zones de saisie heuresTravaillees et joursTravailles affichent des chaînes vides.
• La liste des commandes (entête) soit réduite à [Faire simulation] [Effacer simulation] [Terminer Session]
Page 6
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Enregistrement d'une simulation : la page affichée devient celle des simulations enregistrées
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.
Page 7
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
Travail à faire : écrire la méthode [enregistrerSimulation] de la classe [Form]. Vérifier que le retour au simulateur
fonctionne dans le cas de la fonctionnalité d'affichage de l'erreur.
On fera en sorte que si la liste des simulations est vide, la vue affichée soit [vueSimulationsVides] (voir fichier
simulationsVides.xhtml):
Page 8
JEE – ISTIA SAGI – TD8 – 4 séances
J.-B. Fasquel
columnClasses="simuNum,simuNom,simuPrenom,simuHT,simuJT,simuSalaireBase,simuIndemnites,simuCotisationsSociales,simuSalaire
Net">
3. ...
4. <h:column>
5. <h:commandLink value="Retirer" action="#{form.retirerSimulation}">
6. <f:setPropertyActionListener target="#{form.numSimulationToDelete}" value="#{simulation.num}"/>
7. </h:commandLink>
8. </h:column>
9. </h:dataTable>
le lien [Retirer] (ligne 5) 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 >. 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]
(« private Integer numSimulationToDelete; »).
Lorsque la méthode [retirerSimulation] de la classe [Form] s'exécutera, elle pourra utiliser la valeur qui aura été stockée
auparavant dans le champ numSimulationToDelete, désigne le numéro de la simulation à retirer (chaque objet « simulation »
embarque son numéro).
Travail à faire : écrire la méthode [retirerSimulation] de la classe [Form].
En terminant la session, on revient au formulaire de saisie (à droite), sachant que la liste de simulations (à gauche) est vidée.
5 Optionnel : intégration de la couche web dans une architecture 3 couches JSF / EJB
Travail à faire : remplacer la couche [métier] simulée, par les couches [métier, DAO, JPA] implémentées par des EJB (voir
TD précédent)
Page 9