Ir al contenido principal

Creando e integrando "custom taglibs" con Struts 2.x, Spring y FreeMarker




Creando un taglib, extendido de los componentes de Struts.


Recientemente surgió la necesidad de crear un “taglib”, para un proyecto en el cual estoy trabajando, hasta ahí todo bien. El problema inicia cuando deseamos reutilizar los componentes de Struts (utilizamos la versión 2.x). Después de investigar un poco, nos encontramos con el “tag” (“<s:component />”), este permite pasarle un nombre de plantilla (de Velocity o FreeMaker) y renderizar el resultado en nuestro Html, por ejemplo, si tenemos:


<component template="/my/custom/component.vm”>

    <s:param name="key1" value="value1"/>

    <s:param name="key2" value="value2"/>

</s:component>


Esto invocará una plantilla de Velocity e introduce al contexto de Velocity, un Mapa llamado “parameters”, del cual podemos obtener los parámetros que anteriormente insertamos en el JSP.

Más o menos, algo así:


${parameters.key1}


Este enfoque esta bastante bien, sin embargo tiene algunos problemas; la implementación queda totalmente expuesta a los WebDev. En caso que deseemos utilizar alguna clase nuestra o solicitar a Spring algún componente del contexto, la tarea se dificulta.


La solución mas adecuada sería extender este funcionamiento, para encapsular y reutilizar la funcionalidad del componente de Struts y proporcionar componentes fáciles de utilizar a los WebDev.


Investigando un poco, tuvimos la sensación que la documentación necesaria para implementar este tipo de extensiones al Framework, al momento no se encuentra muy documentado (por no decir que no existe) o nadie ha necesitado hacerlo. Sin más opción, ni guía, nos consumimos en el código fuente de Struts 2 y del estudio de ciertas partes del código, encontramos una solución, la misma se detalla a continuación:


Nuestra prueba de concepto consiste en crear un “taglib”, llamado HelloWord, el cual recibirá mediante un atributo llamado “name”, el nombre de la persona a saludar.


Lo primero que tenemos que hacer, es crear un componente el cual implemente la interfase “org.apache.struts2.views.TagLibrary”, esta interfase permite a Struts conocer los componentes y “taglibs” que deseamos utilizar, así pues creamos la siguiente clase:


public class MyTagLibrary implements TagLibrary {

       @Override

       public Object getFreemarkerModels(ValueStack stack, HttpServletRequest req,

            HttpServletResponse res) {

             

              return new MyModels(stack, req, res);

       } // getFreemarkerModels.

       @Override

       public List<Class> getVelocityDirectiveClasses() {

              return null;

       } // getVelocityDirectiveClasses.

}


Como podrá ver, esta clase tiene dos métodos, mediante los cuales le damos a conocer a Struts tanto, los componentes de Velocity (ninguno en nuestro caso) y los componentes de FreeMaker (para este caso se utiliza un Objeto, se trata de MyModels ya lo vemos en mas detalle).


Adicionalmente, este objeto debe ser agregado en nuestro archivo de configuración:


<struts>

<bean type="org.apache.struts2.views.TagLibrary" name="my" class="com.mypackage.web.views.MyTagLibrary" />


Esto permite al Struts, conocer que existe una fábrica para los componentes “taglib”, importante colocarle el atributo (type), pues mediante este, Struts se percata de que existe y debe ser procesado como una fabrica de TagLibrary. Importante saber, que el valor de “name”, en nuestro caso “my” es algo así como el nombre del “namespace” del taglib, ósea que al final debe utilizarse el “taglib”, mas o menos así: <my:nombreTagLib.


El siguiente paso, es crear nuestra fábrica de modelos; en el caso que desees utilizar Freemaker, debes crear un objeto el cual supongo que Struts analizará por reflexión, pues no implementa nada, simplemente utiliza la nomenclatura que a continuación te muestro:


public class MyModels {


    protected ValueStack stack;

    protected HttpServletRequest req;

    protected HttpServletResponse res;    

    protected HelloWordModel helloWord;

   

       public MyModels (ValueStack stack, HttpServletRequest req,

                     HttpServletResponse res) {

              super();

              this.stack = stack;

              this.req = req;

              this.res = res;

       }


       public HelloWordModel getHelloWord() {

             

              if (null == this.helloWord) {

                    

                      this.helloWord = new HelloWordModel (this.stack, this.req, this.res);

              }

             

              return helloWord;

       }   

}


Pronto explicaremos en detalle de que se trata este “HelloWordModel”, ahora lo importante es saber, que para que Struts descubra nuestro “taglib” ocupa un objeto “TagModel”, observe que la convension es:


Public XXXModel getXXX(), donde XXX es el nombre del componente, en el ejemplo anterior “HelloWord”, usted puede colocar los componentes que ocupe guardar bajo este “namespace”.


Por cada “TagLib” que ocupemos debemos crear al menos 3 objetos, el primero será el modelo, este le sirve al Struts para describir tanto el TagLib, como el “bean” que será introducido en el contexto del motor de plantillas.

Así pues, veamos el modelo:


public class HelloWordModel extends TagModel {

      

       public HelloWordModel(ValueStack stack, HttpServletRequest req,

                     HttpServletResponse res) {

              super(stack, req, res);

       }

      

       @Override

       protected Component getBean() {

             

              return new HelloWord(this.stack, this.req, this.res);

       }

}


Esta clase extiende de “TagModel”, clase la cual, según el contrato de la misma nos exige devolver un “Component”, al invocar al método “getBean”, Aquí vamos a devolver el objeto que necesitamos sea leído por el motor de plantillas, podemos ver analógicamente este objeto “Model”, como una fabrica “Home” en el contexto de los E.J.B.


A continuación creamos nuestro, Java Bean, HelloWord:


@StrutsTag(name="helloWord", tldTagClass="com.mypackage.web.ui.HelloWordTag", description="Hello word example!")

public class HelloWord extends GenericUIBean {


       final public static String TEMPLATE = "helloword";

      

       private String name = null;


       @Override

       protected String getDefaultTemplate() {

      

              return TEMPLATE;

       }

      

       public String getName() {

              return name;

       }


       public void setName(String name) {

              this.name = name;

       }


      

       public HelloWord(ValueStack stack, HttpServletRequest request,

                     HttpServletResponse response) {

              super(stack, request, response);

                     }


}


Puntos importantes de ver; primero nuestra clase extiende de “GenericUIBean”, existen otras clases en la jerarquía, las cuales podemos utilizar como clase base, algunas proporcionan menos o mas funcionalidad, al parecer. El segundo punto a tomar en cuenta, es sobre escribir el método “getDefaultTemplate”, este método permite indicarle al “Engine” de FreeMaker, al menos estilo del patrón template, que la plantilla de FreeMaker, que deseamos utilizar es tal (ojo que la extensión se omite, solo se coloca el nombre). Por ultimo, cualquier atributo al estilo Java Bean (i.e: atributos con sus respectivos set’s y get’s), podrán ser accedidos desde la plantilla de FreeMaker. El constructor es necesario, así como la invocación al súper. Lo ultimo que debemos prestar atención, es la inclusión de una anotación, la cual relaciona nuestro Java Bean, con un “TagLib”, supongo que el “Engine” de FreeMaker toma nuestro objeto “model”, de ellos el JavaBean y de las anotaciones de estos, el taglib asociado.


Por ultimo implementaremos nuestro componente de vista, o lo que es lo mismo nuestro “taglib” propiamente, el mismo debe extender de la clase “ComponentTag”, la cual tiene el soporte necesario para renderizar la plantilla de FreeMaker, con el componente incluido. A continuación el código:


public class HelloWordTag extends ComponentTag {


       private String name = null;


       public String getName() {

              return name;

       }


       public void setName(String name) {

              this.name = name;

       }


       /**

        *

        */

       public HelloWordTag() {


              super();

       }


       @Override

       public Component getBean(ValueStack stack, HttpServletRequest req,

                     HttpServletResponse res) {


             

              return new HelloWord(stack, req, res);

       }


       @Override

       protected void populateParams() {


              HelloWord helloWord = null;

              super.populateParams();

              helloWord = HelloWord.class.cast(this.getComponent());

              helloWord.setName((null != this.getName()) ? this.getName() : "Fulano de tal");

       }


}


 


Note que la implementación del método; “getBean” al funciona al estilo del patrón plantilla y nos permite obtener el Java Bean que deseamos. También tenemos la sobre escritura del método populateParams, el cual nos permite setear parámetros en el componente, este método para los que conocen la interfase “InitializeBean” de Spring, es análogo al método “afterSetProperties”. Note que este Java Bean, tiene propiedades “bean”, las cuales deben ser expuestas mediante un “tld”.


Como lo indicamos en nuestro Java Bean, HelloWord, necesitamos crear nuestra plantilla de FreeMaker y situarla bajo el paquete Java: “template.simple”, el cual por supuesto debe estar en nuestro “classpath”. A continuación nuestro código para helloword.ftl:


<#assign helloword = tag/>


<p>

   Hello ${helloword.name}

</p>


Majestuoso no es cierto, nuestro bean es introducido con el nombre de “tag”, lo asignamos a “helloword” y solicitamos el nombre para saludar.


 


Una vez codificadas las clases y editados los archivos de configuración y FreeMaker, necesitamos integrar nuestro “Taglib”, con nuestro contexto Web, para ello realizamos los pasos de siempre; creamos un “TLD” con la descripción del “HelloWordTag” e incluimos este “TLD” en nuestro Web.xml.


A continuación el código del my-tags.tld:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

<taglib>

  <tlib-version>2.2.3</tlib-version>

  <jsp-version>1.2</jsp-version>

  <short-name>my</short-name>

  <uri>/my-tags</uri>

  <display-name>"My Tags"</display-name>

  <description><![CDATA["My Specific Tag."]]></description>

  <tag>

    <name>helloWord</name>

    <tag-class>com.mypackage.web.ui.HelloWordTag</tag-class>

    <body-content>JSP</body-content>

    <description><![CDATA[Hello word]]></description>

    <attribute>

      <name>name</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

      <description><![CDATA[Name of the action to be executed (without the extension suffix eg. .action)]]></description>

    </attribute>   

  </tag>

 </taglib>


 


Ahora el código necesario para el web.xml:


<jsp-config>

   

      <taglib id="MyTLD">

         <taglib-uri>/my-tags</taglib-uri>

         <taglib-location>/WEB-INF/my-tags.tld</taglib-location>        

      </taglib>

    </jsp-config>


Por ultimo para utilizar nuestro “taglib”, declaramos el mismo en nuestro JSP y simplemente lo utilizamos:


<%@ taglib prefix="my" uri="/my-tags"%>


<my:helloWord name="Jsanca"></my:helloWord>


// <p>Hello Jsanca</p>


<my:helloWord ></my:helloWord>


// <p>Hello Fulano de tal</p>


Hasta aquí ya tenemos nuestro “taglib”, totalmente integrado con Struts y FreeMaker. En la mayoría de los casos y particularmente si utilizamos Spring, será muy probable que surja la necesidad de utilizar alguna clase declarada en nuestro contexto de Spring, a continuación el código que utilice para realizar la tarea:


HelloWordModel.java:


@Override

       protected Component getBean() {

              ApplicationContext applicationContext = WebApplicationContextUtils

                           .getWebApplicationContext(req.getSession().getServletContext());


              SaludadorService saludador = SaludadorService.class.cast(applicationContext

                           .getBean("saludador"));


              return new HelloWord(this.stack, this.req, this.res, saludador);

       }


HelloWord.java:


@StrutsTag(name="helloWord", tldTagClass="com.mypackage.web.ui.HelloWordTag", description="Hello word example!")

public class HelloWord extends GenericUIBean {


       final public static String TEMPLATE = "helloword";

      

       private String name = null;

      

       private SaludadorService saludador = null;

      


       @Override

       protected String getDefaultTemplate() {

      

              return TEMPLATE;

       }

      

       public String getName() {

              return this. saludador.buildSaludo(this.name);

       }


       public void setName(String name) {

              this.name = name;

       }


       /**

        * @param stack

        * @param request

        * @param response

        */

       public HelloWord(ValueStack stack, HttpServletRequest request,

                     HttpServletResponse response, SaludadorService saludador) {

              super(stack, request, response);

              this. saludador = saludador;

       }


}


HelloWordTag.java:


@Override

       public Component getBean(ValueStack stack, HttpServletRequest req,

                     HttpServletResponse res) {


              ApplicationContext applicationContext = WebApplicationContextUtils

                           .getWebApplicationContext(req.getSession().getServletContext());


              SaludadorService saludador = SaludadorService.class.cast(applicationContext

                           .getBean("saludador"));


              return new HelloWord(this.stack, this.req, this.res, saludador);


       }


 


Aquí el truco recae en utilizar la clase “WebApplicationContextUtils” y tener el acceso al objeto “Request”, pues como supondran la instancia del ApplicationContext de Spring, se encuentra almacenado en el ‘scope’ del ServletContext y mediante este método, podemos obtenerlo.


Espero les haya sido útil!


Comentarios

Entradas más populares de este blog

Impensando acerca de las referencias en Java

Fue hace ya algún tiempo que pase un rato discutiendo con algunos compañeros acerca de si existe o no el paso por referencia; el discurso fue mucho hacia que en Java el comportamiento, en el supuestamente pasamos por referencia un objeto y por valor los objetos primitivos creo mucha polémica. Para ubicarnos en contexto veamos el siguiente ejemplo. public static void main(String[] args) { int value = 10; changeValue(value); System.out.println("value = " + value); User user = new User(); Name name = new Name(); user.setName(name); name.setName("jsanca"); name.setLastName("XXX"); user.setPassword("123queso"); System.out.println("user: " + user.getName().getName() + ", " + user.getName().getLastName() + ", " + user.getPassword()); changeValue1(user); System.out.println("user: " + user.getName().getName() + ", " + user.getName().getLastName() + ", " + user.ge...

Al fin MTV reconoce el potencial de la Internet

Después de mucha lucha y denuncias públicas y demás pleitos para llamar la atención, la cadena mundial de música por televisión mas grande el mundo MTV , abren toda su biblioteca (bueno casi toda, el material en ingles por el momento), al publico. Aseguran tener conciertos, espectáculos acústicos , más todos sus vídeos y a diferencia de YouTube (que es mantenida por una comunidad en buena parte), esta es soportada por MTV , lo que le proporciona idéntica calidad a cada uno de sus temas ( vídeos ), el sitio en donde han publicado el contenido le llaman MTV Music , y yo debo reconocer que estoy como chiquito con juguete nuevo, jejeje . El sitio ofrece una gran cantidad de música , como señale anteriormente y además realiza sugerencias acerca de música o vídeos relacionados, en las búsquedas , te de la oportunidad de ir a un perfil del artista o directamente a los vídeos . Buena noticia y supongo que a MTV le paso como dicta el viejo adajio , " si no puedes vencerlos, unet...

Ideas para un eco-hogar

Un Eco Hogar, Ultimamente he estado pensando al respecto (en la implementación de una casa ecológica), leyendo un poco me entero que existen diferentes alternativas para el ahorro de consumo electrico del hogar; paneles solares, mini hidro turbinas, energía eólica, etc. Algunas alternativas interesantes representan los termos calentados por paneles solares, para no gastar energía en la ducha caliente, etc. Todas estas alternativas están muy bien, aunque la inversión por el momento es algo grande para un hogar promedio, con el consumo masivo, podría convertirse en una opción de facto. Estas opciones representa un ahorro en el consumo eléctrico, pero que hay con el consumo del H2O; sin necesidad de ser muy observador, nos damos cuenta que uno de los mayores puntos donde se desperdicia agua son: el baño y la ducha. En cuanto a la ducha no se me ocurre mas que algunos habitos en vez de soluciones tecnicas, como mojarse, cerrar el tuvo, enjabonarse, etc. Cerrar el tuvo cuando no lo estamos ...