Como primera tarea en mi nuevo trabajo me ha tocado lidiar con XMLs, con los cuáles tengo una relación de amor-odio desde hace unos años, y XSD que me gustan algo (no mucho) más :D
Como de todo se aprende he estado trasteando y usando Java Architecture for XML Binding (JAXB) para llevar cabo las operaciones que se requerían. Como siempre, para la elección de una tecnología o librería hay que tener unos motivos claros que nos decanten o no por ella. En ese caso, lo que se pretendía era, partiendo de unos XSDs generar en tiempo de compilación las respectivas clases Java que representan las estructuras que definen y usar las capacidades de "marshal", "unmarshal" y validación para mapear de las clases a archivos XML correctamente formados.
Con esto en mente JAXB proporciona lo siguiente:
- Generación de estructuras Java a partir de XSDs
- Integración con maven de la herramienta de generación (xjc)
- Funciones de marshal/unmarshal
- Validación de un XML contra un XSD
JAXB allows storing and retrieving data in memory in any XML format, without the need to implement a specific set of XML loading and saving routines for the program's class structure.
Y lo mejor del proceso es que resulta bastante limpio y sencillito aunque tengo un gran "pero" y es sobre las estructuras Java que genera, ya que si tienes un conjunto de XSD complicado (varios namespace, muchos tipos definidos, etc) la cantidad de código generado puede ser abrumador. Como muestra os diré que de 5 XSDs con dependencias entre ellos me ha generado 640 clases... y claro, a la hora de construir los objetos para pasarlos a un XML la cantidad de líneas que hay que poner para un simple etiqueta puede resultar excesivamente complejo. Aquí es donde mi mejor amiga, la refactorización, ha estado a mi lado continuamente y he ido extrayendo métodos, creando variables, dando nombres cada vez un poco más explicativos, etc. Y estoy seguro que se puede mejorar mucho más porque sinceramente sigue quedando guarillo :D
Transformación XSD en clases Java
Para ello he usado la herramienta de compilación xjc que facilita enormemente el trabajo. El único prerrequisito es disponer de todos los XSDs de los cuáles depende el que te interesa. Al fin y al cabo querrás generar un XML basado en un XSD concreto pero, seguramente, tenga namespace de otros y sino los tienes la herramienta te los pedirá antes de generar nada.
Esta herramienta puedes usarla desde consola porque viene ya incluida en Java 1.6, por tanto no necesitas bajar librería adicional. El comando más simple para esto sería:
xjc <schema file/URL/dir/jar> -d <directorio salida>
y, si te pasa como a mi, puedes esperar un poco a que genere todo :D Curiosamente el mismo proceso es más rápido desde Maven. La integración se me atascó bastante porque hay más de un plugin para llevarlo a cabo y cada uno genera estructuras ligeramente diferentes, además que algunos están un poco abandonados y daban problemas los repositorios. Al final me decante por el "plugin-oficial" maven-jaxb2-plugin, podéis encontrar su info y la forma de configuración aquí. La ventaja de esta opción es la generación de todas las estructuras en tiempo de compilación (puedes adjuntar el proceso a la fase maven que quieras) y así te libras de tener que tener en svn 640 clases Java... aunque puede que no te quede otra como al final me ha pasado a mi ya que en el ordenador de un compañero no funcionaba. Sí, lo sé, "en mi PC funciona" no es una frase válida, por eso mismo, tras dedicarle un tiempo no merecía darle más vueltas, tenemos las clases, es el objetivo. Si el día de mañana cambian los XSDs también habrá que cambiar la lógica generada para la construcción de los XMLs por tanto habrá que tocar el módulo igualmente.
Funciones de marshal/unmarshal y validación
Esta es la parte que más me ha gustado de JAXB. Prácticamente 3-4 líneas de código para cada cosa y ya se ocupa de todo él solito :D Voy a poneros código que sé lo estáis deseando:
JAXBContext context = JAXBContext.newInstance(elementType); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(createdElement, outputStream);
Las líneas anteriores reflejan la operación de marshal desde clases Java a XML que siguen la estructura definida. Los pasos son:
1) Crear el contexto para la clase que queremos mapear a XML (por ejemplo, Persona.class) que fue generada por la herramienta de compilación.
2) Obtener el objeto que se encargará de la transformación a partir de ese contexto.
3) Indicarle que le aplique formato de salida.
4) Invocar a la operación de marshal indicando la "estructura completa" que queremos pasar como XML al outputStream. Por estructura completa me refiero a la instancia que hemos ido rellenando de datos y otras clases Java usando los recursos de la fase de generación.
Y esto es todo amigos! :)
El proceso de unmarshal es básicamente similar. En el punto 2) creamos un objeto unmarshal desde el contexto y en el punto 4) invocamos al método correspondiente e indicamos un inputStream. En el módulo que he construido no he tenido que implementar esta funcionalidad pero en las pruebas anteriores que hice todo iba bastante bien.
Una vez hemos generado nuestros XMLs es importante validarlos contra el esquema que, se supone, deben de seguir. Este proceso también se resume en pocas líneas:
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = schemaFactory.newSchema(new File(xsdPath)); Validator validator = schema.newValidator(); Source source = new StreamSource(inputStream); validator.validate(source);
La descripción de los pasos sería:
1) Se obtiene una factory indicando que será un XSD
2) A partir de la factory leemos el esquema
3) Creamos un validador a partir del esquema anterior
4) Obtenemos la fuente a validar, el XML, y lo sometemos al proceso
Si todo va bien el flujo de ejecución no se interrumpe, en caso contrario se arroja una excepción que te indicará el motivo concreto, por ejemplo, que falta alguna etiqueta obligatoria o que un campo no sigue un determinado formato.
Aparte de lo que he comentado aquí de la generación automática también podemos crear nuestras propias estructuras con las anotaciones que existen y van junto con las clases y sus atributos. Existe alguna forma más de declarar entidades válidas para el proceso de JAXB sin anotaciones, simplemente con getters/setters pero ya se me hace el artículo demasiado largo.
Algunos igual llegáis aquí diciendo ¿cómo se testea esto? :) Pues bien, ese tema lo dejo para otro artículo porque, aparte de hacer los test básicos de: esto no es nulo, esto debe arrojar una excepción, esto debe ir relleno, etc. tengo en mente usar XMLUnit para enriquecer los test que han quedado algo cojos. Pero, como dije antes, para otro artículo :D

Interesante tutorial! tienes que probar XStream, te va a encantar! :)
Gracias Alberto!
He mirado XStream y mola mucho más que JAXB xD no he profundizado y creo no tiene la generación automática de clases pero me parece mucho mucho más simple y desde luego su mayor ventaja es que lo puedes usar con cualquier clase, no necesita de anotaciones ni nada, eso mola! :)
Gracias por la recomendación, lo guardo para tenerlo en cuenta en otras ocasiones :)
Muy bueno el artículo. Lo bien que me hubiera venido hace algún tiempo jeje. Y como bien apunta Alberto, XStream puede ser de gran utilidad :)
Saludos!