Tutorial Castor XML (I): Modelo de clases desde un XSD

Castor XML es un “XML data binding framework” de código abierto bastante veterano pero que aún sigue dando guerra y que permite mapear los datos contenidos en objetos Java a documentos XML y viceversa. A diferencia de herramientas como JDOM o SAX, los frameworks como Castor XML realizan este mapeo o binding de forma automática y sin necesidad de tener que escribir una sola línea de código más allá de las necesarias para utilizar el propio framework. Asimismo, el mapeo realizado por Castor XML es configurable, e incluso podemos generar un modelo de clases que represente la estructura definida en un esquema XSD.

El objetivo de este pequeño tutorial no es ser un compendio de todas las posibilidades que ofrece Castor XML, sino mostrar cómo empezar a utilizar las principales características que el framework ofrece con un ejemplo práctico: simular un sistema de importación/exportación de los datos de un foro muy simple. En primer lugar, crearemos un modelo de clases que refleje la estructura de datos definida en un XSD, y posteriormente usaremos instancias de ese modelo de clases para mapaer sus datos a ficheros XML y viceversa, cumpliendo siempre con el esquema XSD. También veremos cómo utilizar cualquier modelo de clases, personalizar los mapeos y, en el último capítulo, cómo integrar Castor XML con Spring y con el módulo Spring OXM incluído en Spring 3.

Entorno de pruebas:

Requisitos: Conocimientos básicos de Java, XML y Maven.

GENERACIÓN DE UN MODELO DE CLASES A PARTIR DE UN XSD

Nuestro sistema de importación-exportación de datos de un foro se basará en ficheros XML que seguirán el esquema definido en un XSD. El uso de este tipo de esquemas a la hora de trabajar con XML nos aporta dos grandes ventajas:

  1. Conocer exactamennte la estructura que debe tener un fichero XML, así como las restricciones que deben cumplir los datos.

  2. Permite validar el XML para asegurar que su formato es el esperado. Esto es, el definido por el XSD.

El XSD que vamos a utilizar, llamado foro.xsd, es el siguiente:

<?xml version="1.0" encoding="utf-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified" xmlns="https://danielme.com/blog/castorxml"
	targetNamespace="https://danielme.com/blog/castorxml">

  <xs:element name="ExportacionForo">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="temas" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
	  <xs:element name="tema" minOccurs="0" maxOccurs="unbounded" type="temaType" />
	</xs:sequence>
          </xs:complexType>
        </xs:element>

        <xs:element name="usuarios" minOccurs="1" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
	  <xs:element name="usuario" minOccurs="1" maxOccurs="unbounded" type="usuarioType" />
	</xs:sequence>
          </xs:complexType>
        </xs:element>

        <xs:element name="categorias" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
	  <xs:element name="categoria" minOccurs="0" maxOccurs="unbounded" type="categoriaType" />
	</xs:sequence>
          </xs:complexType>
        </xs:element>

      </xs:sequence>

      <xs:attribute name="nombre" type="xs:string" use="required" />

    </xs:complexType>
  </xs:element>

  <xs:complexType name="temaType"> 
    <xs:sequence>
      <xs:element name="contenido" type="xs:string" minOccurs="1" maxOccurs="1" />
      <xs:element name="mensajes" minOccurs="0" maxOccurs="1">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="mensaje" minOccurs="0" maxOccurs="unbounded" type="mensajeType" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="agradecimientos" minOccurs="0" maxOccurs="1">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="agradecimiento" minOccurs="0" maxOccurs="unbounded"   type="agradecimientoType"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="usuarioRef" type="usuarioRefType" minOccurs="1" maxOccurs="1" />
    </xs:sequence>
    <xs:attribute name="id" type="xs:nonNegativeInteger" use="required" />
    <xs:attribute name="fecha" type="xs:date" use="required" />
    <xs:attribute name="titulo" type="xs:string" use="required" />
    <xs:attribute name="categoriaId" type="xs:nonNegativeInteger" />
  </xs:complexType>

  <xs:complexType name="usuarioType">
    <xs:attribute name="id" type="xs:nonNegativeInteger" use="required" />
    <xs:attribute name="alias" type="xs:string" use="required" />
    <xs:attribute name="rol" use="required">
      <xs:simpleType>
        <xs:restriction base='xs:string'>
          <xs:enumeration value='ADMIN' />	
          <xs:enumeration value='MODERADOR' />
          <xs:enumeration value='USUARIO' />
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="fechaIngreso" type="xs:date" use="required" />
    <xs:attribute name="email" type="xs:string" use="required" />
    <xs:attribute name="password" type="xs:string" use="required" /> 
  </xs:complexType>

  <xs:complexType name="agradecimientoType">
    <xs:sequence>
      <xs:element name="id" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1" />
      <xs:element name="usuarioRef" type="usuarioRefType" minOccurs="1" maxOccurs="1" />
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="usuarioRefType">
    <xs:sequence>
      <xs:element name="id" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1" />
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="mensajeType">
    <xs:sequence>
      <xs:element name="contenido" type="xs:string" minOccurs="1" maxOccurs="1" />
      <xs:element name="agradecimientos" minOccurs="0" maxOccurs="1">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="agradecimiento" minOccurs="0" maxOccurs="unbounded" type="agradecimientoType"/>
	</xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="usuarioRef" type="usuarioRefType" minOccurs="1" maxOccurs="1" />
    </xs:sequence>
    <xs:attribute name="id" type="xs:nonNegativeInteger" use="required" />
    <xs:attribute name="fecha" type="xs:date" use="required" />
  </xs:complexType>

  <xs:complexType name="categoriaType">
    <xs:attribute name="id" type="xs:nonNegativeInteger" use="required" />
    <xs:attribute name="nombre" type="xs:string" use="required" />
  </xs:complexType>


</xs:schema>

A partir de este esquema, vamos a construir con Maven un jar que contenga el modelo de clases generado por Castor XML. Dicho modelo puede generarse mediante la línea de comandos ejecutando una clase proporcionada por Castor XML, una tarea Ant o un plugin para Maven. Puesto que en este tutorial vamos a utilizar Maven para construir los proyectos de prueba y hoy en día esta herramienta puede considerarse como el estándar de facto para la gestión de proyectos Java, se ha optado por la última opción.

La estructura del proyecto va a ser muy simple, sólo hay que crear el directorio [$dir proyecto] /src/main/resources y colocar ahí el fichero foro.xsd. En la raíz del proyecto creamos el siguiente pom.xml.:

<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.danielme.blog.castorxml</groupId>
  <artifactId>foro</artifactId>
  <version>1.0</version>
  <name>foro</name>

  <description>Modelo de clases del XSD de ejemplo de un foro generado por Castor XML</description>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
	
  <licenses>
    <license>
      <name>El presente proyecto Maven es el código de ejemplo utilizado en el tutorial
	"Introducción a Castor XML", publicado con licencia Creative Commons 
	Reconocimiento-NoComercial-CompartirIgual 3.0 Unported en la web "http://danielme.com"</name>
    </license>
  </licenses>

  <build>
    <pluginManagement>
      <plugins>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <version>2.3.2</version>					
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
              <configuration>
                <source>1.5</source>
                <target>1.5</target>
              </configuration>
         </plugin>

         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>castor-maven-plugin</artifactId>
           <version>2.0</version>
           <configuration>
             <schema>src/main/resources/foro.xsd</schema>					   
             <packaging>com.danielme.blog.castorxml.foro</packaging>
             <dest>src/main/java</dest>	  
           </configuration>					
         </plugin>

      </plugins>
    </pluginManagement>
  </build>

  <dependencies>	
    <dependency>
      <groupId>org.codehaus.castor</groupId>
      <artifactId>castor-xml</artifactId>
      <version>1.3.2</version>
    </dependency>		
  </dependencies>

</project>

La generación de las clases se realizará a través del plugin castor-maven-plugin al que vamos a proporcionar tres parámetros:

  • schema: ubicación del XSD dentro del proyecto.

  • packaging: ruta de paquetes para las clases generadas.

  • dest: ruta en la que se ubicarán los .java generados. Si se omite, los ficheros se escribirán dentro del directorio target/generated-sources, pero se ha definido para que el código fuente generado quede incluído en el proyecto.

Además, se incluye de dependencia de la última versión de Castor XML que en estos momentos es la 1.3.2 (publicada en marzo de 2011).

Ahora que ya tenemos el proyecto creado, vamos a generar nuestras clases mediante Maven desde la línea de comandos (en mi caso uso Linux):

$ mvn castor:generate

Si todo va bien, ya tendremos nuestras clases en la ruta que hemos indicado en el pom.xml . Podemos importar este proyecto en Eclipse, generando los ficheros de configuración necesarios mediante el comando:

$ mvn eclipse:eclipse

 El proyecto tendrá el siguiente aspecto:

Estructura del proyecto en Eclipse

Estructura del proyecto en Eclipse

Como podemos ver, Castor XML ha creado las clases y sus nombres se corresponden con el del element o complexType que representan, además de unas clases Descriptor con la información necesaria para que posteriormente podamos realizar un “marshalling” de nuestros datos que sea consecuente con la estructura definida en el XSD. Asimismo, si echamos un vistazo al código generado vemos que Castor XML utiliza caracteristicas de Java 5 tales como anotaciones y “generics”.

La generación por defecto que ha realizado Castor XML es configurable a través de un fichero XML definido por el esquema http://castor.codehaus.org/binding.xsd. Esto permite, por ejemplo, solucionar problemas de conflictos de nombre en el caso de que tengamos definidos elementos con el mismo name o cambiar nombres y tipos de atributos, evitando la chapuza de modificar el XSD .

Vamos a ver con un ejemplo cómo personalizar el mapeo. Para ello, creamos en el directorio src/main/resources el fichero binding.xml con el siguiente contenido:

<?xml version="1.0" encoding="utf-8"?>

<cbf:binding xmlns:cbf="http://www.castor.org/SourceGenerator/Binding" defaultBindingType='element'>

  <cbf:elementBinding name="ExportacionForo">
	<cbf:java-class name="Foro"/>
          <cbf:attributeBinding name="nombre">
            <cbf:member name="nombreForo" java-type="java.lang.String"/>
          </cbf:attributeBinding>
  </cbf:elementBinding>

</cbf:binding>

Con este fichero estamos indicando a Castor XML que el elemento ExportacionForo lo mapee a una clase denominada Foro, y que el atributo nombre se corresponda con un campo llamado nombreForo de esa clase. Para que este fichero se utilice a la hora de generar el código, simplemente añadimos un nuevo parámetro a la configuración de castor-maven-plugin llamado bindingfile con la ubicación del fichero:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>castor-maven-plugin</artifactId>
    <version>2.0</version>
    <configuration>
        <schema>src/main/resources/foro.xsd</schema>					   
        <packaging>com.danielme.blog.castorxml.foro</packaging>
        <dest>src/main/java</dest>
        <bindingfile>src/main/resources/binding.xml</bindingfile>
     </configuration>					
 </plugin>

 

Volvemos a ejecutar Maven. En esta ocasión, es necesario hacer un clean ya que de lo contrario no se volverán a generar las clases. Asimismo, hay que eliminar el código de /src/main/java/.

$ mvn clean castor:generate

 Podemos comprobar que el mapeo definido en el binding.xml ha surtido efecto:

package com.danielme.blog.castorxml.foro;

/**
 * Class Foro.
 * 
 * @version $Revision$ $Date$
 */
@SuppressWarnings("serial")
public class Foro implements java.io.Serializable {


      //--------------------------/
     //- Class/Member Variables -/
    //--------------------------/

    /**
     * Field _nombreForo.
     */
    private java.lang.String _nombreForo;

Por último, instalamos el jar con el modelo de clases generado en nuestro repositorio local para poder utilizarlo en el siguiente capítulo del tutorial.

$ mvn clean install

Código de ejemplo

Descargar desde WordPress.comWordPress: debido a las limitaciones del servicio ofrecido por WordPress.com, el código de ejemplo está comprimido en formato zip pero con la extensión “.odt”, por lo que hay que cambiar la extensión a “.zip” del fichero descargado o abrirlo directamente con el software adecuado.

  • ejemplo 1 (sin binding) aquí
  • ejemplo 2 (con binding) aquí

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: