Genéricamente se denomina “marshalling” al proceso de volcar la representación en memoria de un objeto a un formato que permita su almacenamiento o transmisión, siendo “unmarshalling” el proceso contrario. En Castor XML, se denomina marshalling a la creación de un fichero XML con los datos contenidos en objetos Java, y unmarshalling al proceso opuesto.
En este segundo capítulo del tutorial vamos a probar el marshalling/unmarshalling que Castor XML proporciona, usando el modelo de clases generado en el capítulo anterior lo que nos permitirá asegurar que siempre trabajaremos con XMLs que serán válidos según el XSD. No obstante, la potencia y flexibilidad de Castor XML radica en que se puede utilizar cualquier modelo de clases para su marshalling/unmarshalling en XML, lo que implica que podemos usarlo en nuestros proyectos en cualquier momento con un impacto prácticamente nulo. Al final de este capítulo se comenta brevemente un ejemplo de marshalling/unmarshalling de un modelo de clases cualquiera no vinculado a un XSD.
Para las pruebas, en primer lugar vamos a crear un proyecto Maven llamado pruebasCastor-21 y su pom sólo tendrá como dependencia nuestro jar del foro creado en el capítulo anterior. Puesto que las librerias de Castor XML ya son dependencias de este jar no es necesario incluirlas en el pom, al igual que el plugin castor-maven-plugin del que ya no vamos a hacer uso.
<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>pruebasCastor-21</artifactId> <version>1.0</version> <name>pruebasCastor-21</name> <description>Pruebas de marshalling/unmarshalling con Castor XML, modelo generado desde XSD.</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.mainClass>com.danielme.blog.castorxml.pruebas21.Main</project.build.mainClass> </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> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${project.build.mainClass}</mainClass> </manifest> </archive> </configuration> </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>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>${project.build.mainClass}</mainClass> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>com.danielme.blog.castorxml</groupId> <artifactId>foro</artifactId> <version>1.0</version> </dependency> </dependencies> </project>
Creamos el subdirectorio src/main/java, y ya estamos listos para generar e importar el proyecto en Eclipse.
$ mvn eclipse:eclipse
Por simplicidad, las pruebas se realizarán con la clase definida en el pom com.danielme.blog.castorxml.pruebas21.Main, así que creamos la ruta de paquetes y esa clase con un método main dentro de /src/main/java. El proyecto resultante tiene la siguiente estructura:
Marshalling
Para probar el volcado de los objetos con los datos correspondientes a un foro en un XML, en primer lugar vamos a crear dichos objetos. Para ello he creado el método foroDePrueba en la clase Main, que devuelve un objeto de la clase Foro que se corresponde con el elemento raíz del XSD y que, por lo tanto, es el objeto sobre el que se realizará el marshalling (y en cascada de todos sus campos).
private static Foro foroDePrueba() { Foro foro = new Foro(); foro.setNombreForo("MI FORO"); UsuarioRef usuarioRef1 = new UsuarioRef(); usuarioRef1.setId(1L); UsuarioRef usuarioRef2 = new UsuarioRef(); usuarioRef2.setId(2L); Usuario usuario1 = new Usuario(); usuario1.setAlias("admin"); usuario1.setEmail("admin@correo.es"); usuario1.setFechaIngreso(new Date(new GregorianCalendar(2012, 0, 1).getTime())); usuario1.setId(1); usuario1.setPassword("QWERTY"); usuario1.setRol(UsuarioTypeRolType.ADMIN); Usuario usuario2 = new Usuario(); usuario2.setAlias("juan español"); usuario2.setEmail("juanesp@correo.es"); usuario2.setFechaIngreso(new Date(new GregorianCalendar(2011, 0, 2).getTime())); usuario2.setId(2); usuario2.setPassword("ASDFG"); usuario2.setRol(UsuarioTypeRolType.USUARIO); Mensaje mensaje1 = new Mensaje(); mensaje1.setId(1); mensaje1.setFecha(new Date(new GregorianCalendar(2011, 2, 1).getTime())); mensaje1.setContenido("mensaje 1"); mensaje1.setUsuarioRef(usuarioRef1); Mensaje mensaje2 = new Mensaje(); mensaje2.setId(2); mensaje2.setFecha(new Date(new GregorianCalendar(2011, 2, 2).getTime())); mensaje2.setContenido("mensaje 2"); mensaje2.setUsuarioRef(usuarioRef2); Agradecimiento agradecimiento1 = new Agradecimiento(); agradecimiento1.setId(1L); agradecimiento1.setUsuarioRef(usuarioRef1); Agradecimiento agradecimiento2 = new Agradecimiento(); agradecimiento2.setId(2L); agradecimiento2.setUsuarioRef(usuarioRef2); Tema tema1 = new Tema(); tema1.setId(1); tema1.setFecha(new Date(new GregorianCalendar(2011, 1, 1).getTime())); tema1.setTitulo("Título del tema 1"); tema1.setContenido("contenido del tema 1"); tema1.setUsuarioRef(usuarioRef1); Tema tema2 = new Tema(); tema2.setId(2); tema2.setFecha(new Date(new GregorianCalendar(2011, 1, 2).getTime())); tema2.setTitulo("Título del tema 2"); tema2.setContenido("contenido del tema 2"); tema2.setUsuarioRef(usuarioRef1); Categoria categoria1 = new Categoria(); categoria1.setId(1); categoria1.setNombre("JAVA"); Categoria categoria2 = new Categoria(); categoria2.setId(2); categoria2.setNombre("XML"); foro.setCategorias(new Categorias()); foro.getCategorias().addCategoria(categoria1); foro.getCategorias().addCategoria(categoria2); tema2.setMensajes(new Mensajes()); tema2.getMensajes().addMensaje(mensaje1); tema2.getMensajes().addMensaje(mensaje2); mensaje1.setAgradecimientos(new Agradecimientos()); mensaje1.getAgradecimientos().addAgradecimiento(agradecimiento1); tema1.setAgradecimientos(new Agradecimientos()); tema1.getAgradecimientos().addAgradecimiento(agradecimiento2); tema1.setCategoriaId(1); foro.setTemas(new Temas()); foro.getTemas().addTema(tema1); foro.getTemas().addTema(tema2); foro.setUsuarios(new Usuarios()); foro.getUsuarios().addUsuario(usuario1); foro.getUsuarios().addUsuario(usuario2); return foro; }
Una vez tengamos el objeto foro, podemos realizar el marshalling fácilmente invocando un método estático de la clase org.exolab.castor.xml.Marshaller. Asimismo, conviene comprobar que la estructura de datos que vamos a volcar es válida según el XSD para asegurar que el XML generado también lo sea. Capturando las excepciones, el método main queda de la siguiente forma:
public static void main(String[] args) throws Exception { try { Foro foro = foroDePrueba(); // asegura que el xml resultante es válido según el XSD if (!foro.isValid()) { throw new Exception("los datos del foro no son válidos según el XSD"); } FileWriter writer = new FileWriter("exportacionForo.xml"); Marshaller.marshal(foro, writer); } catch (IOException e1) { e1.printStackTrace(); } catch (MarshalException e2) { e2.printStackTrace(); } catch (ValidationException e3) { e3.printStackTrace(); } }
Si se ejecuta esta aplicación de ejemplo desde Eclipse o la consola:
$ mvn clean package exec:java
como resultado se obtendrá en el directorio raiz del proyecto el fichero exportacionForo.xml que contendrá los datos del foro definidos en el método foroDePrueba:
<?xml version="1.0" encoding="UTF-8"?> <ExportacionForo xmlns="https://danielme.com/blog/castorxml" nombre="MI FORO"> <temas> <tema id="1" fecha="2011-02-01" titulo="Título del tema 1" categoriaId="1"> <contenido>contenido del tema 1</contenido> <agradecimientos> <agradecimiento> <id>2</id> <usuarioRef> <id>2</id> </usuarioRef> </agradecimiento> </agradecimientos> <usuarioRef> <id>1</id> </usuarioRef> </tema> <tema id="2" fecha="2011-02-02" titulo="Título del tema 2"> <contenido>contenido del tema 2</contenido> <mensajes> <mensaje id="1" fecha="2011-03-01"> <contenido>mensaje 1</contenido> <agradecimientos> <agradecimiento> <id>1</id> <usuarioRef> <id>1</id> </usuarioRef> </agradecimiento> </agradecimientos> <usuarioRef> <id>1</id> </usuarioRef> </mensaje> <mensaje id="2" fecha="2011-03-02"> <contenido>mensaje 2</contenido> <usuarioRef> <id>2</id> </usuarioRef> </mensaje> </mensajes> <usuarioRef> <id>1</id> </usuarioRef> </tema> </temas> <usuarios> <usuario id="1" alias="admin" rol="ADMIN" fechaIngreso="2012-01-01" email="admin@correo.es" password="QWERTY" /> <usuario id="2" alias="juan español" rol="USUARIO" fechaIngreso="2011-01-02" email="juanesp@correo.es" password="ASDFG" /> </usuarios> <categorias> <categoria id="1" nombre="JAVA" /> <categoria id="2" nombre="XML" /> </categorias> </ExportacionForo>
Unmarshalling
Realizar el proceso inverso, esto es, el unmarshalling, es también sumamente sencillo:
//unmarshall FileReader fileReader = new FileReader("exportacionForo.xml"); Foro unmarshal = (Foro) Unmarshaller.unmarshal(Foro.class, fileReader); //comprobamos algunos datos System.out.println("nombre : " + unmarshal.getNombreForo()); System.out.println("temas "); Iterator<? extends Tema> iterateTema = unmarshal.getTemas().iterateTema(); while (iterateTema.hasNext()) { Tema next = iterateTema.next(); System.out.println(" + " + next.getTitulo() + " : " + next.getContenido()); if (next.getMensajes() != null) { for (Mensaje mensaje : next.getMensajes().getMensaje()) { System.out.println(" + " + mensaje.getContenido()); } } }
En la salida estándar deberíamos ver lo siguiente:
Asimismo, al hacer unmarshalling Castor XML validará automáticamente que el XML sea correcto según el XSD, lanzando una excepción en caso contrario.
Ejemplo para cualquier modelo de clases
Vamos a suponer que, en lugar de partir de un esquema XSD, tenemos ya un modelo de clases en nuestro proyecto y queremos utilizar Castor XML para exportar/importar los datos contenidos en objetos de esas clases. Pues bien, tal y como se comentó al comienzo de este capítulo, el código para realizar marshalling/unmarshalling es el mismo que el utilizado en el ejemplo anterior, y las clases del modelo pueden ser usadas directamente sin ninguna modificación.
Para probarlo, vamos a volver a crear un proyecto de ejemplo, llamado pruebasCastor-22, con Maven e importarlo en Eclipse. En este caso, en el pom se cambiará la dependencia de foro.jar por la de Castor 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>pruebasCastor-22</artifactId> <version>1.0</version> <name>pruebasCastor-22</name> <description>Pruebas de marshalling/unmarshalling con Castor XML. Modelo de clases no basado en XSD.</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.mainClass>com.danielme.blog.castorxml.pruebas22.Main</project.build.mainClass> </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> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${project.build.mainClass}</mainClass> </manifest> </archive> </configuration> </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>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>${project.build.mainClass}</mainClass> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>org.codehaus.castor</groupId> <artifactId>castor-xml</artifactId> <version>1.3.2</version> </dependency> </dependencies> </project>
En el paquete com.danielme.blog.castorxml.modeloequipo se ubicarán las siguientes clases (descargar el código de ejemplo):
El proyecto importado en Eclipse queda tal que así:
Y la clase Main es similar a la del ejemplo anterior pero cambiando el modelo de clases para el foro por el de los equipos.
package com.danielme.blog.castorxml.pruebas22; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; 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 com.danielme.blog.castorxml.modeloequipo.Entrenador; import com.danielme.blog.castorxml.modeloequipo.Equipo; import com.danielme.blog.castorxml.modeloequipo.Jugador; /** * Pruebas de marshalling/unmarshalling. * * @author danielme.com * */ public class Main { /** * @param args * @throws Exception */ public static void main(String[] args) { String fichero = "exportacionEquipo.xml"; Equipo equipo = equipoDePrueba(); try { FileWriter writer = new FileWriter(fichero); Marshaller.marshal(equipo, writer); FileReader fileReader = new FileReader(fichero); Equipo unmarshal = (Equipo) Unmarshaller.unmarshal(Equipo.class, fileReader); System.out.println("EQUIPO : " + unmarshal.getNombre()); System.out.println("entrenador: " + unmarshal.getEntrenador().getNombre() + " " + unmarshal.getEntrenador().getApellidos()); System.out.println("jugadores "); for (Jugador jugador: equipo.getJugadores()) { System.out.println(" " + jugador.getDorsal() + ": " + jugador.getNombre() + " " + jugador.getApellidos()); } } catch (IOException e1) { e1.printStackTrace(); } catch (MarshalException e2) { e2.printStackTrace(); } catch (ValidationException e3) { e3.printStackTrace(); } } private static Equipo equipoDePrueba() { Equipo equipo = new Equipo(); Jugador jugador1 = new Jugador(); jugador1.setNombre("Juan"); jugador1.setApellidos("Español"); jugador1.setDorsal(1); jugador1.setEstatura(178); jugador1.setPeso(74.3F); jugador1.setId(1); Calendar fechaNac1 = new GregorianCalendar(1987, 2, 10); jugador1.setFechaNacimiento(fechaNac1); Jugador jugador2 = new Jugador(); jugador2.setNombre("Miguel"); jugador2.setApellidos("Pérez Pérez"); jugador2.setDorsal(2); jugador2.setEstatura(174); jugador2.setPeso(71.1F); jugador2.setId(2); Calendar fechaNac2 = new GregorianCalendar(1985, 3, 7); jugador2.setFechaNacimiento(fechaNac2); Entrenador entrenador = new Entrenador(); entrenador.setEquipo(equipo); entrenador.setId(14); entrenador.setNombre("Joaquín"); entrenador.setApellidos("García"); Calendar fechaNac3 = new GregorianCalendar(1964, 1, 2); entrenador.setFechaNacimiento(fechaNac3); List<Jugador> jugadores = new ArrayList<Jugador>(); jugadores.add(jugador1); jugadores.add(jugador2); equipo.setId(1); equipo.setNombre("JAVA TEAM"); equipo.setEntrenador(entrenador); equipo.setJugadores(jugadores); return equipo; } }
Al ejecutarse esta clase, se creará el XML correspondiente (exportacionEquipo.xml) con los datos establecidos en el método Main.equipoDePrueba:
<?xml version="1.0" encoding="UTF-8"?> <equipo> <jugadores xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:java="http://java.sun.com" xsi:type="java:com.danielme.blog.castorxml.modeloequipo.Jugador"> <estatura>178</estatura> <peso>74.3</peso> <dorsal>1</dorsal> <apellidos>Español</apellidos> <nombre>Juan</nombre> <id>1</id> </jugadores> <jugadores xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:java="http://java.sun.com" xsi:type="java:com.danielme.blog.castorxml.modeloequipo.Jugador"> <estatura>174</estatura> <peso>71.1</peso> <dorsal>2</dorsal> <apellidos>Pérez Pérez</apellidos> <nombre>Miguel</nombre> <id>2</id> </jugadores> <entrenador> <apellidos>García</apellidos> <nombre>Joaquín</nombre> <id>14</id> </entrenador> <nombre>JAVA TEAM</nombre> <id>1</id> </equipo>
Y en la salida estándar, un resumen de los datos que han pasado primero por el marshalling y luego por el unmarshalling:
Nota: obsérvese que no se ha incluído en el XML el campo con la fecha de nacimiento de tipo java.util.Calendar, en el siguiente capítulo veremos cómo solucionarlo.
WordPress: 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.
Hola que tal, disculpa, con el Marshal se puede agregar un nuevo elemento a un xml con datos? Gracias! Saludos!
Que yo sepa sólo puedes leer y escribir los ficheros XML enteros, no puedes agregar objetos sueltos.