Stripes MVC 1.5.7
1 Présentation
Stripes est un framework Java MVC comme l’est Struts ou Spring MVC. Il fonctionne sur le même principe. Il n’est donc pas orienté application avec des composants comme le définit la norme JSF (en utilisant le framework PrimeFaces), ni les alternatives type GWT ou Vaadin.
Stripes permet de construire des sites Web. L’objectif de Stripes est de proposer un framework :
- zéro configuration par défaut,
- possible de l’apprendre en 30 minutes.
Il propose pour cela :
- l’auto-discover des classes d’action et utilisation des annotations,
- un système de localisation,
- suppression des classes de bean,
- support transparent des téléchargements de fichiers,
- possibilité de réutilisation des écrans...
2 Mise en place
2.1 Dépendance maven
Créez un projet maven de type web. Editez le fichier « pom.xml » et ajoutez la dépendance suivante :
<dependency> <groupId>net.sourceforge.stripes</groupId> <artifactId>stripes</artifactId> <version>1.5.7</version> </dependency>
2.2 Configuration principal
Stripes ne requière pas de fichier de configuration mais il faut tout de même configurer son application pour indiquer d’utiliser Stripes comme moteur MVC dans le fichier de configuration src/main/webapp/WEB-INF/web.xml. Voici un exemple :
<?xml version="1.0" encoding="ISO-8859-1"?> <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"> <filter> <filter-name>StripesFilter</filter-name> <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class> <init-param> <param-name>ActionResolver.Packages</param-name> <param-value>org.test.exemple</param-value> </init-param> </filter> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <filter-mapping> <filter-name>StripesFilter</filter-name> <servlet-name>DispatcherServlet</servlet-name> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>Home.htm</welcome-file> </welcome-file-list> </web-app>
On vient donc de mettre Stripes comme filtre de requête. On indique le package de base des classes d’action org.test.exemple pour permettre l’auto-discover.
2.3 Fichier de ressources
Stripes nécessite tout de même un fichier de ressources avec les messages d’erreurs src/main/resources/StripesResources.properties. Dont voici le contenu :
# Resource strings used by the <stripes:errors> tag when there are no nested tags stripes.errors.header=<div style="color:#b72222; font-weight: bold">Please fix the following errors:</div><ol> stripes.errors.beforeError=<li style="color: #b72222;"> stripes.errors.afterError=</li> stripes.errors.footer=</ol> # Resource strings used by the <stripes:errors> tag when displaying errors for a # specific field (e.g. <stripes:errors field="password"/>). If not supplied the # values above will be used instead. stripes.fieldErrors.header= stripes.fieldErrors.beforeError=<span style="color: #b72222;"> stripes.fieldErrors.afterError=</span><br /> stripes.fieldErrors.footer= # Resource strings used by the stripes:messages tag stripes.messages.header=<ul class="messages"> stripes.messages.beforeMessage=<li> stripes.messages.afterMessage=</li> stripes.messages.footer=</ul> # Validation error messages produced by Stripes' built-in converter classes. These # are default error messages and can be overridden on per-field and per-form levels. # Using the 'invalidNumber' error for a field 'age' of a form posting to # '/user/Profile.action', the keys looked for (in order) would be: # 1: /user/Profile.action.age.invalidNumber # 2: /user/Profile.action.age.errorMessage # 3: age.errorMessage # 4: /user/Profile.action.invalidNumber # 5: converter.number.invalidNumber converter.number.invalidNumber=The value ({1}) entered in field {0} must be a valid number converter.byte.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3} converter.short.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3} converter.integer.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3} converter.float.outOfRange=The value ({1}) entered in field {0} was out of the range {2} to {3} converter.enum.notAnEnumeratedValue=The value "{1}" is not a valid value for field {0} converter.date.invalidDate=The value ({1}) entered in field {0} must be a valid date converter.email.invalidEmail=The value ({1}) entered is not a valid email address converter.creditCard.invalidCreditCard=The value ({1}) entered is not a valid credit card number # Validation error messages produced by Stripes' annotation based validations. These # are default error messages and can be overridden on per-field and per-form levels. # Using the 'valueNotPresent' required field error for a field 'username' of a form # posting to '/user/Register.action', the keys looked for (in order) would be: # 1: /user/Register.action.username.valueNotPresent # 2: /user/Register.action.username.errorMessage # 3: username.errorMessage # 4: /user/Register.action.valueNotPresent # 5: validation.required.valueNotPresent validation.required.valueNotPresent={0} is a required field validation.minlength.valueTooShort={0} must be at least {2} characters long validation.maxlength.valueTooLong={0} must be no more than {2} characters long validation.minvalue.valueBelowMinimum=The minimum allowed value for {0} is {2} validation.maxvalue.valueAboveMaximum=The maximum allowed value for {0} is {2} validation.mask.valueDoesNotMatch=<em>{1}</em> is not a valid {0} validation.expression.valueFailedExpression=The value supplied ({1}) for field {0} is invalid validation.file.postBodyTooBig=Total upload size of {3} KB exceeds the maximum size of {2} KB
3 Page d’accueil
Dans l’exemple précédent, on indique que les pages .html sont gérées par Stripes. On indique aussi que la page d’accueil est Home.html.
Bien évidemment, il n’existe pas de fichier Home.html.
3.1 Classe d’action
La classe d’action sera donc dans le package org.test.exemple. Elle portera un nom non significatif.
En effet, c’est une annotation qui indiquera l’url de la classe HomeActionBean.java :
package org.test.exemple.action; import net.sourceforge.stripes.action.DefaultHandler; import net.sourceforge.stripes.action.ForwardResolution; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.action.UrlBinding; import net.sourceforge.stripes.action.ActionBean; @UrlBinding("/Home.htm") public class HomeActionBean extends ActionBean { private ActionBeanContext context ; private String message ; @DefaultHandler public Resolution view() throws IOException { message = "Bonjour ! Ca s'affiche" ; return new ForwardResolution("/WEB-INF/jsp/home.jsp"); } public String getMessage() { return message; } public void setContext(ActionBeanContext actionbeancontext) { this.context = actionbeancontext ; } public abstract ActionBeanContext getContext() { return context ; } }
Que constate-t-on ?
- La classe d’action est dans un sous package sans rien dire de plus,
- L’annotation UrlBinding + l’héritage de la classe ActionBean indique à Stripes qu’il s’agit d’une classe d’action,
- On a un attribut message avec un getter associe,
- On a une méthode view() avec une annotation et un retour,
- La nécessité d’implémenter setContext() et getContext().
3.1.1 Annotation UrlBinding
L’annotation UrlBinding fait le lien entre l’url récupérer par Stripes et la classe. Dans notre exemple, la page d’accueil est Home.html c’est donc cette classe qui va s’afficher.
3.1.2 Annotation DefaultHandler
Cette annotation indique à Stripes que par défaut, c’est cette méthode qui est appelée. En effet, Stripes permet à une même classe d’avoir plusieurs « évènement ». C’est-à-dire que dans la même classe d’action on peut regrouper les traitements qui seront déclenchés en fonction d’un paramètre de l’url.
3.1.3 Attribut de la classe
Stripes, à la différence de Struts, n’a pas de Bean. C’est la classe d’action (qui est instanciée à chaque requête) qui porte les données.
Cela permet de ne pas avoir une classe inutile et finalement c’est confortable et plutôt logique. Ces attributs sont accessibles depuis la JSP à condition d’avoir un getter (et un setter dans le cadre d’un formulaire).
3.1.4 Les méthodes setContext() et getContext()
Nous reviendrons sur ces deux méthodes plus tard dans le document. Ces méthodes sont appelées par Stripes pour passer le context (session) à la classe d’action et à la JSP.
3.2 La présentation (JSP)
3.2.1 Présentation simple
La Jsp d’affichage se trouve dans src/main/webapp/WEB-INF/jsp/home.jsp :
<%-- Stripes TLD --%> <%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> <%@ taglib prefix="sdyn" uri="http://stripes.sourceforge.net/stripes-dynattr.tld" %> <%-- JSTL TLDs --%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%-- This is so that you can conveniently refer to the context path with ${contextPath} --%> <c:set var="contextPath" value="${pageContext.request.contextPath}"/> <html> <head> <title>Test</title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="${contextPath}/css/style.css"> </head> <body> ${actionBean.message} </body> </html>
Pour afficher notre message, il suffit de mettre dans la Jsp : ${actionBean.message}.
3.2.2 Présentation en utilisant les layouts
Stripes permet d’utiliser des « calques » de présentation. Cela se rapproche un peu des thèmes.
Nous allons donc créer un « thème » dans src/main/webapp/WEB-INF/jsp/layout.jsp :
<%@ include file="/WEB-INF/jsp/taglibs.jsp" %> <s:layout-definition> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>${title}</title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <link rel="stylesheet" type="text/css" href="${contextPath}/css/style.css"> <s:layout-component name="head"> </s:layout-component> </head> <body> <s:layout-component name="body"> </s:layout-component> </body> </html> </s:layout-definition>
La Jsp home aura alors ce contenu src/main/webapp/WEB-INF/jsp/home.jsp :
<%@ include file="/WEB-INF/jsp/taglibs.jsp" %> <s:layout-render name="/WEB-INF/jsp/layout.jsp" title="Demo Stripes"> <s:layout-component name="body"> ${actionBean.message} </s:layout-component> </s:layout-render>
Vous avez peut-être remarqué que le layout prend un paramètre ${home} qui ne vient pas de notre classe d’action mais de la Jsp qui l’inclus.
4 Gestion du context et personnalisation
Précédemment, nous avons vu qu’il fallait implémenter un getter et un setter de context. Ce context correspond au context de la requête/session, avec des éléments fournis par Stripes.
4.1 Classe de base
On peut créer déjà une classe de base afin d’éviter de ré-implémenter à chaque fois les deux méthodes:
public class BaseActionBean implements ActionBean { protected ActionBeanContext context; public ActionBeanContext getContext() { return context; } public void setContext(ActionBeanContext context) { this.context = context; } }
4.2 Personnalisation du context
Il pourrait être intéressant de personnaliser le context. Par exemple, lorsqu’un utilisateur s’identifie, on met en session son nom et ses préférences (langue, couleur….).
Il serait judicieux d’ajouter à ce context des méthodes qui font cela. Et bien Stripes le permet.
Il suffit de changer la classe de base :
public class BaseActionBean implements ActionBean { protected MonActionBeanContext context; public ActionBeanContext getContext() { return context; } public void setContext(ActionBeanContext context) { this.context = (MonActionBeanContext) context; } }
Mais comment Stripes va savoir qu’il faut instancier cette classe ?
Cela se fait dans le web.xml comme ceci :
<?xml version="1.0" encoding="ISO-8859-1"?> <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"> <filter> <filter-name>StripesFilter</filter-name> <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class> <init-param> <param-name>ActionResolver.Packages</param-name> <param-value>org.test.exemple</param-value> </init-param> <init-param> <param-name>ActionBeanContext.Class</param-name> <param-value>org.test.exemple.action.MonActionBeanContext</param-value> </init-param> </filter> ... </web-app>
Et voilà, on indique à Stripes quelle classe instancier. A présent voici la classe :
import net.sourceforge.stripes.action.ActionBeanContext; public class MonActionBeanContext extends ActionBeanContext { }
Ici elle est vide pour l’exemple, mais s’il n’y a pas de ClassCastException, c’est que cela fonctionne. Vous pouvez vérifier en ajoutant quelque chose dans la classe ou via un debugger.
5 Paramètres d’url et lien
Stripes permet de récupérer dans l’url directement des valeurs à l’aide de l’annotation UrlBinding. Cela permet entre autre de faire comme de l’url rewriting qui est souvent mis en place avec PHP.
Imaginons que nous avons une liste de produit récupéré en base de données. Nous voulons lorsqu’on clique sur le lien passer l’id.
Plutôt que d’avoir une url de type http://localhost/test_stripes/produit.html?id=1 nous souhaiterions avoir http://localhost/test_stripes/produit-1.html.
Voilà comment y parvenir très simplement :
@UrlBinding("/produit-{idProduit}.htm") public class DisplayProduitActionBean extends BaseActionBean { private int idProduit ; @DefaultHandler public Resolution view() throws IOException { return new ForwardResolution("/WEB-INF/jsp/produit.jsp"); } public int getIdProduit() { return idProduit; } public setIdProduit(id) { idProduit = id ; } }
On indique à Stripes qu’un des attributs que notre classe est alimenté par l’url. Attention ne pas oublier le getter pour récupérer la valeur depuis la Jsp.
Attention de ne pas oublier le setter pour récupérer la valeur depuis l’url !
Note : la classe BaseActionBean est une classe qui implémente setContext() et getContext() pour plus de facilité. Dans la Jsp qui affiche la liste de produit, on fait le lien sur le produit comme ceci :
<s:link beanclass="org.test.exemple.action.DisplayProduitActionBean" > <s:param name="idProduit" value="${item.id}"></s:param> Afficher </s:link>
La balise Stripes link permet de retrouve automatiquement l’url d’appel via le nom de la classe d’action. On indique la valeur du paramètre.
Note : ${item.id} correspond à la variable contenant l’id du produit. Nous verrons par la suite comment itérer sur des listes.
6 Gestion des évènements
Stripes permet à une classe d’action à répondre à plusieurs « événement ». Par exemple, la classe d’action ProduitActionBean doit gérer l’ajout, suppression, modification.
Traditionnellement, cela se fait avec un paramètre dans l’url. Ce paramètre est récupéré dans la classe et il y a un if ou un swith. Pas très élégant.
Stripes permet de gérer cela de la même façon (via un paramètre) mais nous affranchi de lire ce paramètre.
6.1 Mise en place d’évènement dans la classe d’action
Nous avons vu l’annotation @DefaultHandler, elle indique que s’il n’y a pas de paramètre d’évènement passer dans l’url, c’est la méthode dessous qui sera appelé. Il est possible d’utiliser l’annotation @HandlesEvent("xxxx") pour ajouter un évènement.
Imaginons la classe suivante :
@UrlBinding("/produit-maj.htm{$event}") public class MajProduitActionBean extends BaseActionBean { private int idProduit ; @DefaultHandler public Resolution view() throws IOException { idProduit = 1 ; return new ForwardResolution("/WEB-INF/jsp/produit.jsp"); } @HandlesEvent("update") public Resolution add() { return new RedirectResolution(HomeActionBean.class); } public int getIdProduit() { return idProduit; } public setIdProduit(id) { idProduit = id ; } }
On a donc une classe qui affiche un produit par défaut. Lorsqu’on l’évènement « update » est déclenché, il retourne vers la page d’accueil.
6.2 Déclenchement des évènements via les Jsp
Via un formulaire le déclenchement se fera :
<s:form beanclass="org.test.exemple.MajProduitActionBean"> <s:text name="idProduit " value="${actionBean.idProduit }" /> <s:submit name="update" value="Mettre a jour"/> </s:form>
Via un lien :
<s:link beanclass="org.test.exemple.action.DisplayProduitActionBean" event="update"> <s:param name="idProduit" value="${item.id}"></s:param> Afficher </s:link>
7 Formulaire
7.1 Validation
Stripes propose une validation de formulaire par annotation. Voici un exemple simple :
@Validate(required = true, on = "view") private int idAnounce ; @ValidateNestedProperties({ @Validate( required = true, on = "update", field= "title"), @Validate( required = true, on = "update", field= "price") }) private ItemBean itemBean = new ItemBean() ;
Comme on peut le remarquer la validation peut être différente selon l’évènement.
7.2 Champs
Stripes reprend les principes de ses précédesseurs. Pour déclarer un formulaire :
<s:form beanclass="org.test.exemple.action.DisplayProduitActionBean"> </s:form>
Pour un champ caché :
<s:hidden name="itemBean.id" value="${actionBean.itemBean.id}" />
Pour une zone de texte simple ligne :
Titre : <s:text name="itemBean.title" value="${actionBean.itemBean.title}" />
De type nombre :
Prix : <s:text name="itemBean.price" value="${actionBean.itemBean.price}" formatType="number" />
Il aussi possible de faire des listes déroulantes :
Categorie : <s:select name="itemBean.category"> <s:option value="">Select One</s:option> <s:options-map map="${actionBean.mapCategory}"/> </s:select>
Dans la classe d’action la catégorie est initialisée :
mapCategory = new HashMap() ; mapCategory.put("Voiture", "Voiture") ; mapCategory.put("Informatique", "Informatique") ;
La clef sera la value de la ligne et la donnée sera le libellé affiché.
8 Conclusion
Bien que nous ayons parcourus rapidement les possibilités de Stripes, il ressort néanmoins une grande facilité de mise en oeuvre. La documentation est assez bien fournie. Il manque seulement quelques fois des petites précisions.
Néanmoins, comparée à la documentation très volumineuse de Spring MVC, la documentation de Stripes permet un accès simple et rapide.
Aucun commentaire:
Enregistrer un commentaire