2. Mensajería XML

Podemos intercambiar mensajes a través de Internet utilizando la especificación estándar SOAP. El contar con dicho estándar nos permitirá la interoperabilidad de aplicaciones heterogéneas, ya que sabemos que cuando enviemos un mensaje SOAP que cumpla con la especificación, nos podremos comunicar con cualquier otra aplicación que entienda este tipo de mensajes, independientemente de su implementación interna.

En el caso ideal esto debe ser así, pero muchas veces una implementación concreta de SOAP no cumple estrictamente con el estándar, por lo que no podremos contar con una interoperabilidad total, sino parcial. Existen herramientas que nos permiten comprobar como se ajusta a la especificación una determinada implementación de SOAP, como por ejemplo la herramienta SOAP Message Validator.

Para utilizar SOAP desde Java contamos con la librería JAXM (Java API for XML Messaging). Podemos distinguir dentro de ella dos APIs distintas, cada una de las cuales corresponde a un paquete de la implementación:

API Paquete Descripción
SAAJ javax.xml.soap Creación y manipulación de mensajes SOAP. Permite el envio de mensajes SOAP de forma síncrona (petición-respuesta).
JAXM javax.xml.messaging Contiene un proveedor de mensajeria, que permite hacer envios asíncronos de mensajes.

Como podemos ver, separa la parte que se encarga de manipular los mensajes, y la encargada de gestionar el transporte de éstos. De esta forma, tenemos que SAAJ es una API independiente, mientras que JAXM dependerá de SAAJ ya que es la que contiene la descripción de los mensajes que JAXM debe transportar.

Esta librería nos permitirá enviar mensajes SOAP orientados al documento, es decir, podremos incluir el contenido que queramos dentro del mensaje SOAP. Podremos trabajar tanto con mensajes SOAP, como con mensajes SOAP con anexos.

Podemos ver los mensajes orientados a RPC como un tipo más concreto que los mensajes orientados al documento, en los que el contenido del mensaje es el método y los parámetros que queremos invocar en la petición, y el valor que devuelve el método en la respuesta. La librería JAX-RPC se encargará de gestionar estos mensajes SOAP orientados a RPC, apoyándose en SAAJ para construir y analizar estos mensajes. Aquí podemos ver las ventajas de la separación de SAAJ y JAXM, ya que de esta forma JAX-RPC sólo dependerá de SAAJ que ofrece todo lo necesario para trabajar con mensajes orientados a RPC, y no tendrá que depender de JAXM, que no es necesaria en este caso.

Podemos encontrar información sobre la interoperabilidad de JAX-RPC con otras plataformas en http://java.sun.com/wsinterop/sb/index.html.

2.1 Manejo de mensajes SOAP

Un mensajes SOAP consta de una parte SOAP, que contiene un sobre SOAP donde tenemos una cabecera SOAP de forma opcional, y un cuerpo del mensaje SOAP, tal como podemos ver en la siguiente figura:

Vemos que cada parte del mensaje está asociada con una clase Java que la representa. Todas estas clases las proporciona la API SAAJ.

Para crear un mensaje deberemos obtener un objeto capaz de generar mensajes SOAP (MessageFactory), y a partir de él construir nuestro nuevo mensaje:

MessageFactory mf = MessageFactory.newInstance();
SOAPMessage mensajeSOAP = mf.createMessage();

Una vez hemos obtenido el mensaje podemos acceder a cada una de las partes de nuestro mensaje mediante las clases proporcionadas por SAAJ:

SOAPPart parteSOAP = mensajeSOAP.getSOAPPart();
SOAPEnvelope sobreSOAP = parteSOAP.getEnvelope();
SOAPHeader cabeceraSOAP= sobreSOAP.getHeader();
SOAPBody cuerpoSOAP= sobreSOAP.getBody();

Todos estos elementos son creados por defecto cuando construimos un mensaje SOAP utilizando MessageFactory. Además, podremos añadir o eliminar nodos del documento SOAP, igual que con cualquier documento XML.

2.1.1 Errores SOAP

Dentro del cuerpo podemos adjuntar un error SOAP, para indicar los errores que se puedan producir. Este error SOAP se puede crear de la siguiente forma:

SOAPFault errorSOAP = cuerpoSOAP.addFault();

Dentro de la parte de error SOAP podemos establecer los siguientes atributos:

Atributo Métodos para consultar / establecer Descripción
Código

String getFaultCode()
setFaultCode(String fc)

Código de error definido en la especificación de SOAP.
Actor
String getFaultCode()
setFaultCode(String fc)
Identifica el recipiente que causó el error, entre todos por los que ha pasado el mensaje. Sólo es necesario especificarlo si en la cabecera se han especificado varios actores.
Mensaje
String getFaultCode()
setFaultCode(String fc)
Mensaje de texto que describe el error en lenguaje natural.
Detalles

Detail getDetail()
Detail addDetail()

Permite añadir detalles sobre el error. Por defecto el error no incluye detalles, por lo que debemos crearlos con este método. Como mucho podemos tener un objeto Detail en el error, pero dentro de este objeto podemos añadir tantas entradas DetailEntry como queramos.

Los códigos de error definidos en la especificación se pueden identificar en JAXM con las siguientes cadenas:

Código Descripción
VersionMismatch El espacio de nombres del sobre SOAP es inválido
MustUnderstand El destinatario no ha conseguido entender alguno de los campos especificados como obligatorios.
Client El mensaje enviado no era correcto (el cliente cometió un error al codificarlo)
Server El mensaje era correcto pero no pudo procesarse correctamente (el servidor no pudo procesarlo)

Cuando obtenemos una respuesta, podemos comprobar si este mensaje SOAP adjunta un error, y en tal caso consultar el error que se ha producido. Para comprobar si hay un eror deberemos hacer lo siguiente:

if ( cuerpoSOAP.hasFault() ) {
   SOAPFault errorSOAP = cuerpoSOAP.getFault();
   String codigo = errorSOAP.getFaultCode();
   String mensaje = errorSOAP.getFaultString();
   String actor = errorSOAP.getFaultActor();
}

2.1.2 Modificar el contenido

Podremos añadir contenido a los distintos nodos del documento SOAP. Contamos con las clases SOAPBodyElement, SOAPHeaderElement y SOAPFaultElement, que se utilizarán para encapsular los elementos que se añadirán al cuerpo, a la cabecera y al error SOAP respectivamente.

Estos tres tipos de elementos, junto a los anteriores SOAPEnvelope, SOAPBody, SOAPHeader, Detail, DetailEntry y SOAPFault heredan de la clase SOAPElement.

La clase SOAPElement contiene los métodos genéricos para manipular cualquier elemento del mensaje SOAP, y hereda de una clase más general que es Node, representando cualquier nodo del documento XML. Estas clases nos proporcionarán métodos para añadir nodos hijos a un nodo dado, eliminar nodos (detach), o bien consultar el contenido de estos nodos.

Podremos crear un nuevo elemento a partir de nuestro objeto sobre (SOAPEnvelope):

Name nombre = sobreSOAP.createName(nombre);
Name nombre = sobreSOAP.createName(nombre, prefijo, uri);

Donde nombre se refiere al nombre local de la etiqueta XML, prefijo al prefijo del espacio de nombres al que pertenece, y uri a la URI de este espacio de nombres. Con esto obtenemos un objeto Name que hace referencia al nombre (a la etiqueta XML) que vamos a añadir. Podremos añadir esta etiqueta como contenido de alguna de las partes que tenemos en nuestro mensaje:

Por ejemplo, podemos añadir un nuevo elemento al cuerpo del mensaje de la siguiente forma:

Name nombre = sobreSOAP.createName("ConsultaTemperatura", "dccia", 
                                   "http://www.dccia.ua.es");
SOAPBodyElement elementoSOAP = cuerpoSOAP.addBodyElement(nombre);

Dentro del elemento recién creado podremos seguir insertando elementos:

Name nombre = envelope.createName("area");
SOAPElement area = elementoSOAP.addChildElement(nombre);
area.addTextNode("Alicante");

Con esto habremos generado el siguiente mensaje SOAP:

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  <SOAP-ENV:Body>
    <dccia:ConsultaTemperatura xmlns:dccia="http://www.dccia.ua.es">
      <area>Alicante</area>
    </dccia:ConsultaTemperatura>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Igual que podemos añadir hijos a cada elemento, cuando recibamos la respuesta y queramos analizar el mensaje podremos consultar los hijos que tiene cada elemento y sus atributos.

2.1.3 Mensajes SOAP con anexos

Muchas veces podemos necesitar enviar en un mensaje SOAP datos que no son XML. Puesto que dentro del mensaje sólo podemos usar XML, para enviar estos datos tendremos que usar los anexos al mensaje.

Esta parte anexa puede contener datos de cualquier tipo, incluido XML, y se encapsula en la clase AttachmentPart. Podemos crear una parte anexa a partir de nuestro mensaje SOAP de la siguiente forma:

AttachmentPart anexoSOAP = mensajeSOAP.createAttachment();

Una vez creado este anexo, podremos añadir su contenido, especificando un objeto (Object) con el contenido, y una cadena con el tipo de contenido (Content-type). Por ejemplo, para añadir texto sin formato podemos hacer lo siguiente:

anexoSOAP.setContent("Este es el contenido del anexo SOAP", "text/plain");

Podremos establecer como contenido cualquier tipo de datos existente: "image/jpeg", "text/html", etc.

2.2 Envio de mensajes síncrono

Para intercambiar mensajes SOAP lo primero que deberemos hacer es obtener una conexión. Tenemos dos opciones para hacer esto:

Vamos a ver la utilización de una conexión punto-a-punto para intercambiar mensajes de forma síncrona. Para ello tendremos que obtener un objeto SOAPConnection, para lo que necesitaremos un objeto SOAPConnectionFactory:

SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
SOAPConnection con = scf.createConnection();

Una vez tenemos nuestra conexión abierta podemos hacer una petición a una determinada dirección. Se enviará como petición el mensaje SOAP que especifiquemos, y la llamada nos devolverá otro mensaje SOAP como respuesta.

URL endpoint = new URL("http://rvg.ua.es/endpoint");
SOAPMessage respuestaSOAP = con.call(mensajeSOAP, endpoint);

Debemos especificar la URL de la dirección a la que irá dirigido nuestro mensaje. Esta dirección a la que enviamos la petición de servicio es lo que se conoce como endpoint.

Podremos analizar el mensaje SOAP que nos ha devuelto como respuesta tal como vimos en el apartado anterior, accediendo a cada una de sus partes y dentro de ellas a sus nodos hijos.

Una vez hayamos terminado de hacer peticiones, deberemos cerrar la conexión SOAP con:

con.close();

Este tipo de clientes SOAP se denominan clientes standalone, ya que no necesitan ningún componente externo (proveedor de mensajes) para funcionar.

2.3 Envio de mensajes asíncrono

Vamos a ver cómo utilizar un proveedor de mensajes para enviar nuestros mensajes. A diferencia del método de conexión anterior, con el proveedor de mensajes podremos enviar mensajes de forma asíncrona, sin tener que esperar una respuesta por parte del servidor para continuar. Esta parte se incluye en la API JAXM.

Lo primero que debemos hacer es obtener un proveedor de mensajes, encapsulado en la clase ProviderConnection, para lo cual tenemos dos alternativas:

ProviderConnectionFactory pcf = ProviderConnectionFactory.newInstance();
ProviderConnection con = pcf.createConnection();
Context ctx = new InitialContext(); 
ProviderConnectionFactory pcf =
   (ProviderConnectionFactory)ctx.lookup("MiProveedor");
ProviderConnection pc = pcf.createConnection();

En este caso deberemos crear los mensajes a partir del proveedor de conexiones que vayamos a utilizar, en lugar de utilizar el MessageFactory por defecto como hicimos anteriormente.

Para crear este objeto MessageFactory debemos proporcionar el perfil que queramos utilizar para los mensajes. Un ejemplo de perfil puede ser "ebxml":

MessageFactory mf = pc.createMessageFactory("ebxml");

Antes de especificar un perfil, deberemos asegurarnos de que nuestro proveedor soporta dicho perfil. Podemos consultar todos los perfiles disponibles en nuestro proveedor de la siguiente forma:

ProviderMetaData metaData = pc.getMetaData();
String [] perfilesSoportados = metaData.getSupportedProfiles();

Una vez obtenido el MessageFactory, podremos construir nuestro mensaje y añadir a éste la información necesaria, tal como hemos visto anteriormente.

Una vez tengamos el mensaje listo para enviar, podremos hacerlo llamando al siguiente método:

pc.send(mensajeSOAP);

Podemos ver que no es necesario indicar el destinatario del mensaje, ya que será el proveedor de mensajes quien determine quien es dicho destinatario. Hemos de recalcar que en este caso sólo podremos enviar mensajes creados a partir del objeto proveedor de mensajes que estemos usando, ya que no funcionarán si se crean de otra forma.

Pero además de enviar mensajes, es posible que queramos recibir respuestas. Para hacer esto de forma asíncrona necesitaremos un listener. Dicho listener se encuentra en la interfaz OnewayListener que nos obliga a definir el siguiente método:

public void onMessage(SOAPMessage mensaje);

Que se invocará cuando recibamos un mensaje, proporcionando como parámetro el mensaje recibido. Para que el listener sea efectivo deberemos configurarlo en el despliegue de nuestra aplicación.

2.4 Crear un servidor SOAP

Hemos visto como crear clientes que envian mensajes SOAP, pero para que esta comunicación sea posible deberá haber un destinatario que reciba estos mensajes, un servidor que acepte los mensajes SOAP.

Podemos implementar este servidor mediante un servlet. Podemos encontrar una serie de clases útiles dentro de la API JAXM. El tipo de servlet que podremos utilizar como servidor SOAP se encuentra en la clase JAXMServlet, que nos proporciona facilidades para desarrollar nuestro servidor SOAP de forma sencilla. Este servlet deberá implementar una de las siguientes interfaces:

public void onMessage(SOAPMessage mensaje);

Que se invoca cuando nos llega un mensaje, proporcionándonos como parámetro dicho mensaje.

public SOAPMessage onMessage(SOAPMessage mensaje);

En este caso vemos que en el mismo método además debemos devolver otro mensaje como respuesta, ya que el cliente estará esperando una respuesta del servidor.

La clase JAXMServlet se encarga de gestionar la recepción del mensaje SOAP de entrada y el envío de nuestra respuesta internamente, por lo que nuestro único trabajo será implementar uno de estos dos métodos onMessage, según el modelo seguido.