Struts2 Convention plugin : e' quasi magia




written by marco ferretti on August 05, 2011, at 02:48 PM

Oggi mi era una giornata fiacca. Mi sono detto : " Guardiamo un po' di cose che mi ero lasciato da fare" e l'occhio mi e' cascato su Struts2. Intendiamoci, non che non abbia mai visto come funziona questo framwork ( chiunque lavori con Java in ambito Web prima o poi ha a che fare con Struts ), semplicemente non ero mai andato + che tanto a fondo nella versione 2 di Struts.

Ho cominciato a guardare un po' di documentazione ( l'indice ) e l'occhio mi e' caduto su Convention plugin ... e ne sono entusiasta !

Ecco un piccolo diario delle mie prove ...

Per prima cosa, mi sono creato un progetto web dinamico con Eclipse. Poi ho preso tutte le librerie dall'applicazione di esempio "struts2-rest-showcase" ... tanto per non sbagliare o avere dipendenze non risolte ( prima o poi dovro' passare a Maven, ma che ci volete fare ... sono affezionato ad Ant ! ) . Ho quindi velocemente letto la prima parte della pagina linkata prima ( Convention plugin ) e poi ho cominciato a smanettare un po' con i files delle risorse e ... senza una riga di configurazione ( apparte il caricamento del filtro per struts ) mi sono ritrovato per le mani una pagina jsp che ha associato un bean e un file di risorse che risponde in maniera dinamica !!!

Ecco il gioco spiegato in due parole : Per default Convention cerca le pagine in WEB-INF/content, quindi se create una pagina pippo.jsp qui' dentro viene automaticamente creata una azione pippo che risponde sulla root della vostra applicazione ; inoltre, il plugin cerca tutto quello che implementa com.opensymphony.xwork2.Action e lo trasforma in una azione ... o quasi . Tutto quello che si trova all'interno di <un nome package>.action o <un nome package>.actions o <un nome package>.struts diventa un azione e viene mappato a partire dal piu' "anziano" parente. Facciamo un esempio :

com.pippo.action.PippoAction viene mappato in / com.pippo.action.pippo.Paperino ( che implementa com.opensymphony.xwork2.Action ) viene mappato in /pippo/ com.pippo.struts.pippo.paperino.QuackQuackAction viene mappato in /pippo/paperino

e viene fatto senza bisogno che scriviate una riga di configurazione o mappatura !

In piu' il nome dell'azione viene interpretato secondo il camel case e la classe rispondera' a com.pippo.action.PippoAction == /pippo com.pippo.action.pippo.Paperino == /pippo/paperino com.pippo.struts.pippo.paperino.QuackQuackAction == /pippo/paperino/quack-quack

La cosa ancora piu' intrigante e' che, sempre gratis, se create le relative pagine jsp in /WEB-INF/content queste vengono associate alla view dell'azione e automaticamente bindati ai relativi JavaBean !!! E quindi :

com.pippo.action.PippoAction == /pippo == /WEB-INF/content/pippo.jsp com.pippo.action.pippo.Paperino == /pippo/paperino == /WEB-INF/content/pippo/paperino.jsp com.pippo.struts.pippo.paperino.QuackQuackAction == /pippo/paperino/quack-quack == /WEB-INF/content/pippo/paperino/quack-quack.jsp

Ed eccoci all'esempio :

HelloAction.java

 package com.fermasoft.prove.convention.action;

 import java.text.SimpleDateFormat;
 import java.util.Date;

 import com.opensymphony.xwork2.ActionSupport;

 public class HelloAction extends ActionSupport {

 	public String execute(){
 		return SUCCESS;
 	}

 	public String getName(){
 		return "Goofy";
 	}

 	public String getTime(){
 		SimpleDateFormat formatter = new SimpleDateFormat("HH.mm");
 		return formatter.format(new Date(System.currentTimeMillis()));
 	}
 } 

HelloAction.properties

greeting=Hello ${name} ! It's ${time} o' clock !
testing.param=  ${name} you passed me {0}
value=I am string coming from the class resources

package.properties

package.value=I am a string coming from the package wide resources
value=I am another string coming from the package wide resources

hello.jsp

 <%@ taglib prefix="s" uri="/struts-tags"%>
 <html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>hello convention !</title>
 </head>
 <body>
 <p>Testing the Convention plugin</p>

here's a greeting that takes parameters from a bean I didn't explicitly declare in this page :


 <div style="font-style: italic;">
 <s:text name="greeting"/>
 <br />
 </div>

here's a parametrized text that comes from a resource bundle I didn't declare either ; the first part in the result of a call the the same bean as above, the second part is a parameter I directly passed from the page :


 <div style="font-style: italic;">
 <s:text name="testing.param">
 	<s:param value="'aValue'" />
 </s:text>
 <br />
 </div>

here's a text that comes from a package wide bundle :

 <div style="font-style: italic;"><s:property value="getText('package.value')" /></div> <br />
 here's a text that comes from a Class bundle that overrides the package bundle :  
 <div style="font-style: italic;"><s:property value="getText('value')" /> </div><br />


 </body>
 </html>

web.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
 	<display-name>sanity</display-name>
 	<filter>
 		<filter-name>struts2</filter-name>
 		<filter-class>
 			org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
 		<init-param>
 			<param-name>struts.devMode</param-name>
 			<param-value>true</param-value>
 		</init-param>

 	</filter>
 	<filter-mapping>
 		<filter-name>struts2</filter-name>
 		<url-pattern>/*</url-pattern>
 	</filter-mapping>

 	<welcome-file-list>
 		<welcome-file>index.jsp</welcome-file>
 	</welcome-file-list>
 </web-app>

e la struttura del progetto :

 Hello
 ├── build
 │   └── classes
 │       └── com
 │           └── fermasoft
 │               └── prove
 │                   └── convention
 │                       └── action
 │                           ├── HelloAction.class
 │                           ├── HelloAction.properties
 │                           └── package.properties
 ├── src
 │   └── com
 │       └── fermasoft
 │           └── prove
 │               └── convention
 │                   └── action
 │                       ├── HelloAction.java
 │                       ├── HelloAction.properties
 │                       └── package.properties
 └── WebContent
     ├── META-INF
     │   └── MANIFEST.MF
     └── WEB-INF
         ├── content
         │   └── hello.jsp
         ├── lib
         │   ├── commons-fileupload-1.2.1.jar
         │   ├── commons-io-1.3.2.jar
         │   ├── commons-logging-1.0.4.jar
         │   ├── freemarker-2.3.16.jar
         │   ├── javassist-3.7.ga.jar
         │   ├── log4j-1.2.16.jar
         │   ├── ognl-3.0.jar
         │   ├── struts2-config-browser-plugin-2.2.1.1.jar
         │   ├── struts2-convention-plugin-2.2.1.1.jar
         │   ├── struts2-core-2.2.1.1.jar
         │   └── xwork-core-2.2.1.1.jar
         └── web.xml

e questo e' il risultato della pagina :

 Testing the Convention plugin

 here's a greeting that takes parameters from a bean I didn't explicitly declare in this page :
 Hello Goofy ! Its 17.54 o clock ! 
 here's a parametrized text that comes from a resource bundle I didn't declare either ; the first part in the result of a call the the same bean as above, the second part is a parameter I directly passed from the page :
 Goofy you passed me aValue 
 here's a text that comes from a package wide bundle :
 I am a string coming from the package wide resources

 here's a text that comes from a Class bundle that overrides the package bundle :
 I am string coming from the class resources