Mis à jour le vendredi 10 mars 2017
  • 40 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

TP Fil rouge - Étape 7

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Septième et dernière étape du fil rouge : vous allez pouvoir pratiquer JPA dans un contexte un peu plus compliqué que celui du cours, car faisant intervenir un modèle de données plus complexe à appréhender. Vous en profiterez également pour travailler avec vos nouveaux outils : GlassFish et BoneCP !

Objectifs

Fonctionnalités

Vous êtes équipés pour refondre votre TP en utilisant JPA. Vous allez devoir :

  • migrer votre projet sur GlassFish ;

  • mettre en place un pool de connexions pour votre projet avec BoneCP ;

  • reprendre le code existant pour y mettre en place les annotations, configurations et classes nécessaires au bon fonctionnement de JPA ;

  • supprimer les classes devenues obsolètes.

En apparence, tout cela paraît bien léger, mais vous allez faire face à quelques petits obstacles qui vous obligeront à chercher, et à assimiler un peu mieux encore comment fonctionne JPA. Bon courage, et ne flanchez pas dans cette dernière ligne droite ! :)

Conseils

Environnement de développement

Contexte du projet

Vous allez pouvoir réutiliser le serveur GlassFish mis en place dans le cadre du cours pour déployer votre projet. Faites une duplication de la correction du projet telle qu'elle était à l'issue de l'étape 6, afin de conserver des sources "propres". Pour migrer le projet vers votre nouveau serveur, vous devrez ensuite vous rendre dans le build-path du projet dupliqué, et modifier les points suivants :

  • dans l'onglet "Libraries", remplacez celle qui mentionne Tomcat dans la liste affichée par celle de GlassFish en suivant Add Library > Server Runtime, puis en choisissant votre instance de GlassFish ;

  • l'entrée intitulée "Targeted runtimes" dans les propriétés de votre projet ;

  • et enfin l'entrée intitulée "Server" toujours dans les propriétés de votre projet.

Configuration du pool

Vous profiterez de l'occasion pour vous exercer à mettre en place un pool de connexions BoneCP sous GlassFish et à l'utiliser. Pour cela, vous pouvez essayer de modifier le fichier XML que je vous avais préparé dans le cadre du cours, afin qu'il cible cette fois, non plus la base bdd_sdzee, mais votre base tp_sdzee. Sinon, je vous ai préparé un fichier prêt à l'emploi que vous pouvez télécharger en cliquant ici.

Pour le reste de la manipulation, vous pouvez vous reporter au cours si vous ne vous souvenez plus comment faire.

Configuration de l'application

Une fois le contexte en place, vous devrez ensuite créer un fichier WEB-INF/glassfish-web.xml, et un fichier src/META-INF/persistence.xml, comme nous l'avons fait dans le cours. Reportez-vous au chapitre précédent si vous avez des doutes.

Reprise du code existant

Suppression des objets devenus obsolètes

Vous allez pouvoir dès à présent vous débarrasser de la DAOFactory, du Listener l’initialisant et des utilitaires DAO.

Transformation des JavaBeans en EJB Entity

Vous allez ensuite devoir insérer des annotations JPA dans vos beans, comme nous l'avons fait dans le cours.

Toutefois, vous allez vite vous rendre compte qu'un champ est différent des autres : celui qui correspond à une clé étrangère dans la table Commande. Pour le gérer correctement, il y a une particularité à prendre en compte : il va falloir préciser au conteneur quel type de relation existe entre le champ et la table ciblée par la clé. Vous pouvez chercher par vous-mêmes si vous vous en sentez capables, ou suivre les indications suivantes sinon.

Il existe plusieurs types de relation entre des champs de tables relationnelles : un à un, un à plusieurs, ou plusieurs à un. Ceci relevant du design de la BDD et clairement pas du développement Java EE, je ne vous demande pas de comprendre exactement de quoi il est question ici. En l'occurrence, dans notre TP nous avons affaire à une relation de type plusieurs à un, car plusieurs entrées de la table Commande peuvent pointer vers un même Client. Il faut donc utiliser une annotation JPA prévue à cet effet, nommée @ManyToOne. En complément, il faut également expliciter le champ de la table visé par la clé étrangère, via l'annotation nommée @JoinColumn( name = "..." ).

Autre petite difficulté, le fait que nous utilisions un champ de type DateTime dans notre entité Commande. Les développeurs de la bibliothèque JodaTime ont pris la peine d'écrire une classe pour convertir un tel objet vers une date utilisable par le frameworkHibernate, une solution ORM très répandue. Cependant ils n'ont pas fait le travail pour EclipseLink, qui est pourtant le framework utilisé par défaut par GlassFish. Ainsi, vous allez devoir vous occuper vous-mêmes de la conversion... Pas d'inquiétude cependant, car ce petit développement n'est pas l'objectif de ce TP. Ainsi, je vous propose une classe prête à l'emploi, réalisée par Xandacona et fournie sur cette page de son blog :

package com.sdzee.tp.tools;

import java.sql.Timestamp;

import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;

public class JodaDateTimeConverter implements Converter {

    private static final long serialVersionUID = 1L;

    @Override
    public Object convertDataValueToObjectValue( Object dataValue, Session session ) {
        return dataValue == null ? null : new DateTime( (Timestamp) dataValue );
    }

    @Override
    public Object convertObjectValueToDataValue( Object objectValue, Session session ) {
        return objectValue == null ? null : new Timestamp( ( (DateTime) objectValue ).getMillis() );
    }

    @Override
    public void initialize( DatabaseMapping mapping, Session session ) {
    }

    @Override
    public boolean isMutable() {
        return false;
    }

}

Déposez cette classe nommée JodaDateTimeConverter dans un nouveau package intitulé com.sdzee.tp.tools, et vous pourrez ensuite à l'aide des annotations @Converter et @Convert préciser à votre conteneur comment effectuer la conversion de manière automatisée. Là encore, pas de panique, je vous montre comment annoter l'attribut date dans votre entité Commande :

@Column( columnDefinition = "TIMESTAMP" )
@Converter( name = "dateTimeConverter", converterClass = JodaDateTimeConverter.class )
@Convert( "dateTimeConverter" )
private DateTime date;
Transformation des DAO en EJB Session Stateless

Annotations, EntityManager et méthodes d'accès à la BDD sont au programme. Cette fois par contre, à la différence du cours, puisqu'aucune requête de sélection complexe n'est réalisée, nous avons uniquement besoin de recherches basées sur les clés primaires de nos tables (leurs id). Ainsi, il ne vous sera pas nécessaire d'écrire des requêtes JPQL à la main, vous pourrez utiliser simplement la méthode find() de votre EntityManager. Vous voilà enfin débarrassés du SQL ! ;)

Modification des servlets

Dans les servlets qui manipulaient un DAO auparavant (pour la création de clients et de commandes), vous allez devoir supprimer les références aux anciens composants (DAOFactory), injecter vos EJB Stateless et les transmettre aux objets métier en lieu et place des DAO.

Annotation des servlets et des filtres

Vous allez pouvoir reprendre toutes les servlets et les annoter avec @WebServlet. De même pour le filtre de préchargement avec @WebFilter. Enfin, n'oubliez pas les servlets nécessitant une configuration multipart ou l'ajout de paramètres d'initialisation, avec les annotations @MultipartConfig et @WebInitParam.

Une fois toutes ces modifications apportées, vous pourrez vous débarrasser du fichier web.xml !

Modification des objets métier

Lorsque nous avons utilisé pour la première fois une requête de type MultiPart, je vous avais averti que GlassFish ne respectait pas correctement la spécification Java EE, et ne permettait pas d'appeler directement la méthode request.getParameter() pour obtenir un paramètre classique contenu dans une requête de type MultiPart. Heureusement pour nous, la dernière version du serveur - la 3.1.2.2 que je vous ai fait télécharger dans le chapitre précédent - a corrigé ce léger souci, et il est désormais possible de réaliser ce genre d'appels.

Autrement dit, vous n'avez rien à changer dans vos objets métier ! :)

Correction

Faites attention à bien reprendre les fichiers du cours qui restent inchangés, à corriger les classes qui nécessitent des ajustements, et à supprimer celles qui ne vous servent plus à rien. Et surtout ne baissez pas les bras devant la charge de travail impliquée par l'intégration de JPA dans votre application ! Cet exercice est l'occasion parfaite pour vous familiariser avec les bases de ces concepts avancés !

Une fois n'est pas coutume, ce n'est pas la seule manière de faire, le principal est que votre solution respecte les consignes que je vous ai données !

Le code de configuration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>
  <context-root>/tp7</context-root>
  <class-loader delegate="true"/>
  <jsp-config>
    <property name="keepgenerated" value="true">
      <description>Keep a copy of the generated servlet class java code.</description>
    </property>
  </jsp-config>
</glassfish-web-app>
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  	<persistence-unit name="tp_sdzee_PU" transaction-type="JTA">
  		<jta-data-source>jdbc/bonecp_resource_tp</jta-data-source>
		<class>com.sdzee.entities.Client</class>
		<class>com.sdzee.entities.Commande</class>
		<properties/>		
	</persistence-unit>   
</persistence>

Le code des EJB Entity

package com.sdzee.tp.entities;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Client implements Serializable {

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long   id;
    private String nom;
    private String prenom;
    private String adresse;
    private String telephone;
    private String email;
    private String image;

    public void setId( Long id ) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setNom( String nom ) {
        this.nom = nom;
    }

    public String getNom() {
        return nom;
    }

    public void setPrenom( String prenom ) {
        this.prenom = prenom;
    }

    public String getPrenom() {
        return prenom;
    }

    public void setAdresse( String adresse ) {
        this.adresse = adresse;
    }

    public String getAdresse() {
        return adresse;
    }

    public void setTelephone( String telephone ) {
        this.telephone = telephone;
    }

    public String getTelephone() {
        return telephone;
    }

    public void setEmail( String email ) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    public void setImage( String image ) {
        this.image = image;
    }

    public String getImage() {
        return image;
    }
}
package com.sdzee.tp.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import org.joda.time.DateTime;

import com.sdzee.tp.tools.JodaDateTimeConverter;

@Entity
public class Commande implements Serializable {
    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long     id;
    @ManyToOne
    @JoinColumn( name = "id_client" )
    private Client   client;
    @Column( columnDefinition = "TIMESTAMP" )
    @Converter( name = "dateTimeConverter", converterClass = JodaDateTimeConverter.class )
    @Convert( "dateTimeConverter" )
    private DateTime date;
    private Double   montant;
    @Column( name = "mode_paiement" )
    private String   modePaiement;
    @Column( name = "statut_paiement" )
    private String   statutPaiement;
    @Column( name = "mode_livraison" )
    private String   modeLivraison;
    @Column( name = "statut_livraison" )
    private String   statutLivraison;

    public Long getId() {
        return id;
    }

    public void setId( Long id ) {
        this.id = id;
    }

    public Client getClient() {
        return client;
    }

    public void setClient( Client client ) {
        this.client = client;
    }

    public DateTime getDate() {
        return date;
    }

    public void setDate( DateTime date ) {
        this.date = date;
    }

    public Double getMontant() {
        return montant;
    }

    public void setMontant( Double montant ) {
        this.montant = montant;
    }

    public String getModePaiement() {
        return modePaiement;
    }

    public void setModePaiement( String modePaiement ) {
        this.modePaiement = modePaiement;
    }

    public String getStatutPaiement() {
        return statutPaiement;
    }

    public void setStatutPaiement( String statutPaiement ) {
        this.statutPaiement = statutPaiement;
    }

    public String getModeLivraison() {
        return modeLivraison;
    }

    public void setModeLivraison( String modeLivraison ) {
        this.modeLivraison = modeLivraison;
    }

    public String getStatutLivraison() {
        return statutLivraison;
    }

    public void setStatutLivraison( String statutLivraison ) {
        this.statutLivraison = statutLivraison;
    }
}

Le code des EJB Session

package com.sdzee.tp.dao;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import com.sdzee.tp.entities.Client;

@Stateless
public class ClientDao {

    // Injection du manager, qui s'occupe de la connexion avec la BDD
    @PersistenceContext( unitName = "tp_sdzee_PU" )
    private EntityManager em;

    public Client trouver( long id ) throws DAOException {
        try {
            return em.find( Client.class, id );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public void creer( Client client ) throws DAOException {
        try {
            em.persist( client );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public List<Client> lister() throws DAOException {
        try {
            TypedQuery<Client> query = em.createQuery( "SELECT c FROM Client c ORDER BY c.id", Client.class );
            return query.getResultList();
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public void supprimer( Client client ) throws DAOException {
        try {
            em.remove( em.merge( client ) );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }
}
package com.sdzee.tp.dao;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import com.sdzee.tp.entities.Commande;

@Stateless
public class CommandeDao {

    // Injection du manager, qui s'occupe de la connexion avec la BDD
    @PersistenceContext( unitName = "tp_sdzee_PU" )
    private EntityManager em;

    public Commande trouver( long id ) throws DAOException {
        try {
            return em.find( Commande.class, id );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public void creer( Commande commande ) throws DAOException {
        try {
            em.persist( commande );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public List<Commande> lister() throws DAOException {
        try {
            TypedQuery<Commande> query = em.createQuery( "SELECT c FROM Commande c ORDER BY c.id", Commande.class );
            return query.getResultList();
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }

    public void supprimer( Commande commande ) throws DAOException {
        try {
            em.remove( em.merge( commande ) );
        } catch ( Exception e ) {
            throw new DAOException( e );
        }
    }
}

Le code des servlets

package com.sdzee.tp.servlets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.sdzee.tp.dao.ClientDao;
import com.sdzee.tp.entities.Client;
import com.sdzee.tp.forms.CreationClientForm;

@WebServlet( urlPatterns = { "/creationClient" }, initParams = @WebInitParam( name = "chemin", value = "/fichiers/images/" ) )
@MultipartConfig( location = "/tmp", maxFileSize = 2 * 1024 * 1024, maxRequestSize = 10 * 1024 * 1024, fileSizeThreshold = 1024 * 1024 )
public class CreationClient extends HttpServlet {
    public static final String CHEMIN          = "chemin";
    public static final String ATT_CLIENT      = "client";
    public static final String ATT_FORM        = "form";
    public static final String SESSION_CLIENTS = "clients";

    public static final String VUE_SUCCES      = "/WEB-INF/afficherClient.jsp";
    public static final String VUE_FORM        = "/WEB-INF/creerClient.jsp";

    @EJB
    private ClientDao          clientDao;

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* À la réception d'une requête GET, simple affichage du formulaire */
        this.getServletContext().getRequestDispatcher( VUE_FORM ).forward( request, response );
    }

    public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /*
         * Lecture du paramètre 'chemin' passé à la servlet via la déclaration
         * dans le web.xml
         */
        String chemin = this.getServletConfig().getInitParameter( CHEMIN );

        /* Préparation de l'objet formulaire */
        CreationClientForm form = new CreationClientForm( clientDao );

        /* Traitement de la requête et récupération du bean en résultant */
        Client client = form.creerClient( request, chemin );

        /* Ajout du bean et de l'objet métier à l'objet requête */
        request.setAttribute( ATT_CLIENT, client );
        request.setAttribute( ATT_FORM, form );

        /* Si aucune erreur */
        if ( form.getErreurs().isEmpty() ) {
            /* Alors récupération de la map des clients dans la session */
            HttpSession session = request.getSession();
            Map<Long, Client> clients = (HashMap<Long, Client>) session.getAttribute( SESSION_CLIENTS );
            /* Si aucune map n'existe, alors initialisation d'une nouvelle map */
            if ( clients == null ) {
                clients = new HashMap<Long, Client>();
            }
            /* Puis ajout du client courant dans la map */
            clients.put( client.getId(), client );
            /* Et enfin (ré)enregistrement de la map en session */
            session.setAttribute( SESSION_CLIENTS, clients );

            /* Affichage de la fiche récapitulative */
            this.getServletContext().getRequestDispatcher( VUE_SUCCES ).forward( request, response );
        } else {
            /* Sinon, ré-affichage du formulaire de création avec les erreurs */
            this.getServletContext().getRequestDispatcher( VUE_FORM ).forward( request, response );
        }
    }
}
package com.sdzee.tp.servlets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.sdzee.tp.dao.ClientDao;
import com.sdzee.tp.dao.CommandeDao;
import com.sdzee.tp.entities.Client;
import com.sdzee.tp.entities.Commande;
import com.sdzee.tp.forms.CreationCommandeForm;

@WebServlet( urlPatterns = { "/creationCommande" }, initParams = @WebInitParam( name = "chemin", value = "/fichiers/images/" ) )
@MultipartConfig( location = "/tmp", maxFileSize = 2 * 1024 * 1024, maxRequestSize = 10 * 1024 * 1024, fileSizeThreshold = 1024 * 1024 )
public class CreationCommande extends HttpServlet {
    public static final String CHEMIN                = "chemin";
    public static final String ATT_COMMANDE          = "commande";
    public static final String ATT_FORM              = "form";
    public static final String SESSION_CLIENTS       = "clients";
    public static final String APPLICATION_CLIENTS   = "initClients";
    public static final String SESSION_COMMANDES     = "commandes";
    public static final String APPLICATION_COMMANDES = "initCommandes";

    public static final String VUE_SUCCES            = "/WEB-INF/afficherCommande.jsp";
    public static final String VUE_FORM              = "/WEB-INF/creerCommande.jsp";

    @EJB
    private ClientDao          clientDao;
    @EJB
    private CommandeDao        commandeDao;

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* À la réception d'une requête GET, simple affichage du formulaire */
        this.getServletContext().getRequestDispatcher( VUE_FORM ).forward( request, response );
    }

    public void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /*
         * Lecture du paramètre 'chemin' passé à la servlet via la déclaration
         * dans le web.xml
         */
        String chemin = this.getServletConfig().getInitParameter( CHEMIN );

        /* Préparation de l'objet formulaire */
        CreationCommandeForm form = new CreationCommandeForm( clientDao, commandeDao );

        /* Traitement de la requête et récupération du bean en résultant */
        Commande commande = form.creerCommande( request, chemin );

        /* Ajout du bean et de l'objet métier à l'objet requête */
        request.setAttribute( ATT_COMMANDE, commande );
        request.setAttribute( ATT_FORM, form );

        /* Si aucune erreur */
        if ( form.getErreurs().isEmpty() ) {
            /* Alors récupération de la map des clients dans la session */
            HttpSession session = request.getSession();
            Map<Long, Client> clients = (HashMap<Long, Client>) session.getAttribute( SESSION_CLIENTS );
            /* Si aucune map n'existe, alors initialisation d'une nouvelle map */
            if ( clients == null ) {
                clients = new HashMap<Long, Client>();
            }
            /* Puis ajout du client de la commande courante dans la map */
            clients.put( commande.getClient().getId(), commande.getClient() );
            /* Et enfin (ré)enregistrement de la map en session */
            session.setAttribute( SESSION_CLIENTS, clients );

            /* Ensuite récupération de la map des commandes dans la session */
            Map<Long, Commande> commandes = (HashMap<Long, Commande>) session.getAttribute( SESSION_COMMANDES );
            /* Si aucune map n'existe, alors initialisation d'une nouvelle map */
            if ( commandes == null ) {
                commandes = new HashMap<Long, Commande>();
            }
            /* Puis ajout de la commande courante dans la map */
            commandes.put( commande.getId(), commande );
            /* Et enfin (ré)enregistrement de la map en session */
            session.setAttribute( SESSION_COMMANDES, commandes );

            /* Affichage de la fiche récapitulative */
            this.getServletContext().getRequestDispatcher( VUE_SUCCES ).forward( request, response );
        } else {
            /* Sinon, ré-affichage du formulaire de création avec les erreurs */
            this.getServletContext().getRequestDispatcher( VUE_FORM ).forward( request, response );
        }
    }
}
package com.sdzee.tp.servlets;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet( urlPatterns = { "/images/*" }, initParams = @WebInitParam( name = "chemin", value = "/fichiers/images/" ) )
public class Image extends HttpServlet {
    public static final int TAILLE_TAMPON = 10240; // 10ko

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /*
         * Lecture du paramètre 'chemin' passé à la servlet via la déclaration
         * dans le web.xml
         */
        String chemin = this.getServletConfig().getInitParameter( "chemin" );

        /*
         * Récupération du chemin du fichier demandé au sein de l'URL de la
         * requête
         */
        String fichierRequis = request.getPathInfo();

        /* Vérifie qu'un fichier a bien été fourni */
        if ( fichierRequis == null || "/".equals( fichierRequis ) ) {
            /*
             * Si non, alors on envoie une erreur 404, qui signifie que la
             * ressource demandée n'existe pas
             */
            response.sendError( HttpServletResponse.SC_NOT_FOUND );
            return;
        }

        /*
         * Décode le nom de fichier récupéré, susceptible de contenir des
         * espaces et autres caractères spéciaux, et prépare l'objet File
         */
        fichierRequis = URLDecoder.decode( fichierRequis, "UTF-8" );
        File fichier = new File( chemin, fichierRequis );

        /* Vérifie que le fichier existe bien */
        if ( !fichier.exists() ) {
            /*
             * Si non, alors on envoie une erreur 404, qui signifie que la
             * ressource demandée n'existe pas
             */
            response.sendError( HttpServletResponse.SC_NOT_FOUND );
            return;
        }

        /* Récupère le type du fichier */
        String type = getServletContext().getMimeType( fichier.getName() );

        /*
         * Si le type de fichier est inconnu, alors on initialise un type par
         * défaut
         */
        if ( type == null ) {
            type = "application/octet-stream";
        }

        /* Initialise la réponse HTTP */
        response.reset();
        response.setBufferSize( TAILLE_TAMPON );
        response.setContentType( type );
        response.setHeader( "Content-Length", String.valueOf( fichier.length() ) );
        response.setHeader( "Content-Disposition", "inline; filename=\"" + fichier.getName() + "\"" );

        /* Prépare les flux */
        BufferedInputStream entree = null;
        BufferedOutputStream sortie = null;
        try {
            /* Ouvre les flux */
            entree = new BufferedInputStream( new FileInputStream( fichier ), TAILLE_TAMPON );
            sortie = new BufferedOutputStream( response.getOutputStream(), TAILLE_TAMPON );

            /* Lit le fichier et écrit son contenu dans la réponse HTTP */
            byte[] tampon = new byte[TAILLE_TAMPON];
            int longueur;
            while ( ( longueur = entree.read( tampon ) ) > 0 ) {
                sortie.write( tampon, 0, longueur );
            }
        } finally {
            try {
                sortie.close();
            } catch ( IOException ignore ) {
            }
            try {
                entree.close();
            } catch ( IOException ignore ) {
            }
        }
    }
}
package com.sdzee.tp.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet( urlPatterns = { "/listeClients" } )
public class ListeClients extends HttpServlet {
    public static final String ATT_CLIENT = "client";
    public static final String ATT_FORM   = "form";

    public static final String VUE        = "/WEB-INF/listerClients.jsp";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* À la réception d'une requête GET, affichage de la liste des clients */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }
}
package com.sdzee.tp.servlets;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet( urlPatterns = { "/listeCommandes" } )
public class ListeCommandes extends HttpServlet {
    public static final String ATT_COMMANDE = "commande";
    public static final String ATT_FORM     = "form";

    public static final String VUE          = "/WEB-INF/listerCommandes.jsp";

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* À la réception d'une requête GET, affichage de la liste des commandes */
        this.getServletContext().getRequestDispatcher( VUE ).forward( request, response );
    }
}
package com.sdzee.tp.servlets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.sdzee.tp.dao.ClientDao;
import com.sdzee.tp.dao.DAOException;
import com.sdzee.tp.entities.Client;

@WebServlet( urlPatterns = { "/suppressionClient" } )
public class SuppressionClient extends HttpServlet {
    public static final String PARAM_ID_CLIENT = "idClient";
    public static final String SESSION_CLIENTS = "clients";

    public static final String VUE             = "/listeClients";

    @EJB
    private ClientDao          clientDao;

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* Récupération du paramètre */
        String idClient = getValeurParametre( request, PARAM_ID_CLIENT );
        Long id = Long.parseLong( idClient );

        /* Récupération de la Map des clients enregistrés en session */
        HttpSession session = request.getSession();
        Map<Long, Client> clients = (HashMap<Long, Client>) session.getAttribute( SESSION_CLIENTS );

        /* Si l'id du client et la Map des clients ne sont pas vides */
        if ( id != null && clients != null ) {
            try {
                /* Alors suppression du client de la BDD */
                clientDao.supprimer( clients.get( id ) );
                /* Puis suppression du client de la Map */
                clients.remove( id );
            } catch ( DAOException e ) {
                e.printStackTrace();
            }
            /* Et remplacement de l'ancienne Map en session par la nouvelle */
            session.setAttribute( SESSION_CLIENTS, clients );
        }

        /* Redirection vers la fiche récapitulative */
        response.sendRedirect( request.getContextPath() + VUE );
    }

    /*
     * Méthode utilitaire qui retourne null si un paramètre est vide, et son
     * contenu sinon.
     */
    private static String getValeurParametre( HttpServletRequest request, String nomChamp ) {
        String valeur = request.getParameter( nomChamp );
        if ( valeur == null || valeur.trim().length() == 0 ) {
            return null;
        } else {
            return valeur;
        }
    }
}
package com.sdzee.tp.servlets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.sdzee.tp.dao.CommandeDao;
import com.sdzee.tp.dao.DAOException;
import com.sdzee.tp.entities.Commande;

@WebServlet( urlPatterns = { "/suppressionCommande" } )
public class SuppressionCommande extends HttpServlet {
    public static final String PARAM_ID_COMMANDE = "idCommande";
    public static final String SESSION_COMMANDES = "commandes";

    public static final String VUE               = "/listeCommandes";

    @EJB
    private CommandeDao        commandeDao;

    public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        /* Récupération du paramètre */
        String idCommande = getValeurParametre( request, PARAM_ID_COMMANDE );
        Long id = Long.parseLong( idCommande );

        /* Récupération de la Map des commandes enregistrées en session */
        HttpSession session = request.getSession();
        Map<Long, Commande> commandes = (HashMap<Long, Commande>) session.getAttribute( SESSION_COMMANDES );

        /* Si l'id de la commande et la Map des commandes ne sont pas vides */
        if ( id != null && commandes != null ) {
            try {
                /* Alors suppression de la commande de la BDD */
                commandeDao.supprimer( commandes.get( id ) );
                /* Puis suppression de la commande de la Map */
                commandes.remove( id );
            } catch ( DAOException e ) {
                e.printStackTrace();
            }
            /* Et remplacement de l'ancienne Map en session par la nouvelle */
            session.setAttribute( SESSION_COMMANDES, commandes );
        }

        /* Redirection vers la fiche récapitulative */
        response.sendRedirect( request.getContextPath() + VUE );
    }

    /*
     * Méthode utilitaire qui retourne null si un paramètre est vide, et son
     * contenu sinon.
     */
    private static String getValeurParametre( HttpServletRequest request, String nomChamp ) {
        String valeur = request.getParameter( nomChamp );
        if ( valeur == null || valeur.trim().length() == 0 ) {
            return null;
        } else {
            return valeur;
        }
    }
}

Le code du filtre

package com.sdzee.tp.filters;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ejb.EJB;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.sdzee.tp.dao.ClientDao;
import com.sdzee.tp.dao.CommandeDao;
import com.sdzee.tp.entities.Client;
import com.sdzee.tp.entities.Commande;

@WebFilter( urlPatterns = { "/*" } )
public class PrechargementFilter implements Filter {
    public static final String ATT_SESSION_CLIENTS   = "clients";
    public static final String ATT_SESSION_COMMANDES = "commandes";

    @EJB
    private ClientDao          clientDao;
    @EJB
    private CommandeDao        commandeDao;

    public void init( FilterConfig filterConfig ) throws ServletException {
    }

    public void doFilter( ServletRequest req, ServletResponse res, FilterChain chain ) throws IOException,
            ServletException {
        /* Cast de l'objet request */
        HttpServletRequest request = (HttpServletRequest) req;

        /* Récupération de la session depuis la requête */
        HttpSession session = request.getSession();

        /*
         * Si la map des clients n'existe pas en session, alors l'utilisateur se
         * connecte pour la première fois et nous devons précharger en session
         * les infos contenues dans la BDD.
         */
        if ( session.getAttribute( ATT_SESSION_CLIENTS ) == null ) {
            /*
             * Récupération de la liste des clients existants, et enregistrement
             * en session
             */
            List<Client> listeClients = clientDao.lister();
            Map<Long, Client> mapClients = new HashMap<Long, Client>();
            for ( Client client : listeClients ) {
                mapClients.put( client.getId(), client );
            }
            session.setAttribute( ATT_SESSION_CLIENTS, mapClients );
        }

        /*
         * De même pour la map des commandes
         */
        if ( session.getAttribute( ATT_SESSION_COMMANDES ) == null ) {
            /*
             * Récupération de la liste des commandes existantes, et
             * enregistrement en session
             */
            List<Commande> listeCommandes = commandeDao.lister();
            Map<Long, Commande> mapCommandes = new HashMap<Long, Commande>();
            for ( Commande commande : listeCommandes ) {
                mapCommandes.put( commande.getId(), commande );
            }
            session.setAttribute( ATT_SESSION_COMMANDES, mapCommandes );
        }

        /* Pour terminer, poursuite de la requête en cours */
        chain.doFilter( request, res );
    }

    public void destroy() {
    }
}
Exemple de certificat de réussite
Exemple de certificat de réussite