Stripes è un presentation framework open source: l'obiettivo di un presentation framework è quello di rendere più semplice ed intuitiva la costruzione di applicazioni web incentrate sul paradigma MVC, garantendoci al tempo stesso il supporto allo sviluppo tramite convenzioni e costrutti specifici, e rendendo trasparenti alcune attività che sottraggono tempo.
In particolare Stripes si pone l'obiettivo di semplificare uno dei punti più critici dei framework, la configurazione: spesso la configurazione all'interno di un framework risulta essere un aspetto complesso (o quanto meno difficoltoso) perchè distribuito su diversi file, il più delle volte XML, e richiede tempo e curve di apprendimento non sempre banali.
Con Stripes invece è possibile partire "from scratch" in pochissimo tempo, in quanto la sua configurazione è veramente minimale. Inoltre, l'uso delle Annotation rende il framework semplice ed intuitivo, oltre che facilmente estensibile.
In questo articolo, oltre a fare una panoramica introduttiva su Stripes, vedremo come questo framework ci renda facilmente operativi, realizzando una piccola applicazione di Login.
Le principali caratteristiche di Stripes sono:
- configurazione basata su Annotation
- convention over Configuration
- auto-discovery degli action beans e dei form beans
- sviluppo modulare ed indipendente delle viste e dei modelli
- installazione e configurazione semplice e rapida
- facilità di estensione
- ricca disponibilità di taglibs
- supporto integrato e trasparente per il file uploading
Il flusso tipico di Stripes può essere rappresentato come segue:
Grazie all'applicazione di esempio ripercorreremo il ciclo di vita della richiesta.
Per iniziare a realizzare la nostra applicazione di esempio lavoriamo su una pagina di benvenuto che presenti un form di autenticazione ed un form di registrazione. Il form di autenticazione sarà utilizzato per implementare una semplice validazione dei dati, mentre analizzando gli ActionBean vedremo come poter intercettare in uno stesso ActionBean diversi eventi.
Con il nostro esempio avremo modo di:
- toccare con mano la gestione semplificata della validazione dei dati, senza utilizzare configurazioni dedicate, ma solo annotazioni
- utilizzare i java bean per effettuare il binding dei dati
- implementare eventi diversi all'interno dello stesso ActionBean handler
- focalizzare l'attenzione sulle tre componenti principali di Stripes: configurazione, view, ActionBean
Configurazione
Per essere immediatamente funzionante, Stripes richiede la configurazione di uno StripesFilter
e di una StripesDispatchServlet
nel file web.xml.
...
<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>it.html.stripes.action</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>index.jsp</welcome-file>
</welcome-file-list>
...
Lo StripesFilter assicura che tutte le request verso Stripes siano intercettate nello stesso modo. È importante notare il parametro di inizializzazione ActionResolver.Packages
: indica i packages in cui sono presenti i nostri ActionBean. Questo è l'unico parametro obbligatorio richiesto dallo StripesFilter.
Come per altri framework, esiste un unico controller, la DispatcherServet, che intercetta tutte le request e le instrada verso gli appropriati ActionBean.
Un altro file richiesto da Stripes è il file StripesResources.properties. Utilizzato per effettuare il lookup dei messaggi in fase di validazione, sia per le validazioni disponibili di default all'interno del framework, sia per quelle che saranno implementate in fase di sviluppo. Il file StripesResource.properties
deve essere disponibile all'interno del classpath.
Di seguito una porzione del file StripesResources.properties
:
#STRIPES VALIDATION MESSAGES 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.file.postBodyTooBig=Total upload size of {3} KB exceeds the maximum size of {2} KB # CUSTOM VALIDATION MESSAGES custom.validation.error.valueNotValid={0} value {1} is not a possible value
L'ultimo messaggio del properties sar? utilizzato nella validazione custom all'interno del nostro ActionBean di esempio.
View con Stripes
Stripes di default supporta l'utilizzo delle JSP come tecnologia standard per le views. È possibile integrare con estrema facilità altre tecnologie come FreeMarker, noto componente java basato su template per la generazione di pagine html . Inoltre sono a disposizione una serie di taglibs simili ai corrispettivi tag HTML per agevolare e semplificare lo sviluppo dei template.
La prima pagina che visualizziamo è la jsp della welcome-page:
Di seguito il codice della pagina di index:
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Stripes Sample</title>
</head>
<body>
<h1>Welcome to stripes sample</h1>
<stripes:link event="login" beanclass="it.html.stripes.action.HomeActionBean">
Login
</stripes:link><br/><br/>
<stripes:link event="register" beanclass="it.html.stripes.action.HomeActionBean">
Register
</stripes:link>
</body>
</html>
Attraverso le taglib di Stripes abbiamo creato due link che effettuano una richiesta verso l'URL associata all'ActionBean it.html.stripes.action.HomeActionBean
, comunicando anche l'evento definito all'interno dell'attributo event
. L'HTML di output sarà:
<a href="/Home.htm?login=">Login</a>
<a href="/Home.htm?register=">Register</a>
Il framework, in fase di runtime, converte l'attributo beanClass
nell'URL a cui è associato.
A questo punto è lecito chiedersi dove sia definita questa URL: la risposta la troveremo di seguito, in una annotation all'interno della definizione della classe, che ci consentirà di associare l'url all'ActionBean. Da notare che i due link puntano alla stessa url, li differenzia un parametro che è il valore immesso nell'attributo event, tramite il quale sarà possibile invocare l'handler corretto all'interno dell'ActionBean.
La pagina di registrazione è in puro html e non utilizza nulla che debba essere commentato.
L'ultima pagina della nostra applicazione è una form di login con due text input e due tasti di submit. I due tasti di submit consentono di generare rispettivamente l'evento "login" ed un altro generico evento da noi chiamato "other". Come nella welcome-page, per dimostrare la possibilità di avere più di un handler nell'ActionBean, abbiamo usato l'evento "other" con la funzione di reindirizzare l'utente alla form di registrazione.
Inoltre per agevolare la comprensione, il campo password è stato definito come text.
Il codice della nostra pagina login.jsp è il seguente:
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
<%@ taglib prefix="dyna" uri="http://stripes.sourceforge.net/stripes-dynattr.tld" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>FIRTS STRIPES SAMPLE</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div id="main">
<stripes:errors/>
<stripes:form beanclass="it.html.stripes.action.LoginActionBean">
<table align="center">
<tr>
<td>login:</td>
<td><stripes:text name="credentials.userName"/></td>
</tr>
<tr>
<td>password: </td>
<td><dyna:text name="credentials.password" extra="extraAttr"/></td>
</tr>
<tr>
<td colspan="2">
<stripes:submit name="submit" value="login"/>
<stripes:submit name="other" value="other"/>
</td>
</tr>
</table>
FAIL è un valore non valido per il campo login
</stripes:form>
</div>
</body>
</html>
Stripes ha una serie di jstl che rendono intuitiva la scrittura delle view. Le jstl offrono un supporto immediato per:
- la gestione degli errori di validazione
- la corretta action da impostare nei tag form
- la creazione di link (vedi index.jsp)
- la creazione dei tag html
In breve:
Tag | Descrizione |
---|---|
<stripes:error> | fornisce un supporto di default per mostrare gli errori di validazione. è possibile modificare la visualizzazione o gestire diversamente la visualizzazione degli stessi |
<stripes:form> | crea una html form che sarà utilizzato per l'invio della request all'ActionBean indicato nell'attributo beanclass |
<stripes:text...> | crea un html type input. Nel nostro esempio abbiamo due input di tipo text. Il loro valore è rispettivamente quello delle propriet? userName e password dell'oggetto Credential esposto dall'ActionBean |
<stripes:submit...> | crea un html button. Nel nostro esempio sono i due bottoni per gli eventi "login" ed "other" definiti nell'ActionBean |
Esistono due tipologie di taglibs:
- le standard: non consento l'utilizzo di attributi non standard, cioè non definiti nel Tag Library Definition
- le Dynamic Attributes: accettano anche attributi arbitrari, non standard, per esempio le librerie Ajax.
Nella nostra applicazione, il campo di immissione password è generato utilizzando la taglib dynattr,ed è stato aggiunto un attributo non standard al campo.
La form mostrata all'utente sarà:
Nella form, i campi login e password sono obbligatori. Il campo login genera un errore di validazione nel caso contenga il valore FAIL
.
Se tentiamo di effettuare la login senza aver immesso i dati all'interno della form, all'utente saranno notificati gli errori di validazione secondo le regole definite all'interno dell'ActionBean:
In caso di dati corretti, si visualizza una pagina di benvenuto con il valore immesso nel campo login: "WELCOME USER"
ActionBean
Un ActionBean è un controller che a seguito di una azione di submit, riceve la request dell'utente e ne processa i dati.
Per permettere allo StripesDispatcher di iniettare l'oggetto ActionBeanContext, ogni ActionBean deve necessariamente implementare l'interfaccia Stripes ActionBean o estendere una classe che la implementi. Questa implementazione rende possibile accedere agli oggetti HttpServletRequest, HttpServletResponse e ServletContext.
Il ciclo di vita di una request associata ad un ActionBean può essere descritto come segue:
- in base alla url richiesta si rende disponibile un ActionBean e si iniettata l'istanza di ActionBeanContext
- si risolve il metodo handler che si occupa di elaborare la richiesta per quel determinato evento
- si effettua un bind dei valori dalla HttpServletRequest all'ActionBean, e quindi se è stata prevista la validazione dei dati, viene eseguita
- si invoca un qualsiasi altro metodo di validazione custom
- si invoca il metodo handler precedentemente risolto
- si esegue l'oggetto Resolution restituito dall'handler
Analizziamo in dettaglio gli ActionBean.
Il primo è l'ActionBean HomeActionBean:
@UrlBinding("/Home.htm")
public class HomeActionBean implements ActionBean {
ActionBeanContext actionBeanContext;
public void setContext(ActionBeanContext actionBeanContext) {
this.actionBeanContext = actionBeanContext;
}
public ActionBeanContext getContext() {
return this.actionBeanContext;
}
@HandlesEvent(value = "login")
public Resolution view() {
return new RedirectResolution("/Login.htm");
}
@HandlesEvent(value = "register")
public Resolution register(){
return new RedirectResolution("/Register.htm");
}
}
La seconda? la LoginActionBean:
@UrlBinding("/Login.htm")
public class LoginActionBean implements ActionBean {
@ValidateNestedProperties({
@Validate(field = "userName",required = true,on = { "submit" }) ,
@Validate(field = "password",required = true,on = { "submit" })
})
private Credentials credentials;
private ActionBeanContext context;
public Credentials getCredentials() {
return credentials;
}
public void setCredentials(Credentials credentials) {
this.credentials = credentials;
}
public void setContext(ActionBeanContext actionBeanContext) {
this.context = actionBeanContext;
}
public ActionBeanContext getContext() {
return context;
}
@DefaultHandler
@DontValidate
public Resolution init(){
return new ForwardResolution("WEB-INF/jsp/login.jsp");
}
@HandlesEvent(value = "submit")
public Resolution login() {
return new ForwardResolution("WEB-INF/jsp/hello.jsp");
}
@ValidationMethod(on = "submit")
public void extraValidation(ValidationErrors errors) {
if(credentials.getUserName().equals("FAIL")){
ValidationError error = new LocalizableError(
"custom.validation.error.valueNotValid",
new Object[]{credentials.getUserName()}
);
errors.add("credentials.userName",error);
}
}
@HandlesEvent( value = "other")
public Resolution otherHandler() {
return new RedirectResolution("/Register.htm");
}
}
Si può notare la presenza nel codice di esempio di diverse annotation.
Annotazione | Descrizione |
---|---|
@UrlBinding |
esegue un bind del path specificato: ogni volta che il client richiede questo specifico path, l'ActionBean a cui ? associato viene invocato |
@ValidateNestedProperties |
è utilizzata per la validazione di oggetti complessi. Nel nostro esempio sono validate le proprietà userName e password dell'oggetto Credential , ma solo quando è generato l'evento submit.
Qualora l'utente immetta dati che non rispettano le regole di validazione, viene effettuato un ridirezionamento sulla pagina sorgente ed uno o più messaggi di errore sono visualizzati. I messaggi di errore sono basati sul tipo di validazione eseguita e sul campo validato. |
@DefaultHandler |
marca il metodo a cui è applicata come handler di default, qualora non sia specificato nessun evento all'interno della request o sia specificato un evento che non è possibile associare a nessun altro handler. |
@HandlesEvent |
marca il metodo a cui è applicata come handler dell'evento specificato. Ad esempio nella LoginActionBean @HandlesEvent( value = "other") indica che quando viene generato l'evento "other" il metodo otherHandler si occupa di gestire la richiesta. |
@ValidationMethod |
determina l'esecuzione del metodo a cui è applicata ed è utilizzata per definire validazioni custom. L' esecuzione avverrà prima di eseguire l'handling dell'evento dichiarato nell'attributo on (on = "submit" ). |
Analizzando l'esecuzione dell'evento sumbit, nella nostra form di login possiamo sintetizzare i seguenti step:
- l'utente effettua la submit dei dati
- Il framework imposta i valori ed effettua la validazione definita nella annotazione ValidateNestedProperties
- in caso di errori di validazione, viene effettuato un redirect verso la pagina sorgente
- in caso di nessun errore, viene richiesta l'esecuzione dell'handler associato all'evento specificato, nel nostro caso all'evento sumbit è associato il metodo login()
- poiché l'evento submit è marcato come da validare nell'annotation ValidationMethod, viene eseguito il metodo extraValidation
- in caso di errori di validazione viene ridirezionata la pagina sorgente
- in caso di nessun errore viene eseguito il metodo handler
L'annotazione @DontValidate dichiara che nessuna validazione deve essere eseguita quando viene generato quello specifico evento, o meglio quando viene invocato il metodo associato a quello specifico evento.
Conclusioni
Questo articolo, lungi dall'esaurire l'argomento, fornisce le basi per lavorare con Stripes. Grazie all'applicazione di esempio, abbiamo sperimentato l'immediatezza della configurazione. Inoltre, abbiamo visto che Stripes fornisce una validazione molto veloce, ricca di controlli di default già disponibili e facilmente customizzabile.
Punto di forza di Stripes è la comodità di avere in un unico ActionBean diversi handler, senza dover ricorrere a workaround quali: campi hidden, specifici parametri della request o if/else all'interno delle Action. Questa caratteristica di Stripes permette di implementare tutte le operazioni di CRUD associate ad un'entità in un unico ActionBean.
La semplicità si traduce in facilità di apprendimento, in concreto:
- non c'è bisogno di mettere insieme tante (troppe) cose. Quindi mantenere un bean ed una view, sincronizzandone gli aggiornamenti, può essere sufficente;
- non serve introdurre dipendenze extra (essenzialmente: librerie jar) che non forniscano contributi e funzionalità peculiari ed irrinunciabili
Un concetto molto importante è quello di "funzionalità latente": non è indispensabile avere una conoscenza approfondita del pattern interceptor per iniziare ad utilizzare il framework all'interno delle proprie applicazioni, eppure i programmatori più esperti possono facilmente avvantaggiarsene utilizzando le implementazioni dei metodi before/after.
In pratica la struttura convenzionale dei nomi ed il lavoro di design "dietro le quinte" rende accessibile l'uso delle classi del framework a vari livelli di competenza. Per approfondimenti si consiglia la lettura dei principi di implementazione, nonchè naturalmente del wiki ufficiale di Stripes, che fornisce un'esaustiva panoramica di tutte le funzionalità.