Desenredando XmlBeans

Estas dos ultimas semanas he estado trabajando con XmlBeans. Para aquellos que no lo sepáis de lo que hablo, os pongo una pequeña definición de la página oficial de Apache:

XMLBeans is a technology for accessing XML by binding it to Java types. XMLBeans provides several ways to get at the XML

Apache xmlBeans logoMediante este proyecto se pretende facilitar el acceso a documentos XML desde Java proporcionando un sistema que genera a partir del schema XML las clases que representan las entidades XML así como una API para instanciar dichos objetos a partir del documento XML.

La verdad es que todas estas definiciones quedan muy bonitas, pero una vez nos ponemos el mono de trabajo van surgiendo los problemas. Dedicaré este post a explicar todas las piedras que he ido encontrando por el camino y creo que puede ser realmente útil para todos aquellos que quieran utilizar este sistema de mapeo en sus aplicaciones.

1. java.io.IOException al ejecutar "scomp":

Scomp es el comando para generar las clases xmlBeans a partir de un esquema. Este problema es debido a que no encuentra el comando javac para ejecutar la compilación del código (aunque tengamos bien el path). La solución es poner el path del JDK en la primera posición del path:

PATH=C:\j2sdk1.4.2_03\bin;...

Mas sobre la solución.

2. Uso de -noupa al ejecutar "scomp":

Al ejecutar "scomp" me encontré tambien con otro problema. Esta vez relacionado con la regla de Unique Particle Attribution:

Content model violates the unique particle attribution rule.

Solucionamos este problema de ambiguación añadiendo la opción "-noupa" al ejecutar "scomp".

Mas sobre la solución.

3. Declaración <?xml?> no se imprime en las clases root generadas por xmlBeans:

Cuando utilizamos la función toString() de la clase java que representa el elemento raíz del esquema XML, la declaración <?xml?> no se imprime al principio del documento. Para solucionar dicho problema tenemos que hacer un pequeño rodeo, pasando primero el contenido del elemento raiz a un OutputStream y de ahí lo convertimos en String. Resultado: tenemos nuestro XML con su cabecera " <?xml version="1.0"?>".

Ejemplo:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
rootElement.save(baos);
} catch (Exception e){
e.printStackTrace();
}
String xmlText = new String (baos.toByteArray());

Mas sobre la solución.

4. Uso de xmlOptions para la definición de prefijos de namespaces propios:

En el caso de que tengamos un documento con diferentes namespaces, y quisiéramos definir el prefijo de los mismos, tenemos que indicárselo a xmlBeans mediante xmlOptions. Vamos a ver un ejemplo de ello. Tenemos el siguiente elemento:

testID

Dicho elemento contiene el prefijo "urn", definido automáticamente por xmlBeans. Para cambiar este prefijo y poner otro que crea mas conveniente como por ejemplo "saml" debemos definir un objeto xmlOptions con las siguientes opciones:

HashMap ns = new HashMap();
ns.put(”urn:oasis:names:tc:SAML:1.0:protocol”, “samlp”);
xmlOptions.setSaveSuggestedPrefixes(ns);

Utilizaremos el elemento xmlOptions a la hora de imprimir el documento de la siguiente manera:

rootElement.xmlText(opts)

Obteniendo como resultado el elemento con el prefijo "saml" :

testID

Finalmente comentar otras de las posibles y útiles opciones de xmlOptions, el uso de espacios en blanco para una lectura mas cómoda:

xmlOptions.setSavePrettyPrint();
xmlOptions.setSavePrettyPrintIndent(4);

Mas sobre la solución.

5. Uso de substitute con tipos abstractos:

XmlBeans nos da también la posibilidad de trabajar con tipos abstractos. Para ver como funcionan vamos a ver un ejemplo a partir del siguiente esquema:










Como vemos, tenemos un elemento abstracto y dos elementos que lo pueden substituir que son "classA" y "classB". El código java generado para las clases abstractas contiene las siguientes funciones:

a) root.addNewAbstractClass();

b) root.setAbstractClass(XmlObject xmlObject);

Lo que nos da un output como el siguiente es:

...........

Cuando nosotros lo que necesitamos es esto:

...........

Para solucionar dicho problema vamos a utilizar la función substitute del xmlObject:

RootDocument doc = RootDocument.Factory.newInstance();
RootDocument.Root root = doc.addNewRoot();
XmlObject xmlObject = root.addNewAbstractClass();
xmlObject = xmlObject.substitute( ClassADocument.type.getDocumentElementName(), ClassAType.type);

Ahora hemos substituido el elemento Abstract Class por un elemento de su Substitution-Group (en el ejemplo el elemento "classA").

Mas sobre la solución.

6. Problema de carga de esquemas similares en la misma aplicación:

Este es posiblemente el problema que mas me ha traído de cabeza. La verdad es que es un poco complejo de entender, así como su solución. Intentaré explicarlo de forma clara y a ver si así puedo evitar algún dolor de cabeza que otro!

La situación es la siguiente. Tenemos dos versiones diferentes de una especificación que esta basada en esquemas XML parecidos. Muchos de los elementos tienen nombres similares y también comparten el mismo namespace.

Compilamos los esquemas en clases diferentes y en diferentes packages, incluso con nombres diferentes (usando xsdconfig).

El problema (ClassCastException) surge al inicializar una de las clases que representan elementos comunes en los dos esquemas con la función parse de la clase Factory, ya que la clase Factory es común y lo que esta generando es una clase de un esquema que no es el apropiado.

La explicación de todo ello viene a continuación:

Yes, this is a known "problem" with how xmlbeans looks up schema information.When xmlbeans builds the jar, all the schema information for elements goes into the 'schema/elements' directory, the attribute information goes into 'schema/attributes', etc. Under those directories, the meta information filed using the QName, for example 'schema/element/someNamespace/myElement.xsb'. In your case you have two jars compiled from schemas with the same target namespace and have elements of the same name. The result is the element .xsb files in the second jar on the classpath is being masked by the .xsb files from the first jar on the classpath.

The reason XmlBeans needs to use this lookup technique is clear when you think about an instance that has an xsi:type attribute to change the element type, eg. . You must be able to lookup everything in the schema type system by QName to find what "otherType" is.

Y a continuación un ejemplo practico, para que nos quede mas claro :) Como comentábamos anteriormente, utilizando la función parse de la clase Factory tenemos problemas de casting:

TopLevelElementDocumentPostFix toplevel = TopLevelElementDocumentPostFix .Factory.parse ( xml );

Debemos substituir esta linea por lo que sigue a continuación:

File[ ] schemaPath = { new File ( "the-path-to-the-jar/xmltypes-postfixed.jar" ) } ;

SchemaTypeLoader stl = XmlBeans.typeLoaderForResource ( XmlBeans.resourceLoaderForPath ( schemaPath ) ) ;
stl = XmlBeans.typeLoaderUnion( new SchemaTypeLoader [] { TopLevelElementDocumentPostFix.type.getTypeSystem(), XmlBeans.getContextTypeLoader()} ) ;

System.out.println ( TopLevelElementDocumentPostFix.type.toString ( ) ) ;
TopLevelElementDocumentPostFix toplevel = ( TopLevelElementDocumentPostFix )stl.parse ( xml , TopLevelElementDocumentPostFix.type , null ) ;

Como vemos, definimos nosotros mismo el esquema a partir del jar. Una vez tenemos definido el nuevo esquema, utilizamos su función parse equivalente a la de la Factory, esta vez sin problemas de casting.

Un par de links sobre esta solución: link1 & link2.

7. Conclusión:

La verdad es que pese a todos estos pequeños y grandes problemas, xmlBeans es una solución que me ha ayudado mucho y considero que es un elemento a tener en cuenta a la hora de tratar con esquemas XML. Lo unico que he echado en falta es algo mas de documentacion, ya que como podeis ver la mayoria de estos problemas pueden ser muy comunes a todos los mortales y sus soluciones se encuentran en foros recognitos y muy dificiles de hallar. Por no hablar de la casi nula documentacion en castellano sobre el tema.

Os dejo unos cuantos links que me han sido de utilidad. Espero que esta pequeña coleccion de errores os haya facilitado vuestros desarrollos con xmlBeans!

Contacto

¿Te interesan nuestros servicios?

Contáctanos