ATTENTION : Tutorial en cours d'écriture ! N'hésitez pas à nous signaler toute erreur ou suggestion.
Accès rapide : Le modèle JavaBeans Liaison aux composants JavaBeans Utilisation d'annotations Liaison aux données et utilisation d'un Repeater
Pour bien comprendre le modèle de liaison aux données du framework NWS, il est important de rappeler qu'en Java, on ne code pas une classe n'importe comment. Normalement, vous êtes censé respecter le modèle JavaBeans : il s'agit en réalité d'une simple convention de codage permettant à un outil quelconque (ici le framework NWS) de trouver à l'exécution (au runtime) l'ensemble des propriétés et des gestionnaires d'événements portés par une instance. La découverte de ces éléments est possible grâce au moteur de réflexion de Java qui a été étendu pour supporter JavaBeans : on parle alors d'introspection. Dit autrement, le moteur d'introspection (package java.beans) est une extension du moteur de réflexion (package java.lang.reflect) permettant de trouver toutes les méthodes préfixées de get/set/is pour déterminer les propriétés et toutes les méthodes respectant le pattern addXxxListener pour déterminer les méthodes d'enregistrement d'écouteurs (Listener == modèle de gestion d'événements de Java).
Pour la liaison aux données, seules les propriétés JavaBeans nous intéressent. A titre de rappel, une propriété est différente d'un attribut. L'attribut correspond à une variable faisant partie de l'objet et elle stocke une valeur : on parle aussi d'une donnée membre (de l'instance). Au contraire une propriété correspond à une paire de méthodes : elles servent généralement à accéder en lecture et en écriture à un attribut (bien que dans certains cas subtils, cela ne soit pas une obligation).
Les composants Web NWS peuvent donc être liés aux données accessibles par l'intermédiaire des propriétés de vos objets JavaBeans. Qui plus est, la classe de page NWS peut elle aussi exposer des propriétés auxquelles vous pourrez lier vos composants WEB.
Le plus simple pour comprendre le modèle de liaison aux données et d'étudier un exemple concret. Cet exemple est accessible à partir de l'application de démonstration du framework NWS (activez ce lien pour la télécharger). Plus précisément, la page en question est samples/DataBinding.wp. Deux classes sont de plus utilisées : corelib.services.web.samples.virtualcaddy.webpages.samples.DataBinding et corelib.services.web.samples.virtualcaddy.business.Article. Voici, en premier lieu, le code de cette dernière classe.
01 package corelib.services.web.samples.virtualcaddy.business; 02 03 public class Article { 04 05 private int idArticle; 06 private String brand; 07 private String designation; 08 private double unitaryPrice; 09 10 11 public Article( int idArticle, String brand, String designation, double unitaryPrice ) { 12 this.setIdArticle( idArticle ); 13 this.setBrand( brand ); 14 this.setDesignation( designation ); 15 this.setUnitaryPrice( unitaryPrice ); 16 } 17 18 19 public int getIdArticle() { 20 return idArticle; 21 } 22 23 public void setIdArticle( int idArticle ) { 24 if ( idArticle < 0 ) throw new RuntimeException( "Identifier must be positive" ); 25 this.idArticle = idArticle; 26 } 27 28 public String getBrand() { 29 return brand; 30 } 31 32 public void setBrand( String brand ) { 33 this.brand = brand.toUpperCase(); 34 } 35 36 public String getDesignation() { 37 return designation; 38 } 39 40 public void setDesignation( String designation ) { 41 this.designation = designation.toLowerCase(); 42 } 43 44 public double getUnitaryPrice() { 45 return unitaryPrice; 46 } 47 48 public void setUnitaryPrice( double unitaryPrice ) { 49 if ( unitaryPrice < 0 ) throw new RuntimeException( "Unitary price must be positive" ); 50 this.unitaryPrice = unitaryPrice; 51 } 52 53 public String toString() { 54 return this.idArticle + ": " + this.designation + " of brand " + 55 this.brand + " - " + this.unitaryPrice + " €"; 56 } 57 }
Notez bien que cette classe expose quatre propriétés : getIdArticle/setIdArticle, getBrand/setBrand, getDesignation/setDesignation et getUnitaryPrice/setUnitaryPrice. Voici maintenant le code de la classe de page Web.
01 package corelib.services.web.samples.virtualcaddy.webpages.samples; 02 03 import corelib.services.web.samples.virtualcaddy.business.Article; 04 import corelib.services.web.server.WebPage; 05 import corelib.services.web.server.events.WebPageEvent; 06 07 @SuppressWarnings( "unused" ) 08 public class DataBinding extends WebPage{ 09 10 private static int displayCounter = 0; 11 private Article theArticle = 12 new Article( 0, "The brand", "many informations", 10 ); 13 14 15 @Override public void page_load( WebPageEvent webPageEvent ) { 16 DataBinding.displayCounter ++; 17 } 18 19 20 public int getDisplayCounter() { 21 return DataBinding.displayCounter; 22 } 23 24 public Article getArticle() { 25 return this.theArticle; 26 } 27 28 }
Notez la présence, sous forme d'attribut, d'une instance de la classe Article. Nous allons revenir par la suite sur les autres membres de cette classe. Néanmoins, il nous manque un fichier pour nos explications : la page Web à proprement parler. En voici le code.
01 <?xml version="1.0" encoding="ISO-8859-1" ?> 02 <web:Html xmlns:web="corelib.services.web.components" 03 xmlns:demo="corelib.services.web.samples.virtualcaddy.webcomponents" 04 codeBehind="corelib.services.web.samples.virtualcaddy.webpages.samples.DataBinding"> 05 <head> 06 <title>Data Binding Sample</title> 07 <link rel="stylesheet" type="text/css" href="../CssStyles.css" /> 08 </head> 09 <body> 10 <h1>Data Binding Sample</h1> <br /> 11 12 <h2>Article description</h2> 13 14 <b>Identifier</b>: <web:OutputText text="#{theArticle.idArticle}" /> <br/> 15 <b>Designation</b>: <web:OutputText text="#{theArticle.designation}" /> <br/> 16 <b>Brand</b>: <web:OutputText text="#{theArticle.brand}" /> <br/> 17 <b>Identifier</b>: <web:OutputText text="#{this.article.unitaryPrice}" /> <br/> 18 <br/> 19 <b>Display count</b>: <web:OutputText text="#{this.displayCounter}" /> 20 </body> 21 </web:Html>
Bon, reprenons tout cela calmement. A la ligne 04 du fichier DataBinding.wp, la page Web est associée à sa classe de page (DataBinding.java). Ensuite, à partir de la ligne 14, la page Web lie des composants Web de type <web:OutputText> à des données portées par la classe de page Web. Une expression de liaisons aux données est introduite par la syntaxe #{ et se termine par }. Le contenu de cette expression se construit en tenant compte du modèle JavaBeans. Néanmoins trois variantes sont tolérées.
Première alternative : l'expression de liaison commence par le nom d'un attribut de la classe de page. Dans ce cas, la suite correspond à une ou plusieurs propriétés. Par exemple, l'expression #{theArticle.idArticle} vous permettra de récupérer la valeur retournée par theArticle.getIdArticle(). Si la valeur d'une propriété est elle aussi un objet vous pouvez continuer à descendre dans ses propres propriétés (ce sans limite de profondeur). Le fait de pouvoir directement accéder aux attributs de la classe de page permet de simplifier le codage de cette dernière.
Seconde alternative : l'expression de liaison commence par le nom d'une entrée de la session utilisateur ou celui d'une entrée des données d'application (les données d'application sont partagées par tous les utilisateurs du site). Dans ce cas la suite de l'expression correspond au nom d'une propriété de l'objet stocké dans la session ou dans l'application.
Troisième alternative : l'expression de liaison aux données commence par this. Dans ce cas, la suite de l'expression doit obligatoirement correspondre à une propriété de la classe de page. Considérons la ligne 17 de la classe de page : on y trouve l'expression de liaison #{this.article.unitaryPrice}. De cette expression on peut directement en déduire que la classe de page fournie une méthode getArticle() : effectivement nous pouvons confirmer ce point à la ligne 24 de la classe de page. Comme la méthode getArticle renvoie un article, on peut ensuite descendre dans les propriétés de la classe considérée (en l'occurrence, getUnitaryPrice()).
ATTENTION : si l'expression de liaison est utilisée sur un composant de saisie de formulaire (quelque soit sa nature <web:TextBox>, ...) et si les méthodes d'accès en écriture (les setters) nécessaires à l'expression sont bien présentes, alors les données saisies dans le formulaire seront stockées dans les beans associés, après soumission du formulaire.
La ligne 19 de la page Web vous montre un exemple ou la valeur calculée par l'expression évolue au cours du temps. Effectivement, en ligne 15 de la classe de page, la méthode page_load (un gestionnaire d'événements qui déclenche à chaque demande de la page) incrémente la valeur d'un compteur statique à chaque appel. A la ligne 20, de ce même fichier, la méthode getDisplaycounter retourne la valeur de ce compteur. L'expression #{this.displayCounter} permet donc de récupérer la valeur de ce compteur (troisième alternative présentée plus haut).
Le framework NWS introduit une possibilité intéressante vis-à-vis de la liaison aux données : vous pouvez utiliser les annotations pour contrôler la persistance de vos beans. Mais, avant de parler de cette possibilité, revenons quelques instants sur la notion d'annotation Java.
Java 5 (J2SE 5.0) a introduit la notion d'annotations. Une annotation est une méta-données qui est adjointe à votre code. L'annotation est compilée et stockée dans le .class associé. Quand cette classe sera montée en mémoire, il sera possible, par l'intermédiaire du moteur de réflexion Java (package java.lang.reflect), de savoir si un élément (attribut, méthode, ...) de la classe considérée a été annoté. Cela permet d'envisager des framework qui vont pouvoir effectuer certaines tâches automatiquement. Par exemple, le framework Hibernate, exploite des annotations pour réaliser un mapping entre des objets Java et des tables dans une base de données relationnelle : ainsi, ce framework génère automatiquement des ordres SQL pour enregistrer vos objets en base. Il en va quasiment de même pour le framework NWS qui peut automatiquement prendre en charge la persistance de certains de vos objets dans la session utilisateur ou dans les données d'applications.
Deux annotations peuvent être utilisées pour indiquer au framework NWS de stocker vos beans en session et dans les données d'application. Ces annotations doivent être placées devant les attributs de la classe de page que vous souhaitez rendre persistants entre les différentes requêtes.
corelib.services.web.annotations.Application : l'annotation @Application permet de stocker l'attribut dans les données d'application. Pour information, pour récupérer une valeur dans les données d'application à partir d'une page NWS, utilisez l'instruction suivante : this.application.getAttribut( "key" );.
this.application.getAttribut( "key" );
corelib.services.web.annotations.Session : l'annotation @Session permet de stocker l'attribut considéré dans la session de l'utilisateur demandant la page web.
Afin de mieux comprendre le concept, étudions la page NWS d'affichage d'articles utilisés dans l'application de démonstration, ainsi que sa classe de page. Les deux sections de code qui suivent correspondent à la mise en oeuvre de la dite page.
01 <?xml version="1.0" encoding="ISO-8859-1" ?> 02 <web:Html xmlns:web="corelib.services.web.components" 03 xmlns:caddy="corelib.services.web.samples.virtualcaddy.webcomponents" 04 codeBehind="corelib.services.web.samples.virtualcaddy.webpages.SelectArticle"> 05 <head> 06 <title>Select your article</title> 07 <link rel="stylesheet" type="text/css" href="CssStyles.css" /> 08 </head> 09 <body> 10 <h1>Select your article</h1> <br /> 11 12 <div align="center"> 13 14 <table border="0" width="40%"> 15 <tr> 16 <td width="40%">Identifier:</td> 17 <td width="60%"> 18 <web:OutputText id="lblIdentifier" cssClass="LabelBox" 19 text="#{catalogBrowser.currentArticle.idArticle}" /> 20 </td> 21 </tr> 22 <tr> 23 <td width="40%">Brand:</td> 24 <td width="60%"> 25 <web:OutputText cssClass="LabelBox" 26 text="#{catalogBrowser.currentArticle.brand}" /> 27 </td> 28 </tr> 29 <tr> 30 <td width="40%">Designation:</td> 31 <td width="60%"> 32 <web:OutputText cssClass="LabelBox" 33 text="#{catalogBrowser.currentArticle.designation}" /> 34 </td> 35 </tr> 36 <tr> 37 <td width="40%">Unitary Price:</td> 38 <td width="60%"> 39 <web:OutputText cssClass="LabelBox" 40 text="#{catalogBrowser.currentArticle.unitaryPrice}" /> 41 </td> 42 </tr> 43 </table> 44 45 <web:Form method="POST"> 46 <web:Button id="btnPrevious" value="Previous" /> 47       48 <web:Button id="btnAdd" value="Add" /> 49       50 <web:Button id="btnNext" value="Next" /> 51 </web:Form> 52 </div> 53 54 <caddy:CaddyFooter /> 55 </body> 56</web:Html>
01 package corelib.services.web.samples.virtualcaddy.webpages; 02 03 04 import corelib.services.web.annotations.Session; 05 import corelib.services.web.components.Button; 06 import corelib.services.web.components.events.ActionEvent; 07 import corelib.services.web.components.events.ActionListener; 08 import corelib.services.web.samples.virtualcaddy.backingbeans.CatalogBrowser; 09 import corelib.services.web.server.WebPage; 10 import corelib.services.web.server.events.WebPageEvent; 11 12 13 public class SelectArticle extends WebPage { 14 15 @Session private CatalogBrowser catalogBrowser; 16 17 private Button btnPrevious = null; 18 private Button btnAdd = null; 19 private Button btnNext = null; 20 21 public void page_load( WebPageEvent event ) { 22 this.btnPrevious.addActionListener( new ActionListener() { 23 public void actionPerformed( ActionEvent event ) { 24 btnPrevious_actionPerformed( event ); 25 } 26 }); 27 this.btnAdd.addActionListener( new ActionListener() { 28 public void actionPerformed( ActionEvent event ) { 29 btnAdd_actionPerformed( event ); 30 } 31 }); 32 this.btnNext.addActionListener( new ActionListener() { 33 public void actionPerformed( ActionEvent event ) { 34 btnNext_actionPerformed( event ); 35 } 36 }); 37 38 } 39 40 41 public void btnPrevious_actionPerformed( ActionEvent event ) { 42 this.catalogBrowser.moveToPreviousArticle(); } 43 44 45 public void btnNext_actionPerformed( ActionEvent event ) { 46 this.catalogBrowser.moveToNextArticle(); 47 } 48 49 public void btnAdd_actionPerformed( ActionEvent event ) { 50 51 } 52 53 }
En ligne 15 du fichier SelectArticle.java vous noterez la présence de l'attribut catalogBrowser. Notez aussi que cet attribut n'est pas initialisé : il a donc comme valeur null. Néanmoins, cet attribut est préfixé de l'annotation @Session. A chaque requête HTTP, le framework NWS va donc chercher à initialiser cet attribut avec une instance stockée dans la session de l'utilisateur. Le nom de la clé dans la session, sera par défaut celui de l'attribut (donc, ici "catalogBrowser"). Si aucune donnée n'existe en session pour l'entrée considérée, une nouvelle instance sera créée : il faut donc impérativement que la classe considérée possède un constructeur acceptant aucun paramètre. Si l'attribut annoté n'est pas du type d'une donnée déjà présente en session (pour la clé considérée), une exception sera produite au runtime.
La classe CatalogBrowser permet de naviguer dans un catalogue d'articles, tout en conservant la position de l'article en cours de présentation. La position étant propre à chaque client, il est donc logique de stocker une telle instance en session utilisateur. Cette classe propose de plus la méthode getCurrentArticle() qui renvoie l'article en cours de visualisation.
Il nous est donc possible d'écrire la page Web SelectArticle.wp de manière à utiliser la liaison aux données. L'expression #{catalogBrowser.currentArticle.designation} permet donc d'accéder à la désignation de l'article en cours de visualisation.
#{catalogBrowser.currentArticle.designation}
Toujours si l'on regarde l'application de démonstration du framework NWS, la méthode CatalogBrowser.getAllArticles() renvoie un tableau d'articles (Article []). Il est possible de lier un composant Web de type <web:Repeater> à un ensemble de beans. Voici le code de la page samples/DataBinding2.wp ainsi que celui de la classe de page associée
01 package corelib.services.web.samples.virtualcaddy.webpages.samples; 02 03 import corelib.services.web.annotations.Session; 04 import corelib.services.web.samples.virtualcaddy.backingbeans.CatalogBrowser; 05 import corelib.services.web.server.WebPage; 06 07 @SuppressWarnings( "unused" ) 08 public class DataBinding2 extends WebPage { 09 10 @Session private CatalogBrowser catalogBrowser; 11 12 }
01 <?xml version="1.0" encoding="ISO-8859-1" ?> 02 <web:Html xmlns:web="corelib.services.web.components" 03 xmlns:demo="corelib.services.web.samples.virtualcaddy.webcomponents" 04 codeBehind="corelib.services.web.samples.virtualcaddy.webpages.samples.DataBinding2"> 05 <head> 06 <title>Data Binding Sample</title> 07 <link rel="stylesheet" type="text/css" href="../CssStyles.css" /> 08 </head> 09 <body> 10 <h1 align="center">Data Binding Sample</h1> <br /> 11 12 <div align="center"> 13 <table border="1" width="60%"> 14 <tr> 15 <th width="25%">Identifier</th> 16 <th width="25%">Brand</th> 17 <th width="25%">Designation</th> 18 <th width="25%">Unitary price</th> 19 </tr> 20 <web:Repeater values="#{catalogBrowser.allArticles}" elementAlias="article"> 21 <tr> 22 <td><web:OutputText text="#{article.idArticle}" /></td> 23 <td><web:OutputText text="#{article.brand}" /></td> 24 <td><web:OutputText text="#{article.designation}" /></td> 25 <td><web:OutputText text="#{article.unitaryPrice}" /></td> 26 </tr> 27 </web:Repeater> 28 <caption align="bottom">Liste des articles du site</caption> 29 </table> 30 </div> 31 </body> 32 </web:Html>
Comme vous pouvez l'observer, le composant Web de type <web:Repeater> est lié aux données. Mais comme la donnée liée est de type tableau (cela marche aussi avec les collections de type java.util.list), le composant Web va dupliquer son contenu pour chaque élément de la collection. Afin de faciliter la définition des expressions de liaison, un alias est créé à chaque itération (ici article).
Dominique LIARD - © 2007 SARL Infini Software - Tous droits réservés Les autres marques et les noms de produits cités dans ces documents sont la propriété de leurs détenteurs respectifs.