Desarrollo de Aplicaciones Web con Tapestry

Puede encontrar la versión original de este artículo en Inglés en:

http://www.devx.com/Java/Article/30316

Tapestry

Tapestry es un marco de trabajo de código abierto para el desarrollo de aplicaciones Web Java basadas en componentes y orientadas a objetos. Simplemente, en lugar de tratar con el API Servlet o con las Actions de Struts, el programador de Tapestry almacena los datos de usuario con propiedades de objetos y maneja las acciones de usuario con métodos manejadores de eventos.

Otra característica importante de Tapestry es su uso de plantillas HTML. En Tapestry cada página es una plantilla HTML que contiene etiquetas HTML amigables para el navegador. Al contrario que las páginas JSP, JSTL o JSF, crear páginas Tapestry es relativamente fácil utilizando herramientas de diseño Web comunes, y puede previsualizarlas en un navegador.

Este artículo explica algunas de las principales caracerísticas de Tapestry y demuestra como Tapestry 4 hace que las cosas sean incluso más sencillas que en las versiones anteriores.

Configurar Tapestry

Tapestry está construido sobre el API Servlet estándar, lo que significa que sólo puede ejecutarse sobre cualquier contenedor de Servlet o servidor de aplicaciones Java. Lo único que necesita es configurar el servlet Tapestry en su fichero web.xml, como se ve en el siguiente fragmento de código:

 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name> Introduction to Tapestry Tutorial</display-name>
<servlet>
<servlet-name> app</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>
		

Puede poner el nombre de servlet que desee. Sin embargo, modificar el patrón de url es un poco lioso, por eso debería dejarlo como está: ("/app").

La Aplicación de Ejemplo

La mejor forma de hacerse una idea de lo que es Tapestry es trabajar con algunos ejemplos. En este artículo, trabajará sobre una site Web para vender billetes de vuelo con descuento para varios destinos. La aplicación utiliza un sencillo modelo de negocio (puede verlo en la Figura 1):

  • Las ofertas promocionales las presenta la clase DestinationOffer, que contiene la ciudad de destino y el precio.
  • El interface FeatureDestinations proporciona métodos de negocio que conciernen a un conjunto de ofertas promocionales diarias.
  • La clase FeatureDestinationsImpl es una implementación basada-en-memoria de este interface.

Figura 1: Modelo UML de la Aplicación de Ejemplo.

Su Primera Página Tapestry

Empecemos con una página sencilla. Las aplicaciones Tapestry tienen una página inicial llamada (apropiadamente) Home. Su página Home mostrará un destino elegido aleatoriamente. También proporcionará un enlace hacia la propia página para poder mostrar otra oferta. La pagina Home requiere dos cosas:

  • Una plantilla de página, que contiene una mezcla de código HTML y componentes Tapestry (veremos los componentes más adelante).
  • Una clase Java correspondiente, que proporciona datos para las partes dinámicas de la página.

En Tapestry 3, cada página también necesita un fichero de especificación. Este fichero es un fichero XML que describe el mapeo entre la plantilla de página y la clase Java. Aunque las especificaciones de la página pueden ser útiles, e incluso necesarias en páginas más complejas. Tapestry 4 utiliza la anotación de Java 5 y atributos extras de etiquetas para reducir enormemente la necesidad de escribir una especificación por cada una de las páginas. No tiene que precuparse de ellas en los ejemplos de este artículo.

La Plantilla de Página

Empecemos mirando la plantilla de página:

 
<html>
<head>
<title>Tutorial: Introduction to Tapestry</title> 
</head>

<body>
<h3>Online Travel Discounts</h3>
<h4>One of today's feature destinations:</h4> 
<p>
A trip to 
<span jwcid="@Insert" value="ognl:featureDestination.destination">Paris</span> 
for only $<span jwcid="@Insert" value="ognl:featureDestination.price">199</span>
</p>     
</body>
</html>
		

Lo primero que podría observar es que la plantilla es muy parecida a un HTML normal. De hecho, puede verla sin problemas en un navegador Web (vea la Figura 2). Cómo utiliza etiquetas HTML estándar en lugar de etiquetas JSP, JSTL, or JSF, un Webmaster sin conocimienos de Java puede fácilmente construir y mantener la plantilla de página utilizando herramientas de diseño Web ordinarias. Por el contrario, las páginas JSF utilizan etiquetas que son un poco extrañas para un diseñador Web tradicional, y no se pueden visualizar en un navegador Web. Los desarrolladores Java también encuentran más conveniente poder previsualizar las páginas sin tener que desplegarlas en un servidor de aplicaciones:


Figura 2: Previsualización de una Página Tapestry

Componentes Tapestry

Lo siguiente que podría haber observado son los atributos de etiquetas un poco extraños ("jwcdi", por ejemplo). Estos atributos identifican componentes Tapestry, que son los responsables de generar (o "renderizar") las partes dinámicas de la página (vea la Figura 3):


Figura 3: Generar una Página Tapestry

Los componentes Tapestry están embebidos en etiquetas HTML normales, como en el siguiente ejemplo:

 
<span jwcid="@Insert" value="ognl:featureDestination.price">199</span>
		

El atributo jwcid viene de Java Web Component ID. Identifica el componente dinámico en el que está interesado. Aquí el componente es un Insert, que inserta datos recuperados desde una propiedad de la correspondiente clase Java. El valor del atributo indica donde se encontrarán los datos dinámicos. El prefijo ognl viene de Object Graph Navigation Language, un poderoso lenguaje de expresión que es muy útil para identificar propiedades dentro de objetos Java. En este caso partícular estamos capturando el atributo price anidado en el atributo featureDestination de la clase Java asociada con esta página. El contenido de la etiqueta (199) es sólo para propósitos de visualización: será reemplazado en tiempo de ejecución por el texto generado dinámicamente.

Existen alrededor de unos 50 componentes internos en Tapestry, que generalmente cubren la mayoría de las necesidades de los desarrolladores Web, incluyendo bucles, condicionales, y campos de entrada de fechas. Si realmente lo necesita, también puede escribir su propio componente fácilmente.

La Clase de la Página

La mayoría de las páginas Tapestry con algún contenido dinámico importante tienen su correspondiente clase Java, que trabaja con la plantilla de página para rellenar las partes dinámicas de la página. La clase debería tener el mismo nombre que la página, y, por conveniencia, extender la clase BasePage:

¿Dónde Busca Tapestry sus Clases?

Tapestry 4 le hace la vida más fácil permitiéndole decidir donde buscar sus clases de página. Puede hacer esto configurando un fichero especial de application specification, que es un fichero (opcional) de configuración XML que contiene parámetros para toda la aplicación. Tapestry espera que este fichero tenga el nombre de la aplicación (con la extensión ".application"), y lo buscará en el directorio WEB-INF. En la aplicación de ejemplo, el fichero application specification (llamado app.application) es el siguiente:

 
<!DOCTYPE application PUBLIC 
"-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<application>  
<meta key="org.apache.tapestry.page-class-packages" 
value="com.wakaleo.tutorials.tapestry.pages"/>
</application>
					

Una clase de página Tapestry normalmente proporciona:

  • Métodos get y set para los objetos usados en la página.
  • Métodos que son llamados en diferentes momentos del ciclo de vida de la página.
  • Métodos que serán invocados por las acciones del usuario sobre la página.

Abajo tiene el código de la clase para la página Home del ejemplo, que representa sólo los dos primeros tipos de métodos:

 
public abstract class Home extends BasePage implements PageBeginRenderListener {
    public abstract DestinationOffer getFeatureDestination();
    public abstract void setFeatureDestination(DestinationOffer featureDestination);

    public void pageBeginRender(PageEvent event) {
        setFeatureDestination(BusinessFactory
        getFeatureDestinations()
        getDailyFeatureDestination());  	
    }
}
		

Una de las peculiaridades de Tapestry es que las propiedades de la página normalmente son abstract, en vez de ser las típicas propiedades JavaBean. Otra cosa importante es que, por razones de rendimiento, las páginas Tapestry son almacenadas en un repositorio y pueden ser compartidas entre usuarios. Por eso debe re-inicializar cualquier dato especifico de usuario antes de reutilizar la página. Mediante la declaración abstract de los métodos get y set de una propiedad, está permitiendo que Tapestry maneje todos los detalles tediosos de la limpieza e inicialización de propiedades.

Tapestry también proporciona varios interfaces, que usted puede utilizar para implementar métodos que son llamados en ciertos momentos del ciclo de vida de la página. Uno de los más útiles es el interface PageBeginRenderListener, que junto con el método pageBeginRender(), inicializa las propiedades de la página que se está generando, Tapestry llama al método pageBeginRender() justo antes de mostrar la página. El ejemplo inicializa el atributo featureDestination de la página con un valor recuperado de la clase de negocio apropiada.

Trabajar con Links y Listeners

Una de las principales innovaciones de Tapestry (y otros marcos de trabajo más recientes como JSF) es que los enlaces HTML, los botones de envío, etc. son mapeados directamente a métodos arbitrarios de clases Java. En Tapestry la lógica de navegacion y el envío de datos están situados en estos métodos "listener", o, en los casos más simples, directamente configurados en el propio componente Tapestry,

Por ejemplo, suponga que quiere añadir un botón que redibuja la página Home con un nuevo destino. Definir los enlaces a otras páginas es una parte importante de cualquier aplicación Web, y Trapestry proporciona varias formas convenientes de hacerlo. La forma más fácil es simplemente crear un enlace a otra página Tapestry utilizando el componente PageLink, como se muestra aquí:

 
<a href="#" jwcid="@PageLink" page="Home">Show another feature destination!</a>
		

Esto generará una correcta referencia href a la página Home de la aplicación. Una de las cosas bonitas de Tapestry es que casi nunca tendrá que preocuparse sobre la forma exacta de la URL. En este caso, incluso ni necesita añadir un método a la clase de la página.

Ahora suponga que quiere añadir un botón "Show Details" a su página Home. Necesitaría añadir un enlace a la página de detalles y proporcionar un parámetro que indique qué oferta mostrar. En vez de forzarle a usted a construir la URL de forma dolorosa, Tapestry utiliza una aproximación mas orientada-a-objetos: invocando métodos listener. Para usar esta función, necesita añadir un nuevo método (digamos showDetails()) a la clase Java Home con la siguiente firma:

 
public IPage showDetails(DestinationOffer destination)
		

Luego usted llama a este método utilizando el componente DirectLink e indicando dónde deberían estar los parámetos del método con el atributo parameters.

 
<a href="#" jwcid="@DirectLink" 
listener="listener:showDetails"
parameters="ognl:{ featureDestination }">Show Details</a>
		

Tapestry 4 es muy flexible con los métodos listener. Un método listener puede ser cualquier método normal de Java, con o sin parámetros o un tipo de retorno.

En este caso, usted querrá que el método vaya a la página ShowDetails. Para hacer esto, el método debe devolver un ejemplar del interface IPage, que toda página Tapestry debe implementar. Utilizando Java 5, usted puede utilizar las anotaciones de Tapestry 4 para inyectar un método get con la referencia apropiada a la página objetivo, de esta forma:

 
@InjectPage("ShowDetails")
public abstract ShowDetails getShowDetailsPage();

public IPage showDetails(DestinationOffer destination) {
    ShowDetails showDetailsPage = getShowDetailsPage();
    showDetailsPage.setOffer(destination);
    return showDetailsPage;
}
		

En resumidas cuentas, este método inicializará la página ShowDetails con la oferta seleccionada y luego transferirá el control a esta página.

Trabajar con Listas

Tapestry proporciona un número impresionante de componentes útiles. Para ilustrar estos componenes estándar, suponga que quiere una página que liste todos sus destinos. Cada destino debería tener un enlace a la página de detalles. Para hacer esto, debería utilizar el componente For de Tapestry, como se ilustra aquí:

 
<table cellspacing="6">
<tr>
<td>Destination</td>
<td>Price</td>
<td></td>
</tr>
<tr>
<td colspan="3"><hr/></td>
</tr>
<tr jwcid="@For" source="ognl:featureDestinationOffers" value="ognl:offer" element="tr">
<td><span jwcid="@Insert" value="ognl:offer.destination"/></td>
<td>$<span jwcid="@Insert" value="ognl:offer.price"/></td>
<td>
<a href="#" jwcid="@DirectLink" 
listener="listener:showDetails" 
parameters="ognl:{ offer }">Show Details</a>
</td>
</tr>  
<tr jwcid="$remove$">
<td>Paris</td>
<td>199</td>
<td><a href="#">Show Details</a></td>
</tr>
<tr jwcid="$remove$">
<td>Rome</td>
<td>299</td>
</tr>
</table>
		

El atributo source especifica la colección o lista de objetos sobre los que iterar. El atributo value especifica una variable contenedora usada para almacenar el valor del objeto actual por cada iteración.

Los lectores más astutos observarán dos líneas al final de la tabla que contienen datos inútiles y un componente de aspecto extraño: $remove$. Este componente permite a un diseñador Web añadir etiquetas HTML que se pueden utilizar para previsualización pero que serán descartadas durante el renderizado de la página. Esto es muy útil para el correcto formateo y pevisualización de grandes tablas.

La correspondiente clase Java tiene que proporcionar los métodos get y set para los atributos usados en el componente For. También necesitará un método showDetails() para el componente DirectLink similar al utilizado en la página Home. Abajo tiene la clase completa:

 
public abstract class FeatureDestinationList extends BasePage implements PageBeginRenderListener {
    public abstract List<DestinationOffer> getFeatureDestinationOffers();
    public abstract void setFeatureDestinationOffers(List<DestinationOffer> offers);

    public abstract DestinationOffer getOffer();
    public abstract void setOffer(DestinationOffer offer);

    public void pageBeginRender(PageEvent event) {
        setFeatureDestinationOffers(BusinessFactory
        .getFeatureDestinations().getFeatureDestinationOffers());
    }

    @InjectPage("ShowDetails")
    public abstract ShowDetails getShowDetailsPage();

    public IPage showDetails(DestinationOffer destination) {
        ShowDetails showDetailsPage = getShowDetailsPage();
        showDetailsPage.setOffer(destination);
        return showDetailsPage;
    }....
}
		

Figura 4: Una página de Lista

Localización

La localización en Tapestry es una tarea remarcadamente sencilla. Cada página o componente que necesita ser localizado tiene su propio fichero de propiedades en el directorio WEB-INF de su aplicación Web y contiene los mensajes a mostrar. Para usarlos, puede utilizar un atributo especial de la etiqueta span de esta forma:

 
<span key="title">Online Travel Discounts</span>
		

Los contenidos de la etiqueta son sólo para propósitos de visualización: este texto será descartado en tiempo de ejecución y será reemplazado por el texto localizado.

Si necesita utilizar un mensaje formateado, puede utilizar en su lugar el componente Insert y el objeto especial messages accesible desde toda página:

 
<span jwcid="@Insert" 
value="ognl:messages.format('feature-destination',              
featureDestination.destination,
featureDestination.price)">
A trip to Paris for only $199
</span>
		

El primer parámetro es el nombre de la clave del mensaje: los siguientes parámetros son los parámetros de formateo del mensaje.

Abajo puede ver la plantilla final de la página completa incluida lalocalización:

 
<html>
<head>
<title><span key="page-title">Tutorial: Introduction to Tapestry</span></title> 
</head>
<body>
<h3><span key="title">Online Travel Discounts</span></h3>
<h4>
<span key="todays-feature-destination">One of today's feature destinations:
</span>
</h4>
<p>
<span jwcid="@Insert" 
value="ognl:messages.format('feature-destination',              
featureDestination.destination,
featureDestination.price)">
A trip to Paris for only $199
</span>
<a href="#" jwcid="@DirectLink" 
listener="listener:showDetails"
parameters="ognl:{ featureDestination }">
<span key="show-details">Show Details</span>
</a>
</p>

<p>
<a href="#" jwcid="@PageLink" page="Home">
<span key="show-another">Show another feature destination!</span>
</a>
</p> 
<p>
<a href="#" jwcid="@PageLink" page="FeatureDestinationList">
<span key="show-all">Show all your feature destinations!</span>
</a>
</p> 
</body>
</html>
		

Como puede ver, aún es basante legible y todavía se puede previsualizar en un navegador Web estándar. También puede localizar sus plantillas de página, lo que podría ser útil para páginas con mucho texto estático que necesiten ser localizadas. Por ejemplo. Home_fr.html sería la traducción francesa de la plantilla Home.html (vea la Figura 5):


Figura 5: Una Plantilla Localizada.

Muchos más 'Pros' que 'Contras'

Tapestry es un marco de trabajo claro, eficiente y fácil de utilizar (¿debería decir "divertido"?). En la tradición de Hibernate y Spring, en oposición a EJB y JSF, está basado en necesidades y experiencias del usuario real en vez de haber sido diseñado por un comité de especificación de estándar. Su modelo de programación, aunque no está exento de peculiaridades e idiosincrasias, es globalmente claro y elegante. Su arquitectura (especialmente en lo que concierne al control de sesión y al repositorio de componentes) es eficiente y altamente escalable. Proporciona un gran número de componentes estándar muy usables, y se pueden generar fácilmente componentes personalizados si son necesarios. También tiene integrados importante requerimientos de desarrollo Web como la localización.

En el lado negativo, es menos conocido que Struts o JSF, y no está respaldado por los gigantes de la industrua. La documentación no es tan abundante como la de Struts o JSF. Y aunque la comunidad Tapestry está activa y crece muy rápidamente, es relativamente difícil encontrar desarolladores Tapestry experimentados.

A pesar de todo, Tapestry es una buena pieza de arquitectura y merece que la tenga en consideración para su próximo proyecto de desarrollo Web.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP