TP1 2011 EJB 3.1/JPA/JSF2

De $1

Version de 13:09, 12 Mai 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

Introduction

Ce TP est un premier jet pour une prise contacte directe et violente avec les technologies précitées. Les cours et TPs suivants expliqueront le détail de ce que vous allez mettre en oeuvre aujourd'hui. Il s'agit d'une adaptation plus ou moins fidèle de l'article paru sur le site developer Zone : http://netbeans.dzone.com/articles/d...-ee-6-app-jsf2

Voici l'architecture de l'application que vous allez développer :

2-1-Architecture.gif

Logiciels utilisés

Nous allons travailler avec la version 7.0.1 de netbeans, la version la plus complète (colonne de droite de : http://netbeans.org/downloads), qui vient avec le serveur Java EE6 Glassfish V 3.1.1. Veillez à avoir un JDK récent installé sur votre machine (JDK6 update 22 ou supérieur). Nous utiliserons également à la fin de ce TP quelques composants JSF2 provenant du site http://www.primefaces.org/

Création d'un projet de type Entreprise Application

L'année dernière nous avions travaillé avec des projets de type "Web" qui incluaient tout dans un seul fichier .war, cette année nous allons aussi voir que l'on peut développer des applications de type "entreprise" qui font une séparation très nette entre les parties "web" et les parties métier/accès aux données d'une application.

Pour créer un projet "entreprise" utilisez le menu File/New Project puis choisissez la catégorie Java EE, et un projet de type "Enterprise Application".

Snap1.jpg

Liquez ensuite sur le bouton "Next" puis choisissez l'emplacement du projet et son nom. Attention, pas de caractères bizarres dans le chemin ni le nom s'il vous plait, pas de "_" par exemple, pas d'accents, etc. Ceci pour éviter les mauvaises surprises qui peuvent arriver par la suite (hibernate n'aime pas les noms de chemins bizarres, toplink n'aime pas les "_" dans les noms de projets, etc...)

J'ai choisi comme nom de projet TP1CustomerApplication (comme vous voyez : pas d'espace ou de caractère bizarre dans le nom) et j'ai conservé l'endroit où Netbeans place les projets par défaut (là aussi, je préfère un nom de chemin sans espaces...)

Snap2.jpg

Dans la fenêtre suivante, on indique le nom du serveur et la version de java EE que l'on souhaite pour le projet. Prenez les valeurs par défaut, si elles sont différentes de celles ci-dessous, assurez-vous que cela correspond bien à la dernière version de glassfish et à la version 6 de java EE (qui est celle étudiée en cours).

 Snap3.jpg

Cliquez ensuite sur le bouton "finish", normalement netbeans génère un projet pour votre application. En réalité, une application "enteprise" se compose de trois projets :

  1. un projet précédé par un triangle bleu qui est en fait celui qui réunit les deux autres projets au sein d'un même fichier .ear (enterprise archive), équivalent du .war mais pour les applications enteprises,
  2. Un projet précédé par un grain de café, c'est le projet "métier" qui va contenir la partie métier de l'application ainsi que la partie accès aux données, il est précédé d'un grain de café (un "bean") car il contiendra certainement des "beans", des "composants java" de type "managed beans" "enterprise java beans (EJBs)" ou "entities".
  3. Un projet précédé par une icône en forme de globe terrestre, c'est le projet "web" qui va contenir le front-end web de l'application : les jsps, servlets, pages jsf ou html, les web services, etc.

Snap4.jpg

Créations de classes entités à partir d'une base de données existante

Dans ce projet nous allons utiliser une base de données. Comme nous l'avons vu en cours, on ne manipulera pas des données directement en SQL mais sous forme d'objets particuliers qu'on appelle des "classes entités". Généralement une classe entité correspond à une table, porte le même nom (aux majuscules/minuscules près), et ses attributs correspondent aux colonnes de la table. Les instances de cette classe entité seront des objets correspondant à des lignes dans cette table.

Dans un premier temps nous allons utiliser un wizard de netbeans pour générer une classe entité à partir d'une table.

On va ajouter la classe entité dans le projet "EJBs", celui précédé d'un grain de café. Faire clic droit puis New/Entity Class from Database :

Snap5.jpg

Une fenêtre apparait dans laquelle vous allez indiquer la source de données (le "nom" de la base de données sur laquelle vous allez travailler. Par défaut le jdk vient avec un SGBD qui s'appelle JavaDB (ou "Derby") qui contient une base "sample" dont le nom est "jdbc/sample". Elle contient quelques tables correspondant à un magasin de produits informatiques. Choisissez donc jdbc/sample dans la liste des sources de données :

Snap7.jpg

Normalement dès que vous avez choisi le nom de la base, les tables s'affichent dans la colonne de gauche. Choisissez "CUSTOMER" et cliquez sur le bouton "add", cela va ajouter en fait deux tables : la table CUSTOMER mais aussi la table DISCOUNT_CODE car il existe une jointure entre ces deux tables :

Snap8.jpg

Cliquez sur le bouton "Next". Maintenant on va vous proposer de changer le nom des classes entités correspondants à chacune des tables. Je conseille de laisser tout par défaut, cependant je conseille aussi de faire générer ces classes dans un package "entities" pour mieux structurer le projet :

Snap9.jpg

Cliquez sur le bouton "Next". L'étape suivante propose de modifier les valeurs par défaut pour les types de données correspondant à la relation 1-N entre les deux tables :

Snap10.jpg

Laissez tout par défaut et cliquez sur le bouton Finish. Netbeans va un peu travailler puis vous verrez trois nouvelles choses dans le projet :

  1. Les classes correspondant aux deux tables, dans le package "entities", regardez donc à quoi elles ressemblent...
  2. Un fichier persistence.xml sous "configuration", qui correspond à la définition d'une "persistence unit" par défaut. En réalité la persistence unit est l'objet qui va s'occuper de manipuler les données relationnelles à travers les classes et instances des entités. C'est la persistence unit qui connait la base, qui génère le SQL, qui va syncrhroniser les objets en mémoire et les données dans les tables, etc.

Double cliquez sur le fichier persistence.xml, vous devriez voir un formulaire de configuration (une "vue" sur le fichier xml, en réalité) :

Snap12.jpg

Sans entrer dans les détails, nous voyons ici que :

  1. On autorise pas la création de nouvelles tables ni la suppression (None)
  2. On va utiliser le gestionnaire de transaction (Java Transaction API) lors de l'accès aux données,
  3. Cette persistence Unit va gérer les accès BD pour toutes les classes entités du projet.

Création d'un Stateless Session Bean CustomerManager pour la gestion des clients

On va centraliser la gestion des Customers (clients, en français) dans un Stateless Session Bean. De manière classique on crée une "façade" pour les opérations élémentaires sur les clients : création, suppression, recherche, modification.

Faire clic droit New/Session Bean sur le projet EJB (celui précédé par un grain de café) :

Snap13.jpg

Donnez un nom à votre gestionnaire de client, et indiquez que vous le voulez dans le package "sessions" qui contiendra les session beans. On ne va pas préciser d'interface (locale ou remote) pour le moment (on verra plus tard à quoi cela peut servir) :

Snap14.jpg

Ajout de méthodes métier dans le session bean CustomerManager

Double cliquez sur "CustomerManager.java" dans le projet pour voir le source dans l'éditeur. Cliquez ensuite dans le source de la classe, entre les { et } de la classe et faites clic droit/insert code. Une fenêtre propose alors diverses options, choisissez "add business method" :

Snap15.jpg

Un menu apparait proposant diverses options de génération automatique de code, choisir "business méthod" puis créez une méthode getAllCustomers() qui renvoie une collection de Customer. Cette méthode servira à afficher la liste des clients :

Snap16.jpg

Snap17.jpg

Dans le source, ajoutez les imports qui vont bien pour ne plus avoir d'erreurs (en cliquand sur la petite lampe jaune ou en faisant clic droit/fix imports). Ajoutez de la même manière une méthode "update" qui prend en paramètre un Customer. Cette méthode servira à mettre à jour un client.

Snap18.jpg

Vous devriez avoir un code source comme celui-ci :

package session;

import entities.Customer;
import java.util.Collection;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;

@Stateless
@LocalBean
public class CustomerManager {

    public List<Customer> getAllCustomers() {
        return null;
    }

    public Customer update(Customer customer) {
        return null;
    }
    
}

Bien sûr vous auriez pu aussi bien taper les méthodes à la main... C'est juste pour vous montrer quelques trucs avec netbeans...

Maitenant nous allons modifier le contenu de ces méthodes. Les explications sont dans le cours. On va commencer par ajouter un "PersistenceContext" à l'application, c'est une variable qui correspond à la persistence unit et qui va nous servir à envoyer des requêtes, insérer/supprimer des objets ou faire des mises à jour. Faites clic droit dans le source/insert code/Use Entity Manager. Cela va ajouter automatiquement dans le code une variable + une annotation :

@PersistenceContext(unitName = "TP1CustomerApplication-ejbPU")
 private EntityManager em;

La variable em n'a pas besoin d'être initialisée, en réalité son code d'initialisation va être "injecté" par l'annotation. Regardez le nom "TP1CustomerManager-ejbPU", on le retrouve dans le fichier persistence.xml, en gros, ce que l'on doit comprendre par ces deux lignes :

  1. La variable em correspond à une persistence unit (celle dont le nom est précisé, qui gère la base jdbc/sample),
  2. Je n'ai pas besoin de l'initialiser.

On va maintenant modifier le code des deux méthodes pour qu'elles jouent leur rôle :

package session;

import entities.Customer;
import java.util.Collection;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
@LocalBean
public class CustomerManager {

    @PersistenceContext(unitName = "TP1CustomerApplication-ejbPU")
    private EntityManager em;

    public List<Customer> getAllCustomers() {
        Query query = em.createNamedQuery("Customer.findAll");
        return query.getResultList();
    }

    public Customer update(Customer customer) {
        return em.merge(customer);
    }

    public void persist(Object object) {
        em.persist(object);
    }
}

Voilà, on a rempli les deux méthodes avec pour la première un ocde qui va exécuter une requête JPQL dont le nom est "findAll" et qui est définie dans la classe Customer.java (allez voir, la requête correspond à un select * sur les clients), la seconde, la méthode update indique que l'on souhaite modifier un client dans la table avec les valeurs de celui qui est passé en paramètre. Cela suppose que l'attribut id du client passé en paramètre correspond à une clé primaire dans la table des clients.

Normalement vous devez ajouter un import pour enlever l'erreur de syntaxe sur le type Query. Attention, un piège classique est d'ajouter le mauvais import. Faites clic droit/fix import :

Snap19.jpg

Voilà, on a terminé pour la partie "métier"/EJB dans ce projet. On va passer au front-end web.

Création de la partie front-end web

Dans cette partie on va utiliser le framework MVC de java EE6 qui se base sur l'utilisation de backing beans et de pages JSF2.

Ajout du Framework Java Server Faces (JSF2) au projet web

Faites clic droit/properties sur le projet web (celui proposé par un globe terrestre bleu) et ajoutez le framework Java Server Faces au projet :

Snap20.jpg

Vérifiez bien que JSF 2.0 est bien le choix par défaut puis cliquez OK :

Snap21.jpg

Création d'un Bean Managé en tant que "contrôleur web"

Faites sur le projet web clic droit/new/other et choisissez dans la catégorie "java server faces", "jsf managed bean" :

Snap22.jpg

Ensuite, renseignez le nom de la classe que vous souhaitez ainsi que le package où va se trouver la classe générée :

Snap23.jpg

Si tout va bien vous devriez obtenir une classe CustomerMBean.java dans le package "managedbeans", et le source devrait être celui-ci :

package managedbeans;

import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;

@Named(value = "customerMBean")
@SessionScoped
public class CustomerMBean implements Serializable {

    public CustomerMBean() {
    }
}

Notez l'annotation @SessionScoped qui indique clairement que les instances de cette classe seront placées dans la session HTTP. Si vous n'avez pas cette annotation, c'est que vous avez dû oublier de la spécifier dans le wizard de génération, ce n'est pas grave, vous pouvez la rajouter à la main ou utiliser la complétion automatique (ctrl-espace). Nous allons maintenant rajouter de la fonctionnalité dans ce bean :

  1. Le préparer pour qu'il puisse communiquer avec le session bean stateless de l'autre projet (celui avec les EJBs) : CustomerManager,
  2. Ajouter un attribut correspondant à un client dans la session, utile pour afficher dans une page web les détails d'un clients, ou encore lors de la mise à jour d'un client à partir d'un formulaire,
  3. Ajouter des méthodes de contrôle qui seront appelées depuis une page web (une page JSF en fait).

Pour que le bean soit client d'un EJB, faites dans le code clic droit/insert code/call enterprise bean, et sélectionnez l'EJB CustomerManager, ceci devrait insérer dans le source les lignes suivantes :

@EJB
  private CustomerManager customerManager;

Là aussi, l'annotation @EJB injectera le code d'initialisation, vous n'avez pas à faire de "new" (surtout pas !) sur des session beans. Voir le cours pour plus de détails sur ce mécanisme.

Maitenant, complétons  le code du bean de manière à ce qu'il ressemble à ceci (on a rajouté des properties et action handlers utilisés par les pages JSF par la suite, vous allez comprendre) :

package managedbeans;

import entities.Customer;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
import java.util.Collection;
import session.CustomerManager;

@Named(value = "customerMBean")
@SessionScoped
public class CustomerMBean implements Serializable {

    @EJB
    private CustomerManager customerManager;
    /* Client courant dans la session, utilisé pour afficher ses détails ou
     * pour faire une mise à jour du client modifié dans la base */
    private Customer customer;

    public CustomerMBean() {
    }

    /**
     * Renvoie la liste des clients pour affichage dans une DataTable
     * @return
     */
    public Collection getCustomers() {
        return customerManager.getAllCustomers();
    }

    /**
     * Renvoie les détails du client courant (celui dans l'attribut customer de
     * cette classe), qu'on appelle une propriété (property)
     * @return
     */
    public Customer getDetails() {
        return customer;
    }

    /**
     * Action handler - appelé lorsque l'utilisateur sélectionne une ligne dans
     * la DataTable pour voir les détails
     * @param customer
     * @return
     */
    public String showDetails(Customer customer) {
        this.customer = customer;
        return "DETAILS";
    }

    /**
     * Action handler - met à jour la base de données en fonction du client
     * passé en paramètres
     * @return
     */
    public String update() {
        System.out.println("###UPDATE###");
        customer = customerManager.update(customer);
        return "SAVED";
    }

    /**
     * Action handler - renvoie vers la page qui affiche la liste des clients
     * @return
     */
    public String list() {
        System.out.println("###LIST###");
        return "LIST";
    }
}

Ajout d'une page JSF pour afficher la liste des clients

Pour ajouter une page JSF, faire sur le projet web clic droit/new/other/Java Server Faces et choisir "JSF Page" :

Snap24.jpg 

Puis faites "next" et renseignez le nom de la page :

Snap25.jpg

Notez que cela joute un fichier CustomerList.xhtml dans le projet, à côté de la page index.jsp. Double cliquez pour voir le source. Modifiez le Title pour mettre "Liste des clients", vous pouvez rajouter un titre HTML <h1>liste des clients </h1> dans le <h:body>...</h:body> etc.

Ajout d'une DataTable JSF dans la page

Faites apparaitre la Palette dans netbeans (menu Window/Palette ou raccourci ctrl-shift-8). Ouvrez la partie JSF puis faite un drag'n'drop de JSF Data Table from Entity, en dessous du titre <h1>...

Snap26.jpg

Une fenêtre de dialogue va appraitre demandant de préciser pour quelle classe entité vous voulez une Data Table, indiquez la classe entité Customer, puis comme property indiquez le nom du managed bean pour les clients suivi du nom de la propriété correspondant à une liste de clients. Dans notre cas, il faut mettre customerMbean.customers.

Pourquoi ? Car lorsque dans une JSP ou dans une page JSF on référencera une "propriété" le lecture, cela appellera la méthode get correspondante. Par exemple, si on référence la propriété "customers" du managed bean customerMBean, cela ne fonctionnera que si il existe une méthode getCustomers() qui renvoie une Collection de Customer (en effet, la Data Table gère des Collection). Il n'est pas nécessaire qu'une variable "customers" existe dans le bean, il suffit que la méthode Collection<Customer> getCustomers() existe. La preuve :

 

@Named(value = "customerMBean")
@SessionScoped
public class CustomerMBean implements Serializable {
...
public Collection getCustomers() {
    return customerManager.getAllCustomers();
}

Voici donc la fenêtre avec les bonnees valeurs :

 Snap27.jpg

 Ceci devrait insérer de nombreuses lignes dans la page JSF. Avec netbeans 7.0.1 il faut rajouter aussi le namespace

xmlns:f="http://java.sun.com/jsf/core"

Pour ne pas qu'il y ait d'erreurs dans la page...

Exécution du projet et premier test

Pour exécuter le projet, faites clic droit sur le projet en forme de triangle (le projet "enterprise") et Run. Une page va s'afficher, modifiez l'URL pour afficher la bonne page :

  1. Ajoutez "faces" dans l'URL,
  2. Indiquez le nom de la page JSF avec son suffixe .xhtml

Vous devriez obtenir le résultat suivant :

Snap28.jpg

Pour le moment ce tableau n'est pas très bien présenté car il n'y a aucune fioriture de mise en page. Les données proviennent de la base jdbc/sample. Vous pouvez vérifier que ces données sont les bonnes, allez dans l'onglet "Services" de netbeans qui comprend un gestionnaire de base de données assez simple, mais très pratique. Ouvrez la vue sur les tables de la base jdbc/sample et faites clic droit/view data :

Snap29.jpg

Vous pouvez vérifier que ce sont bien les mêmes données qui ont été affichées dans la page JSF.

Maintenant on va modifier l'affichage du discountCode qui est une relation, en effet, voir "entities.DiscountCode[discountCode=M]" n'est pas très satisfaisant. Si vous avez compris le concept de "propriétés", vous pouvez modifier la ligne qui affiche le code. remplacez :

<h:outputText value="#{item.discountCode}"/>

par :

<h:outputText value="#{item.discountCode.discountCode} : #{item.discountCode.rate}%" />

Ce qui aura pour effet de remplacer la première expression par le résultat de l'appel de la méthode getDiscountCode de la classe DiscountCode.java, et la seconde par la valeur renvoyée par getRate() de cette même classe. L'affichage sera bien meilleur :

Snap37.jpg

Etudiez maintenant le code de la page JSF, du managed bean, du session bean de l'entity bean, et essayez de retrouver le workflow présenté dans la toute première illustration de cette page.

Remplacement de la DataTable par une provenant de la librairie de composants JSF PrimeFaces

Ajout de la librairie PrimeFaces dans le projet

PrimeFaces propose des composants évolués/complémentaires pour JSF2, le site web de référence est : http://www.primefaces.org

Cette librairie est déjà présente dans netbeans, il suffit de l'ajouter au projet pour pouvoir l'utiliser. Pour cela, faite sur le projet war (celui précédé d'une icône en forme de globe terrestre) clic droit/properties, puis allez dans "libraries" et ajoutez la librairie PrimeFaces 2.2.1 :

Snap30.jpg

Modification de la page JSF

Pour pouvoir utiliser des tags provenant de PrimeFaces dans une page JSF il faut ajouter le namespace suivant :

xmlns:p="http://primefaces.prime.com.tr/ui"

A partier de là on pourra utiliser des tags PrimeFaces avec le préfixe p:

  • Remplacez simplement tous les tags <h:dataTable> et </h:dataTable> par <p:dataTable> et </p:dataTable>
  • Idem pour les tags <h:column> et </h:column>, remplacez les h: par p:

Déployez le projet (clic droit sur le projet avec le triangle/deploy) puis exécutez à nouveau la page qui affiche la liste des clients. Il est nécessaire de déployer car il faut que la librairie que nous venons d'ajouter au projet soit déployée dans le serveur. Il suffit de le faire une fois pour le projet, ensuite le déploiement incrémental (avec save ou ctrl-s) suffira. Vous devriez obtenir le résultat suivant :

Snap31.jpg

C'est déjà mieux présenté non ? Bon, ce qui est intéressant, c'est que le composant dataTable de PrimeFaces possède de nombreuses options pour la pagination, rendre les colonnes triables, éditables, etc. Allez donc voir les démos/sources sur : http://www.primefaces.org/showcase/u...atableHome.jsf

  1. Par exemple, ajoutez dans le tag <p:dataTable ...> les attributs paginator="true" et rows="10", sauvez, rechargez la page, ça y est, les résultats sont paginés (en mémoire, nous verrons plus tard comment faire des requêtes paginées sur la BD),
  2. Ensuite, regardez les démos et faites en sorte que les colonnes soient triables,
  3. Enfin, regardez http://www.primefaces.org/showcase/u...eFiltering.jsf et installez quelques filtres et champs de recherche sur la table.

Affichage des détails d'un client lorsqu'on clique sur une ligne

Maintenant nous allons voir comment afficher dans une autre page les détails d'un client lorsqu'on clique sur une ligne.

Ajout d'un lien dans le tableau pour déclencher l'affichage des détails d'un client

 Modifiez la page CustomerList.xhtml de manière à ce que lorsqu'on clique sur la colonne Id on affiche le détail d'un client, comme à la ligne 7 du listing ci-dessous.

<p:dataTable var="item" value="#{customerMBean.customers}" id="customerList"
                         paginator="true" rows="10">
    <p:column>
        <f:facet name="header">
            <h:outputText value="Customer ID" />
        </f:facet>
        <h:commandLink action="#{customerMBean.showDetails(item)}" value="#{item.customerId}"/>
    </p:column>
...

la ligne ajoutée indique que lorsqu'on clique sur la colonne, on appelle la méthode showDetails du managedBean (n'oubliez pas : le managed bean est dans la session), et que l'on affiche dans le texte du lien l'id du client (attribut "value=..."). Dans ce cas, lorqu'on clique sur la colonne, on appelle la méthode d'instance showDetails avec en paramètre l'objet item qui correspond au client de la ligne courante. Si vous regardez le source de la méthode showDetails de CustomerMBean.java :

/**
     * Action handler - appelé lorsque l'utilisateur sélectionne une ligne dans
     * la DataTable pour voir les détails
     * @param customer
     * @return
     */
    public String showDetails(Customer customer) {
        this.customer = customer;
        return "DETAILS";
    }

vous voyez que le paramètre attendu est un client, et que cette méthode fait deux choses :

  1. Elle initialise la variable customer (qui est dans la session, remplie maintenant par le paramètre passé),
  2. Elle renvoie vers la page DETAILS, qui pourra accèder à la variable de session "customer" pour en afficher les détails.

Si vous sauvegardez la page JSF et l'exécutez, vous verrez que la colonne id contient maintenant un lien hypertexte. Pour le moment il ne mène à rien car la page qui affiche les détails n'existe pas encore.

Ajout d'une page JSF pour afficher les détails d'un client

  1. Faites sur le projet web clic droit/new/jsf page et créez une page CustomerDetails.xhtml,
  2. Drag and droppez dans le body de la page "jsf form from entity".

Snap32.jpg

Indiquez ensuite le nom de la classe entité dont vous voulez afficher les informations dans un formulaire, ainsi que le nom de la méthode du managedBean qui renvoie les détails d'un client :

Snap33.jpg

Ici on a mis customerMBean.details qui signifie que c'est la méthode getDetails() qui sera appelée. On aurait pu aussi mettre customerMBean.customer qui aurait appelé getCustomer(), qui fait la même chose.

Une fois qu'on a validé, la page JSF se remplit avec des lignes qui sont en fait un formulaire qui sera pré-rempli dès l'affichage si il existe un objet "customer" dans la session (ce qui sera le cas, voir ce que nous avons expliqué dans la section précédente).

Il est encore un peu tôt pour que tout fonctionne. Il reste maintenant à spécifier la navigation entre la page qui affiche la liste des clients et la page qui affiche le détail d'un client.

Specifier la navigation entre pages JSF2 : modifier le fichier faces-config.xml

Le projet ne contenant pas de fichier faces-config.xml, nous allons en générer un. Pour cela, dans le projet web (avec le globe terrestre), faire clic droit/new/java server faces et sélectionner "JSF configuration file" :

Snap34.jpg

cela va ajouter le ficher faces-config.xml dans le projet, sois "configuration". Double cliquez dessus et vous devriez avoir une vue de toutes les pages web du projet :

Snap35.jpg

Profitez-en pour supprimer les pages qui ne servent à rien (dans mon exemple, index.xhtml, newjsf.xhtml, index.jsp). Selectionnez-les et clic droit/delete.

Maintenant :

  1. positionnez le page CustomerList.xhtml au-dessus de CustomerDetails.xhtml puis cliquez sur le petit carré gris tout à droite du rectangle de CustomerList.xhtml et tirez vers le rectangle CustomerDetails.xhtml,
  2. Faites de même dans l'autre sens. Vous verrez deux labels appraître sur les traits du diagramme,
  3. Double cliquez dessus et éditez ces labels de manière à ce que le label qui va de la page d'affichage de la liste à celle qui affiche le détail soit égale à "DETAILS",
  4. Celui qui va de la page de détails à la page de la liste soit "LIST".

Vous devriez obtenir cela :

Snap36.jpg

Les labels "DETAILS" et "LIST" sont exactement les valeurs de retour des méthodes showDetails() et list() du managed bean CustomerMBean. Lorsqu'une de ces méthodes de contrôle (on les appelle des "Action Handlers" est invoquée, la valeur de retour indique le nom de la page à afficher ensuite. Dans la liste, lorsqu'on clique sur un client, on appelle la méthode showDetails qui initialise la variable de session "customer" puis on renvoie "DETAILS", ce qui va invoquer la page d'affichage des détails.

Executez le projet et testez que les détails s'affichent bien

Maintenant vous pouvez exécuter le projet et voir si lorsque vous cliquez sur le lien Id dans la liste ça affiche bien le détail d'un client. Vous devriez obtenir ceci pour le client d'id=1 :

Snap38.jpg

Voilà, les détails du client numéro 1 s'affichent bien, cependant le code de discount pour ce client n'est pas correctement affiché.

Nous allons voir maintenant comment arranger ce petit problème, comment ajouter des boutons de navigation et comment tant qu'à faire permettre une modification des données.

Règler l'affichage des DiscountCodes dans le détail d'un client

Dans la base de données la table des DISCOUNT_CODE contient juste 4 valeurs correspondants aux quatre types de réductions possibles. Nous allons simplement rajouter une méthode getDiscountCodes() dans le bean managé CustomerMBean, qui renverra la liste des codes possibles.

On va commencer par rajouter au projet EJB un session bean stateless dans lequel on ajoutera une seule méthode List<DiscountCode> getAllDiscountCodes(). Inspirez-vous du gestionnaire de client et de la méthode qui renvoit tous les clients. Plutot que copier/coller le code ci-dessous, essayez de refaire les étapes que nous avions faites lors de l'écriture du gestionnaire de clients (avec insert code/call enterprise bean, insert code/user Entity Manager etc...)

A la fin, vous devriez avoir un code comme ceci :

package session;

import entities.DiscountCode;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

@Stateless
@LocalBean
public class DiscountCodeManager {

    @PersistenceContext(unitName = "TP1CustomerApplication-ejbPU")
    private EntityManager em;

    public List<DiscountCode> getDiscountCodes() {
        Query query = em.createNamedQuery("DiscountCode.findAll");
        return query.getResultList();
    }

    public void persist(Object object) {
        em.persist(object);
    }
}

Donc, maintenant on a un EJB qui renvoit toutes les valeurs possibles pour un DiscountCode. Attention, c'est une collection de DiscountCode !

On va ajouter dans le managed bean du projet web une méthode utilitaire qui servira à remplir le menu déroulant de la page JSF CustomerDetails.xhtml. Pour cela, commencez par indiquer au bean qu'il va appeler un EJB : DiscountCodeManager, celui que nous venons d'écrire. Procédez comme dans le début du TP (insert code/call enterprise bean), puis insérez dans le bean la méthode suivante :

/**
     * renvoie un tableau de SelectItem pour affichage dans le menu déroulant
     * de la page des détails d'un client
     * @return
     */
    public javax.faces.model.SelectItem[] getDiscountCodes() {
        SelectItem[] options = null;
        List<DiscountCode> discountCodes = discountCodeManager.getDiscountCodes();

        if (discountCodes != null && discountCodes.size() > 0) {
            int i = 0;
            options = new SelectItem[discountCodes.size()];
            for (DiscountCode dc : discountCodes) {
                options[i++] = new SelectItem(dc.getDiscountCode(),
                        dc.getDiscountCode() + " (" + dc.getRate() + "%)");
            }
        }
        return options;
    }

Si vous regardez ce code, il se contente de récupérer la liste des codes et de la transformer en tableau de SelectItems qui est une classe JSF utilisée par le composant <f:selecteditems ... /> qui indique le contenu du menu <h:selectOneMenu.../> situé dans la page CustomerDetail.xhtml. Chaque SelectItem prends en paramètre lors de la construction un objet et une string qui le décrit.

On va pouvoir maintenant modifier la partie "menu" de la page CustomerDetails.xhtml afin que la méthode que nous venons d'écrire soit appelée. Elle s'appelle getDiscountCodes() et correspond à une propriété discountCodes que nous allons manipuler. Voici le code qu'il faut modifier dans CustomerDetails.xhtml :

<h:selectOneMenu id="discountCode" value="#{customerMBean.details.discountCode.discountCode}" 
                 title="DiscountCode" required="true" requiredMessage="The DiscountCode field is required.">
     <f:selectItems value="#{customerMBean.discountCodes}"/>
</h:selectOneMenu>

Remarquons plusieurs choses :

  1. Le contenu du menu est invoqué par value="#{customerMBean.discountCodes}", cette expression (il s'agit du langage EL de JSF) correspond à un appel à getDscountCodes() sur le bean de nom customerMBean, c'est la méthode que l'on vient d'écrire,
  2. La valeur par défaut affichée est celle du client dont on consulte les détails, elle est donnée par l'attribut "value=#customerMBean.details.discountCode.discountCode", hmmmmm décryptons cette expression : details correspond à l'appel de getDetails() du bean CustomerMBean, qui renvoie le client courant (dans la session). discountCode (le premier) correspond à l'appel de getDiscountCode sur le client (qui est de type Customer), cette méthode dans la classe Customer.java renvoie une instance de DiscountCode, le dernier "discountCode" correspond à l'appel de getDicountCode() sur l'instance de DiscountCode renvoyée précédemment, qui elle, renvoie un Character. On a besoin d'une String, la conversion est implicite, ça marche !

Voilà, sauvegardez, testez, ça doit fonctionner  :

Snap39.jpg