h1

Desarrollo de servicios web sin SOAP con XMLBeans

06/03/2008

Wizdoc [Icon By Buuf]

 Tips & Tricks

Generalmente se piensa que los Web Services [o Servicios Web] son sinónimo de servicios ofrecidos a través de SOAP. Esta es una visión limitada de los web services. Cualquier pieza de código con una descripción WSDL de sus aspectos funcionales y protocolos de acceso puede ser considerada un Web Service.

— Nirmal Mukhi, Web service invocation sans SOAP, 01 Sep 2001

Especificación General

Como puede verse en la siguiente figura, se requiere hacer la llamada al sistema Y desde el sistema X mediante el envío de un XML sobre una conexión HTTP/POST síncrona. Del lado del servicio publicado se encontrará un servlet que recibirá las peticiones y fungirá como un Service Facade, atrapando las peticiones y remitiéndolas a los componentes de parseo, validación y ejecución correspondientes para este servicio (denominados Core API del sistema o capa de negocio). Del lado del cliente o consumidor del servicio se espera implementar un componente que realice una conexión HTTP con el servicio y envíe el XML esperado a través del método POST. Más tarde, el servicio responderá con otro XML detallando el éxito o error resultante de la llamada que a su vez será parseado y validado por el cliente para tomar las acciones pertinentes.

Diagrama de componentes UML 2.0 (no estándar) que contempla la distribución a grosso modo de los elementos que conforman el servicio.

[Click en imagen para ver original]

Especificación de los mensajes

En la siguiente tabla se especifican los puntos requeridos en forma y formato de los mensajes a enviar. Nótese que entre los requerimientos no se incluye la necesidad de implementar SOAP como protocolo de envío.

Protocolo

XML codificado en UTF-8 sobre HTTP versión 1.1

Método de petición al servidor

HTTP/POST

Formato XML petición (X a Y)

<?xml version="1.0" encoding="UTF-8"?>
<ProvisioningRQ>
  <IdTransaction>0</IdTransaction>
  <TypeTransaction>BAJA</TypeTransaction>
  <Dn>1234567890</Dn>
</ProvisioningRQ>

Formato XML respuesta (Y a X)

<?xml version="1.0" encoding="UTF-8"?>
<ProvisioningRS>
  <IdTransaction>0</IdTransaction>
  <CodError>0</CodError >
  <DescError>OK</DescError >
</ProvisioningRS>

Aunque no es requerido, debemos generar un WSDL como referencia para saber cómo se realizará la llamada al servicio:

<?xml version="1.0" ?>
<definitions
 targetNamespace="http://everac99.spaces.live.com/wsdlexample&quot;
 xmlns:ytox="http://everac99.spaces.live.com/ytoxxsd&quot;
 xmlns:xsd="http://www.w3.org/1999/XMLSchema&quot;
 xmlns="http://schemas.xmlsoap.org/wsdl/"&gt;

  <message name="ProvisionInput">
   <part name="ProvisionRequest" type="ytox:ProvisioningRQ"/>
  </message>

  <message name="ProvisionOutput">
   <part name="ProvisionResponse" type="ytox:ProvisioningRS"/>
  </message>

  <portType name="ProvisionPT">
   <operation name="Provision">
    <input message="tns:ProvisionInput"/>
    <output message="tns:ProvisionOutput"/>
   </operation>
  </portType>
</definitions>

Los componentes e información seguirán el siguiente flujo:

Diagrama de secuencia UML que contempla el flujo de eventos a seguir por el servicio.
Notas:
1.Si la sintaxis o valores de negocio de la petición al servicio son incorrectos, se construirá la respuesta correspondiente.
2.Si la sintaxis o valores de negocio son correctos, se prosigue con la llamada a las funciones que requiere la operación.
3.Por "operaciones necesarias" contemplamos la llamada a una o más funciones del sistema "Y", tratando todo como una sola unidad transaccional.

[Click en imagen para ver original]

A partir del diagrama, podemos ver que se requiere un framework XML basado en DOM que nos facilite el trabajo de parsear y validar los valores contenidos en el XML; adicionalmente nos debe permitir construir documentos XML de forma eficiente y con un buen desempeño al generar nuestra respuesta. Por ello, se decidió utilizar la implementación 2.3.0 de XMLBeans. Este framework nos permite utilizar las funciones de parseo, validación o generación de XMLs en base a una jerarquía de componentes de java generados mediante las herramientas que posee dicho framework. A continuación describiremos el proceso de generación de nuestros componentes en base a tan sólo los XMLs de prueba que tenemos más arriba y cómo utilizarlos en un servicio web que recibe y envía los XMLs a través de la conexión HTTP.

Paso 1: Generando un XSD

Uno de los buenos hábitos al utilizar XML, es que siempre es recomendable definir un XSD (XML Schema Definition) para gestionar nuestros XMLs de manera centralizada. Adicionalmente, para generar los correspondientes XMLBeans requerimos un XSD.

Puesto que sólo disponemos de los XML de petición y respuesta deberemos generar el XSD a través de éstos. Existen varias herramientas de conversión XML a XSD en el mercado, pero podemos utilizar la herramienta de conversión on-line ofrecida por HiT Software donde podemos transformar nuestros XMLs en los XSDs que necesitamos.

Herramienta de conversion xml a xsd
Un screenshot de la herramienta de conversión de XML a XSD y el archivo XML con el ProvisioningRQ de nuestro servicio. NOTA: La herramienta no parsea correctamente XMLs con varios elementos, es decir, hay que generar un XSD con el merge de los resultados la transformación de ProvisioningRQ y ProvisioningRS.

[Click en imagen para ver original]

El resultado de la conversión de nuestros XMLs será algo así:

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

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
 <xs:element name="Dn">
  <xs:complexType mixed="true"/>
 </xs:element>

 <xs:element name="IdTransaction">
  <xs:complexType mixed="true"/>
 </xs:element>

 <xs:element name="ProvisioningRQ">
  <xs:complexType>
   <xs:sequence>
    <xs:element ref="IdTransaction"/>
    <xs:element ref="TypeTransaction"/>
    <xs:element ref="Dn" />
   </xs:sequence>
  </xs:complexType>
 </xs:element>

 <xs:element name="TypeTransaction">
  <xs:complexType mixed="true"/>
 </xs:element>

</xs:schema>

El código mostrado es el XSD del elemento ProvisioningRQ. Para generar el XSD de la aplicación, es necesario realizar un merge de los XSDs del request y response e incluir las reglas de formato, tipos de datos o restricciones que sean requeridas para parsear correctamente los XMLs que utilizará la aplicación. Las propiedades que pueden tomar estos elementos pueden verse en el tutorial sobre XSDs del W3Schools; para validar los XMLs contra nuestra XSD refinada podemos utilizar una pequeña herramienta llamada XmlValidator. La definición final para esta aplicación sería la siguiente:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;

 <!– Tipos de datos –>
 <xs:element name="IdTransaction" type="xs:string"/>
 <xs:element name="Dn" type="xs:string"/>
 <xs:element name="TypeTransaction" type="xs:string"/>
 <xs:element name="CodError" type="xs:string"/>
 <xs:element name="DescError" type="xs:string"/>

 <xs:element name="ProvisioningRQ">
  <xs:complexType>
   <xs:sequence>
    <xs:element ref="IdTransaction" minOccurs="1" maxOccurs="1"/>
    <xs:element ref="TypeTransaction" minOccurs="1" maxOccurs="1"/>
    <xs:element ref="Dn" minOccurs="1" maxOccurs="1"/>
   </xs:sequence>
  </xs:complexType>
 </xs:element>

 <xs:element name="ProvisioningRS">
  <xs:complexType>
   <xs:sequence>
    <xs:element ref="IdTransaction" minOccurs="0" maxOccurs="1"/>
    <xs:element ref="CodError" minOccurs="0" maxOccurs="1"/>
    <xs:element ref="DescError" minOccurs="0" maxOccurs="1"/>
   </xs:sequence>
  </xs:complexType>
 </xs:element>

</xs:schema>

Paso 2: Generando los XMLBeans

En Adictos al Trabajo podemos encontrar un buen tutorial para instalar y ejecutar las herramientas de conversión de los XMLBeans. Sin embargo, existen tres detalles que es necesario observar:

•  Hay que tener cuidado con qué versión de Java estamos trabajando. Es decir, necesitamos verificar que el compilador y el ambiente de ejecución (javac.exe y java.exe, respectivamente) estén correctamente configurados en el path del ambiente de desarrollo y en el archivo ${XMLB_HOME}binscomp(.cmd) porque puede surgir el error "CreateProcess Exception" al tratar de compilar el XSD (En el Java Boutique se encuentra la explicación y solución al problema).

•  Por otro lado, si estamos usando una versión anterior a Java 1.5, adicionalmente a la librería base de XMLBeans llamada xbean.jar, deberemos incluir en la distribución de la aplicación el archivo xmlbeans-qname.jar para poder utilizar correctamente esta funcionalidad.

•  Finalmente, la tarea de ant para generación de XMLBeans deja mucho que desear. La forma más flexible de crear y empaquetar correctamente los componentes es mediante la línea de comando:

>${XMLB_HOME}/bin/scomp -javasource 1.4 -out ${ProjectHome}/lib/xmltypes.jar ${ProjectHome}/config/xsd/ytoxxsd.xsd ${ProjectHome}/config/xsd/ytox.xsdconfig
Time to build schema type system: 0.937 seconds
Time to generate code: 0.328 seconds
Time to compile code: 3.422 seconds
Compiled types to: ${ProjectHome}/lib/xmltypes.jar
>

Donde:
•  ${XMLB_HOME} Es el directorio donde está instalado el framework.
•  ${ProjectHome} Es el directorio donde está nuestro proyecto.
•  ytoxxsd.xsd Es el XSD que generamos en el paso anterior.
•  ytox.xsdconfig Es un archivo de configuración XML que permite mapear los namespaces del XSD con paquetes de java:

<?xml version="1.0" encoding="UTF-8"?>
<xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config"&gt;
  <xb:namespace uri="##any">
    <xb:package>
     com.spaces.everac99.xml.types
    </xb:package>
  </xb:namespace>
</xb:config>

Esto dará por resultado un JAR con los componentes de Java que podrán ser utilizados en el proyecto de Eclipse:

El JAR con XMLBeans en nuestro proyecto de Eclipse
El JAR con XMLBeans en nuestro proyecto de Eclipse.

Paso 3: Usando los XMLBeans

Como puede verse en el siguiente fragmento de código:

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {

/* Parseo del HttpRequest InputStream para construir un XMLBean */
BufferedInputStream in = new BufferedInputStream(request.getInputStream());
ProvisioningDocument req = ProvisioningDocument.Factory.parse(in);
String idTransaction = req.getProvisioning().getIdTransaction();

/* Validacion de reglas de negocio y ejecucion de operacion (llamada a Core API)*/

/* Construccion de un nuevo XMLBean desde cero y envio al HttpResponse OutputStream*/
ProvisioningResponseDocument res = ProvisioningResponseDocument.Factory.newInstance();
res.documentProperties().setVersion("1.0");
res.documentProperties().setEncoding("UTF-8");
res.addNewProvisioningResponse();
res.getProvisioningResponse().setIdTransaction("12345");
res.getProvisioningResponse().setCodError("007");
res.getProvisioningResponse().setDescError("UNKNOWN ERROR");

BufferedWriter out = new BufferedWriter(response.getWriter());
res.save(out);
out.flush();
out.close();

}

Construir y manipular los XMLBeans generados a partir del XSD es muy intuitivo: En el primer segmento de código se está construyendo un XMLBean a través de la secuencia de llamadas BeanClassDocument.Factory.parse(InputStream). Por otro lado se está construyendo un nuevo documento mediante la secuencia BeanClassDocument.Factory.newInstance() y se está almacenando en la respuesta de la petición mediante el método BeanObjectDocument.save(OutputStream).

XMLs con elementos mixtos

Un detalle que puede presentarse durante la construcción de un servicio de este tipo es la generación o parseo de documentos XML que poseen elementos mixtos: es decir, nodos que pueden contener más subnodos, texto o combinaciones de ambos:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <RESULT msisdn="telefono1">ABCD999999A1B</RESULT>
  <RESULT msisdn="telefono2">
    <ERROR id="045">UNKNOWN NUMBER</ERROR>
  </RESULT>
</Response>

En el ejemplo se muestra la respuesta a un servicio de búsqueda donde puede traer de 1 a 10 elementos de tipo RESULT. Cada elemento contiene una propiedad denominada msisdn y dentro de la etiqueta en sí puede existir un texto o un elemento de tipo ERROR.

Sin embargo, esta situación no tiene por qué generar preocupación, pues con un XSD de este tipo de documento podremos generar los XMLBeans que necesitamos:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;

 <xs:element name="ERROR">
  <xs:complexType mixed="true">
   <xs:attribute name="id" type="xs:string"/>
  </xs:complexType>
 </xs:element>

 <xs:element name="RESULT">
  <xs:complexType mixed="true">
   <xs:sequence>
    <xs:element ref="ERROR" minOccurs="0" maxOccurs="1"/>
   </xs:sequence>
   <xs:attribute name="msisdn" type="xs:string" use="required"/>
  </xs:complexType>
 </xs:element>

 <xs:element name="RESPONSE">
  <xs:complexType>
   <xs:sequence>
    <xs:element ref="RESULT" minOccurs="1" maxOccurs="10" />
   </xs:sequence>
  </xs:complexType>
 </xs:element>

</xs:schema>

Al utilizar este tipo de documentos tendremos que utilizar una característica algo enredosa de los XMLBeans denominada XMLCursor:

/* Leyendo un elemento de tipo mixed de un XMLBean */
BufferedInputStream in = new BufferedInputStream(request.getInputStream());

ResponseDocument res = ResponseDocument.Factory.parse(in);

if(res.getResponse().getRESULTArray(0).getERROR() == null) {
  /* Significa que este elemento contiene texto */
  XmlCursor cursor = res.getResponse().getRESULTArray(0).newCursor();
  cursor.toFirstContentToken();
  String valor = cursor.getChars();
} else {
  /* Significa que este elemento contiene un subnodo */
  String errorId = res.getResponse().getRESULTArray(0).getError().getId();
}

/* Escribiendo texto en un elemento de tipo mixed en un XMLBean*/
ResponseDocument doc = ResponseDocument.Factory.newInstance();
doc.addNewResponse();
doc.getResponse().addNewRESULT();
doc.getResponse().getRESULTArray(0).setMsisdn("telefono1");
XmlCursor cursor = doc.getResponse().getRESULTArray(0).newCursor();
cursor.setTextValue("ABCD999999A1B");
cursor.dispose();

Esta interfaz nos permite saltar a lo largo de la estructura del documento XML facilitando la modificación de los valores e incluso agregar nuevas etiquetas de elementos.

Conclusiones

Como hemos visto, no es difícil construir un servicio web utilizando los XMLBeans como framework de conversión XML a Java, pues como tienen métodos que nos permiten cargar o enviar nuestros XMLs desde o hacia streams de datos, podemos utilizar sus características para ahorrar tiempo, dinero y esfuerzo a la hora de construir la solución. Un beneficio adicional: si necesitamos implementar web services con SOAP también podemos utilizar XMLBeans y trabajar sobre clases de Java, no sobre cadenas de XML que son bastante difíciles de codificar y peor aún de mantener.

2 comentarios

  1. al generar mas de un jar y cargarlos al proyecto, sale un error por que por defecto los deja como noNameSpace.¿Que se puede hacer en ese caso?


  2. Cuando generas los XMLBeans con el comando scomp, por default genera un JAR con clases de Java cuyo paquete se llama noNamespace (por ejemplo, noNamespace.MyDocument, noNamespace.MyBean). Como estás generando más de un JAR de esta manera, los nombres de las clases chocan unos con otros, generando el error que comentas. Existen dos maneras de corregirlo: en el primero, generas un solo XSD con la definición de todos los elementos que convertirás a XMLBeans, pero implica integrar varios XSDs y es un proceso tardado que puede complicarse si varía la estructura de los mismos. La segunda opción requiere que generes un archivo de configuración (por ejemplo, C.xsdconfig) y ejecutes el comando de la siguiente forma:

    scomp -javasource 1.4 -out A.jar B.xsd C.xsdconfig

    donde C.xsdconfig tiene la siguiente estructura:

    <?xml version="1.0" encoding="UTF-8"?>
    <xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config"&gt;
    <xb:namespace uri="##any"><xb:package>com.package.mypackage</xb:package>
    </xb:namespace></xb:config>

    Y donde com.package.mypackage es el paquete que tendrán las clases en el JAR que estás generando. Si utilizas diferentes paquetes para cada JAR, debería resolverse el error.



Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: