JavaServer Faces - techniques avancées

Chapitres traités   

Cette étude fait suite à l'étude précédente qui nous a permis de prendre connaissance de JSF. Cette connaissance est un pré-requis indispensable aux sujets que nous allons aborder tout au long de cette nouvelle étude. Il est évident que les notions que nous venons d'apprendre sont insuffisantes pour développer une application de gestion complète. Effectivement, nous devons appréhender un certains nombre de critères qui vont nous permettre d'aboutir à une démarche de qualité. Nous devons aborder les points suivants :

  1. Nous devons maîtriser la structure et le mécanisme interne d'un système élaboré par JSF. Il est en effet important de connaître l'enchaînement des opérations qui conduisent au résultat final lorsqu'une issue du requête est proposée.
  2. Lors de la saisie de données par l'utilisateur, il est souvent indispensable de valider et ou de convertir les valeurs saisies avant de les injecter dans un JavaBean. Des messages adaptés peuvent alors survenir si les saisies sont incorrectes.
  3. Dans certaines situations, il est souhaitable de prendre en compte une gestion fine des événements afin que l'application Web soit suffisamment réactive au comportement de l'utilisateur.
  4. Dans le cas de développements importants, il également utile de créer des composants réutilisables pour augmenter la productivité et faciliter la maintenance. (Cette partie n'est pas traitée).

Choix du chapitre Cycle de traitement des requêtes JSF

Toutes les requêtes JSF suivent le même cycle de traitement, constitué de six étapes distinctes. Les lignes pleines décrivent le cycle normal et les lignes en pointillé rouge le traitement des erreurs.

  1. Création de l'arborescence des composants : À la réception de la requête, la hiérarchie des composants (arbre de vue) de la page demandée est créée.
  2. Récupération des valeurs de la requête : Les valeurs de la requête sont récupérées et stockées dans les composants de l'arbre de vue. La servlet contrôleur FacesServlet, non représentée ici, parcourt l'arbre de vue et appelle la méthode decode() de chacun de ses composants. Ensuite, les événements et les validators sont générés et stockés dans le contexte JSF.
  3. Traitement des validations : La méthode validate() est appelée pour tous les validators stockés dans l'arbre de vue créé à l'étape précédente.
  4. Modification des valeurs du modèle objet : Les valeurs contenues dans les composants sont ensuite recopiées dans les objets métiers (JavaBeans) éventuellement associés.
  5. Appel de l'événement application : L'événement de type <f:form> ou <h:command...> correspondant à la demande d'une nouvelle page est traitée.
  6. Rendu de la réponse : La hiérarchie de composants s'occupe de créer toutes les balises standard nécessaires pour représenter la réponse au format désiré.

Phase 1 - Création de l'arborescence des composants côté serveur (arbre de vue)

Une page HTML est un fichier texte qui contient un assemblage de balises spécialisées pour le formattage de l'information. Par contre JSF utilise, côté serveur, un arbre d'objets pour représenter la vue équivalente de cette page Web. Il s'agit là d'une structure bien différente d'un fichier texte. Cet arbre d'objets est un mirroir (un représentant) de l'interface visuelle du client.

Cette phase va donc consister à reconstituer l'arbre de composants qui correspond à la vue HTML de l'utilisateur qui soumet la requête. Cette arbre se nomme : arbre de vue.

Lorsque le client soumet la requête pour la première fois, JSF doit créer la vue correspondante au travers de cet arbre d'objets. A chaque balise de type <h:...> correspond un objet Interface Utilisateur équivalent qui va donc être placé sur l'arbre à une branche qui correspond à l'imbrication des balises proposées sur la page Web. Chaque objet est donc stocké dans cet arbre à l'endroit convenable.

La racine de cet arbre de vue est systématiquement une instance de la classe UIViewRoot qui correspond en réalité à la balise <f:view>. Nous remarquons au passage que le placement de cette balise dans une page JSP devient prépondérante afin de permettre l'élaboration de cet arbre de vue.

Cet arbre d'objets, qui est une image de la vue côté client, se créer automatiquement. Je veux dire que c'est le serveur qui s'en occupe sans aucune intervention de notre part.

Indépendamment de cet arbre, ces objets sont bien entendus issus d'un ensemble de classes qui respecte la hiérarchie suivante :

Toutes les classes ne sont pas représentées. Nous pouvons, à l'intérieur des Javabeans, travailler directement avec les classes correspondantes aux balises présentes sur la page Web, celles qui sont représentés en bleu. Toutefois, il est possible de travailler plutôt avec une des classes parentes, représentée en jaune. En effet, ces classes disposent déjà de certaines propriétés correspondantes aux attributs proposés par les balises équivalentes. Ainsi, par exemple, à partir de la balise suivante :

<h:outputText value="Un texte"/>

Vous pouvez créer un objet de type UIOutput qui dispose de la propriété équivalente à cet attribut value. Ainsi, vous pouvez solliciter les méthodes getValue() et setValue() qui travaillent donc en tâche de fond sur l'attribut value (de type Object) de la classe UIOutput.

Que se passe-t-il lorsque le client propose la requête la première fois, ou lorsqu'il la propose de nouveau au serveur ?

  1. Si la vue est demandée pour la première fois, le serveur d'application crée une instance de UIViewRoot et lui associe un nom, souvent le même nom que la page JSP. JSF mémorise alors l'arbre de vue.
  2. Si la vue existe déjà pour JSF, alors l'arbre de composants correspondant devient la vue courante. Dans ce cas là, la méthode restoreState() de la classe ancêtre UIComponent est appelée récursivement de façon polymorphique sur chacun des composants constituant l'arbre de vue. Ainsi, elle rétablit l'état du composant à partir de l'état sauvegardé durant la dernière phase appelée Rendu de la réponse (voir plus loin).

Lorsque JSF a déterminé la vue courante, celle-ci est accessible via la classe FacesContext.
.

Phase 2 - Récupération des valeurs de la requête

Lorsque la phase précédente est terminée, nous disposons d'un arbre de composants dont la racine est UIViewRoot. La requête HTTP soumise par le navigateur est porteuse des actions de l'utilisateur sur la vue. Ceci implique qu'il faut synchroniser la vue côté JSF avec la vue côté client. En effet, si l'utilisateur modifie une valeur dans un champs de formulaire, il faut que le composant graphique correspondant côté serveur reflète ce changement d'état.

Le but de cette phase est donc de récpercuter les actions de l'utilisateur sur l'arbre de composants courant (arbre de vue courant). Il faut décoder les valeurs contenues dans l'objet HttpServletRequest (nous connaissons particulièrement bien cet objet depuis que nous manipulons les servlets) et récupérer les changement d'états de la vue sur les composants concernés.

A cet effet, la classe UIComponent qui est la classe ancêtre de tout composant JSF dispose de la méthode processDecodes(). Cette méthode est appelée récursivement sur chaque composant de l'arbre. Par polymorphisme, les composants ont la responsabilité de décoder les informations qui les concernent.

Ainsi, chaque information envoyée lors de la requête est associée à l'objet correspondant dans l'arbre de vue. Plus précisément, les attributs de cet objet sont remis à jour par les nouvelles valeurs envoyées. Ainsi, si nous avons par exemple côté client cette ligne là :

<h:inputText value="5"/>

vous retrouver alors cette valeur 5 sur l'objet HtmlInputText correspondant qui est mis à jour par la propriété équivalente value, et ceci par l'intermédiaire de la méthode setValue().

Phase 3 - Traitement des validations

Certaines balises JSF sont parfois liés à des JavaBeans au moyen des expressions EL JSF. Les JavaBeans représentent la logique métier. Il est alors impératif de valider et ou de convertir les valeurs qui proviennent du client avant de les injecter dans le JavaBean correspondant. En effet, par exemple, si l'utilisateur saisie la chaîne suivante "345red" en lieu et place d'un nombre entier (valeur attendue), alors cela provoquera une erreur.

Vous allez le découvrir dans les chapitres qui suivent, JSF offre les concepts de validation (Validator) et de conversion (Converter) pour effectuer les traitements sur les valeurs soumises par l'utilisateur avant leurs injections dans les JavaBeans.

Un composant peut donc éventuellement posséder des Validator et un Converter qui sont invoqués lors de l'appel de la méthode processValidators() de la classe ancêtre UIComponent. Cette méthode est appelée récursivement sur l'arbre de vue. JSF procède d'abord à la conversion de la valeur extraite de la requête HTTP (String) puis valide cette valeur en utilisant les Validator.

Si une erreur survient durant cette phase, à cause d'un problème de validation, alors JSF arrête le processus normal et saute directement vers la dernière phase qui correspond au rendu de la réponse.

Phase 4 - Modification (Mise à jour) des valeurs du modèle objet

Lorsque la requête arrive à cette phase de traitement, les objets sont dans un état qui correspond à la vue du client. Les informations de la requête HTTP qui sont destinées à mettre à jour les données métiers ont été validées. La méthode processUpdates() de UIComponent est appelée récursivement sur l'arbre des composants. Sa responsabilité consiste à mettre à jour, cette fois-ci, les Javabeans correspondant à la logique métier.

Egalement durant cette phase, si une erreur survient, alors JSF arrête le processus normal et saute directement à la phase du rendu de la réponse.
.

Vous allez le découvrir dans les chapitres qui suivent, JSF offre un modèle événementiel qui permet de détecter les changements du modèle. A ce niveau, l'appel de la méthode processUpdates() peut donc éventuellement poster des événements.

Phase 5 - Appel de l'événement application

Lorsque cette phase est atteinte, le modèle métier est mis à jour, des événements sont en attentes dans la queue des événements. Ces événements sont de deux natures. Soit ils sont issus, de façon classique de l'attribut action des balises <h:commandButton> ou <h:commandLink>. Soit ils sont plutôt issus de l'attribut actionListener qui met donc en oeuvre un système d'écouteur supplémentaire (ActionListener). -- peut-être à revoir --

La méthode processApplication() de UIComponent est appelée récursivement sur l'arbre de vue. Sa responsabilité consiste à diffuser (Broadcast) certains événements de la queue vers les écouteurs d'événements associés (listeners).

Phase 6 - Rendu de la réponse

C'est la dernière phase du traitement d'une requête JSF. Sa première responsabilité consiste à encoder l'arbre de vue courant dans un langage compréhensible par le client, ici le HTML. JSF peut encoder le HTML, le WML, le XML, etc. Ici donc, nous partons de l'arbre de vue et chaque objet s'occupe de créer la balise correspondante dans le format standard requis.

Pour ce faire, le UIComponent dispose d'un jeu de méthodes dont la signature commence par encodeXXX(). ces méthodes sont particulièrement adaptées à l'encodage dans des langages à balises. Ces méthodes sont au nombre de trois :

  1. encodeBegin(),
  2. encodeChildren(),
  3. encodeEnd().

Ces méthodes correspondent grossièrement aux étapes d'encodage d'une balise : balise ouvrante, balises filles, balise fermante (sujet traité ultérieurement).
.

Sa deuxième responsabilité consiste à sauvegarder l'état des objets de l'arbre de vue. Pour ce faire une deuxième méthode saveState() est également appelée récursivement. Cette méthode répond à la méthode restoreState() que nous avons découvert durant la première phase Création de l'arbre de vue. Ce comportement est donné par l'interface StateHolder implémentée par UIComponent.

 

Choix du chapitre Classes JSF utiles pour le développement côté JavaBean

Lorsque nous developpions des JavaBeans avec les pages JSP sans tenir compte de la technologie JSF, il était très difficile d'être directement en interaction avec les éléments constituant la page JSP et tout ce qui concernait l'application Web dans son ensemble. Nous étions souvent obligés de passer par des servlets. Une autre solution était de faire en sorte que la page JSP passe tous les renseignements requis avec des propriétés supplémentaires dans le JavaBean concerné. ce qui alourdissait considérablement le code.

Dans le cas de JSF, les JavaBeans peuvent récupérer directement tous les renseignements nécessaires pour mettre en oeuvre les traitements désirés. C'est un des très gros avantage de cette technologie. Nous allons recenser ici, sans toutefois être pleinement exhaustif, les classes qui peuvent nous aider dans notre démarche. Le but de ce chapitre est juste de présenter ces classes sans s'attarder sur les détails d'implémentation. Dans les chapitres suivant, nous développerons des sujets qui interagiront avec ces classes et nous démontrerons l'intérêt de les utiliser.

Hiérarchie des composants Interface Utilisateur

Nous l'avons déjà découvert dans le chapitre précédent, JSF intègre une hiérarchie de composants qui représente l'interface utilisateur côté client. Ces composants sont issus du paquetage javax.faces.component.

Toutes les classes ne sont pas représentées. Nous pouvons, à l'intérieur des Javabeans, travailler directement avec les classes correspondantes aux balises présentes sur la page Web, celles qui sont représentés en bleu. Toutefois, il est possible de travailler plutôt avec une des classes parentes, représentée en jaune. En effet, ces classes disposent déjà de certaines propriétés correspondantes aux attributs proposés par les balises équivalentes. Ainsi, par exemple, à partir de la balise suivante :

<h:outputText value="Un texte"/>

Vous pouvez créer un objet de type UIOutput qui dispose de la propriété équivalente à cet attribut value. Ainsi, vous pouvez solliciter les méthodes getValue() et setValue() qui travaillent donc en tâche de fond sur l'attribut value (de type Object) de la classe UIOutput.

Présentation de tableaux dynamiques

A titre d'exemple, nous pouvons consulter quelques méthodes intéressantes de la classe UIData qui représente la balise <h:dataTable> par l'intermédiaire de HtmlTableData, et qui peuvent s'avérer bien utiles dans bien des cas. Effectivement, nous avons souvent besoin de cette balise lors de la mise en valeur d'un ensemble d'éléments.

javax.faces.component.UIData
Méthodes Retour Explication
getRowCount() int Retourne le nombre de lignes total que comporte l'ensemble du tableau de données ou alors -1 si le tableau est inconnu.
getRows() int Retourne le nombre de lignes qui peut être affiché dans la page, ou 0 pour toutes les lignes restantes dans la table. Le nombre de lignes que nous pouvons afficher est déterminé par la méthode setRows() ci-dessous.
getRowsIndex() int Retourne la valeur de l'index de la ligne courante.
getFirst() int Retourne la valeur de l'index de la première ligne qui peut être affichée.
getRowData() Object Retourne la valeur de la donnée de la ligne courante.
setRowIndex(int index) void Sélectionne la ligne que nous désirons consulter.
setFirst(int index) void Sélectionne la première ligne qui peut être affichée.
setRows(int lignes) void Sélectionne le nombre de lignes à afficher.

Récupérer le contexte de la page courante - FacesContext

C'est certainement la classe la plus importante. Elle est issue du paquetage javax.faces.context. Cette classe nous permet d'être en relation avec la page JSP courante qui communique avec notre JavaBean. L'objet relatif de cette classe FacesContext représente donc la page JSF, et la plupart du temps, nous passerons par cet objet pour consulter d'autres objets d'une autre nature qui auront un comportement spécifique sur la page.

Voici la démarche à suivre pour récupérer l'objet représentant la page JSP courante :

FacesContext contexte = FacesContext.getCurrentInstance();

Ici, l'objet contexte représente donc la page Web qui est en communication avec notre JavaBean. Nous pouvons, dès lors l'utiliser pour effectuer un certain nombre d'opérations intéressantes par l'intermédiaire de méthodes adaptées à la situation. Voici juste quelques unes de ses méthodes.

javax.faces.context.FacesContext
Méthodes Retour Explication
getCourentInstance() FacesContext Retourne le contexte courant, c'est-à-dire, la page JSP qui sert de conteneur.
addMessage(String client, FacesMessage message) void Permet d'envoyer un message d'alerte ou d'erreur directement sur le composant client qui a provoqué l'erreur.
getApplication() Application Retourne l'objet représentant l'ensemble de l'application Web. Nous connaissons déjà cet objet que nous avons appris à connaître lors de l'étude sur les pages JSP.
getExternalContext() ExternalContext Retourne un objet relatif à tous les éléments externes à la page courante. Cet objet est d'une grande utilitée. Il permet de récupérer toutes les informations issues de la requête, donc d'être en relation avec l'objet request que nous avons déjà utilisé avec les servlets. Nous pouvons également connaître l'objet response, l'objet session, etc.

Il existe bien d'autres méthodes comme getViewRoot() ou getRenderResponse() où nous voyons bien à quoi elles correspondent. Mais, elles sont généralement d'un intérêt plutôt limité.

Ajout de messages sur l'interface utilisateur du client - FacesMessage

Nous pouvons afficher des messages d'alerte sur nos pages JSP par l'intermédiaire de balises adaptées <h:messages> ou <h:message>. Ces alertes apparaissent dans le cas, par exemple, d'une mauvaise saisie. Nous en avons déjà donné un petit aperçu dans l'étude précédente. L'intérêt de ces messages, c'est qu'ils n'apparaissent qu'en cas de problème. Ces messages sont implémentés par la classe javax.faces.application.FacesMessage. Dans la suite de cette étude, nous consacrerons tout un chapitre entier à la gestion de ces messages.

Voici pour l'instant comment nous pouvons créer un message :

FacesContext contexte = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Attention", "Entier attendu");
contexte.addMessage("saisie", message);
javax.faces.application.FacesMessage
Méthodes Retour Explication
SEVERITY_ERROR FacesMessage.Severity Indique qu'une erreur est survenue.
SEVERITY_FATAL FacesMessage.Severity Indique qu'une erreur fatale est survenue.
SEVERITY_INFO FacesMessage.Severity Juste un message d'information.
SEVERITY_WARN FacesMessage.Severity Message qui réclame une attention particulière.
FacesMessage()   Construit un message sans valeurs initiales.
setSeverity(FacesMessage.Severitysévérité) void Précise le type de message.
setSummary(String erreurSommaire) void Donne le message mais de façon sommaire.
setDetail(String détailErreur) void Indique précisément le type d'erreur.
FacesMessage(String erreurSommaire)   Construit un message sommaire.
FacesMessage(FacesMessage.Severity sévérité, String sommaire, String détail)   Construit un message complet avec le type d'erreur adapté. Certainement le constructeur le plus utilisé.

Application Web - Application

La classe javax.faces.application.Application permet de gérer des éléments qui sont placés dans l'application Web. Depuis n'importe quelle page, il est alors possible de créer de nouveaux éléments. Par la suite, une autre page peut ensuite consulter ces nouvelles informations. Nous pouvons par exemple créer de nouveaux types de convertisseur, de nouveaux types de validateur, gérer l'internationalisation.

Voici comment obtenir ce type d'info :

FacesContext contexte = FacesContext.getCurrentInstance();
Application externe = contexte.getApplication();
javax.faces.application.Application
Méthodes Retour Explication
getRessourceBundle(FacesContext ctx, String nom) RessourceBundle Récupère la ressource qui gère l'internationalisation spécifiée en argument.

Contexte de l'application Web - ExternalContext

La classe javax.faces.context.ExternalContext permet d'être en relation avec tous les éléments qui sont en relation avec notre page Web. Ainsi, il est possible de connaître tout ce qui concerne la requête, la réponse, la session en cours, etc.

Voici comment obtenir ce type d'info :

FacesContext contexte = FacesContext.getCurrentInstance();
ExternalContext externe = contexte.getExternalContext();
javax.faces.context.ExternalContext
Méthodes Retour Explication
getApplicationMap() Map Retourne les objets (les attributs) stockés dans l'application Web.
getInitParameter(String paramètre) String Retourne le paramètre initialisé dans le contexte de l'application Web spécifié par le descripteur de déploiement.
getInitParameterMap() Map Délivre l'ensemble des paramètres d'initialisation de l'application Web.
getRemoteUser() String Procure le nom de loggin de l'utilisateur fabriqué dans la requête courante.
getRequestCookieMap() Map Récupère l'ensemble des cookies de la requête.
getRequest() Object Récupère l'objet request que nous connaissons bien.
getRequestContextPath() String Retourne la portion d'URL qui correspond à l'emplacement de l'application Web.
getRequestHeaderMap() Map Retourne l'ensemble de l'en-tête de la requête.
getRequestHeaderValuesMap() Map Retourne l'ensemble de l'en-tête de la requête. Les valeurs sont cette fois-ci sous forme de tableaux de chaînes pour certains éléments d'en-tête.
getRequestMap() Map Retourne les attributs de l'application courante.
getRequestParameterMap() Map Retourne les paramètres de la requête.
getRequestParameterNames() Iterator Retourne les paramètres de la requête en passant par un itérateur.
getRequestParameterValuesMap() Map Retourne les paramètres de la requête en prenant en compte plutôt les tableaux de chaînes.
getRequestPathInfo() String Retourne uniquement la portion de l'URL qui suit le nom de l'application Web.
getRessource(String chemin) URL Permet de récupérer l'URL d'une ressource.
getRessourceAsStream(String chemin) InputStream Récupère une ressource sous forme de flux d'entrée.
getResponse() Object Récupère l'objet response que nous connaissons bien.
getSession(boolean création) Object Mise en oeuvre ou consultation d'une session.
getSessionMap() Map Récupère les attributs créés dans la session.
log(String message) void Envoie un message dans le journal de l'application Web.

Choix du chapitre Attribut binding

Le binding permet de lier un composant JSF à une propriété d'un bean. Lorsque le binding d'un composant est fait, il est alors possible d'agir sur ce composant, et ainsi de changer éventuellement son comportement par programmation directement à l'intérieur du bean. L'attribut binding est présent sur la plupart des balises JSF. Lorsque la balise est exécutée, alors le composant JSF associé sera accessible à partir du bean spécifié.

Nous allons mettre en oeuvre cette spécifité au travers de l'application Web qui permet de retrouver un nombre aléatoire. Nous avons déjà construit cette application Web lors de l'étude précédente. Bien entendu, nous allons rajouter un certain nombre de comportements spécifiques qui vont nous permettre de valider nos propos.

Nous allons modifier notre page d'accueil de telle sorte que lorsque la valeur est trouvée, d'une part la zone de saisie se retrouve en lecture seule, et d'autre part, le bouton "Valider votre choix" se retrouve lui aussi désactivé. Ainsi, il ne sera plus possible de proposer et de valider une autre valeur alors que le nombre est déjà trouvé. La seule possiblité, si nous désirons rester sur cette application Web, est de demander à tout recommencer. Ce qui est d'ailleurs le processus normal attendu.

Dans le cas où le nombre n'est pas trouvé, la page fini.jsp s'affiche alors. Là aussi, nous avons mis en oeuvre une petite nouveauté. Nous rajoutons cette fois-ci une nouvelle colonne à gauche dans le tableau qui précise en réalité le numéro de la ligne courante du tableau.

Nous allons profiter de cette restructuration pour étoffer le comportement de notre application Web. Ainsi, cette fois-ci, il sera possible de régler le nombre de tentative possible pour trouver le bon chiffre. Nous pourrons également choisir la fourchette des valeurs. C'est réglages doivent être spécifiés dans le fichier de configuration <faces-config.xml>.

Modèle MVC

Notre modèle continu à prendre en compte la navigation entre les deux pages alea.jsp et fini.jsp. Par ailleurs, le traitement des informations est toujours réalisé par le bean alea.Nombre qui comportera toutefois des attributs et des méthodes supplémentaires, vu que notre site s'est enrichi.

Constitution de l'application Web et descripteur de déploiement

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file> faces/alea.jsp </welcome-file> </welcome-file-list> </web-app>

Notre descipteur de déploiement <web.xml> est totalement identique à celui que nous avions déjà défini. Effectivement, nous faisons appel à la même page d'accueil. Par ailleurs le contrôleur est toujours le même lorsque nous développons un système JSF. Je rappelle que c'est à l'aide du fichier de configuration <faces-config.xml> que nous décrivons la navigation entre les deux pages Web alea.jsp et fin.jsp ainsi que la déclaration du bean traitant de l'information.

Fichier de configuration <faces-config.xml>

Ce fichier de configuration dispose toujours de trois éléments. Effectivement, nous le savons déjà, nous devons gérer le bean nombre. Par ailleurs, nous disposons de deux pages Web qui sont sources, à partir desquelles, nous pouvons évoluer vers une autre page. Par contre, notre bean nombre possède deux propriétés supplémentaires que nous réglons à l'aide de ce fichier de configuration. Il s'agit de la propriété valeurMaxi qui permet de donner la limite supérieure du nombre à rechercher. Nous avons également la propriété tentativeMaxi qui précise le nombre de coups possibles avant de trouver la solution.

faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
   <navigation-rule>
      <navigation-case>
         <from-action>#{nombre.fini}</from-action>
         <from-outcome>finir</from-outcome>
         <to-view-id>/fin.jsp</to-view-id>
         <redirect />
      </navigation-case>
   </navigation-rule>
   
   <navigation-rule>
      <navigation-case>
         <from-action>#{nombre.recommencer}</from-action>
         <from-outcome>recommencer</from-outcome>
         <to-view-id>/alea.jsp</to-view-id>
         <redirect />
      </navigation-case>
   </navigation-rule>
   
   <managed-bean>
      <managed-bean-name>nombre</managed-bean-name>
      <managed-bean-class>alea.Nombre</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
         <property-name>valeurMaxi</property-name>
         <value>15</value>
      </managed-property>
      <managed-property>
         <property-name>tentativeMaxi</property-name>
         <value>4</value>
      </managed-property>
   </managed-bean>
</faces-config>

Page d'accueil du site alea.jsp

Sur cette page d'accueil, nous devons faire en sorte, cette fois-ci, que la zone de saisie, ainsi que le bouton "Valider votre choix" sont éventuellement grisés lorsque l'opérateur trouve la valeur désirée. C'est le bean nombre qui doit gérer cette situation, puisque c'est lui qui dispose de tous les renseignements nécessaires pour indiquer à quel moment ces composants graphiques doivent être non utilisables.

alea.jsp
 1 <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 2 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 3 
4 <f:view> 5 <html> 6 <body style="background-color: yellow"> 7 <h2><h:outputText value="Recherche d'un nombre entre 1 et #{nombre.valeurMaxi}" /></h2> 8 <hr /> 9 <h:form> 10 <h4> 11 Introduisez votre nombre : 12 <h:inputText value="#{nombre.valeur}" size="2" binding="#{nombre.saisie}" /> 13 <h:outputText value="#{nombre.progression}" style="color: red" /> 14 </h4> 15 <h4>Tentatives : <h:outputText value="#{nombre.tentative}" style="color: red" /> fois</h4> 16 <p> 17 <h:commandButton action="#{nombre.fini}" value="Valider votre choix" binding="#{nombre.bloquer}"/> 18 <h:commandButton action="#{nombre.recommencer}" value="Tout recommencer" /> 19 </p> 20 </h:form> 21 <hr /> 22 <h3><h:outputText value="#{nombre.résultat}" style="color: blue"/></h3>
23 </body> 24 </html> 25 </f:view>

Il faut donc qu'il existe une liaison entre la balise concernée et une propriété qui va représentée le composant dans le bean nombre. Cela s'effectue par l'intermédiaire de l'attribut binding. Vous réglez l'attribut binding en spécifiant la propriété qui va gérer l'intercommunication.

Il faut bien comprendre qu'en réalité une balise JSF est une représentation graphique d'un composant interne non visible. D'ailleurs, nous avons vu que, par exemple, à la balise <h:inputText> correspond l'objet issu de la classe équivalente HtmlInputText. Ainsi, le binding permet tout simplement au bean de se mettre en relation directement avec cet objet interne non visuel. Du coup, tout changement que vous effectuez sur cet objet se répercute bien évidemment sur la partie visuelle. La propriété doit alors correspondre au type du composant pour que la liaison puisse se faire correctement. Ainsi, en reprenant l'exemple de la balise <h:inputText>, nous devons proposer une propriété soit de type HtmlInputText, soit de type UIInput. Si vous désirez faire des manipulations sophistiquées, il est préférable de prendre le premier type d'objet puisqu'il disposera de plus de propriétés. Si seule la valeur saisie vous intéresse, il est alors plus judicieux de prendre un objet de type UIInput.

Nous avons deux composants à griser. Nous devons donc mettre en place l'attribut binding sur les balises correspondantes :

  1. <h:inputText> (Ligne 12 ) : Ici, la propriété saisie du bean nombre est l'objet qui représente cette balise d'entrée.
  2. <h:commandButton> (Ligne 17) : Ici, la propriété bloquer du bean nombre est l'objet qui représente le bouton "Valider votre choix".

Le bean nombre issu de la classe alea.Nombre

Nous passons tout de suite sur le bean nombre afin de découvrir comment est gérer le binding. Le traitement à réaliser est relativement sophistiqué puisque nous agissons sur l'apparence des composants, donc sur leurs parties visuelles. Il parait donc judicieux de prendre des objets qui représentent parfaitement les balises à gérer.

alea.Nombre
  1 package alea;
2
3 import javax.faces.component.*;
4 import javax.faces.component.html.*;
5
6 public class Nombre {
7 private int valeurMaxi;
8 private int tentativeMaxi;
9 private int valeur;
10 private int nombreARechercher;
11 private int tentative;
12 private boolean test;
13 private Integer[] historique;
14
15 private HtmlInputText saisie;
16 private HtmlCommandButton bloquer;
17 private UIData table;
18
19 public HtmlInputText getSaisie() {
20 return saisie;
21 }
22
23 public void setSaisie(HtmlInputText saisie) {
24 this.saisie = saisie;
25 }
26
27 public HtmlCommandButton getBloquer() {
28 return bloquer;
29 }
30
31 public void setBloquer(HtmlCommandButton bloquer) {
32 this.bloquer = bloquer;
33 }
34
35 public UIData getTable() {
36 return table;
37 }
38
39 public void setTable(UIData table) {
40 this.table = table;
41 }
42
43 public int getTentativeMaxi() {
44 return tentativeMaxi;
45 }
46
47 public void setTentativeMaxi(int tentativeMaxi) {
48 this.tentativeMaxi = tentativeMaxi;
49 historique = new Integer[tentativeMaxi];
50 }
51
52 public int getValeurMaxi() {
53 return valeurMaxi;
54 }
55
56 public void setValeurMaxi(int valeurMaxi) {
57 this.valeurMaxi = valeurMaxi;
58 nombreARechercher = (int)(Math.random()*valeurMaxi)+1;
59 }
60
61 public void setValeur(int valeur) {
62 this.valeur = valeur;
63 historique[tentative++] = valeur;
64 test = nombreARechercher == valeur;
65 if (test) {
66 saisie.setReadonly(true);
67 bloquer.setDisabled(true);
68 }
69 }
70
71 public int getValeur() {
72 return valeur;
73 }
74
75 public int getTentative() {
76 return tentative;
77 }
78
79 public String getRésultat() {
80 if (tentative==0) return "Tentez votre chance";
81 if (test) return "Bravo, vous avez trouvé !";
82 else return "Non, ce n'est pas le bon chiffre, refaites un essai.";
83 }
84
85 public String getProgression() {
86 if (tentative==0 || test) return "";
87 return "Le nombre est plus "+(nombreARechercher>valeur ? "grand" : "petit");
88 }
89
90 public String fini() {
91 return !test && tentative>=tentativeMaxi ? "finir" : "continuer";
92 }
93
94 public String recommencer() {
95 nombreARechercher = (int)(Math.random()*valeurMaxi)+1;
96 tentative = 0;
97 valeur = 0;
98 saisie.setReadonly(false);
99 bloquer.setDisabled(false);
100 return "recommencer";
101 }
102
103 public Integer[] getHistorique() {
104 return historique;
105 }
106
107 public int getNombreARechercher() {
108 return nombreARechercher;
109 }
110 }
  1. Lignes 15, 16 et 19 à 33 : Je mets donc en oeuvre la propriété saisie qui est un objet de type HtmlInputText. De même, la propriété bloquer est de type HtmlCommandButton. Il est à noter que nous devons proposer systématiquement des propriétés en mode lecture et écriture. Ainsi, les objets saisie et bloquer représentent l'état actuel des balises correspondantes, bien entendu, celles seulement qui ont proposées le binding (la liaison).
  2. Lignes 65 à 68 et 98 à 99 : Une fois que nous sommes en communication avec ces balises par l'intermédiaire de ces objets, nous pouvons modifier leurs comportement respectifs en écrivant un code adapté. Il s'agit tout simplement de faire appel aux méthodes correspondant aux traitements souhaités. Ainsi, la méthode setReadonly() de l'objet HtmlInputText permet de griser cette zone de saisie et donc d'empêcher momentanément l'écriture de nouvelles valeurs. De même, nous faisons appel à la méthode setDisabled() de l'objet HtmlCommandButton pour que ce dernier bloque le comportement normal du bouton "Valider votre choix".
  3. Lignes 17 et 35 à 41 : Nous disposons ici d'une propriété table qui correspond au tableau de l'historique géré par la page fin.jsp. Remarquez au passage qu'aucun traitement particulier n'est demandé. Cette fois-ci, cette propriété est de type UIData. Je n'ai pas voulu prendre la classe HtmlTableData. En effet, cette classe UIData est suffisante, puisqu'elle comporte déjà les propriétés qui m'intéressent pour le traitement que je souhaite réaliser.

La page fin.jsp

Passons donc à cette page fin.jsp où nous allons découvrir comment nous récupérons la ligne courante d'un tableau dynamique issu de la balise <h:dataTable>.

fin.jsp
 1 <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 2 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 3 
4 <style type="text/css"> 5 .cadre { background-color: #FFCC00; padding: 5px; border : groove; font-weight: bold; } 6 .rouge { color: red; } 7 </style> 8
9 <f:view> 10 <html> 11 <body style="background-color: yellow"> 12 <h2 class="cadre"> 13 C'est fini : il fallait trouver le nombre en 14 <h:outputText value="#{nombre.tentativeMaxi}" styleClass="rouge"/> 15 coups 16 </h2> 17 <h:dataTable value="#{nombre.historique}" var="historique" styleClass="cadre"
18 binding="#{nombre.table}" rules="all" cellpadding="3">
19 <f:facet name="header"> 20 <h:outputText value="Historique des coups réalisés" /> 21 </f:facet> 22 <h:column> 23 <h:outputText value="Coup n°#{nombre.table.rowIndex}" /> 24 </h:column> 25 <h:column> 26 <h:outputText value=" #{historique}" /> 27 </h:column> 28 </h:dataTable> 29 <h2 class="cadre"> 30 Nombre à retrouver : 31 <h:outputText value="#{nombre.nombreARechercher}" styleClass="rouge"/> 32 <h:form> 33 <h:commandLink action="#{nombre.recommencer}" value="Recommencer" title="Refaire d'autres essais"/> 34 </h:form> 35 </h2>
36 </body> 37 </html> 38 </f:view>
  1. Ligne 17 et 18 : mise en place du binding en mettant en relation cette balise <h:dataTable> avec la propriété table que nous venons d'évoqué dans la rubrique précédente.
  2. Ligne 23 : Cette propriété table est donc un objet de type UIData et représente le tableau dynamique de cette page. Cet objet dispose, bien entendu, d'un certain nombre de propriétés que nous pouvons utiliser directement. Ici, j'utilise tout simplement la propriété rowIndex qui me délivre le numéro de la ligne courante sans que j'ai besoin d'écrire une quelconque ligne de code dans le bean correspondant. Du coup, nous pouvons nous demander si la déclaration de l'objet de type UIData est nécessaire dans le bean nombre, vu qu'aucune ligne de code n'est écrite dans ce dernier. Effectivement, il reste nécessaire de créer une propriété dans le bean correspondant à cette table afin que par la suite nous puissions l'utiliser à des endroits différents dans la page Web.

Choix du chapitre Récupération des informations sur le contexte externe à la page courante

Ce chapitre va nous permettre de découvrir comment récupérer des informations sur l'ensemble du contexte de l'application Web. Nous en profiterons pour réaliser un téléchargement. De nouveau, nous allons nous servir de la même application Web que nous avons mis en oeuvre dans le chapitre précédent. Nous allons juste rajouter sur la page d'accueil un bouton "Informations générales" qui lorsque nous cliquons dessus permet de télécharger les informations requises.

Ce type d'information n'a pas un gros intérêt sur cette application Web. En réalité, j'ai pris cette application pour éviter d'en refaire une autre.
.

Pour ce chapitre, nous consulterons juste les fichiers qui ont subit des modifications. Nous allons donc revoir le fichier alea.jsp et le JavaBean alea.Nombre.
.

Page d'accueil du site alea.jsp

Sur cette page d'accueil, je place juste une ligne supplémentaire qui permet d'afficher ce nouveau bouton (Ligne 19). Comme action, je propose de lancer la méthode info() du bean nombre.

alea.jsp
 1 <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 2 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 3 
4 <f:view> 5 <html> 6 <body style="background-color: yellow"> 7 <h2><h:outputText value="Recherche d'un nombre entre 1 et #{nombre.valeurMaxi}" /></h2> 8 <hr /> 9 <h:form> 10 <h4> 11 Introduisez votre nombre : 12 <h:inputText value="#{nombre.valeur}" size="2" binding="#{nombre.saisie}" /> 13 <h:outputText value="#{nombre.progression}" style="color: red" /> 14 </h4> 15 <h4>Tentatives : <h:outputText value="#{nombre.tentative}" style="color: red" /> fois</h4> 16 <p> 17 <h:commandButton action="#{nombre.fini}" value="Valider votre choix" binding="#{nombre.bloquer}"/> 18 <h:commandButton action="#{nombre.recommencer}" value="Tout recommencer" /> 19 <h:commandButton action="#{nombre.info}" value="Informations générales" /> 20 </p> 21 </h:form> 22 <hr /> 23 <h3><h:outputText value="#{nombre.résultat}" style="color: blue"/></h3>
24 </body> 25 </html> 26 </f:view>

Le bean nombre issu de la classe alea.Nombre

Nous passons tout de suite sur le bean nombre afin de découvrir ce que fait cette méthode info(). D'ailleurs, j'ai enlevé tout le reste du code pour que nous soyons concentré sur le sujet.

alea.Nombre
  1 package alea;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import javax.faces.component.*;
6 import javax.faces.component.html.*;
7 import javax.faces.context.*;
8 import javax.servlet.http.*;
9
10 public class Nombre {
114 ...
115 public void info() {
116 ExternalContext externe = FacesContext.getCurrentInstance().getExternalContext();
117 HttpServletResponse réponse = (HttpServletResponse) externe.getResponse();
118 HttpServletRequest requête = (HttpServletRequest) externe.getRequest();
119 réponse.setContentType("application/octet-stream");
120 try {
121 PrintWriter out = réponse.getWriter();
122 out.println("Type de contenu du document : "+requête.getContentType());
123 out.println("Adresse IP locale : "+requête.getLocalAddr());
124 out.println("Application Web : "+requête.getContextPath());
125 out.println("Nom local : "+requête.getLocalName());
126 out.println("Port local : "+requête.getLocalPort());
127 out.println("Page web sollicité : "+requête.getPathInfo());
128 out.println("Répertoire local : "+requête.getPathTranslated());
129 out.println("Protocole : "+requête.getProtocol());
130 out.println("Adresse client : "+requête.getRemoteAddr());
131 out.println("Nom ordinateur client : "+requête.getRemoteHost());
132 out.println("Port du client : "+requête.getRemotePort());
133 out.println("URI complète de l'application Web : "+requête.getRequestURI());
134 out.println("Identifiant de la session : "+requête.getRequestedSessionId());
135 out.println("Nom du serveur : "+requête.getServerName());
136 out.println("Port du serveur : "+requête.getServerPort());
137 out.println("URI du servlet contrôleur : "+requête.getServletPath());
138 out.println("Application Web : "+externe.getRequestContextPath());
139 out.println("Page web sollicité : "+externe.getRequestPathInfo());
140 out.println("URI du servlet contrôleur : "+externe.getRequestServletPath());
141 out.close();
142 }
143 catch (IOException ex) {
144 ex.printStackTrace();
145 }
146 }
147 }
  1. Lignes 116 : Vu que je m'intéresse au contexte général de l'application Web, je passe par un objet de type ExternelContext qui est fourni par le contexte de la page elle-même au travers, cette fois-ci, de la classe FacesContext..
  2. Lignes 117 et 118 : A partir de là, il est possible de récupérer à la fois l'objet request correspondant à la requête proposée sur notre page Web courante, ainsi que l'objet response qui représente l'information qui va être envoyée en résultat de la requête.
  3. Lignes 119 : Pour qu'un téléchargement soit proposé au moyen d'une boîte de dialogue lancée automatiquement par les navigateurs, il faut régler le type MIME en conséquence au moyen de la méthode setContentType() de l'objet response. Si vous prévoyez par exemple le type "text/html", tout ce que vous écrivez dans response va être considéré comme la structure d'une page Web, et va donc être affiché par le navigateur à la place de la page courante. Si vous prévoyez plutôt le type "text/plain", le navigateur va également affiché votre texte à la place de votre page courante sans toutefois l'interpréter. Il s'agit dans ce cas là d'un texte brut. Si vous désirez conserver votre page Web courante et ainsi solliciter le téléchargement, il faut prendre alors un type MIME plus générique, non interprétable par quoi que ce soit. Pour cela, vous indiquez que c'est une application spécifique (et non le navigateur) qui va gérer cette information. Du coup, nous envoyons notre ensemble d'information tout simplement sous forme de flux d'octets qui sera interprété, non plus par le navigateur, mais par le logiciel prévu à cet effet, qui sera choisi ultérieurement après téléchargement. Finalement, le type MIME adapté à tout téléchargement est "application/octet-stream".
  4. Lignes 121 : Le navigateur va recevoir un flux d'octets. Pour lui, la seule solution est donc d'afficher une boîte de dialogue qui propose le téléchargement. Par contre, rien empêche pour le programme client, d'avoir une information de plus haut niveau. Ce qui m'intéresse ici, c'est de stocker mes informations sous forme de texte. Ainsi, il sera possible de l'ouvrir avec un simple bloc-note. Du coup, je propose de mettre en oeuvre un flux de plus haut niveau correspondant finalement à un flux de texte au moyen de la classe PrintWriter.
  5. Lignes 122 à 140 : Ensuite, il suffit de faire appel à une méthode particulière de l'objet request pour récupérer votre renseignement spécifique. L'objet externe propose également quelques petites méthodes qui délivrent peut-être plus directement l'information souhaitée.

Nous avons ici une technique simple pour mettre en oeuvre un téléchargement. Malheureusement, il n'est pas prévu de choisir le nom du fichier à télécharger. Le système choisi, comme nom de fichier, le nom de la page Web en cours, donc ici alea.jsp. Si vous désirez maîtriser le nom du fichier, vous êtes obligé de passer par une servlet annexe et de régler le descripteur de déploiement en conséquence. Nous avons déjà mis en oeuvre ce dernier point lors d'une étude précédente.

Choix du chapitre Rôle de la conversion et de la validation des données

La question qui se pose souvent est : Que se passe-t-il si l'opérateur fait une mauvaise saisie ? La première démarche consisterait à prendre en compte ce problème au niveau du modèle métier, c'est-à-dire à l'intérieur du JavaBean qui traite l'information. Dans ce cas de figure, le code du bean serait considérablement alourdi. Par ailleurs, nous retrouverions du traitement qui ne correspondrait plus à la logique métier. Cette démarche n'est pas souhaitable. Il est préférable de séparer les problèmes.

En réalité, JSF réagit tout autrement. L'idée ici, est que le JavaBean reçoivent déjà les bonnes valeurs adaptées au traitement souhaité pour qu'il sache uniquement comment se comporter devant une situation normale sans qu'il soit nécessaire de rajouter du code supplémentaire. Du coup, lors de la saisie de données par l'utilisateur, il est souvent indispensable de valider et ou de convertir les valeurs saisies avant de les injecter dans un JavaBean.

Il est vraiment très important que les valeurs saisies soient d'abord adaptées au traitement souhaité, correspondent ainsi au format prédéfini et que les valeurs proposées correspondent également à la fourchette des valeurs requises.

Dans ce chapitre nous allons réaliser juste une petite expérience sur la validation et la conversion de valeurs, ainsi qu'à l'affichage de messages d'erreurs adaptés à la situation. Nous reprendrons chacun de ces points bien en détail dans les chapitres suivants.

Application Web - Alea

Afin de valider notre petite expérience, nous allons de nouveau se servir de l'application Web sur la recherche d'un nombre aléatoire.

Sources du bean alea.Nombre et de la page d'accueil

Sur ce bean, nous rajoutons juste une petite propriété jour qui calcule et nous renvoie la date du jour :

Nombre.java
public class Nombre {
...
public Date getJour() {
return new Date();
}
}

Par ailleurs, nous modifions très légèrement le code de la page d'accueil.

alea.jsp
 1 <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 2 <%@ taglib uri="http://java.sun.com/jsf/html"