sábado, 9 de marzo de 2013

Skinning de web usando Apache Tapestry

Apache Tapestry
Algunas páginas o aplicaciones web tienen la necesidad de variar su aspecto en función de determinadas variables. La aplicación puede ser la misma pero necesitar cambiar el aspecto o la imagen de marca en función de esas variables. Si los cambios a realizar solo implican cosas visuales como colores, tipos de fuentes y tamaños, imágenes, etc... con cambiar las hojas de estilo de la web puede ser suficiente, pero si es necesario modificar el html que genera la aplicación porque los elementos sean diferentes o estén en diferente posición dentro del html generado necesitaremos incluir lógica en forma de condiciones en las plantillas  que forman parte de la vista del framework que usemos. Si únicamente tenemos una variable incluir lógica en las plantillas puede ser manejable pero si son varias tarde tarde o temprano su mantenimiento será un problema y podremos tener como resultado código spaghetti en el html. Otra alternativa a usar una única plantilla con lógica es tener una plantilla distinta por variable o conjunto de variables si las plantillas generan código html suficientemente diferente de forma que manejar las diferentes plantillas por separado sea más sencillo, a pesar de que probablemente estaremos duplicando código en las diferentes plantillas. Pero para tener diferentes plantillas tendremos que inventar un mecanismo que nos permita seleccionar la plantilla adecuada en función de las variables y las cosas pueden complicarse si se puede dar combinaciones de ellas.

¿Cuales podrían ser las variables por las que una aplicación quisiese cambiar el html generado? Algunas de ellas podrían ser:
  • El nombre del dominio. Si aplicación necesita de diferente imagen de marca en función del nombre de dominio por el que se accede al servidor donde está alojada la aplicación.
  • El tipo de usuario. Aquí podría entrar cualquier información que el usuario nos envía en las peticiones http, desde el lenguaje al país de la dirección IP pero una candidata podría ser el dispositivo utilizado para acceder a la aplicación, si se tratase de un dispositivo móvil podríamos eliminar las cosas suplerfuas para aligerar en peso la página y que se cargase más rápido o para que esté más adaptada a un ambiente táctil de un table o smartphone.
  • Otra podría ser la hora o fecha en la que se accede a la aplicación por ejemplo para adaptar la aplicación a épocas navideñas o ciertos periodos relevantes para la aplicación.
Si tenemos una aplicación con esta problemática deberemos buscar la mejor forma que el framework que usemos nos posibilite. A continuación voy a explicar como conseguirlo usando el framework Apache Tapestry. Usando este framework veremos que añadiendo una pocas cosas podemos resolver el problema de forma simple, sencilla y limpia evitando tener la misma lógica desperdiga por toda la aplicación. Una vez realizada la selección de la plantilla adecuada su uso nos será transparente y nos podremos olvidar de esta funcionalidad y centrarnos únicamente en crear las plantillas.

En Tapestry a las variables se les denomina «axis» o ejes, una de ellas disponible por defecto y especial es el «locale» o idioma del usuario, de esta forma en función del idioma podemos tener una plantilla distinta y completamente diferente del resto, si no existe la más específica se usará la mejor opción al igual que ocurre con los ResourceBundle de Java. Por ejemplo, si el idioma es es_ES puede ocurrir los siguientes casos:
  • Que la plantilla de una página o componente con eje del idioma es_ES exista, si la página es Index, su plantilla es Index_es_ES.tml si existe se selecciona esta.
  • Si la plantilla Index_es_ES.tml no existe se elige la siguiente mejor opción que es Index_es.tml.
  • Si Index_es.tml tampoco existe se selecciona Index.tml que será la más general y opción de último recurso.
En Tapestry el eje del idioma se aplica también a los recursos estáticos como puede ser las imágenes, css, javascripts, etc... en el caso de las imágenes nos puede ser muy útil si estas llevan texto. Los ejes personalizados fueron introducidos en la versión de Tapestry 5.3, aunque ya era posible conseguirlos en versiones anteriores se necesitaba más esfuerzo. Podemos tener tantos ejes como deseemos pero si son muchos las combinaciones y plantillas necesarias pueden crecer en gran medida aunque al igual con el eje del «locale» si la plantilla no existe se seleccionará la de por defecto, por suerte no será necesario que definamos todas las plantillas para todos los posibles casos de combinaciones de ejes solo deberemos crear las plantillas personalizadas que nos interesen. Las combinaciones de los ejes y las plantillas puede observarse en la siguientes figuras.

Quizá todo parezca más complicado de lo que realmente es, la implementación en código de los ejes se traduce básicamente en dos partes:
  • La lógica que determina los ejes a aplicar a una petición.
  • La lógica que en función de los ejes de la petición selecciona la plantilla adecuada.
Una de las grandes características de Tapestry es que las cosas pueden ser redefinidas, extendidas y adaptadas, todo lo que esté definido como un servicio en el contenedor de dependencias puede serlo y esto es mucha de la funcionalidad interna del framework ya que muchas cosas se definen como servicios, solo deberemos implementar la interfaz adecuada en una clase y hacer la contribución en el contenedor de dependencias para que se use.

Veamos la clase que contiene la lógica que determina el eje a aplicar en función del dominio suponiendo que la plantilla a usar sea diferente si se entra a la aplicación por el dominio .com.es o por .net, la interfaz a implementar es ComponentRequestSelectorAnalyzer:


La clase CustomComponentRequestSelectorAnalyzer para determinar si la aplicación cumple un eje necesitará información, en el caso del eje del dominio bastará con la clase HttpServletRequest que podrá ser inyectada por el contenedor IoC de Tapestry en la implementación de la clase, si necesitásemos más información será suficiente con indicar el servicio que la proporciona en el constructor. DominioAxis es un enumerado de utilidad para identificar el dominio. Si el nombre del dominio acaba en com.es o .net devuelve el enum correspondiente:


La segunda parte de los ejes es proporcionada por una clase que implementa la interfaz ComponentResourceLocator y seleccionará la plantilla adecuada en función de los ejes detectados en la petición por la clase CustomComponentRequestSelectorAnalyzer anterior. La parte importante está en el método locateTemplate:


Se obtienen los ejes por los que pueden seleccionar las plantillas, en este caso solo uno pero podrían ser múltiples y combinados de cualquier forma aunque en ese caso la lógica para selecciona la plantilla sería más complicada. Independientemente del número de ejes el resultado final ha de ser el nombre de la plantilla y si existe se usa sino se usa la plantilla que no tiene ningún eje (salvo el locale que se puede aplicar siempre incluso usando ejes personalizados). En el caso concreto del ejemplo la plantilla sigue el formato «[nombre plantilla]_[eje dominio].tml» que en el caso de la página Index podría ser Index_com_es.tml o Index_net.tml. Si la petición no tuviese ningún eje la decisión de seleccionar la plantilla se delegaría en el servicio por defecto de Tapestry.

Una vez codificadas estas clases para que se usen se han de hacer unas pocas contribuciones en el módulo de la aplicación, en concreto lo que necesitamos definir son dos métodos, contributeServiceOverride y decorateComponentResourceLocator. Si los nombres de estos métodos no son suficientemente explicativos diré que contributeServiceOverride hace que se use un servicio en vez de otro, como queremos que se use nuestro CustomComponentRequestSelectorAnalyzer en vez del de por defecto que usa Tapestry lo sobreescribimos, nuestro servicio lo definimos en el método bind. El método decorateComponentResourceLocator aplica el patrón de diseño Decorator sobre otro servicio, si la clase CustomComponentResourceLocator detecta un eje y encuentra la plantilla la selecciona sino siguiendo el patrón decorate delega en la clase que decora.


Y este sería el resultado de acceder por los diferentes dominios:

 

Estas son las plantillas que se usan en cada uno de los casos:
  • En el caso del dominio que acaba en .local se usan las plantillas de la página Index.tml y HolaMundo.tml del componente HolaMundo ya que CustomComponentRequestSelectorAnalyzer no establece el eje del dominio.
  • En el dominio .com.es si se establece el eje de dominio y se buscan las plantillas de este eje, en el caso de la plantilla de la página se Index.tml ya que no existe Index_com_es.html pero en el caso del componente se usa HolaMundo_com_es.tml ya que si existe.
  • En el dominio .net se usa Index_net.tml y HolaMundo_net.tml ya que en ambos casos existen.

El código fuente completo lo puedes encontrar en mi repositorio de GitHub. ¿Quieres saber más sobre Apache Tapestry? Visita Documentación sobre Apache Tapestry.

Referencia:
http://blog.tapestry5.de/index.php/2011/06/24/template-skinning/
Código fuente completo del ejemplo Skinning de plantillas con Apache Tapestry