Mostrando entradas con la etiqueta castor-xml. Mostrar todas las entradas
Mostrando entradas con la etiqueta castor-xml. Mostrar todas las entradas

miércoles, 17 de junio de 2009

Java: Castor y XML - Mappings

Para continuar con lo prometido de Castor – XML

Como les comente, si lo que queremos es un xml con cierta complejidad, no bastará con utilizar los mentodos que coloque en el post anterior, pues dichos metodos solo nos sirven para un POJO (DTO, JavaBean........W como lo quieran llamar).

Para poder “convertir” una clase o un conjunto de clases “relacionadas” debemos maperlas dentro de un archivo que luego deberá ser cargado en el contexto de ejecución de castor, para esto explico brebemente las herramientas que las personas del proyecto java-castor nos proporcionan:

Mapping: esta clase permite cargar archivos y/u objetos (Reader) para realizar el java binding con castor, es decir, los mapeos que generemos deben ser cargados en una instancia de este objeto.

Marshaller: siver para realizar el paso de objetos a un xml

Unmarshaller: es el analogo a Marshaller, pasa un xml a objeto

XMLContext: esta clase es basicamente un Factory de objetos con algunas otras utilidades, posee metodos para crear objetos Marshaller, Unmarshaller y Mapping, esta clase es la que se recomienda utilizar para la creación de estos objetos, tambien en este objeto podemos precargar los mappeos (Objetos Mapping), realmente este objeto simplifica de manera significativa el parseo.

La clase que utilizaremos:

public class XMLDataBinding {

private XMLContext ctx;

public XMLDataBinding(){
this.ctx = new XMLContext();
}

public Object readXML(Reader xml, Class clazz) throws MarshalException, ValidationException, SAXException, IOException{
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setClass(clazz);
return unmarshaller.unmarshal(new InputSource(xml));
}

public static Object xml2Object(Reader xml, Class clazz) throws MarshalException, ValidationException{
return Unmarshaller.unmarshal(clazz, xml);
}

public String writeXML(Object object) throws MarshalException, ValidationException, IOException{
StringWriter writer = new StringWriter();
Marshaller marshaller = context.createMarshaller();
marshaller.setWriter(writer);
marshaller.marshal(object);
return writer.getBuffer().toString();
}

public static String object2XML(Object object) throws MarshalException, ValidationException{
StringWriter writer = new StringWriter();
Marshaller.marshal(object, writer);
return writer.getBuffer().toString();
}

public void addMappingToContext(Reader mappingFile) throws MappingException{
Mapping mapping = this.ctx.createMapping();
mapping.loadMapping(new InputSource(mappingFile));
this.ctx.addMapping(mapping);
}

public void addMappingToContext(InputStream mappingFile) throws MappingException{
Mapping mapping = this.ctx.createMapping();
mapping.loadMapping(new InputSource(mappingFile));
this.ctx.addMapping(mapping);
log.debug("Mapping loaded: " + mappingFile);
}

public XMLContext getXMLContext(){
return this.ctx;
}

}

Las clase que utilizaremos para convertirlas a xml son relativamente sencillas (sino nos aburrimos todos), lo haremos con dos clases unicamente:


public class Persona implements Serializable {

private static final long serialVersionUID = 1L;
private String nombre;
private String apellido;
private int edad;
private ArrayList telefonos;
private Automovil automovil;

public Persona(){
}

public String getApellido() {
return apellido;
}
public void setApellido(String apellido) {
this.apellido = apellido;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public List getTelefonos() {
return telefonos;
}
public void setTelefonos(ArrayList telefonos) {
this.telefonos = telefonos;
}

public Automovil getAutomovil() {
return automovil;
}

public void setAutomovil(Automovil automovil) {
this.automovil = automovil;
}

public String getNombreCompleto(){
return this.nombre + “ ” + this.apellido;
}

}

public class Automovil implements Serializable {
private static final long serialVersionUID = 1L;
private String marca;
private String modelo;
private String placa;

public Automovil(){

}

public String getMarca() {
return marca;
}
public void setMarca(String marca) {
this.marca = marca;
}
public String getModelo() {
return modelo;
}
public void setModelo(String modelo) {
this.modelo = modelo;
}
public String getPlaca() {
return placa;
}
public void setPlaca(String placa) {
this.placa = placa;
}
}

El mapeo es el siguiente:

Link de descarga aquí

Deben notar que la clase principal es test.castor.xml.contenedores.Persona y esta clase en el mapeo posee un tag map-to, este tag determina el nombre del tag root del xml resultante, tambien notar que los atributos type de cada tag field determinan el tipo de objeto que será creado (que puede ser un tipo primitivo de java o uno propio) y el atributo name establece el metodo accesor que será buscado en la clase (no el nombre del atributo, notar que nombreCompleto no exite en la clase), adicionalmente si poseemos collecciones de objetos (no mapas, pues estos se manipulan diferente) hay que adicionar un tag collection que contiene el tipo de colección que manipularemos (los tipos aceptados por castor-xml se encuentran en el dtd http://castor.org/mapping.dtd), el tag bind-xml (hijo de field) determina el nombre que en el xml poseera el atributo correspondiente a la clase.

La escritura del xml seria la siguiente:

Persona persona = new Persona();
persona.setNombre("nombre");
persona.setApellido("apellido");
persona.setEdad(100);
ArrayList telefonos = new ArrayList();
telefonos.add("12345");
telefonos.add("56789");
persona.setTelefonos(telefonos);
Automovil auto = new Automovil();
auto.setMarca("marca");
auto.setModelo("modelo");
auto.setPlaca("SV123465");
persona.setAutomovil(auto);
XMLDataBinding db = new XMLDataBinding();
db.addMappingToContext("test/castor/xml/PersonaMapping.xml");
System.out.println(db.writeXML(persona));

El xml que obtenemos como resultado es el siguiente:

Link de descarga aquí

El orden de los tags en el xml obtenido corresponde al orden establecido en el mapeo, es decir, si necesitamos un orden diferente de los tags en el xml no tenemos que modificar nada mas que el mapeo de nuestras clases al igual que si queremos que cambiar el nombre a algun tag, solo hay que modificar el mapping.

La lectura de un xml podriamos hacerla con el mismo xml que hemos generado y la misma instancia del objeto XMLDataBinding:

Persona p = (Persona)db.readXML(new StringReader(xml), Persona.class);

Castor-XML posee mas herramientas para la manipulación de xml, para el caso de handlers que permiten la transformación de objetos por ejemplo, ademas el proyecto castor proporciona muchas herramientas adicionales

La referencia oficial de castor-xml la encuentran aquí y las descargas las encuentran aquí.

viernes, 24 de abril de 2009

Java: Castor y XML

Ya es tiempo de abrir un poco de espacio a lo que tanto me gusta y en lo que me "gano la vida", esencialmente es programación en Websphere Message Broker y Webshpere MQ, pero tambien un poco de java (que es lo que de verdad me gusta). Hace ya varios meses mientras hacia mis últimos desarrollos para el equipo al que pertenecía me tope con la misión de hacer un XML data binding (a lo que en un principio me dijeron "parser") para Java, dicha misión no fue tan sencilla pues ya habían desarrollos enteros en los cuales se utilizaba jdom, herramienta que es bastante buena pero requiere cierto esfuerzo y paciencia para leer xmls bastante complejos, además de ser ad-hoc.

Luego de realizar un esfuerzo con xtream, componente que parece muy útil e interesante pero muy poco flexible pues los nombres y el orden de los tags (que realmente puede llegar a importar) era aleatorio, realmente no se si utilice mal el componente, ya tendré tiempo para leer mas y lo comento. El punto es que se evaluaron a mi gusto muy pocos java xml binding, pero el que tomo la delantera por flexibilidad y por la capacidad de encapsulamiento que proporciona es castor-xml (aunque tambien existe el nada despreciable betwixt)

Castor-xml se basa en dos frameworks, Unmarshaller y Marshaller (xml-java y java-xml, respectivamente), en dichos frameworks lo que se encapsula es todo lo referente a la manipulación de xml.

Un ejemplo sencillo, supongamos que tenemos una clase (en java por supuesto), y que esta clase describe a una persona y contiene nombre, apellido, edad, dui, etc........ nos debemos limitar a usar atributos "simples" (String, enteros, ...)

package com.home;
import java.io.Serializable;
@SuppressWarnings(value={"serial"})
public class Persona implements Serializable {
private String nombre;
private String apellido;
private String genero;
private int edad;
public Persona(){
}
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 getGenero() {
return genero;
}
public void setGenero(String genero) {
this.genero = genero;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
}

La forma inmediata que castor proporciona para convertir el contenido del objeto se escribe de la siguiente forma, encapsulado en un método por su puesto:

public static String object2XML(Object object) throws MarshalException, ValidationException{
StringWriter writer = new StringWriter();
Marshaller.marshal(object, writer);
return writer.getBuffer().toString();
}

Y la forma de hacer Unmarshall es esta:

public static Object xml2Object(Reader xml, Class clazz) throws MarshalException, ValidationException{
return Unmarshaller.unmarshal(clazz, xml);
}

Utilizando el método object2XML

try{
Persona persona = new Persona();
persona.setApellido("Perez");
persona.setNombre("Juan");
persona.setEdad(33);
persona.setGenero("M");
System.out.println(XMLDataBinding.object2XML(persona));
}
catch (Exception e) {
log.error("Error!!!",e);
}
}

Nota: XMLDataBinding es la clase donde coloque los metodos que ya les mencione

Lo que tenemos como resultado es el siguiente xml:


Notar que los nombres de los campos corresponden a los nombres de los accesores de la clase persona y el tag root corresponde al nombre de la clase y ¿cuál es la razón por la que el campo edad resulta ser un atributo al convertirlo a xml?
Esta situación tiene su razón en la forma en que castor realiza el proceso de Introspección, 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 del xml, además por salud mental se debe de colocar la siguiente entrada a dicho archivo properties:
org.exolab.castor.xml.naming=mixed
El cual generará los tags del xml de forma más clara, de otra forma por defecto los nombres se separarán por un "-".

Convertir el xml que hemos generado a un objeto Persona sería solo cuestión de crear un StringReader con el xml y ejecutar el método xml2Object enviándole dicho reader y la clase sobre la que haremos el binding (Persona.class) y verán la magia.

En post posteriores intentaré explicar el proceso de Marshall y Unmarshall utilizando xml para mapear las clases y poder tomar completo control de la estructura de nuestros xmls ademas poder realizar la conversión de clases más complejas así como el uso de Handlers.