martes, 28 de junio de 2011



Castor de ExoLab es una herramienta de data binding de código abierto para Java que
soporta binding con XML, bases de datos relacionales y LDAP.
Castor XML trabaja con los datos definidos en documentos XML a través de un modelo de objetos propietario (Castor Schema Object Model) que los representa. Está diseñado para trabajar con modelos de objetos Java ya existentes y para generar nuevos modelos basados en un esquema XML origen, proporcionando un binding entre componentes de XML Schema y construcciones propias del lenguaje Java.



Los jar se obtienen de los siguientes links:

Castor XML
http://dist.codehaus.org/castor/1.3/castor-1.3-xml.jar
Castor Core
http://dist.codehaus.org/castor/1.3/castor-1.3-core.jar


Los objetos deben ser Beans.

Hay tres formas de hacer marshal y un-marshal desde y a XML:

a)      Modo instrospectivo
b)      Modo mapping
c)       Modo descriptor



El primer modo es el más sencillo y se hace uso de los métodos estáticos de los clases org.exolab.castor.xml.Marshaller y org.exolab.castor.xml.Unmarshaller.

Para pasar de objeto a XML:


Main. Java
package main;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;


import dominio.Persona;

public class Main {
      /**
       * @param args
       */
      public static void main(String[] args) {
            System.out.println ("Marshaling - De objeto a XML...");
            // Creo una nueva persona
            Persona persona1 = new Persona(1,"Claudia","Alvarez","MiCalle 1234","clalferrey@gmail.com",36);
           
            // Creo un archivo para hacer el marshal
            FileWriter writer;
            try {
                  writer = new FileWriter("c:\\test.xml");
                  // Marshal
                  try {
                        Marshaller.marshal(persona1, writer);
                        System.out.println ("Fin de Marshaling...");
                  } catch (MarshalException e) {
                        e.printStackTrace();
                  } catch (ValidationException e) {
                        e.printStackTrace();
                  }
                 
            } catch (IOException e) {
                  e.printStackTrace();
            }
            FileReader reader;
            try {
                  reader = new FileReader("c:\\test.xml");
                  try {
                        System.out.println ("UnMarshaling - De XML a objeto...");
                        Persona persona2 = (Persona) Unmarshaller.unmarshal(Persona.class, reader);
                        System.out.println ("Fin de UnMarshaling...");
                        System.out.println(persona2.toString());
                  } catch (MarshalException e) {
                        e.printStackTrace();
                  } catch (ValidationException e) {
                        e.printStackTrace();
                  }

            } catch (FileNotFoundException e) {
                  e.printStackTrace();
            }
      }
}

Para pasar de XML a objeto:


public class Main {
      /**
       * @param args
       */
      public static void main(String[] args) {
            FileReader reader;
            try {
                  reader = new FileReader("c:\\test.xml");
                  try {
                        System.out.println ("UnMarshaling - De XML a objeto...");
                        Persona persona2 = (Persona) Unmarshaller.unmarshal(Persona.class, reader);
                        System.out.println ("Fin de UnMarshaling...");
                        System.out.println(persona2.toString());
                  } catch (MarshalException e) {
                        e.printStackTrace();
                  } catch (ValidationException e) {
                        e.printStackTrace();
                  }

            } catch (FileNotFoundException e) {
                  e.printStackTrace();
            }
      }
}


Persona.java

package dominio;

import java.io.Serializable;

public class Persona implements Serializable{
      private Integer id;
      private String nombre;
      private String apellido;
      private String direccion;
      private String correo;
      private Integer edad;
     
     
      public Persona() {
            super();
      }


      public Persona(int id, String nombre, String apellido, String direccion,String correo, int edad) {
            super();
            this.id = id;
            this.nombre = nombre;
            this.apellido = apellido;
            this.direccion = direccion;
            this.correo = correo;
            this.edad = edad;
      }


      public int getId() {
            return id;
      }

      public void setId(int id) {
            this.id = id;
      }

      public String getNombre() {
            return nombre;
      }

      public void setNombre(String nombre) {
            this.nombre = nombre;
      }

      public String getApellido() {
            return apellido;
      }

      public void setApellido(String apellido) {
            this.apellido = apellido;
      }

      public String getDireccion() {
            return direccion;
      }

      public void setDireccion(String direccion) {
            this.direccion = direccion;
      }

      public String getCorreo() {
            return correo;
      }

      public void setCorreo(String correo) {
            this.correo = correo;
      }

      public int getEdad() {
            return edad;
      }

      public void setEdad(int edad) {
            this.edad = edad;
      }

      @Override
      public String toString() {
            return "Persona [apellido=" + apellido + ", correo=" + correo+ ", direccion=" + direccion + ", edad=" + edad + ", id=" + id
                        + ", nombre=" + nombre + "]";
      }
}


Test.xml

clalferrey@gmail.com
MiCalle 1234
Alvarez
Claudia

Nota: El modo instrospección utiliza java reflection para realizar el binding entre las clases de java y XML siguiendo un conjunto de reglas por defecto. No es posible sobreescribir estas reglas, si quisieramos hacerlo debemos utilizar el modo mapping.
Cualquier variable miembro que sea de tipo primitivo por defecto será colocado como atributo, la forma de modificar esto es crear el archivo castor.properties en nuestro classpath y colocar la siguiente entrada:
org.exolab.castor.xml.introspector.primitive.nodetype=element
con lo cual lograremos que todos los campos sean convertidos como elementos de xml.
En el ejemplo, la edad y el id, fueron tomados como attribute.
  

En este modo, el usuario provee a Castor XML un mapping con una definicion personalizada del mapeo entre clases Java y XML.
Cuando se usa un archivo de mapeo, se crea una instancia de la clase org.exolab.castor.xml.XMLContext y se usa el método org.exolab.castor.xml.XMLContext.addMapping(Mapping)con uno o más archivos de mapeo.
Para realizar el marshalling o unmarshalling se debe crear una instancia de org.exolab.castor.xml.Marshaller y org.exolab.castor.xml.Unmarshaller usando los siguientes métodos:

createMarshaller
Crea una instancia de Marshaller.
createUnmarshaller
Crea una instancia de Unmarshaller

y se llaman a los métodos no estáticos marshall y unmarshall.


Contenido del archivo mapping


Name
Description
description
An optional description.
cache-type
Used with Castor JDO only; for more information on this field, please see see the JDO documentation.
map-to
Used if the name of the element is not the name of the class. By default, Castor will infer the name of the element to be mapped from the name of the class: a Java class named 'XxxYyy' will be transformed in 'xxx-yyy'. If you don't want Castor to generate the name, you need to use to specify the name you want to use. is only used for the root element.
field
Zero or more elements, which are used to describe the properties of the Java class being mapped.





Etiqueta en archivo de mapeo
Descripcion
map-to
Es usado si el nombre del elemento no es el nombre de la clase. Por defecto, el nombre del elemento es mapeado del nombre de la clase: Una clase llamada XxxYyy será transformada en xxx-yyy. Si no queremos que Castor genere el nombre, hay que especificarlo en .
Para hacer unmarshall, si no se especifica map-to para un elemento llamado test-element, Castor tratará de utilizar una clase lamada TestElement
Field
Cero o más elementos que son usados para describir las propiedades de la clase que será mapeada.
Xml
Nombre del elemento al que la clase está asociada.

Mapping.xml





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


<class name="dominio.Persona">
           
   <map-to xml="person"/>
   <field name="id" type="int">
      <bind-xml name="identidad" node="attribute"/>
   </field>
   <field name="nombre" type="string">
      <bind-xml name="name" node="attribute"/>
   </field>
   <field name="apellido" type="string">
      <bind-xml name="apellido" node="attribute"/>
   </field>
      <field name="correo" type="string">
      <bind-xml name="email" node="attribute"/>
   </field>
      <field name="edad" type="int">
      <bind-xml name="edad" node="attribute"/>
   </field>
   
</class>
MainMapp.java

package main;

import java.io.FileReader;
import java.io.IOException;

import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.exolab.castor.xml.XMLContext;

import dominio.Persona;

/**
 * @author calvarez
 *
 */
public class MainMapp {
     
      public static void main(String[] args) throws IOException, MappingException, MarshalException, ValidationException {
            // Cargo archivo Mapping
            Mapping mapping = new Mapping();
            mapping.loadMapping("./src/xml/mapping.xml");
            System.out.println("Archivo mapping cargado.");
            // Inicializo y configuro XMLContext
            XMLContext context = new XMLContext();
            context.addMapping(mapping);
            // Creo reader para hacer Unmarshall
            System.out.println("Leo test.xml para mapear a objeto.");
            FileReader reader = new FileReader("./src/xml/test.xml");
            // Creao un nuevo Unmarshaller
            Unmarshaller unmarshaller =
            context.createUnmarshaller();
            unmarshaller.setClass(Persona.class);
            // Mapeo a objeto person
            Persona person = (Persona)
            unmarshaller.unmarshal(reader);
            System.out.println("Objeto Mapeado");
            System.out.println(person.toString());
      }
}


Al  escribir en el test.xml un string en la edad, las excepciones son las siguientes:

22-jun-2011 0:05:29 org.exolab.castor.mapping.Mapping setBaseURL
INFO: ./src/xml is not a URL, trying to convert it to a file URL
22-jun-2011 0:05:30 org.exolab.castor.mapping.Mapping loadMapping
INFO: Loading mapping descriptors from mapping.xml
Archivo mapping cargado.
Leo test.xml para mapear a objeto.
Exception in thread "main" org.exolab.castor.xml.MarshalException: For input string: "ErrorDeTipo"{File: [not available]; line: 2; column: 36}
      at org.exolab.castor.xml.Unmarshaller.convertSAXExceptionToMarshalException(Unmarshaller.java:794)
      at org.exolab.castor.xml.Unmarshaller.unmarshal(Unmarshaller.java:760)
      at org.exolab.castor.xml.Unmarshaller.unmarshal(Unmarshaller.java:626)
      at main.MainMapp.main(MainMapp.java:48)
Caused by: java.lang.NumberFormatException: For input string: "ErrorDeTipo"
      at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
      at java.lang.Integer.parseInt(Integer.java:449)
      at java.lang.Integer.(Integer.java:660)
      at org.exolab.castor.xml.UnmarshalHandler.toPrimitiveObject(UnmarshalHandler.java:3757)
      at org.exolab.castor.xml.UnmarshalHandler.toPrimitiveObject(UnmarshalHandler.java:3710)
      at org.exolab.castor.xml.UnmarshalHandler.setAttributeValueOnObject(UnmarshalHandler.java:3142)
      at org.exolab.castor.xml.UnmarshalHandler.processAttribute(UnmarshalHandler.java:3111)
      at org.exolab.castor.xml.UnmarshalHandler.processAttributes(UnmarshalHandler.java:2776)
      at org.exolab.castor.xml.UnmarshalHandler.startElement(UnmarshalHandler.java:1726)
      at org.exolab.castor.xml.UnmarshalHandler.startElement(UnmarshalHandler.java:1435)
      at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501)
      at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:767)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1359)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1317)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3095)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:922)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
      at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807)
      at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
      at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107)
      at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
      at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
      at org.exolab.castor.xml.Unmarshaller.unmarshal(Unmarshaller.java:748)
      ... 2 more
Caused by: java.lang.NumberFormatException: For input string: "ErrorDeTipo"
      at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
      at java.lang.Integer.parseInt(Integer.java:449)
      at java.lang.Integer.(Integer.java:660)
      at org.exolab.castor.xml.UnmarshalHandler.toPrimitiveObject(UnmarshalHandler.java:3757)
      at org.exolab.castor.xml.UnmarshalHandler.toPrimitiveObject(UnmarshalHandler.java:3710)
      at org.exolab.castor.xml.UnmarshalHandler.setAttributeValueOnObject(UnmarshalHandler.java:3142)
      at org.exolab.castor.xml.UnmarshalHandler.processAttribute(UnmarshalHandler.java:3111)
      at org.exolab.castor.xml.UnmarshalHandler.processAttributes(UnmarshalHandler.java:2776)
      at org.exolab.castor.xml.UnmarshalHandler.startElement(UnmarshalHandler.java:1726)
      at org.exolab.castor.xml.UnmarshalHandler.startElement(UnmarshalHandler.java:1435)
      at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501)
      at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:767)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1359)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1317)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3095)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:922)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
      at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
      at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807)
      at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
      at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107)
      at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
      at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
      at org.exolab.castor.xml.Unmarshaller.unmarshal(Unmarshaller.java:748)
      at org.exolab.castor.xml.Unmarshaller.unmarshal(Unmarshaller.java:626)
      at main.MainMapp.main(MainMapp.java:48)


public static void main(String[] args) throws IOException, MappingException, MarshalException, ValidationException {
            // Cargo archivo Mapping
            Mapping mapping = new Mapping();
            mapping.loadMapping("./src/xml/mapping.xml");
            System.out.println("Archivo mapping cargado.");
            // Inicializo y configuro XMLContext
            XMLContext context = new XMLContext();
            context.addMapping(mapping);
            // Creo un Writer para la salida del marshall
            Writer writer = new FileWriter("./src/xml/out.xml");
            // create a new Marshaller
            Marshaller marshaller = context.createMarshaller();
            marshaller.setWriter(writer);
            // Creo una nueva persona
            Persona person = new Persona(1,"Claudia","Alvarez","MiCalle 1234","clalferrey@gmail.com",36);
            marshaller.marshal(person);
      }

Out.xml

clalferrey@gmail.com
MiCalle 1234
Alvarez
Claudia



Con la versión 1.1.2, la clase XMLContext ha sido agregada al framework. Esta clase orece varios métodos para obtener un nuevo org.exolab.castor.xml.Marshaller, org.exolab.castor.xml.Unmarshaller.
Cuando se necesita más de una instancia de UnMarshaller, se debe llamar al método org.exolab.castor.xml.XMLContext.createUnmarshaller(). Como todas las instancias de Unmarshaller son creadas de la misma instancia XMLContext, el overhead será mínimo. El uso de una instancia de Unmarshaller no es thread-safe.


Castor puede mapear “casi” cualquier objeto arbitriario desde un XML. Cuando los descriptores no están disponibles para una clase específica, el frameqork utiliza reflection para obtener información del objeto.
Hay una importante restricción para mapear objetos. Las clases deben tener un constructor público por defecto y getters y setters para poder ser marshalled y unmarshalled.
  

Los descriptores de clases proveen al framework Castor de información necesaria para mapear las clases de forma apropiada.



Cuando Castor mapea un objeto:

-          Usa la información del archivo, si existe, para encontrar el nombre del elemento a crear o
-          Por defecto, crea un nombre usando el nombre de la clase

Utiliza la información de los fields en el archivo de mapero para determinar cómo tiene que ser traducida la propiedad del objeto:

-          Attribute
-          Element
-          Text
-          Nada, ya que podemos optar por ignorar un campo en particular

Este proceso es recursivo: si se encuentra una propiedad que es de tipo clase que està definida en el archivo de mapeo, se utiliza dicha información.

Si Castor no encuentra la definición para una clase dada, se aplican las reglas por defecto:

-          Todos los tipos primitivos, incluyendo los tipos wrappers (Boolean, Short, etc…) son tomados como attributes.
-          Todos los demás objetos son considerados con contenido element o text.

Escribiendo un FieldHandler

A veces necesitamos tratar con un formato de dato que Castor no soporta, tal como una representación de fecha. Para manejar estos casos, Castor premite especificar un FileHandler personalizadoque puede realizar estas conversiones durante las llamadas a setters y getters.

FieldHandler es una interfase usada por Castor cuando accede a los valores de los campos o los setea. Para especificar un FieldHandler personalizado debemos proveer implementaciones a varios métodos de la interfase. Los dos principales son: getValue y setValue que básicamente manejarán nuestra conversión. Los otros métodos proveen caminos para crear una nueva instancia del valor del campo o resetear su valor.
Una forma más sencilla de escribir un manejador de campos se hace usando GeneralizedFieldHandler.



Handler:

public class MiHandler2 implements FieldHandler, ConfigurableFieldHandler {

    private SimpleDateFormat formatter;

      @Override
      public void checkValidity(Object objeto) throws ValidityException,
                  IllegalStateException {
           
      }

      @Override
      public Object getValue(Object objeto) throws IllegalStateException {
            Persona root = (Persona)objeto;
        Date value = root.getFechaNac();
        if (value == null) return null;
        return formatter.format(value);

      }

      @Override
      public Object newInstance(Object arg0) throws IllegalStateException {
            // TODO Auto-generated method stub
            return null;
      }

      @Override
      public void resetValue(Object objeto) throws IllegalStateException,
                  IllegalArgumentException {
            ((Persona)objeto).setFechaNac(null);
           
      }

      @Override
      public void setValue(Object objeto, Object valor)
                  throws IllegalStateException, IllegalArgumentException {
            Persona root = (Persona)objeto;
        Date date = null;
        try {
            date = formatter.parse((String)valor);
        }
        catch(ParseException px) {
            throw new IllegalArgumentException("Formato no valido :"+px.getMessage());
        }
        root.setFechaNac(date);
           
      }

      @Override
      public void setConfiguration(Properties config) throws ValidityException {
            String pattern = config.getProperty("date-format");
      if (pattern == null) {
            throw new ValidityException("Valor requerido \"date-format\" , no puede ser nulo.");
      }
      try {
            formatter = new SimpleDateFormat(pattern);
      } catch (IllegalArgumentException e) {
            throw new ValidityException("Formato no valido \""+pattern+"\".");
      }
           
      }

}


Mapping.xml

<?xml version="1.0"?>
<mapping>


<field-handler name="miHandler" class="handler.MiHandler2">
<param name="date-format" value="dd/MM/yyyy"/>
</field-handler>
<class name="dominio.Persona">
<field name="fecha-nac" type="string" handler="miHandler"/>
</class>
</mapping>

MainMapp.java

public class MainMapp {

      public static void main(String[] args) throws IOException, MappingException, MarshalException, ValidationException {
            // Cargo archivo Mapping
            Mapping mapping = new Mapping();
            mapping.loadMapping("./src/xml/mappingHandler.xml");
            System.out.println("Archivo mapping cargado.");
            // Inicializo y configuro XMLContext
            XMLContext context = new XMLContext();
            context.addMapping(mapping);
            // Creo reader para hacer Unmarshall
            System.out.println("Creo reader para mapear a objeto.");
            FileReader reader = new FileReader("./src/xml/test.xml");
            // Creao un nuevo Unmarshaller
            Unmarshaller unmarshaller = new Unmarshaller(Persona.class);
            //context.createUnmarshaller();
            //unmarshaller.setClass(Persona.class);
        unmarshaller.setMapping(mapping);
            // Mapeo a objeto person
            Persona person = (Persona)
            unmarshaller.unmarshal(reader);
            System.out.println("Objeto Mapeado");
            System.out.println(person.toString());
            reader.close();
            // Para vericar el funcionamiento hay que ejecutarlos por separado, de ahi que esté comentado. Luego descomentar lo siguiente y comentar lo anterior según se quiera hacer marshall o unmarshall.
            /*// Cargo archivo Mapping
            Mapping mapping = new Mapping();
            mapping.loadMapping("./src/xml/mappingHandler.xml");
            System.out.println("Archivo mapping cargado.");
            // Inicializo y configuro XMLContext
            XMLContext context = new XMLContext();
            context.addMapping(mapping);
            // Creo un Writer para la salida del marshall
            Writer writer = new FileWriter("./src/xml/out.xml");
            System.out.println("Creo writer out.xml para el marshall.");
            // create a new Marshaller
            Marshaller marshaller = context.createMarshaller();
            marshaller.setValidation(true);
            marshaller.setWriter(writer);
            // Creo una nueva persona
            Persona person = new Persona(1,"Claudia","Alvarez",new Date(11/19/2011),"clalferrey@gmail.com",36);
            marshaller.marshal(person);
            System.out.println("Marshall finalizado.");*/
      }
}

El Bean Persona es similar al anterior con un único cambio. El atributo dirección fue cambiado por el atributo fechaNac de tipo Date.

Test.xml

36
1
clalferrey@gmail.com
12-13-2000
Alvarez
Claudia

 Referencias

-       Personalización del data binding entre XML y Java - Álvaro Muñoz Fajardo