20 octubre, 2013

Spring Framework y Vistas XSLT

XSLT es un lenguaje de transformación para XML y es popular como una tecnología de vistas con aplicaciones web. XSLT puede ser una buena elección como una tecnología de vista si tu aplicación naturalmente negocia con XML, o si tu modelo puede ser fácilmente convertido a XML. Este post muestra como producir un documento XML como modelo de datos y tenerlo transformado con XSLT en una aplicación Spring Web MVC.

Este ejemplo es una aplicación trivial de Spring que crea una lista de palabras en el Controlador y las agrega al mapa de modelo. El mapa es retornado junto con el nombre de vista de nuestra vista XSLT. La vista XSLT pondrá la lista de palabras en un documento simple XML listo para la transformación.

La estructura de la aplicacion

Nuestra aplicacion es una aplicacion Simple de Maven y deberá tener una estructura similar a esto:


Usando Maven

Como yo estoy usando Maven para el manejo de las dependencias de la aplicacion, mi archivo pom.xml se ve asi:
<project xmlns="http://maven.apache.org/POM/4.0.0" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                    http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.codelious.mixslt</groupId>
  <artifactId>MiXSLT</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <dependencies>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.0.1.RELEASE</version>
   </dependency>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.0.1.RELEASE</version>
   </dependency>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>3.0.1.RELEASE</version>
   </dependency>
   <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>3.0.1.RELEASE</version>
   </dependency>
   <dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
   </dependency>
   <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
   </dependency>
   <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
   </dependency>
  </dependencies>
</project>

Los archivos de configuracion y dispatcher sevlet

El archivo web.xml tiene el siguiente aspecto:
<?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_3_0.xsd"
 id="WebApp_ID" version="3.0">
 
 <display-name>MiXSLT</display-name>
 
 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>

 <servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>*.htm</url-pattern>
 </servlet-mapping>

</web-app>
y El archivo spring-servlet.xml que es nuestro despachador de solicitudes sigue asi:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 <bean id="viewResolver"
  class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
  <property name="order">
   <value>1</value>
  </property>
  <property name="basename" value="views" />
 </bean>
 
 <bean id="xsltViewResolver"
  class="org.springframework.web.servlet.view.xslt.XsltViewResolver">
  <property name="order">
   <value>2</value>
  </property>
  <property name="viewClass"
   value="org.springframework.web.servlet.view.xslt.XsltView" />
  <property name="sourceKey" value="obj" />
  <property name="suffix" value=".xsl" />
  <property name="prefix" value="/WEB-INF/xsl/" />
 </bean>

 <bean id="simpleUrlMapping"
  class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  <property name="mappings">
   <value>
    /home.htm=homeController
   </value>
  </property>
 </bean>

 <bean id="homeController" class="com.codelious.mixslt.controller.HomeController" /> 
</beans>

Definicion de Beans

Como se ve arriba, la configuracion es estandar para una aplicacion simple de Spring. El archivo de configuracion de dispatcher servlet contiene una referencia a un ViewResolver, URL mappings y un Bean de controlador que encapsula nuestra logica de generacion de palabras:
<bean id="homeController" class="com.codelious.mixslt.controller.HomeController" />

Codigo del Controlador MVC

La logica del controlador es encapsulada en una subclase de AbstractController, con el metodo handler que se define como sigue...
package com.codelious.mixslt.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

public class HomeController extends AbstractController {

    protected ModelAndView handleRequestInternal(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
  
        Map mapa = new HashMap();
        List listaPalabras = new ArrayList();
  
        listaPalabras.add("Hola");
        listaPalabras.add("Universo");
  
        mapa.put("listaPalabras", listaPalabras);
  
        return new ModelAndView("home", mapa);
    }

}
Hasta aqui no tenemos nada especifico de XSLT. Los datos del modelo han sido creados en la misma forma en que hariamos otra aplicacion Spring MVC. Dependiendo de la configuracion de la aplicacion, esta lista de palabras podria ser renderizada por JSP/JSTL teniendolas agregadas como atributos en la solicitud (request attributes), o podrian ser manipuladas por Velocity agregando el objeto al VelocityContext. Para tener XSLT renderizalos, y por supuesto tienen que ser convertidos en un documento XML de alguna manera. Existen paquetes de software que automaticamente 'DOMifican' los objetos, pero con Spring, tienes completa flexibilidad para crear el DOM desde tu modelo en cualquier forma que quieras. Esto evita el juego exesivo con la transformacion de XML, lo cual es peligroso cuando usamos herramientas para manejar procesos de DOMificacion

Convertir los datos del modelo a XML

Para crear un documento DOM desde nuestra lista de palabras o cualquier otro dato del modelo, debemos extender de la clase org.springframework.web.servlet.view.xslt.AbstractXsltView. Haciendo esto, debemos tambien implementar el metodo abstracto createXsltSource(..). El primer parametro pasado a este metodo es nuestro mapa del modelo. Aqui esta la clase HomePage en nuestra aplicacion:
package com.codelious.mixslt.controller;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;

import org.springframework.web.servlet.view.xslt.AbstractXsltView;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

public class HomePage extends AbstractXsltView {

    protected Source createXsltSource(Map modelo, String nombreRaiz,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {

        Document documento = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element raiz = documento.createElement(nombreRaiz);

        List palabras = (List) modelo.get("listaPalabras");
        for (Iterator it = palabras.iterator(); it.hasNext();) {
            String palabraSiguiente = (String) it.next();
            Element nodoPalabra = documento.createElement("palabra");
            Text nodoTexto = documento.createTextNode(palabraSiguiente);
            nodoPalabra.appendChild(nodoTexto);
            raiz.appendChild(nodoPalabra);
        }
        return new DOMSource(raiz);
    }
}

Una serie de parametros nombre/valor pueden ser definidos opcionalmente por tu subclase la cual sera agregado al objeto de transformacion. Los nombres de parametro deben coincidir con los definidos en la plantilla XSLT declarados con <xsl:param name="miParametro">valorDefecto</xsl:param><bean id="homeController" class="xslt.HomeController" />. Para especificar los parametros, sobreescribe el metodo getParameters() de la clase AbstractXsltView y retorna un Map de nombre/valor. Si tus parametros necesitan derivar informacion desde la solicitud actual, tu puedes sobreescribir el metodo getParameters(HttpServletRequest request) en su lugar.

Definiendo las propiedades de la vista

El archivo views.properties (o definicion xml equivalente si estas usando un view resolver basado en XML) luce como este para la unica vista de la aplicacion que es 'Mis Primeras Palabras':
home.(class)=com.codelious.mixslt.controller.HomePage 
home.stylesheetLocation=/WEB-INF/xsl/home.xsl
home.root=palabras
Aqui, puedes ver como la vista esta enlazada con la clase HomePage recien escrita la cual maneja el modelo domificacion en la primera propiedad '.class'. La propiedad 'stylesheetLocation' apunta al archivo XSLT el cual manipulara para nosotros la transformacion XML dentro de HTML y la propiedad final '.root' es el nombre que sera usado como la raiz del documento XML. Este obtiene acceso a la clase HomePage de arriba en el segundo parametro para los metodos createXsltSource(..).

Transformacion del documento

Finalmente, tenemos el codigo XSLT usado para transformar el documento de arriba. Como muestra en el archivo 'views.properties', la hoja de estilos es llamada 'home.xslt' y esta ubicada en el directorio 'WEB-INF/xsl'.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" omit-xml-declaration="yes" />

    <xsl:template match="/">
        <html>
            <head>
                <title>Hola!</title>
            </head>
            <body>
                <h1>Mis Primeras Palabras</h1>
                <xsl:apply-templates />
            </body>
        </html>
    </xsl:template>

    <xsl:template match="palabra">
        <xsl:value-of select="." />
    </xsl:template>
</xsl:stylesheet>

El archivo index.jsp solamente es la pagina de entrada a la aplicacion y en ella hemos puesto un link a nuestro controlador http://localhost:18080/MiXSLT/home.htm
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
 pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>MiXSLT</title>
    </head>
    <body>
        <h1>Bienvenido a mi XSLT</h1>
        Prueba la vista XSL en <a href="http://localhost:18080/MiXSLT/home.htm">
                      http://localhost:18080/MiXSLT/home.htm</a>
    </body>
</html>

Solo nos queda probar nuestra aplicacion en un navegador usando la pagina de entrada o bien directamente escribiendo la url http://localhost:18080/MiXSLT/home.htm y deberia verse algo asi:
Puedes descargarte el codigo en github https://github.com/codelious/Spring_XSL_views

No hay comentarios.:

Publicar un comentario