viernes, 25 de febrero de 2011

Implementación de un Comparator genérico en Java con ayuda de Groovy

JavaGroovy
Al programar en Java ¿no has tenido la necesidad de hacer unas cuantas clases que implementen la interfaz Comparator para realizar ordenaciones con el método Collections.sort pero no querías hacer una clase por cada una de ellas con unas pocas líneas? Cuando necesitas uno o dos Comparator puede valer hacer una clase para cada una de ellas pero cuanto empiezas a tener muchas la cosa empieza a exasperar por tener que hacer todo el rato lo mismo con un pequeño cambio, que es la expresión que obtiene los objetos que se comparan.

Para evitar tener que crear una clase por cada Comparator que queramos podemos hacer uso del soporte de Java 6 para los lenguajes de scripting (Consulta el enlace para ver como poder hacer uso de Groovy en Java) para crear un Comparator genérico. Básicamente la siguiente implementación de la interfaz Comparator, GroovyComparator, recibirá un script de groovy que lo aplicará a cada uno de los objetos del método compare de la interfaz Comparator y que devolverá un objeto que implememnte la interfaz Comparable por el que haremos la comparación entre los dos objetos.

package com.blogspot.elblogdepicodev.util;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class GroovyComparator implements Comparator {

 private String script;
 private Map bindings;

 public GroovyComparator(String script) {
  this(script, null);
 }

 public GroovyComparator(String script, Map bindings) {
  this.script = script;
  this.bindings = bindings;
 }

 @SuppressWarnings({ "unchecked" })
 @Override
 public int compare(T o1, T o2) {
  try {
   ScriptEngineManager manager = new ScriptEngineManager();
   engine = manager.getEngineByName(Constantes.GROOVY_ENGINE_NAME);
   
   Bindings b = engine.createBindings();
   if (bindings != null) {
    b.putAll(bindings);
   }

   b.put("it", o1);
   Comparable r1 = (Comparable) engine.eval(script, b);

   b.put("it", o2);
   Comparable r2 = (Comparable) engine.eval(script, b);

   return r1.compareTo(r2);
  } catch (ScriptException e) {
   throw new RuntimeException(e);
  }
 }
}

Suponiendo que tengamos una lista de objetos con una propiedad nombre (String) y queramos ordenar la lista por esa propiedad:

List l = ...;
 ...
 Collections.sort(l, new GroovyComparator("it.nombre"));

La verdad es que utilizando los lenguajes de scripting para hacer ciertas tareas se nos puede hacer la vida más fácil a los programadores.

Referencia:
Lenguajes de scripting sobre la plataforma Java
Scripting for the Java Platform
The Mustang Meets the Rhino: Scripting in Java 6

viernes, 18 de febrero de 2011

Enviar correos electrónicos mediante Java Mail

JavaTomcat
En algún caso puede que necesitemos enviar correos electrónicos desde una aplicación java. En esta entrada vamos a ver como realizarlo desde una aplicación web desplegada en un servidor tomcat y a través de una cuenta de correo de gmail.

Con estas premisas, sin más, el código java para enviar un correo elctrónico es el siguiente, que no sería muy distinto si utilizásemos otro servidor de aplicaciones e incluso no fuese una aplicación web:

...

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

...
try {
    // Obtener la sesión para enviar correos electrónicos del directorio JNDI
    Context ic = new InitialContext();
    Session session = (Session) ic.lookup("java:comp/env/mail/gmail");
   
    // Crear el mensaje a enviar
    MimeMessage mm = new MimeMessage(session);
  
    // Establecer las direcciones a las que será enviado 
    // el mensaje (test2@gmail.com y test3@gmail.com en copia oculta)
    mm.setFrom(new InternetAddress("test1@gmail.com"));
    mm.addRecipient(Message.RecipientType.TO, new InternetAddress("test2@gmail.com"));
    mm.addRecipient(Message.RecipientType.BCC, new InternetAddress("test3@gmail.com"));

    // Establecer el contenido del mensaje
    mm.setSubject("Hola mundo!");
    mm.setText("Hola mundo!");

    // Enviar el correo electrónico
    Transport.send(mm);
} catch (Exception e) {
    e.printStackTrace();
}

...

¿Pero donde se configura como nos conectamos al servidor de gmail para enviar los correos electrónicos? Pues bien, esto lo podemos hacer en la configuración de contexto para la aplicación. Estos  recursos se definen normalmente en un archivo «$TOMCAT_HOME/conf/Catalina/localhost/[contexto app].xml» y básicamente se asocia a un nombre del árbol JNDI un objeto de interfaz javax.mail.Session que será el que obtengamos en la aplicación y utilicemos para enviar los correos electrónicos. Esto es lo que hace la etiqueta Resource del ejemplo. JNDI es básicamente un registro donde se asocian nombres con servicios, en este caso el nombre «java:comp/env/mail/gmail» con objeto de interfaz javax.mail.Session que utilizaremos para enviar los correos electrónicos. La configuración para el tomcat sería:

<Context docBase="/home/tomcat/web">
    <Resource type="javax.sql.DataSource"
            auth="Container"
            maxActive="30" 
            maxIdle="10"
            maxWait="10000" 
            name="jdbc/db"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost/db?autoReconnect=true"
            username="root"
            password=""/>
    <Resource type="javax.mail.Session"
            auth="Container"
            name="mail/gmail"
            mail.transport.protocol="smtp"
            mail.smtp.host="smtp.googlemail.com"
            mail.smtp.port="465"
            mail.smtp.auth="true"
            mail.smtp.user="test1@gmail.com"
            password=""
            mail.smtp.starttls.enable="true"
            mail.smtp.socketFactory.port="465"
            mail.smtp.socketFactory.class="javax.net.ssl.SSLSocketFactory"
            mail.smtp.socketFactory.fallback="false"
            mail.smtp.debug="true"/>
</Context>

Al igual que podemos configurar un recurso de tipo javax.sql.DataSource en el mismo archivo de configuración de la aplicación  para obtener conexiones a bases de datos configuramos un recurso de tipo javax.mail.Session, lo que logicamente cambiará serán los parametros para configurar uno y otro. En ambos casos el parámetro name indica en que punto del árbol JNDI donde se deja disponible el recurso. En el ejemplo los parámetros indicados son los necesarios para conectarse al servidor smtp con una cuenta de gmail.

¿Y porque configuramos el DataSource y la Sesion en el tomcat? Podríamos evitarnos esta configuración y meterla en el código de la aplicación pero esto tiene la desventaja de que para utilizar una nueva configuración necesitaríamos recompilar la aplicación, en el caso de tener la configuración externalizada en un archivo «$TOMCAT_HOME/conf/Catalina/localhost/[contexto app].xml» únicamente sería necesario un redespliegue de la aplicación lo que es más rápido y sencillo.

Para hacer uso de las clases java del paquete javax.mail.* necesitaremos la librerias de java mail (http://www.oracle.com/technetwork/java/javamail/index.html) pero también necesitaremos las clases de JAF (JavaBeans Activation Framework) (http://www.oracle.com/technetwork/java/javase/downloads/index-135046.html). Ambas librerias activation.jar y mail.jar deberemos colocarlas en el directorio lib de tomcat (en la versión 7.x). El lugar donde ponerlas es importante ya que si las colocamos en el directorio WEB-INF/lib de la aplicación java mail no va a funcionar.

Desde luego este es un ejemplo básico de como enviar un mensaje de correo electrónico de un texto plano desde java y si tenemos necesidad de enviar correos electrónicos puede que también necesitemos enviarlos en formato html para lo cual quizá nos convenga hacer uso de motores de plantillas como freemarker o velocity.

Referencia:
http://es.wikipedia.org/wiki/JNDI
http://tomcat.apache.org/tomcat-7.0-doc/config/context.html
http://tomcat.apache.org/tomcat-7.0-doc/config/resources.html
http://tomcat.apache.org/tomcat-7.0-doc/jndi-resources-howto.html

viernes, 11 de febrero de 2011

Unir Apache HTTPD y Tomcat mediante un reverse proxy

Apache HTTPDApache Tomcat

Al diseñar el despliegue de una aplicación es habitual hacer que un servidor web devuelva el contenido estático de la página web o de la aplicación. Esto nos puede interesar por varias razones, una de ellas es que quita la carga de trabajo correspondiente al servir el contenido estático al servidor de aplicaciones que dependiendo de la aplicación o página web puede ayudar a aliviar su carga. Pero también puede ser usado para realizar balanceo de carga entre varios servidores de de aplicaciones, cacheo o para acceder a un servidor de nuestra intranet que está detrás de un firewall y no es accesible directamente por los usuarios de internet. El esquema es el siguiente:


Viendo un gráfico se entiende mejor ¿verdad?. En él tenemos el cliente que realiza la petición, el servidor web que se encargará de devolver los recursos estáticos (en la figura denominado reverse proxy) y finalmente el servidor de aplicaciones que se encargará de generar el contenido dinámico de la aplicación (posiblemente accediendo a un servidor de base de datos que en el gráfico no se muestra, en la figura denominado Web1 y Web2). En un primer paso el cliente realiza una petición que llega al servidor web, este determina si el recurso solicitado lo puede servir él o lo tiene que delegar en el servidor de aplicaciones. En el caso de que el servidor web tenga que delegar la petición en el servidor de aplicaciones el servidor web realiza un petición al servidor de aplicaciones solicitando el recurso que no puede servir él. Una vez que el servidor de aplicaciones genera el recurso se lo devuelve al servidor web que a su vez este se lo devuelve al cliente. Para el cliente todo este proceso es transparente, es como si todos los recursos los pudiese servir el servidor web. En este esquema el servidor web actúa de proxy para el servidor de aplicaciones (tambien se llama reverse proxy).

¿Como se configura Apache HTTPD para actuar en modo reverse proxy?

En Apache para delegar las peticiones debemos utilizar las directivas ProxyPass, ProxyPassReverse, ProxyRequests y ProxyPreserveHost. En el primer parámetro de la directiva ProxyPass indicamos las URL que delegamos (/) y en el segundo parámetro indicamos la dirección del servidor de aplicaciones que devolverá realmente el contenido de la petición (ajp://localhost:8009/, ajp es un protocolo específico para comunicarse con Tomcat, también podríamos haber utilizado el protocolo http con http://localhost:8080/, que es lo que haremos si utilizamos otro servidor web que no sea Apache). Para que el servidor web devuelva los contenidos estáticos de la aplicación debemos indicarle a Apache que las peticiones que empiecen por /static/ no las delegue en el servidor de aplicaciones sino que las procese él, esto lo conseguimos con la directiva ProxyPass pero donde pondríamos la dirección del servidor de aplicaciones ponemos un !. En el siguiente ejemplo de configuración para Apache he definido un host virtual basado en nombres.

Descomentamos las siguientes líneas en el archivo httpd.conf:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_ajp_module modules/mod_proxy_http.so

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

Lineas a añadir en el archivo httpd-vhosts.conf:

<virtualhost *:80>
 ServerName www.dominio.com
 ServerAdmin admin@dominio.com
 DocumentRoot "/apache/www"
 ErrorLog "logs/www.dominio-error.log"
 CustomLog "logs/www.dominio-access.log" common

 ProxyRequests Off
 ProxyPreserveHost On

 ProxyPass /static/ !
 ProxyPass / ajp://localhost:8009/
 ProxyPassReverse / ajp://localhost:8009/
 
 <directory />
  Order deny,allow 
  Allow from all 
 </directory>
 
 Alias /static/ "/apache/www/"
</virtualhost>

Ciertas aplicaciones del servidor de aplicaciones pueden necesitar conocer la máquina original que realizó la petición (hay que darse cuenta que para el servidor de aplicaciones todas las peticiones provienen del servidor web) esto se consigue con la directiva ProxyPreserverHost, de esta forma el servidor de aplicaciones podrá conocer cierta información del origen real de la petición.

Según esta configuración la aplicación en el Tomcat esta desplegada en el contexto ROOT y accederíamos a ella con http://www.midominio.com/. Este es un aspecto importante y que en pocos sitios se comenta, el contexto de la aplicación en Apache y en el servidor de aplicaciones ha de coincidir sino las URLs que devuelva el servidor de aplicaciones en el contenido no van a ser utilizables por el cliente. Más especificamente no va a funcionar lo siguiente:

ProxyPass / ajp://localhost:8009/aplicacion
ProxyPassReverse / ajp://localhost:8009/aplicacion

En todo caso deberiamos indicar /aplicacion como URL a delegar y desplegar la aplicación en Tomcat en el contexto /aplicacion:

ProxyPass /aplicacion ajp://localhost:8009/aplicacion
ProxyPassReverse /aplicacion ajp://localhost:8009/aplicacion

Si nuestro servidor web es Apache HTTPD y nuestro servidor de aplicaciones es Tomcat en vez de utilizar un reverse proxy también se puede utilizar el conector mod_jk para conectarlos pero he preferido utilizar esta forma de reverse proxy ya que el conector mod_jk no está disponible para otros servidores web y servidores de aplicaciones, es decir, el este concepto de reverse proxy es más universal y normalmente es implementado por la mayoría de servidores web lo que nos permite poder cambiar de servidor web fácilmente en caso de que quisiéramos. Podríamos cambiar más fácilmente Apache HTTPD por cherokee, lighttpd o nginx que son utilizados hoy día en importantes webs de internet.

Referencia:
Reverse proxy (wikipedia)

domingo, 6 de febrero de 2011

1º aniversario del blog

Pues pacere que fue ayer pero ya se ha cumplido un año desde que cree el blog. En todo este tiempo han sido 48 entradas las que he publicado hablando sobre Arch Linux y programación en java principalmente pero también he tenido tiempo para hablar sobre otros temas como Minix, personalización de Linux, juegos y algún otro. Durante este año he quedado bastante contento con las entradas que he publicado sobretodo con las referentes a Tapestry por haber podido devolverle algo por los buenos momentos que paso con él programando y del que espero seguir escribiendo.

En cuanto a los usuarios que visitan este blog prácticamente todos llegais por alguna búsqueda en Google, en estos momentos recibo en el blog unas 70 visitas diarias y durante este año en total han sido 24755 páginas vistas, 18200 visitas únicas con un promedio de 3 minutos en el sitio. Si ya sé, alguno dirá que no son muchas pero en el momento que inicie el blog no pensé que fueran tantas :).

Las entradas más visitadas han sido Ubuntu Enterprise Cloud, las guías de instalación de Arch Linux (I y II), como configurar Grub en modo gráfico y escuchar radios a través de internet con VLC.

A todos los que leáis esto, ¡gracias por visitar mi blog! espero que os resulte interesante.

viernes, 4 de febrero de 2011

Chrome Web Store

Ayer Google liberó otra versión estable de su navegador web, y ya van por la 9, en Arch Linux ya está en testing asi que no tardaremos mucho en tenerla disponible para actualizar.


Entre las principales novedades que incorpora esta versión es el soporte para WebGL que permitirá dotar a las aplicaciones de nuevas posibilidades. Si quieres probar que es esto de WebGL dale un vistazo a Chrome experiments. La otra novedad presentada junto con el navegador es la tienda de aplicaciones Chrome Web Store desde la cual prodremos instalar aplicaciones en el navegador Chrome.


En esta tienda podremos encontrar aplicaciones (Web Apps), extensiones y temas para el navegador Chrome. En cuanto a las aplicaciones están divididas en diversas categorias, educación, entretenimineto, juegos, noticias, etc... algunas son de pago, otras prueden probase y otras son gratuitas. Para adquirir las de pago deberemos hacer uso del sistema de pagos Google Checkout.


Entre las aplicaciones que podemos encontrar es el famoso juego Plants vs Zombies, otros buenos como The fancy pants adventure y algunas reediciones de clásicos como CodeBummer, Balloono y Bubble Witch todos estos gratuitos aunque algunos son una versión de prueba jugable.

Plants vs ZombiesThe fancy pants adventure

Code BummerBubble Witch

Sin duda otro gran paso para Google.

viernes, 28 de enero de 2011

Componente lista para Tapestry 5 (paginable y anidable)

Tapestry 5 viene con muchos componetes útiles,  varios tipos de enlaces, campos de formulario, tabla paginada, bucles, gestión de errores y otros muchos otros que se le pueden
añadir mediante librerías externas. Sin embargo, hay uno que he echado en falta que es una lista de elementos paginada y que podamos usarla de forma anidada. Lo más parecido que hay es el componente Grid sin embargo este presenta los datos en una tabla y puede que no nos sirva.

Una de las cosas buenas de Tapestry es que es muy sencillo crear un nuevo componente. Asi que tomando como bse el código del componente Grid, ya que tiene muchas similitudes con él, he desarrollado un componente lista paginada.

El componente Lista está formado por las siguientes clases:

com.blogspot.elblogdepicodev.tapestry.components.Lista: representa la clase principal del componente.
com.blogspot.elblogdepicodev.tapestry.components.ListaPager: genera una lista de enlaces a las páginas de la lista según los datos devueltos por el objeto ListaDataSource y emite los eventos de cambio de página.
com.blogspot.elblogdepicodev.tapestry.components.ListaRows: procesa los elementos de la lista para la página que se está visualizando.

A partir del componente Gird, he eliminado lo que no era necesario y añadido el soporte para poder anidar varios componentes Lista. Su uso podría ser tan sencillo como lo siguiente, aunque se pueden pasar otros parámetros como el número de elementos en cada página y algunos otros parámetros que también existen en el componente Grid como inPlace, rowIndex,y rowClass:

<t:lista source="list1" row="x1" rowindex="i1" rowsperpage="literal:3" inplace="true">
  <t:lista source="list2" row="x2" rowindex="i2" rowsperpage="literal:10"> 
    ${x1} x ${x2} = ${multiplica(x1, x2)}
  </t:lista>
</t:lista>

Sencillo para todo lo que hace, ¿no?. Piensa como lo podrías hacer con otro framework ¿tendrías que copiar y pegar código para poder reutilizar la funcionalidad? ¿tendrías que manejar parámetros de la request y variables en sesión? Pues eso es una de las cosas que me gusta de Tapestry que te puedes olvidar de todo esto es usar el componente y listo no hace falta saber como funciona internamente de eso ya se encarga el componente. El resultado sería este.


Otra clase importante es ListaDataSource en la cual se apoya la clase Lista para obtener los datos a mostrar.

com.blogspot.elblogdepicodev.tapestry.misc.ListaDataSource: es una interfaz que permitirá al componente lista obtener los datos a mostrar.

package com.blogspot.elblogdepicodev.tapestry.misc;

import org.apache.tapestry5.ValueEncoder;

public interface ListaDataSource {
 /**
  * Devuelve el número de elementos totales en la lista.
  */
    int getAvailableRows();

 /**
  * Permite preparar el modelo para mostrar los elementos desde el índice de inicio hasta el índice de fin.
  */
    void prepare(int startIndex, int endIndex);

 /**
  * Obtiene el objeto de índice indicado.
  */
    Object getRowValue(int index);

 /**
  * Devuelve la clase de los objetos de la lista.
  */
    @SuppressWarnings("rawtypes")
    Class getRowType();
    
    @SuppressWarnings("rawtypes")
 /**
  * Encoder para el objeto devuelto por el método getContext().
  */
    ValueEncoder getEncoder();

 /**
  * Dato asociado a los elementos lista, usado como clave para persistir la página actual de la lista a visualizar y permitir el anidamiento de componentes Lista.
  */
    Object getContext();
}

La clase ListaDataSource es una interfaz y por tanto necesitamos alguna implementación de ella para poder usar el componente Lista. Con las siguientes dos abarcamos muchos de los casos que podemos necesitar.

com.blogspot.elblogdepicodev.tapestry.misc.CollectionListaDataSource: crea un objeto ListaDataSource a partir de una colección de objetos.
com.blogspot.elblogdepicodev.tapestry.misc.HibernateListaDataSource: crea un objeto ListaDataSource que obtendrá los datos de una sesión de hibernate.

Otra de las buenas características de Tapestry es el concepto de Coercer que básicamente es una forma que tiene Tapestry de convertir un objeto en otro, por ejemplo un int a un String, un String con cierto formato a una lista, etc... en el caso que nos ocupa una Collection en un CollectionDataSource, de esta forma en el parámetro source del componente Lista podremos pasarle también un objeto que implemente la interfaz Collection y Tapestry buscará una forma de convertir esa Collection a un objeto que implemente el tipo del parámetro (ListaDataSource). Para ello debemos indicarle a Tapestry en la clase del módulo de la aplicación el coercer necesario para ello.

package com.blogspot.elblogdepicodev.tapestry.services;

public class AppModule {
...
@SuppressWarnings("rawtypes")
public static void contributeTypeCoercer(Configuration configuration) {
    Coercion coercion = new Coercion() {
        public CollectionListaDataSource coerce(Collection input) {
            return new CollectionListaDataSource(input);
        }
    };

    configuration.add(new CoercionTuple(Collection.class, CollectionListaDataSource.class, coercion));

    add(configuration, ListaPagerPosition.class);
}
...
}

El código del componente lista es demasiado como para ponerlo en esta entrada pero podéis descargarlo desde el apartado referencia. Si a alguien le parece interesante puede sentirse libre de usarlo y modificarlo.

Referencia:
Documentación sobre Apache Tapestry
Código fuente del componente lista paginada para Tapestry 5 (source code)

miércoles, 12 de enero de 2011

Componente cache para Tapestry 5

En el desarrollo de un proyecto web suele ser interesante tener alguna forma de cachear el contenido de ciertas regiones dinámicas de la página que puede ser costoso generarlas pero que es poco habitual que varíen su contenido, como por ejemplo, el menú, el pie de página u otras partes comunes. El cachear estas regiones ayuda a generar la página más rápidamente y supone un ahorro de recursos del servidor con lo que conseguimos dos importantes cosas: evitar que el usuario pierda el interés y se vaya de nuestra web porque la página tarda mucho en cargarse y poder atender a más usuarios con los mismos recursos del servidor.

Para Tapestry 4 había disponible un componente cache en la librería tapfx pero estos componentes no son compatibles con Tapestry 5. Con lo que he tenido la necesidad de desarrollar uno compatible con esta versión (y me ha sorprendido lo sencillo que me ha resultado como veréis por el número de lineas del mismo).

El componente Cache que he desarrollado hace uso de la librería estándar de facto para esta funcionalidad en Java ehcache, a la que si tenemos necesidad posteriormente podemos añadirle características de cache distribuida con terracotta de forma simple y sin afectar al código del componente.

Sin más vayamos a ver el código del componente:

package com.blogspot.elblogdepicodev.tapestry.components;

import java.util.Collections;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.RequestGlobals;

public class Cache {

    @Inject
    @Property
    private RequestGlobals requestGlobals;
    
    @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL)
    private String cacheName;
    
    @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.LITERAL)
    private String key;
    
    @Parameter(value = "false", defaultPrefix = BindingConstants.PROP)
    private boolean disabled;
    
    @Inject
    private CacheManager cacheManager;

    boolean beforeRenderBody(MarkupWriter writer) {
        if (!disabled) {
            Ehcache cache = cacheManager.getEhcache(cacheName);
            net.sf.ehcache.Element element = (net.sf.ehcache.Element) cache.get(key + "-" + requestGlobals.getRequest().getLocale().toString());
            if (element != null) {
                String value = (String) element.getValue();
                writer.writeRaw(value);
                return false;
            }
            writer.element("cache", Collections.EMPTY_LIST.toArray());
        }
        return true;        
    }
    
    void afterRenderBody(MarkupWriter writer) {
        if (!disabled) {
            Element e = writer.getElement();
            if (e.getName().equals("cache")) {
                String value = e.getChildMarkup();
                writer.end();
                e.pop();
            
                net.sf.ehcache.Element element = new net.sf.ehcache.Element(key + "-" + requestGlobals.getRequest().getLocale().toString(), value);
                Ehcache cache = cacheManager.getEhcache(cacheName);
                cache.put(element);
            }
        }
    }
}

Como se puede ver en el código el componente tiene 3 parámetros: cacheName para saber en que cache de ehcache se cacheará el contenido html, key para identificar el contenido en la cache y disabled para habilitar la cache o deshabilitarla según alguna condición.

La funcionanilidad está dividida en dos métodos: beforeRenderBody y afterRenderBody. En el primero lo que se hace es comprobar si se habilita el uso de la cache, si es que no se devuelve true para que se procesen el cuerpo del componente, si está habilitada la cache (disabled == false) se comprueba si en la cache indicada por el parámetro cacheName existe una clave indicada por el parámetro key, si existe el contenido html del cuerpo del componente se ha generado anteriormente y ya está cachedo por lo que no es necesario volver a procesarlo y se escribe directamente el contenido, finalmente se devuelve false para que no se procese el cuerpo del componente. Si se llama al método afterRenderBody es que se ha procesado el cuerpo del componente, si la cache está habilitada tendremos que almacenar el contenido html generado por los componentes del cuerpo del componente cache para posteriores usos, se obtiene la cache y en la clave indicada junto con el locale en el que se ha generado el contenido se guarda el html del cuerpo.

Otra parte del uso de este componente es definir el servicio CacheManager que se inyecta en el componente. Para ello tendremos que definir un método (buildCacheManager) en la clase del módulo de la aplicación para construir el servicio y poder inyectarlo en el componente.

package com.blogspot.elblogdepicodev.tapestry.services;

import java.util.Collection;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.constructs.blocking.BlockingCache;

import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.annotations.SubModule;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.util.StringToEnumCoercion;

...

public class AppModule {

    ...
    
    public static CacheManager buildCacheManager() {
        CacheConfiguration defaultCacheConfig = new CacheConfiguration("defaultCache", 10000);
        defaultCacheConfig.setDiskStorePath("java.io.tmpdir");
        defaultCacheConfig.setEternal(false);
        defaultCacheConfig.setTimeToIdleSeconds(120);
        defaultCacheConfig.setTimeToLiveSeconds(120);
        defaultCacheConfig.setOverflowToDisk(false);
        defaultCacheConfig.setDiskPersistent(false);
        defaultCacheConfig.setMemoryStoreEvictionPolicy("LRU");
        Cache defaultCache = new Cache(defaultCacheConfig);

        CacheConfiguration fragmentosTapestryConfig = new CacheConfiguration("fragmentos-tapestry", 50);
        fragmentosTapestryConfig.setEternal(false);
        fragmentosTapestryConfig.setTimeToIdleSeconds(1800);
        fragmentosTapestryConfig.setTimeToLiveSeconds(3600);
        fragmentosTapestryConfig.setOverflowToDisk(false);
        fragmentosTapestryConfig.setDiskPersistent(false);
        fragmentosTapestryConfig.setMemoryStoreEvictionPolicy("LRU");
        Cache fragmentosTapestryCache = new Cache(fragmentosTapestryConfig);        
        
        CacheManager cacheManager = new CacheManager();
        cacheManager.addCache(defaultCache);
        cacheManager.addCache(fragmentosTapestryCache);
        
        Cache cache = cacheManager.getCache("fragmentos-tapestry");
        BlockingCache fragmentosTapestryBlockingCache = new BlockingCache(cache);
        cacheManager.replaceCacheWithDecoratedCache(cache, fragmentosTapestryBlockingCache);
        
        return cacheManager;
    }
}

La configuración del ehcache puede hacerse definiendo un archivo ehcache.xml o como he preferido en este caso de forma programática, aquí se definen dos cache: la cache por defecto y una cache para los fragmentos html que cacheará el componente Cache.

El uso del componente cache sería tan sencillo como:

<t:cache cacheName="fragmentos-tapestry" key="ejemplo">
    [Componentes de tapestry que generarán en contenido HTML que se cacheará]
</t:cache>

Y esto es todo, unas 65 líneas de código que pueden mejorar notablemente el tiempo de respuesta de nuestras aplicaciones del ya de por si excelente rendimiento que ofrece Tapestry. Si este componente te ha resultado interesante o puede serte útil para tu proyecto siente libre de usarlo, modificarlo o proponer mejoras, solo pido que dejes un comentario en esta entrada para conocer a otras personas que usan Tapestry o tienen interés en él.

Referencia:
Documentación sobre Apache Tapestry
http://ehcache.org/
http://www.terracotta.org/
http://andyhot.di.uoa.gr/tapfx/app
http://tapfx.sourceforge.net/