L'envoi de fichiers avec JSF

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Dans cet ultime chapitre, nous allons découvrir comment gérer l'envoi de fichiers depuis un formulaire avec JSF. Ce sujet va en réalité nous servir de prétexte pour découvrir les fameuses bibliothèques de composants JSF ! En guise de clôture, je vous donnerai ensuite des indications pour réaliser la huitième et dernière étape du fil rouge, et nous ferons enfin un rapide point sur ce qui vous attend dans le monde du développement web avec Java EE !

Le problème

« J'ai beau chercher dans la documentation, je ne trouve aucune balise qui ressemble de près ou de loin à quelque chose qui s'occupe des fichiers ! »

Ne perdez pas davantage votre temps, aucun composant standard n'existe actuellement dans JSF pour générer une balise <input type="file">, ni pour gérer correctement les requêtes de types multipart impliquées par les transferts de données occasionnés.

Cependant, c'est au programme de la version JSF 2.2, prévue pour fin mars 2013 au moment de l'écriture de ce cours. Une balise standard <h:inputFile> va faire son apparition, et apporter notamment un support AJAX pour l'envoi de fichiers ! Mais en attendant cette nouveauté, nous devons trouver un autre moyen…

Nous sommes aujourd'hui fin 2014, et la mise à jour de ce chapitre est en cours. Patience ! :)

Au passage, si vous parcourez ce programme vous apprendrez que cette nouvelle mouture de JSF apportera enfin le support de l'injection via l'annotation @EJB dans tous les objets JSF, problème auquel nous nous étions heurtés dans le chapitre précédent. Vous pouvez par ailleurs noter que le support des requêtes de type GET et des éléments HTML5 est également annoncé.

« Avec JSF, il est possible de définir ses propres composants. Ne peut-on pas en créer un pour l'occasion ? »

C'est une très bonne idée sur le papier, et effectivement JSF permet la création de composants personnalisés. Cependant rien que pour un simple et unique composant, c'est une charge de travail assez conséquente. Afin de mener à bien un tel objectif, il est notamment nécessaire de connaître les concepts de FacesRenderer et de FacesComponents. Autrement dit, il faut mettre les mains dans le code d'une bibliothèque ou d'une balise existante pour comprendre comment c'est fait, et en déduire comment l'adapter pour notre besoin. C'est un exercice très formateur je vous l'accorde, et je vous encourage à vous y atteler (voir la bulle d'information ci-après), mais dans le cadre de notre cours c'est trop d'efforts pour ce que nous souhaitons faire.

En outre, un autre obstacle se dresse sur notre chemin : par défaut, JSF et sa FacesServlet n'embarquent rien qui permette de gérer les requêtes de type multipart. Qu'est-ce que cela signifie exactement ? Si vous vous souvenez bien, dans notre servlet d'upload « faite maison », nous avions fait intervenir une annotation particulière, nommée @MultipartConfig, afin de préciser au conteneur que notre servlet était équipée pour traiter des requêtes de ce type. Ensuite seulement, nous pouvions y utiliser les méthodes request.getParts(), etc.

Eh bien voilà ce qu'il manque actuellement à JSF : la FacesServlet n'est pas annotée avec @MultipartConfig. Elle n'est capable dans les coulisses que d'utiliser les méthodes de récupération de paramètres traditionnelles, comme request.getParameter(). Par conséquent, il n'est pas possible de se baser directement sur elle pour réaliser un système d'envoi de fichiers.

Il existe bien entendu une parade. Il faudrait pour commencer réaliser tout le travail de conversion et de gestion des Parts à la main, comme nous l'avions fait dans notre servlet d'upload auparavant. Seulement cela n'est pas suffisant : pour intégrer ça proprement avec JSF, il faudrait créer un filtre, qui s'occuperait de précharger les traitements nécessaires en amont de la FacesServlet, de manière à rendre disponibles les données d'une requête multipart à travers les méthodes traditionnelles que la FacesServlet sait utiliser.. Elle pourrait ainsi, comme si de rien n'était et peu importe le type de requête entrante (normale ou multipart), permettre de manipuler les données envoyées.

Bref, vous devez vous rendre compte que c'est un travail massif qui nous attend, difficile à faire d'une part, et encore plus difficile à faire proprement. Autant de raisons pour nous pousser à choisir une autre solution : utiliser un composant permettant l'envoi de fichiers, prêt à l'emploi et fourni… dans une bibliothèque de composants externe !

Pour information, un bienfaiteur très actif dans le développement et la promotion de JSF surnommé BalusC, a déjà :

  • codé des filtres prêts à l'emploi pour la gestion des requêtes multipart, et les a rendu disponibles dans ce premier article de blog ;
  • créé un composant personnalisé pour permettre la gestion de l'envoi de fichiers avec JSF, et expliqué la démarche sur ce second article de blog.

C'est en anglais, mais je vous encourage à jeter un œil à ses codes et explications. C'est clair, propre et très formateur !

Les bibliothèques de composants

De manière générale, quand vous avez besoin de quelque chose qui semble assez commun, et que vous vous rendez compte que ça n'existe pas dans le standard JSF, vous devez prendre le réflexe de vous dire que quelqu'un a déjà dû se poser la question avant vous, que quelqu'un y a probablement déjà apporté une réponse, et qu'il existe sûrement une ou plusieurs manières de résoudre votre problème.

Ça paraît bête à première vue, mais nombreux sont les développeurs qui ont tôt fait de l'oublier et qui s'épuisent à réinventer la roue avant, bien souvent, de se rendre compte du temps qu'ils ont perdu !

D'ailleurs si vous vous souvenez du charabia que je vous avais raconté en introduisant le framework JSF, vous savez que nous avons déjà parlé de PrimeFaces, RichFaces, IceFaces, etc. Ces fameuses bibliothèques de composants évolués et arborant des sites vitrines parfois époustouflants. Vous pensez vraiment que des solutions capables de proposer des fonctionnalités avancées comme du drag & drop, de la génération de graphiques ou du dessin vectoriel en temps réel clé en mains, ne permettent pas de gérer l'envoi de fichiers ? Vous pensez bien, c'est un des premiers travaux qu'elles ont pour la plupart entrepris ! Ainsi, si vous cherchez un peu sur la toile vous tomberez notamment sur :

Ce sont là les trois mastodontes, les trois bibliothèques de composants JSF les plus fournies, les plus reconnues, les plus utilisées et les plus maintenues.

Seulement ils sont un peu massifs, ces mastodontes. Et sortir une telle machinerie (qui se matérialise dans votre projet par une flopée d'archives jar à placer dans le dossier /WEB-INF/lib, et qui alourdit donc votre application) simplement pour réaliser de l'envoi de fichiers, c'est un peu sortir un tank pour chasser une mouche.

À côté de ça, il existe des projets un peu moins ambitieux, mais tout aussi utiles comme le célèbre TomaHawk, qui offre la balise <t:inputFileUpload>. Nous allons donc nous pencher sur cette solution "légère", si tant est que l'on puisse vraiment la qualifier ainsi, et nous en servir pour mettre en place l'envoi de fichiers via un formulaire.

L'envoi de fichier avec Tomahawk

Préparation du projet

Récupérez Tomahawk en choisissant sur cette page de téléchargement la dernière version disponible, dans le format qui vous convient (archive .zip ou .tar.gz). Le fichier se nomme MyFaces Tomahawk 1.1.14 for JSF 2.0, le numéro de version pouvant changer si une nouvelle version existe lorsque vous lisez ce cours.

Décompressez ensuite le contenu de l'archive sur votre poste, vous obtiendrez alors un dossier portant le même nom que l'archive. Dans mon cas, ce dossier s'intitule /tomahawk20-1.1.14. Dans ce dossier se trouve un répertoire intitulé /lib, qui contient à son tour 18 archives jar ! Ce sont tous ces fichiers que vous allez devoir copier dans le répertoire /WEB-INF/lib de votre projet afin d'y intégrer Tomahawk.

Comme je vous l'ai déjà expliqué, il est nécessaire de mettre en place un filtre afin de rendre la gestion des requêtes multipart possible. Pour notre plus grand bonheur, Tomahawk en fournit un prêt à l'emploi, qu'il nous suffit de déclarer dans le fichier web.xml de notre application :

1
2
3
4
5
6
7
8
<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

Déclaration du filtre multipart de Tomahawk dans le fichier web.xml

Notez bien que le nom précisé dans le champ <servlet-name> doit absolument être celui que vous avez donné à votre FacesServlet. Si vous avez repris à la lettre le fichier web.xml que je vous ai proposé lors de notre premier projet JSF, alors vous pouvez également reprendre cette configuration telle quelle.

Création de la Facelet

Créez ensuite une Facelet d'upload, en prenant exemple sur la page JSP que nous avions créée à cet égard lorsque nous avions découvert l'envoi de fichiers via une servlet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="fr"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:t="http://myfaces.apache.org/tomahawk">
    <h:head>
        <title>Tomahawk - Envoi de fichier</title>
        <h:outputStylesheet library="default" name="css/form.css"  />
    </h:head>
    <h:body>
        <h:form enctype="multipart/form-data">
            <fieldset>
                <legend>Envoi de fichier</legend>

                <h:outputLabel for="description">Description du fichier</h:outputLabel>
                <h:inputText id="description" value="#{uploadBean.fichier.description}"/>
                <h:message id="descriptionMessage" for="description" errorClass="erreur" />
                <br />

                <h:outputLabel for="fichier">Emplacement du fichier <span class="requis">*</span></h:outputLabel>
                <t:inputFileUpload id="fichier" value="#{uploadBean.fichier.contenu}" />
                <h:message id="fichierMessage" for="fichier" errorClass="erreur" />
                <br />

                <h:messages globalOnly="true" infoClass="info" />

                <h:commandButton value="Envoyer" action="#{uploadBean.envoyer}" styleClass="sansLabel"/>
                <br />
            </fieldset>
        </h:form>
    </h:body> 
</html>

Facelet d'upload

La seule nouveauté ici est l'utilisation du composant <t:inputFileUpload> de la bibliothèque Tomahawk à la ligne 22, que nous déclarons dans l'en-tête <html> à la ligne 6 de la même manière que les bibliothèques de composants standard de JSF.

En ce qui concerne les trois expressions EL employées aux lignes 17, 22 et 28, elles vous donnent une idée de l'architecture que nous allons mettre en place derrière cette Facelet : un backing-bean nommé UploadBean, qui contient un JavaBean intitulé fichier en tant que propriété, qui à son tour contient deux propriétés nommées description et contenu. Le backing-bean contient également une méthode envoyer(), appelée lors du clic sur le bouton de validation du formulaire.

Vous n'oublierez pas de remarquer à la ligne 12 l'ajout de l'attribut enctype="multipart/form-data" à la balise <h:form>, c'est indispensable pour que le formulaire envoie correctement le fichier au serveur.

Création du JavaBean

Commençons donc par créer un objet intitulé Fichier, contenant deux propriétés :

  • la description du fichier, stockée sous forme d'une simple String ;
  • le contenu du fichier, stockant les données envoyées par l'utilisateur.

Un rapide parcours de la documentation de la balise <t:inputFileUpload> nous apprend que l'objet utilisé pour stocker le fichier envoyé par l'utilisateur est de type UploadedFile.

Nous allons également reprendre les vérifications que nous effectuions dans notre système d'upload basé sur les servlets, à savoir :

  • un fichier doit obligatoirement être transmis ;
  • une description, si elle est renseignée, doit contenir au moins 15 caractères.

Pour les mettre en place, nous allons à nouveau pouvoir utiliser de simples annotations comme nous l'avons découvert dans le chapitre précédent. Voici le code du bean en résultant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.sdzee.entities;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.myfaces.custom.fileupload.UploadedFile;

public class Fichier {

    @Size( min = 15, message = "La phrase de description du fichier doit contenir au moins 15 caractères" )
    private String       description;
    @NotNull( message = "Merci de sélectionner un fichier à envoyer" )
    private UploadedFile contenu;

    public String getDescription() {
        return description;
    }

    public void setDescription( String description ) {
        this.description = description;
    }

    public UploadedFile getContenu() {
        return contenu;
    }

    public void setContenu( UploadedFile contenu ) {
        this.contenu = contenu;
    }
}

com.sdzee.entities.Fichier

Vous retrouvez sans surprise les annotations @Size et @NotNull appliquées aux propriétés, accompagnées de leur message d'erreur respectif.

Création du backing-bean

La dernière étape consiste à créer le backing-bean qui va mettre tout ce petit monde en musique. Celui-ci doit donc s'intituler UploadBean, contenir et initialiser une instance d'un bean de type Fichier, ainsi qu'une méthode intitulée envoyer() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.sdzee.beans;

import java.io.IOException;
import java.io.Serializable;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;

import com.sdzee.entities.Fichier;

@ManagedBean
@RequestScoped
public class UploadBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private Fichier           fichier;

    // Initialisation du bean fichier
    public UploadBean() {
        fichier = new Fichier();
    }

    public void envoyer() throws IOException {
        String nomFichier = FilenameUtils.getName( fichier.getContenu().getName() );
        String tailleFichier = FileUtils.byteCountToDisplaySize( fichier.getContenu().getSize() );
        String typeFichier = fichier.getContenu().getContentType();
        byte[] contenuFichier = fichier.getContenu().getBytes();

        /*
         * Effectuer ici l'enregistrement du contenu du fichier sur le disque,
         * ou dans la BDD (accompagné du type du contenu, éventuellement), ou
         * tout autre traitement souhaité...
         */

        FacesContext.getCurrentInstance().addMessage( null, new FacesMessage(
                String.format( "Fichier '%s', de taille '%s' et de type '%s' envoyé avec succès !",
                        nomFichier, tailleFichier, typeFichier ) ) );
    }

    public Fichier getFichier() {
        return fichier;
    }

    public void setFichier( Fichier fichier ) {
        this.fichier = fichier;
    }
}

com.sdzee.beans.UploadBean

Vous reconnaissez dans cet exemple la structure d'un backing-bean, très semblable à celui que nous avons créé dans le chapitre précédent : les annotations @ManagedBean et @RequestScoped, le constructeur pour initialiser le bean de type Fichier et la paire de getter/setter associée.

C'est dans la méthode envoyer() que vous devrez appeler les traitements que vous souhaitez effectuer sur votre fichier : l'écrire sur le disque comme nous l'avions fait dans notre système basé sur les servlets, ou pourquoi pas l'enregistrer dans une table dans votre base de données, etc.

J'ai, pour ma part, choisi d'effectuer de simples récupérations d'informations concernant le fichier dans cet exemple, afin de ne pas encombrer le code inutilement. Cela me permet tout de même de vous montrer l'utilisation des méthodes de l'objet UploadedFile :

  • à la ligne 29, je récupère le nom du fichier via UploadedFile.getName() et le convertis dans un format propre grâce à la méthode utilitaire FilenameUtils.getName() ;
  • à la ligne 30, je récupère la taille du fichier via UploadedFile.getSize() et la convertis dans un format lisible grâce à la méthode utilitaire FileUtils.byteCountToDisplaySize() ;
  • à la ligne 31, je récupère directement le type du fichier via UploadedFile.getContentType().

Je génère finalement un simple FacesMessage, initialisé avec un identifiant à null et un message récapitulant le nom, la taille et le type du fichier envoyé, que j'ajoute au FacesContext. Tout ceci est fait, comme vous devez vous en douter, en prévision d'un affichage dans notre Facelet via la balise <h:messages globalOnly="true">.

Tests et vérifications

Tout est prêt, nous pouvons tester notre formulaire d'envoi. Rendez-vous sur la page http://localhost:8088/pro_jsf/upload.xhtml. Vous devrez alors observer le même formulaire vierge que celui mis en place dans notre ancien système d'upload basé sur les servlets (voir la figure suivante).

L'affichage du champ de sélection du fichier peut varier selon le navigateur que vous utilisez et la langue de votre système. Ici, il s'agit de l'affichage sous le navigateur Chrome sur un système en langue anglaise. Ne vous inquiétez donc pas si vous obtenez un rendu sensiblement différent.

Essayons pour commencer de valider sans rien saisir ni sélectionner, comme indiqué à la figure suivante.

Nous observons alors un message d'erreur sur le champ du fichier, et rien sur la description. C'est bien le comportement attendu, puisque nous n'avons mis une annotation @NotNull que sur la propriété contenu, et pas sur la description.

Essayons ensuite de valider en saisissant une description de moins de 15 caractères, sans sélectionner de fichier (voir la figure suivante).

Cette fois, l'annotation @Size entre en jeu, et nous sommes prévenus par un message d'erreur sur le champ description que nous devons saisir au moins 15 caractères.

Essayons enfin de valider en saisissant une description assez longue, et en sélectionnant un fichier léger quelconque depuis notre disque, ainsi qu'indiqué à la figure suivante.

J'ai, pour ma part, sélectionné un fichier de type PDF et nommé très sobrement 1.pdf. Cette fois, l'envoi est validé et nous observons le message d'information que nous avons construit dans la méthode envoyer() de notre backing-bean. En l'occurrence, le message me précise bien le titre de mon fichier, sa taille et son type MIME.

Nous avons donc réussi à mettre en place un système d'envoi de fichiers très simplement, grâce au composant fourni par la bibliothèque Tomahawk. Si vous souhaitez reproduire fidèlement le comportement de notre ancien système d'upload, il ne vous reste plus qu'à intégrer une méthode d'écriture du fichier sur le disque directement dans votre backing-bean, ou bien dans une classe utilitaire que vous appellerez depuis la méthode envoyer() !

Limitation de la taille maximale autorisée

Avant d'en terminer avec ce formulaire, je tiens à vous préciser une information importante au sujet du composant que nous utilisons pour l'envoi de fichiers : il ne sait pas très bien gérer une limitation de la taille maximale autorisée. Voyons cela ensemble en détail.

Dans la documentation du filtre de Tomahawk, nous apprenons que dans la section <filter> de la déclaration du filtre dans le web.xml, nous avons la possibilité d'insérer un paramètre d'initialisation afin de définir la taille maximale autorisée par fichier :

1
2
3
4
5
6
7
8
<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
    <init-param>
        <param-name>uploadMaxFileSize</param-name>
        <param-value>10m</param-value>
    </init-param>
</filter>

Ajout d'une limite de taille maximale pour les fichiers

Le format à respecter dans le champ <param-value> est le suivant :

  • 10 -> 10 octets de données ;
  • 10k -> 10 Ko de données ;
  • 10m -> 10 Mo de données ;
  • 1g -> 1 Go de données.

Le problème est que lorsque cette limite est dépassée, Tomahawk ne prépare pas un joli FacesMessage prêt à être affiché dans notre Facelet à côté du composant <t:inputFileUpload>. Il envoie à la place une exception de type SizeLimitExceededException, qui sort du cadre normal d'exécution de JSF. Je ne rentre pas dans les détails, mais pour faire simple il n'est tout bonnement pas possible de gérer proprement cette exception !

Vous pouvez faire le test si vous le souhaitez : mettez en place cet ajout dans la déclaration du filtre dans votre web.xml, par exemple avec une taille maximale de 1Mo, et essayez ensuite d'envoyer via votre formulaire un fichier dont la taille dépasse cette limite. Vous ne visualiserez en retour aucun message d'erreur sur votre formulaire, ni aucun message de validation. La seule erreur que vous pourrez trouver sera affichée dans le fichier /glassfish/domains/domain1/logs/server.log de GlassFish, qui contiendra une ligne mentionnant l'exception dont je viens de vous parler…

Comment faire pour gérer cette exception ?

Je vous l'ai déjà dit, ce n'est pas possible proprement. Ce qu'il est possible de faire par contre, c'est de mettre en place une parade : nous pouvons mettre en place une limite de taille très grande, par exemple 100Mo de données, et nous occuper de la vérification de la taille du fichier envoyé grâce à un Validator JSF.

Alors bien évidemment, cela ne règle pas entièrement le problème : dans l'absolu, un utilisateur pourra toujours tenter d'envoyer un fichier trop volumineux (pesant plus de 100Mo), et se retrouver face à un formulaire sans erreur apparente. Mais nous pouvons considérer que si un utilisateur s'amuse à envoyer un fichier aussi gros, alors ça sera bien fait pour lui, il ne mérite pas d'être proprement informé de l'erreur ! :-°

Mettons donc en place une limitation large dans le web.xml :

1
2
3
4
5
6
7
8
<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
    <init-param>
        <param-name>uploadMaxFileSize</param-name>
        <param-value>100m</param-value>
    </init-param>
</filter>

Ajout d'une limite de taille de 100Mo

Il nous faut alors créer un Validator, comme nous l'avons fait à deux reprises dans le chapitre précédent. Nous allons le nommer TailleMaxFichierValidator et le placer comme ses confrères dans le package com.sdzee.validators :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.sdzee.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import org.apache.myfaces.custom.fileupload.UploadedFile;

@FacesValidator( "tailleMaxFichierValidator" )
public class TailleMaxFichierValidator implements Validator {

    private static final long   TAILLE_MAX_FICHIER = 1 * 1024 * 1024;                          // 1Mo
    private static final String MESSAGE_ERREUR     = "La taille maximale autorisée est de 1Mo";

    public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException {
        if ( value != null && ( (UploadedFile) value ).getSize() > TAILLE_MAX_FICHIER ) {
            throw new ValidatorException( new FacesMessage( FacesMessage.SEVERITY_ERROR, MESSAGE_ERREUR, null ) );
        }
    }
}

com.sdzee.validators.TailleMaxFichierValidator

Vous retrouvez l'annotation @FacesValidator, permettant de déclarer l'objet comme tel auprès de JSF.

Le code de la méthode validate() est ensuite très simple. Nous nous contentons de vérifier que la taille du fichier ne dépasse pas la limite définie - en l'occurrence j'ai fixé cette limite à 1Mo - et envoyons le cas échéant une exception initialisée avec un FacesMessage de gravité erreur et contenant un message d'avertissement destiné à l'utilisateur.

Pour terminer, nous devons lier notre Validator au composant d'envoi de fichiers dans notre Facelet, par l'intermédiaire de la balise <f:validator> que vous connaissez déjà. Remplacez tout bêtement la déclaration de la balise <t:inputFileUpload> par :

1
2
3
<t:inputFileUpload id="fichier" value="#{uploadBean.fichier.contenu}">
    <f:validator validatorId="tailleMaxFichierValidator" />
</t:inputFileUpload>

Pour rappel, le nom passé dans l'attribut validatorId est le nom défini dans l'annotation @FacesValidator de notre objet TailleMaxFichierValidator.

Nous pouvons maintenant vérifier le bon fonctionnement de notre système de contrôle de la taille d'un fichier. Rendez-vous à nouveau sur le formulaire d'envoi depuis votre navigateur, et essayez d'envoyer un fichier dont le poids est supérieur à 1Mo (et bien évidemment, inférieur à 100Mo).

Vous observez alors que le fichier est correctement envoyé au serveur, ce qui est normal puisque la limite de taille autorisée est fixée à 100Mo. Une fois le fichier récupéré, JSF applique alors le Validator que nous avons créé et constate que le fichier est trop volumineux. Il affiche donc le message d'erreur à l'utilisateur, celui que nous avons préparé dans l'exception envoyée depuis la méthode validate(), sur le champ de sélection du fichier.

Vous savez maintenant vérifier la taille d'un fichier, malgré l'incapacité du composant à gérer proprement la taille maximale qu'il est permis de définir dans le filtre.

Et plus si affinités...

TP Fil rouge - Étape 8

Il vous reste encore un bout de chemin à arpenter seuls, et vous serez prêts pour la huitième et dernière étape du fil rouge ! Eh oui, je vous avais à moitié menti en vous disant que la septième étape du fil rouge était la dernière. Pourquoi à moitié ? Parce qu'il y a bel et bien une huitième étape, mais cette fois, je ne vous fournirai pas de correction ! Vous allez devoir vous débrouiller tous seuls pour intégrer JSF dans votre application, et vous devrez notamment apprendre à utiliser les composants standard suivants :

  • la balise <h:selectOneRadio>, pour générer le bouton radio de choix entre l'utilisation d'un client existant et la saisie d'un nouveau client ;
  • la balise <h:selectOneMenu>, pour générer la liste déroulante de sélection d'un client existant ;
  • la balise <h:link>, pour générer les liens HTML présents dans les différentes vues ;
  • la balise <h:dataTable>, pour générer le tableau récapitulatif des clients existants et des commandes passées ;
  • la balise <ui:include>, pour remplacer l'inclusion du menu jusqu'à présent réalisée via une balise de la JSTL.

Nous avons déjà appris les rudiments de JSF ensemble, ce petit exercice en solitaire ne devrait pas vous poser de problème particulier : vous allez simplement devoir chercher par vous-mêmes dans la documentation et sur le web pour trouver les informations qui vous manquent.

Pourquoi est-ce que nous n'apprenons pas ensemble à utiliser ces quelques composants ?

La première raison de ce choix est simple : je ne souhaite pas vous donner l'impression d'avoir terminé l'apprentissage de JSF ! Ce que j'entends par là, c'est qu'il ne faut surtout pas vous imaginer qu'une fois capables de reproduire le fil rouge avec JSF, vous n'aurez plus rien à apprendre. Alors certes, j'aurais pu vous tenir la main jusqu'à la fin du TP, et vous avertir ensuite comme je suis en train de le faire maintenant, mais je suis persuadé que cela n'aurait pas eu le même effet. En ne vous donnant que des pistes pour cette ultime étape du fil rouge, je vous prépare en douceur à ce qui vous attend ensuite : l'apprentissage par vous-mêmes.

La seconde raison de ce choix est que pour couvrir l'intégralité de JSF, tout en gardant ce rythme lent, pédagogique et adapté aux novices, il faudrait une collection de livres entière ! Il y a bien trop d'aspects à aborder, de fonctionnalités à découvrir, de ressources à apprendre à manipuler, etc. De toute manière, maîtriser le framework de A à Z n'est nécessaire que si vous envisagez de travailler à son amélioration ! Pour un usage commun, c'est-à-dire l'utilisation de JSF dans vos projets, il vous suffit d'en connaître suffisamment pour pouvoir vous débrouiller seuls lorsqu'un problème survient : cela inclut la connaissance de l'API Servlet, du processus de traitement des requêtes par JSF, des Facelets, des principaux composants, etc. Bref, tout ce que nous avons découvert ensemble dans ce cours !

Enfin, vous ne devez jamais oublier que l'expérience, les tests, les erreurs et la recherche de solutions sont les outils les plus pédagogiques qui puissent être. Lorsque vous rencontrez un problème, posez-vous les bonnes questions et interrogez-vous avant tout sur son origine avant de songer à y trouver une solution.

Ce que l'avenir vous réserve

Ce qu'il vous manque pour pouvoir voler de vos propres ailes

Les bibliothèques de composants JSF sont richissimes, et vous devez absolument vous y intéresser si vous comptez vous lancer dans le développement avec JSF. Nous les avons citées à plusieurs reprises dans le cours : PrimeFaces, RichFaces, IceFaces, OpenFaces, Tomahawk… Il existe même des solutions intermédiaires comme OmniFaces, qui se focalise sur la simplification du développement avec JSF et qui gagne en maturité et en popularité.

Sans parler de ces bibliothèques, ni même des composants standard restants que nous n'avons pas étudiés, il y a encore bon nombre de concepts JSF que nous n'avons pas eu la chance de pouvoir aborder ensemble dans ce cours, notamment : la création de composants personnalisés ave JSF, la création de FacesConverters, la compréhension des portées @Custom et @None sur un backing-bean, le découpage d'une page en plusieurs templates…

Plus important encore, il y a surtout bon nombre de concepts Java/Java EE au sens large que nous n'avons pas eu la chance de pouvoir aborder ensemble dans ce cours, notamment : l'envoi de mail depuis une application web, la réécriture d'URL (ou url-rewriting), la mise en place de webservices, l'écriture de tests unitaires, la génération d'une base de données directement depuis un modèle d'entités JPA…

Toutes ces mentions ne sont que des exemples, pour vous montrer que même lorsque vous savez créer une application web, il existe toujours des domaines que vous ne connaissez ou ne maîtrisez pas encore, et qui n'attendent que vous ! :)

Bientôt dans les bacs…

Pour clore ce triste paragraphe aux allures de testament, n'oubliez pas de rester attentifs aux mises à jour apportées régulièrement aux technologies Java et Java EE. La sortie de Java EE 7 est proche (prévue pour mars 2013 lors de la rédaction de ce cours), et par conséquent celles de JSF 2.2, de JPA 2.1, de GlassFish 4, etc. Un lot de nouveautés fera comme toujours son apparition, et vous devrez vous y frayer un chemin si vous souhaitez rester efficace !

La mise à jour du cours avec GlassFish 4, Java EE 7 et JSF 2.2 est en cours, soyez patients ! :)

Vous devez maintenant en avoir pleinement conscience : le monde Java EE est immensément vaste ! Vous avez acquis, en suivant ce cours, les bases, les bonnes pratiques et les réflexes nécessaires pour être autonomes dans votre progression future. Par-dessus tout, j'espère que ce cours vous a donné l'envie de continuer et d'en apprendre plus, d'aller plus loin, que ce soit pour simplifier le développement de vos applications, ou pour développer des fonctionnalités toujours plus avancées.


  • Dans sa version courante (JSF 2.1), le framework ne fournit pas de composant pour l'envoi de fichiers, et ne sait pas gérer les requêtes de type multipart.
  • La prochaine version du framework (JSF 2.2) est prévue à ce jour pour mars 2013 et devrait pallier ce manque.
  • La plupart des bibliothèques de composants proposent une balise dédiée à l'envoi de fichiers, et un filtre pour la gestion des requêtes multipart.
  • Avec la bibliothèque Tomhawak, il suffit de :
    • déclarer le filtre ExtensionsFilter dans le web.xml du projet ;
    • mettre en place la balise <t:inputFileUpload> dans le formulaire ;
    • utiliser un objet de type UploadedFile pour récupérer le fichier envoyé.
  • Tomahawk ne permet pas de gérer proprement la limitation de la taille maximale autorisée pour l'envoi de fichiers.
  • Un contournement simple et rapide consiste à relever la limite autorisée, et à mettre en place un Validator pour vérifier manuellement la taille du fichier envoyé.
  • Les univers JSF, JPA et Java EE en général sont immenses : faire preuve d'autonomie, de curiosité et d'initiative vous permettra de progresser efficacement !