JAX-WS: Un cliente y un servidor

(Imagen : Rembrandt - La ascención de Cristo y María Magdalena)

Es hora de hacer un ejemplo, que ya estábamos demasiado "académicos". Se tendrá una clase de implementación Hello, que estará anotada como endpoint de web service mediante @WebService. Hello declara un sólo método denominado sayHello, anotado con @WebMethod. @WebMethod expone al método anotado a los clientes del Web Service. El método sayHello devuelve un saludo a los clientes, usando el nombre pasado como parámetro para armar el saludo. La clase de implementación define un constructor por defecto público, que no recibe argumentos (parámetros).

package helloservice.endpoint;

import javax.jws.WebService;

@WebService
public class Hello{
private String message = new String("Hello, ");

public void Hello(){}

@WebMethod
public String sayHello(String name){
return message + name + ".";
}
}


Ahora veamos al cliente. HelloClient es un programa Java stand-alone que accede al método sayHello de HelloService. Hace esta llamada a través de un port, un objeto local que actúa como proxy del servicio remoto. Este port es creado en tiempo de desarrollo por la herramienta wsimport, que genera artefactos portables de JAX-WS en base a un archivo WSDL.

AL invocar métodos remotos en el port, el cliente realiza lo siguiente:

Se utiliza la anotación javax.xml.ws.WebServiceRef para declarar una referencia a un web service. @WebServiceRef utiliza el elemento wsdlLocaltion para especificar el URI del WSDL del servicio desplegado

@WebServiceRef(wsdlLocation="http://localhost:8080/helloservice/hello?wsdl")
static HelloService service;


Se obtiene un proxy para el servicio, tambien conocido como port, al invocar getHelloPort en el service.

Hello port = service.getHelloPort();


El port implementa la SEI definida por el servicio.

Se invoca al método sayHello del port, pasándole al servicio un name.

String response = port.sayHello(name);


Aquí tenemos todo el código de HelloClient:

package simpleclient;

import javax.xml.es.WebServiceRef;
import helloservice.endpoint.HelloService;
import helloservice.endpoitn.hello;

public class HelloClient{
@WebServiceRef=(wsdlLocation="http://localhost:8080/helloservice/hello?wsdl")
static HelloService service;

public static void main(String[] args){
try{
HelloClient client = new HelloClient();
client.doTest(args);
}catch(Exception e){
e.printStackTrace();
}

}

public void doTest(String[] args){
try{
Hello port = service.getHelloPort();
String name;
if (args.length > 0){
name = args[0];
}else{
name = "No Name";
}
String response = port.sayHello(name);
System.out.println(response);

}catch(Exception e){
e.printStackTrace();
}
}
}


Basado en la Guía SCDJWS de Mikalai Zaikin

@WebServiceProvider

(Imagen: Rembrandt - La partida del arcángel)

Un web service puede procesar mensajes de protocolo (por lo general SOAP) o payloads de un mensaje (para SOAP, los contenidos del elemento body de un mensaje SOAP). Un Web Service de esta naturaleza debe implementar la interfaz javax.xml.ws.Provider ser anotada con @WebServiceProvider. Veamos como se ve uno de estos Web Services:





package com.ivan;

@WebServiceProvider(
wsdlLocation="StringProcessorService.wsdl",
portName="StringProcessorServicePort",
serviceName="StringProcessorService",
targetNamespace="http://www.ivan.com/stringprocessor"
)
@ServiceMode (value=Service.Mode.PAYLOAD)
@SOAPBinding(
parameterStyle=SOAPBinding.ParameterStyle.WRAPPED,
style = SOAPBinding.Style.DOCUMENT,
use = SOAPBinding.Use.LITERAL
)

public class StringProcessor implements Provider{
public Source invoke (final Source inRequestMessage){
Source theResponseSource = null;
try{
JAXBContext theJaxbContext = JAXBContext.newInstance("com.ivan.beans");
Unmarshaller theUnmarshaller = theJaxbContext.createUnmarshaller();
Marshaller theMarshaller = theJaxbContent.createMarshaller();

JAXBElement theReqElem = (JAXBElement)theUnmarshaller.unmarshall(inRequestMessage);
ReverseStringReq theReq = theReqElem.getValue();

String theResultString = (new StringBuffer(theReq.getString())).reverse().toString();
ObjectFactory theObjFactory = new ObjectFactory();
ReverseStringResp theResponse = theObjFactory.createReverseStringResp();
theResponse.setReturn(theResultString);

theResponseSource = new JAXBSource(theJaxbContext, theResponse),
}catch (JAXBException theException){
theException.printStackTrace();
}
return theResponseSource;
}
}

Antes de empezar esto, es necesario decir que contábamos con el Schema Definition (StringProcessorService_payloads.xsd) y el documento WSDL (StringProcessorService.wsdl). Con estos archivos y la herramienta XJC hemos creado las siguientes clases Java, necesarias para el trabajo con JAXB:

  • ObjectFactory.java
  • package-info.java
  • ReverseStringReq.java
  • ReverseStringResp.java

Habiendo dicho esto, proseguimos.

La anotación @WebServiceProvider se utiliza cuando la clase service endpoint implementa la interfaz Provider. Como se ve, la clase de arriba no contiene métodos a ser expuestos como operaciones de Web Service, sino sólamente el método invoke. El método invoke será "invocado" cada vez que el web service reciba un request y el mensaje de protocolo - o el payload del mensaje- serán enviados como parámetros al método invoke.

La anotación @ServiceMode nos indica si la clase provider recibirá y producirá mensajes de protocolo completos o sólamente los payloads. En el ejemplo, se opta por lo segundo.

La anotación @SOAPBinding nos dice lo siguiente:

  • Messaging style: style = SOAPBinding.Style.DOCUMENT
  • Encoding: use = SOAPBinding.Use.LITERAL
  • ParameterStyle: parameterStyle = SOAPBinding.ParameterStyle.WRAPPED

Los valores de arriba son los valores por defecto, así que podríamos haber omitido la anotación.

Al declarar la clase provider:

public class StringProcessor implements Provider

Las clases Service Endpoint deben implementar la interfaz javax.xml.ws.Provider, para permitirles el recibir y procesar mensajes de protocolo o payloads de mensaje. La especificación JAX-WS da soporte a los siguientes providers:

  • Provider En el modo Payload de mensaje. La interfaz Source es implementada por las siguientes clases: DOMSource, JAXBSource, SAXSource, StaxSource, StreamSource
  • Provider En modo mensaje
  • Provider

En lo referente a la clase Provider, se establecen también las siguientes restricciones:

  • Las clases Provider deben implementar el constructor por defecto, o no implementar ninguno.
  • Una clase provider debe estar ligada a un tipo específico. No se puede implementar Provider
  • Una clase provider debe estar anotada con @WebServiceProvider

Un ejemplo: Calculadora Web Service

(Imagen: Rembrandt - Festín de Belshassar)

En este ejemplito vamos a utilizar la herramienta wsgen para generar los artefectos del web service antes del despliegue. Este servicio tendrá una Service Endpoint Interface (SEI) que contendrá los métodos que la Service Implementation Bean expondrá como operaciones de Web Service. Si es que no generamos la SEI explícitamente, el runtime de JAX-WS se encargará de hacerlo.

Service Implementation Bean

Esta clase contendrá sólamente dos métodos: un método de inicialización y un método que será expuesto como Web Service. Dado que vamos a generar el documento WSDL y las clases wrapper de request y response con la herramienta wsgen, hemos hecho uso extensivo de anotaciones. Así:

package com.ivan;

@WebService(
name="Calculator",
serviceName="CalculatorService",
targetNamespace="http://www.ivan.com/calculator",
wsdlLocation="CalculatorService.wsdl"
)
@SOAPBinding(
parameterStyle=SOAPBinding.ParameterStyle.WRAPPED,
style=SOAPBinding.Style.DOCUMENT,
use=SOAPBinding.Use.Literal
)

public class Calculator{
@Resource
private WebServiceContext mWSContext;
@PostConstruct
@WebMethod (exclude=true)
public void init(){
System.out.println("Web Service initialized, service context: " + mWSContext);
}
@WebMethod(operationName="addNumber", action="urn:Add")
@ResponseWrapper(
className = "com.ivan.javax.AddResponse",
localName = "addNumbersResponse",
targetNamespace = "http://www.ivan.com/calculator"
)
@RequestWrapper(
className = "com.ivan.javax.Add",
localName = "addNumbers",
targetNamespace = "http://www.ivan.com/calculator"
)
public int add (final int intNumber1, final int intNumber2){
System.out.println("Adding "+ intNumber1 + " and " + intNumber2);
return intNumber1 + intNumber2;
}
}

La anotación @WebService le indica a JAX-WS que esta clase contiene métodos que van a ser expuestos como Web Services. Además, contiene los siguientes elementos opcionales:

  • name="Calculator"
Nombre del Web Service. En WSDL 1.1, esto se traduciría en el nombre del elemento <wsdl:portType> : <portType name= "Calculator">

  • serviceName="CalculatorService"
Nombre del Servicio para el Web Service. En WSDL 1.1, esto se traduciría en el elemento <wsdl:service>: <service name= "CalculatorService">

  • targetNamespace = "http://www.ivan.com/calculator"
Especifica el namespace para <wsdl:portType> y/o <wsdl:service> en el documento WSDL asociado.

  • wsdlLocation="CalculatorService.wsdl"
Especifica la ubicación del documento WSDL que describe al Web Service

La anotación @SOAPBinding especifica lo siguiente:

Messaging Style: style= SOAPBinding.Style.DOCUMENT
Encoding: use=SOAPBinding.Use.LITERAL
Parameter Style: parameterStyle=SOAPBinding.ParameterStyle.WRAPPED

Los valores de arriba son los valores por defecto para la anotación SOAPBinding. Nótese que el parameter Style Wrapped es necesario dado que hay más de un parámetro en el método a exponer como Web Service.

También, tenemos una variable de instancia anotada con @Resource:

@Resource private WebServiceContext mWSContext;


Que le permite a la clase del Web Service acceder al contexto del Web Service.

El primer métodos de la clase es init():

@PostConstruct
@WebMethod(exclude=true)
public void init()

La anotación @PostConstruct marca al método para que sea invocado después de realizarse la inyección de dependencia en la clase de WS, pero antes que WS entre en uso.

La anotación @WebMethod se utiliza para excluir al método init() de ser expuesto como operación de Web Service.

Finalmente, tenemos al método add(), que queremos exponer como Web Service:

@WebMethod(operationName="addNumber", action="urn:Add")
@ResponseWrapper(
className = "com.ivan.javax.AddResponse",
localName = "addNumbersResponse",
targetNamespace = "http://www.ivan.com/calculator"
)
@RequestWrapper(
className = "com.ivan.javax.Add",
localName = "addNumbers",
targetNamespace = "http://www.ivan.com/calculator"
)
public int add (final int intNumber1, final int intNumber2){


La anotación @WebMethod se utilza ahora para marcar un método para ser expuesto como operación de Web Service. Aquí se especifica:

  • operationName="addNumbers"
En WSDL 1.1, se traduce en el atributo name del elemento <wsdl:operation> en <wsdl:portType>: <operation name="addNumbers">

  • action="urn:Add"
Especifica la SOAPAction para la operación: <soap:operation soapAction="urn:Add">

Las anotaciones @ResponseWrapper y @RequestWrapper especifican lo siguiente para el request y response de la operación:

  • className = "com.ivan.javax.AddResponse"
El nombre de la clase Java que implementa el bean JAXB que será usado para almacenar la data del request/response.

  • localName= "addNumbersResponse"
El nombre del esquema XML en donde el request/response será "wrapped". Así: <xs:element name="addNumbersResponse" type="tns:addNumbersResponse">

  • targetNamespace="http://www.ivan.com/calculator"
El nombre del esquema XML en el cuál se encuentra el elemento wrapper del request/response.

Esto ya se hizo largo. En otro post la seguimos.



Estilos de documento: wrapped y unwrapped


(Imagen: Rembrandt - El cegamiento de Sansón)

El estilo document es valor defecto según WS-Basic Profile. Este estilo nos permite, a través de XSD en la sección types del WSDL, definir los tipos de datos para los mensajes SOAP.

Al usar la convención wrapped, se le da a los servicios con estilo document el look and feel de un servicio de estilo rpc. Primero, veamos un mensaje SOAP unwrapped:

<?xml version="1.0" ?>
<!-- Unwrapped document style -->
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Body>
<num1 xmlns:ans="http://ts.ch01/">27</num1>
<num2 xmlns:ans="http://ts.ch01/">94</num1>
</soapenv:Body>
</soapenv:Envelope>


Y ahora, un mensaje wrapped:

<?xml version="1.0" ?>
<!-- Wrapped document style -->
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Body>
<addNums xmlns:ans="http://ts.ch01/">
<num1>27</num1>
<num2>94</num1>
</addNums>
</soapenv:Body>
</soapenv:Envelope>


El body del SOAP envelope de request unwrapped tiene dos elementos: num1 y num2. Estos números deben sumarse. El cuerpo del SOAP no contiene el nombre de la operación del servicio que va a realizar la suma y enviar el resultado como respuesta. Por otro lado, el body del SOAP Envelope de request wrapped tiene un sólo elemento: addNums (que se corresponde con la operación a invocar) y dos subelementos, cada uno conteniendo el número a sumar. La versión wrapped hace la operación de servicio explícita. Los argumentos de la operación estñan anidados, como sub-elementos dentro del elemento de la operación addNums.

Para implementar la convención wrapped, para el estilo document, hacer lo siguiente:

El cuerpo del SOAP Envelope debe tener sólamente una parte, o sea, un sólo elemento XML con la cantidad de sub-elementos requerida

La relación entre el XSD del WSDL y el elemento XML del cuerpo SOAP está definida

Los elementos del XSD nos sirven como wrappers del cuerpo del mensaje SOAP. Así:

<?xml version="1.0" ?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.or g/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soapenv:Body>
<ans:getTimeAsElapsedResponse xmlns:ans="http://ts.ch01/">
<return>1205030105192</return>
</ans:getTimeAsElapsedResponse>
</soapenv:Body>
</soapenv:Envelope>


El wrapper del request tiene el mismo nombre que la operación del servicio, cuyos mensajes tienen asignado un tipo. Así, la sección portType sería:

<portType name="TimeServer">
<operation name="getTimeAsString">
<input message="tns:getTimeAsString"></input>
<output message="tns:getTimeAsStringResponse"></output>
</operation>
<operation name="getTimeAsElapsed">
<input message="tns:getTimeAsElapsed"></input>
<output message="tns:getTimeAsElapsedResponse"></output>
</operation>
</portType>


Basado en el capítulo 2 de Java Web Services: Up and Running de Martin Kalin

WSDL Bindings


(Imagen: Rembrandt- Saskia, como Flora)

En la sección bindings del WSDL, el atributo style puede tomar los valores de rpc y document, donde document es el valor por defecto. El atributo use puede tomar los valores de literal y encoded, donde literal es el valor por defecto. Entonces, las combinaciones posibles de style y use serían:



De todas las combinaciones listadas, las más frecuentes de Web Services basados en SOAP son document/literal y rpc/literal. El valor encoded del atributo use es válido en un documento WSDL, pero no se ajusta a las directivas de WS-I (Web Services- Interoperability).

Servicios document-style

El estilo document indica que los mensajes de nuestro Web Service basado en SOAP contienen documentos XML completos. Por otro lado, el estilo rpc indica que los mensajes SOAP contienen parametros en los mensajes request y valores de retorno en mensajes response. Veamos un WSDL con estilo rpc:

<types></types>
<message name="getTimeAsString"></message>
<message name="getTimeAsStringResponse">
<part name="time_response" type="xsd:string"></part>
</message>
<message name="getTimeAsElapsed"></message>
<message name="getTimeAsElapsedResponse">
<part name="time_response" type="xsd:long"></part>
</message>


La sección types está vacía porque el servicio utiliza, como valores retornados, sólamente los tipos simples xsd:string y xsd:long. Los tipos simples no requieren una definición en la sección types del WSDL. También, los atributos name de los mensajes muestran la relación con los métodos Java (@WebMethods) correspondientes (getTimeAsString y getTimeAsElapsed). Los mensajes response tienen un sub-elemento part, que contiene el tipo de dato del valor devuelto. Como los mensajes request no requieren argumentos (en este caso), los mensajes request no necesitan sub-elementos part.

Ahora, el WSDL de TimeServer, pero usando como estilo document:

<types>
<xsd:schema>
<xsd:import schemaLocation="http://localhost:9876/ts?xsd=1"
namespace="http://ts.ch02/">
</xsd:import>
</xsd:schema>
</types>
<message name="getTimeAsString">
<part element="tns:getTimeAsString" name="parameters"></part>
</message>
<message name="getTimeAsStringResponse">
<part element="tns:getTimeAsStringResponse" name="parameters"></part>
</message>
<message name="getTimeAsElapsed">
<part element="tns:getTimeAsElapsed" name="parameters"></part>
</message>
<message name="getTimeAsElapsedResponse">
<part element="tns:getTimeAsElapsedResponse" name="parameters"></part>
</message>


Ahora types contiene una directiva import para el documento XSD asociado. Dicho documento es:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:tns="http://ts.ch02/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://ts.ch02/" version="1.0">
<xs:element name="getTimeAsElapsed"
type="tns:getTimeAsElapsed">
</xs:element>
<xs:element name="getTimeAsElapsedResponse"
type="tns:getTimeAsElapsedResponse">
</xs:element>
<xs:element name="getTimeAsString"
type="tns:getTimeAsString">
</xs:element>
<xs:element name="getTimeAsStringResponse"
type="tns:getTimeAsStringResponse">
</xs:element>
<xs:complexType name="getTimeAsString"></xs:complexType>
<xs:complexType name="getTimeAsStringResponse">
<xs:sequence>
<xs:element name="return"
type="xs:string" minOccurs="0">
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="getTimeAsElapsed"></xs:complexType>
<xs:complexType name="getTimeAsElapsedResponse">
<xs:sequence>
<xs:element name="return" type="xs:long"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>


El documento XSD define cuatro tipos complejos cuyos nombres son los nombres de los mensajes correspondientes en la seccción messages. En el estilo rpc, los mensajes llevan los nombresde los @WebMethods; mientras que con el estilo document los mensajes llevan los nombres de los tipos XSD definidos en la sección types del WSDL.

El estilo document soporta servicios con tipos de datos definidos explícitamente, dado que el WSDL del servicio puede hacerlo mediante un documento XSD. Desde una perspectiva de arquitectura, el estilo document es más simple dado que el cuerpo del mensaje SOAP es un documento autocontenido y preciso. El estilo rpc requiere mensajes con los nombres de las operaciones asociadas y con parámetros como sub-elementos.

Ahora, el atributo use determina como los tipos de datos del servicio van a ser codificados y decodificados. En el servicio, el documento WSDL tiene que especificar como los tipos de datos usados en el lenguaje de implementación (Java) son serializados a tipos soportados por WSDL (tipos de esquemas XML). Por el lado del cliente, los tipos soportados por WSDL deben ser deserializados en tipos del lenguaje del cliente (Ruby). La configuración use='literal'implica que los tipos del servicio siguen el esquema XML del documento WSDL "literalmente". Por otro lado, la configuración use='encoded' significa que los definiciones de tipos del servicio provienen de reglas de codificación, por lo general reglas de codificación de la especificación SOAP 1.1.

Basado en el capítulo 2 de Java Web Services: Up and Running de Martin Kalin

Estructura WSDL

(Imagen: Rembrandt - Artemis)

Un documento WSDL es un contrato entre un servicio y sus consumidores. Este contrato establece els ervice endpoint, las operaciones del servicio, y los tipos de datos requeridos para estas operaciones. El elemento más externo de un WSDL se llama definitions, dado que nos provee definiciones agrupadas en las siguientes secciones:

  • La sección types (opcional), nos provee definiciones de tipos de datos sobre un sistema de tipos de datos, como un esquema XML. un documento que define tipos de datos es un XSD (XML Schema Definition). La sección de tipos mantiene referencias a XSD's. Si esta sección está vacía, sólamente se utilizan tipos de datos simples, como xsd:string y xsd:long.

  • La sección message define los mensajes que implementan el servicio. Los mensajes son construidos en base a los tipos de datos definidos en la sección anterior. El orden de los mensajes indica el patrón de servicio.Los mensajes in son los que llegan al servicios, mientras que los out son enviados desde el servicio. Si el orden de los mensajes es in/out, entonces el patrón es request/response, mientras que si el orden es out/in el patrón es solicit/response.

  • La sección portType nos presenta el servicio como un grupo de operaciones, donde cada operación tiene uno o más mensajes. Las operaciones son nombradas de acuerdo a los métodos anotados con @WebMethod.

  • La sección binding es donde las definiciones WSDL se concretizan. Un binding WSDL es una especie de "implementación" de un portType WSDL, y provee detalles concretos del servicio como:

El protocolo de transportea usar al enviar y recibir los mensajes SOAP. Se puede usar HTTP o SMTP como protocolo de capa de aplicación. Por ejemplo:



El valor del atributo transporte señala que los mensajes SOAP del servicio van a ser enviados y recibidos sobre HTTP.

El estilo del servicio, puede tomar los valores de rpc o document. El estilo document es el valor por defecto. La anotación:

@SOAPBinding(style= Style.RPC)

Configura el atributo style para que tenga el valor de rpc, el documento WSDL generado.

  • La sección service especifica uno o más endpoints donde la funcionalidad del servicio -el total de sus operaciones- estará disponible. En términos técnicos, la sección service lista uno o más elementos port, donde un port consiste en un portType jutno a su binding correspondiente


Basado en el capítulo 2 de Java Web Services: Up and Running de Martin Kalin

Algo más elaborado


(Imagen: Rembrandt - Descenso de la cruz)

Veamos el siguiente servicio:

package ch01.team;

import java.util.List;
import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService
public class Teams{
private TeamsUtility utils;

public Teams(){
utils = new TeamsUtility();
utils.make_test_teams();
}

@WebMethod
public Team getTeam(String name){
return utils.getTeam(name);
}

@WebMethod
public List getTeams(){
return utils.getTeams();
}
}


El servicio Teams ha sido implementado en una sóla clase Java, en vez de usar una SEI y un SIB. La operación getTeam recibe un parámetro y retorna una instancia de Team, que es un tipo definido por el programador, que ha su vez es una lista de instancias de Player (otro tipo definido por el programador). Por otro lado, la operación getTeams nos devuelve List, o sea una colección Java.

En un post anterior, se vio que la SEI para el servicio TimeServer contenía la siguiente anotación:

@SOAPBinding(style = Style.RPC)

Esta anotación requiere que el servicio utilice sólamente tipos simples, como String e Integer. Sin embargo, el servicio Teams utiliza tipos más complejos, por lo que el estilo a utilizar es Style.DOCUMENT (el valor por defecto). Este estilo requiere más trabajo de configuración.

Para desplegar el Web Service es necesario compilar a todas las clases involucradas: TeamService, Team, Player, TeamsUtility y TeamsPublisher, cuyo código es:


package ch01.team;

import javax.xml.ws.Endpoint;

class TeamPublisher{
public static void main(String[] args){
int port= 8888;
String url = "http://localhost:"+ port+ "/teams";
System.out.println("Publicando teams en el puerto: " + port);
Endpoint.publish(url, new Teams());
}
}


Ahora necesitaremos la ayudita de wsgen, que está incluido en core Java 6. wsgen generará varios artefactos, entre los que están las clases Java que necesita Endpoint.publish para generar el WSDL.

Ahora si podemos ejecutar TeamPublisher y tener nuestro WS operativo. Para generar el cliente podemos recurrir a wsimport (también en core Java 6), con lo que tendríamos algo como esto:


import teamsC.TeamsService;
import teamC.Teams;
import teamsC.Team;
import teamsC.Player;
import java.util.List;

class TeamClient{
public static void main(String[] args){
TeamsService service = new TeamsService();
Teams port = service.getTeamsPort();
List teams = port.getTeams();
for (Team team: teams){
//...
}
}
}


Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

Java SOAP API

(Imagen: Rembrandt - Abraham e Isaac)

Puede sernos útil en algún momento el generar y procesar mensajes SOAP. Veamos un poco del SOAP API, mediante una aplicación que simula enviar un mensaje SOAP como request y recibir otro como response:

package ch01.soap;

import java.util.Date;
import java.util.Iterator;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.Node;
import javax.xml.soap.Name;

public class DemoSoap{
private static final String LocalName = "TimeRequest";
private static final String Namespace = "http://ch01/mysoap/";
private static final String NamespacePrefix = "ms";

private ByteArrayOutputStream out;
private ByteArrayInputStream in;

private static void main(){
new DemoSoap().request();
}

private void request(){
try{
SOAPMessage msg = create_soap_message();
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
Name lookup_name = create_qname(msg);
hdr.addHeaderElement(lookup_name).addTextNode("time_request");
out = new ByteArrayOutputStream();
msg.writeTo(out);
trace("The sent SOAP message: ", msg);
SOAPMessage response = process_request();
extract_contents_and_print(response);

}
catch (SOAPException e){System.err.println(e);}
catch (IOException e){System.err.println(e);}
}

private SOAPMessage create_soap_message(){
SOAPMessage msg = null;
try{
MessageFactory mf = MessageFactory.newInstance();
msg = mf.createMessage();
}
catch (SOAPException e){System.err.println(e);}
return msg;

}

private SOAPMessage create_soap_message(InputStrean in){
SOAPMessage msg = null;
try{
MessageFactory mf = MessageFactory.newInstance();
msg = mf.createMessage(null, in);

}
catch (SOAPException e){System.err.println(e);}
catch (IOException e){System.err.println(e);}
return msg;

}

private Name create_qname (SOAPMessage msg){
Name name = null;
try{
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
name = env.createName(LocalName, NamespacePrefix, Namespace);
}
catch (SOAPException e){System.err.println(e);}
return name;

}

private void trace(String s, SOAPMessage m){
System.out.println("\n");
System.out.println(s);
try{
m.writeTo(System.out);
}
catch (SOAPException e){System.err.println(e);}
catch (IOException e){System.err.println(e);}
}

private SOAPMessage process_request(){
process_incoming_soap();
coordinate_streams();
return create_soap_message(in);
}

private void process_incoming_soap(){
try{
coordinate_streams();
SOAPMessage msg = create_soap_message(in);
Name lookup_name = create_qname(msg);
SOAPHeader = msg.getSOAPHeader();
Iterator it = header.getChlidElements(lookup_name);
Node next = (Node)it.next();
String value = (next==null)?"Error!": next.getValue();
if (value.toLowerCase().contains("time_request")){
String now = new Date().toString();
SOAPBody body = msg.getSOAPBody();
body.addBodyElement(lookup_name).addTextNode(now);
msg.saveChanges();
msg.writeTo(out);
trace("The received/processed SOAP message: ", msg);
}
}
catch (SOAPException e){System.err.println(e);}
catch (IOException e){System.err.println(e);}

}

private void coordinate_streams(){
in = new ByteArrayInputStream(out.toByteArray());
out.reset();
}

private void extract_contents_and_print(SOAPMessage msg){
try{
SOAPBody body = msg.getSOAPBody();
Name lookup_name = create_qname(name);
Iterator it = body.getChildElements (lookup_name);
Node next = (Node) it.next();
String value = (next==null)? "Error!": next.getValue();
System.out.println("\n\nReturned from server: " + value);
}
catch (SOAPException e){System.err.println(e);}
}
}

Entonces, esta sencillísima y breve aplicación genera un mensaje SOAP y añade la cadena time_request al header del SOAP Envelope. Eso lo hace en las líneas:


SOAPMessage msg = create_soap_message();
SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
Name lookup_name = create_qname(msg);
hdr.addHeaderElement(lookup_name).addTextNode("time_request");
Existen dos maneras de crear un mensaje SOAP. La primera:



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


Y la segunda:


SOAPMessage msg = mf.createMessage(mime_headers, input_stream);

En la que el primer argumento es una colección de headers de la capa de transporte (como los headers HTTP) y el segundo es un inputStream con los bytes para crear el mensaje. Una vez que el mensaje es creado, se extraen las cabeceras del SOAP Envelope y se inserta un nodo de texto XML con el valor de time_request. El mensaje SOAP resultante sería:


<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<ms:TimeRequest xmlns:ms="http://ch01/mysoap/">
time_request
</ms:TimeRequest>
</SOAP-ENV:Header>
<SOAP-ENV:Body/>
</SOAP-ENV:Envelope>


El cuerpo del mensaje (SOAP body) siempre es obligatorio, pero puede estar vacío. La SOAP header es opcional, pero en este caso contiene el texto time_request.

EL método request envía el mensaje SOAP a través de un ByteArrayOutputStream, para simular el enviarlo a través de la red a otro host. El método request invoca al método process_request, que a su vez delega el procesamiento a otros métodos. En fin, el mensaje SOAP es creado de un ByteArrayInputStream,dado que este stream contiene el mensaje SOAP. Así:



SOAPMessage msg = null;
try {
MessageFactory mf = MessageFactory.newInstance();
msg = mf.createMessage(null, in);
}


Y luego procesamos este mensaje SOAP para extraer la cadena time_request. La extracción se hace así: se extrae la SOAP Header del mensaje SOAP y se itera sobre los elementos con el nombre de tag:

Una vez encontrado, se busca que contenga la cadena time_request con la siguiente lógica:


SOAPHeader header = msg.getSOAPHeader();
Iterator it = header.getChildElements(lookup_name);
Node next = (Node) it.next();
String value = (next == null) ? "Error!" : next.getValue();



Si la SOAP header contiene la cadena solicitada, se extrae el SOAP Body del mensaje SOAP recibido, y se le añade un elemento conteniendo la hora actual. El mensaje SOAP modificado es enviado como respuesta. Así:


if (value.toLowerCase().contains("time_request")) {
String now = new Date().toString();
SOAPBody body = msg.getSOAPBody();
body.addBodyElement(lookup_name).addTextNode(now);
msg.saveChanges();

msg.writeTo(out);
trace("The received/processed SOAP message:", msg);
}


El mensaje SOAP enviado sería el siguiente:


<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<ms:TimeRequest xmlns:ms="http://ch01/mysoap/">
time_request
</ms:TimeRequest>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ms:TimeRequest xmlns:ms="http://ch01/mysoap/">
Mon Oct 27 14:45:53 CDT 2008
</ms:TimeRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

Un cliente


(Imagen: Rembrandt - El filósofo)

Veamos como hacer un cliente para el Web Service que hemos creado, en nuestro lenguaje favorito:

package ch01.ts;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.net.URL;


class TimeClient{
URL url = new URL("http://localhost:9876/ts?wsdl");
QName qname = new QName ("http://ts.ch01", "TimeServerImplService");
Service service = Service.create(url, qname);
TimeServer eif = service.getPort(TimeServer.class); System.out.println(eif.getTimeAsString()); System.out.println(eif.getTimeAsElapsed());
}

A ver. Se observa que hemos instanciado la clase QName como nombre calificado XML del servicio. El primer argumento es el URI del servicio, y el segundo es el nombre del servicio publicado en el WSDL. Una vez creado el URL (apuntando a la ruta del WSDL) y el QName, se invoca al método Service.create, para crear una factory para el servicio.

Luego se invoca al método getPort del service creado, que extrae una endpoint interface ("port" del servicio). En el documento WSDL, la sección portType describe las operaciones a incluir en el Web Service. El método getPort devuelve una referencia a un objeto Java que pueda invocar a las operaciones de este porType. La referencia a port es del tipo TimerService, que es la SEI del Web Service.

Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

El SOAP que no vemos

En los Web Services basados en SOAP, un cliente realiza una llamada remota a un procedimiento del servicio invocando a una de las operaciones del Web Service. Para realizar esto se realiza un intercambio de mensajes request y response, que en este caso son mensajes SOAP. Imaginémonos un cliente PERL, que genera un request HTTP para invocar a un Web Service. El cuerpo de este request es un mensaje SOAP. Veámoslo:



En start line HTTP se observa el método request (POST). Se utiliza POST en lugar de GET, dado que los request POST poseen un cuerpo, que encapsula nuestro mensaje SOAP. Después de esto viene el URL del request, seguido de la versión de HTTP (1.1)

A continuación, vienen los headers HTTP, que son pares código/valor, separados por dos puntos. El código Accept se ve tres veces, especificando que el cliente puede aceptar una respuesta XML, una respuesta con adjuntos de cualquier tipo y un documento SOAP. El código SOAPAction siempre esta presente en los headers HTTP de request a Web Services.

Tenemos dos saltos de línea, que separan a los headers HTTP del cuerpo del HTTP. Aquí, el cuerpo HTTP contiene el documento SOAP (o SOAP envelope). El elemento más externo del documento se denomina Envelope, y dentro del Envelope se encuentra el SOAP body que contiene un sólo elemento, cuyo nombre local es getTimeAsString, que se corresponde con el nombre de la operación a invocar en el Web Service.

Por el lado del Web Service, las librerías Java procesan el request HTTP, entraen el SOAP Envelope y determinan que operación del servicio invocar. Se llama al método Java correspondiente (getTimeAsString) y se genera un mensaje SOAP para llevar el resultado del método al cliente. El response HTTP del WS sería así:



Una vez más, el SOAP Envelope es el cuerpo del mensaje HTTP. El start line contiene el código de estatus (200) y el texto correspondiente (OK), indicándonos que se procesó bien la solicitud del cliente. El SOAP Envelope contiene la hora actual entre las tags XML return. La librería SOAP del Perl extrae el SOAP Envelope del response HTTP y, según lo indicado en el documento WSDL, extrae el resultado de la operación del contenido del elemento return.

Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

Web Services: Primeros Pasos


(Imagen: Rembrandt - Artista en su estudio)

Para compilar y desplegar Web Services con Java, lo único que necesitamos es descargar la Java Stantard Edition 6 (o superior). Java 6 soporta JAX-WS (Java API for XML Web Services), la cúal a su vez soporta servicios basados en SOAP y de estilo REST.

Un Web Service basado en SOAP puede implementarse en una clase Java, pero debería existir una interfaz que declare los métodos (operaciones del Web Service) y una implementación que defina estos métodos. A la interfaz la llamaremos SEI (Service Endpoint Interf ace), y a la implementación la llamaremos SIB (Service Implementation Bean). El SIB puede ser un POJO, o un EJB.

Ahora, vemos como es una SEI:

package ch01.ts;
import javax.jws.webService;
import javax.jws.WebMethod;

import javax.jws.soap.SOAPBinding;

import javax.jws.soap.SOAPBinding.Style;


@WebService
@SOAPBinding (style= Style.RPC)

public interface TimeServer{
@WebMethod String getTimeAsString();
@WebMethod long getTimeAsElapsed();
}


Aquí, la anotación @WebService nos indica que se trata de una SEI. La anotación @WebMethod nos señala que cada método es una operación del Web Service. La anotación @SOAPBinding tiene impacto sobre la construcción del contrato del servicio: el documento WSDL (Web Services Definition Language). Style.RPC simplifica este contrato, y con ello el despliegue. Ahora, conozcamos al SIB:

package ch01.ts;

import java.util.Date;

import javax.jws.WebService;


@WebService(endpointInterface= "ch01.ts.TimeServer")
public class TimeServerImpl implements TimeServer{
public String getTimeAsString{ return new Date().toString();}
public long getTimeAsElapsed() {return new Date().getTime();}

}

La propiedad endpointInterface de la anotación @WebService enlaza el SIB (TimeServerImpl ) junto a la SEI correspondiente (TimeServer). Nótese que las implementaciones de los métodos no están anotadas como @WebMethods.

Para desplegar la aplicación, compilamos los archivos Java; y como se trata de un ejemplito inofensivo usamos una clase Java para su publicación (en la vida real se utilizaría un servidor de aplicaciones):

package ch01.ts;
import javax.xml.ws.Endpoint;
public class TimeServerPublisher{
public static voiud main(String [] args){ Endpoint.publish("http://127.0.0.1:9876/ts", new TimeServerImpl()); }
}


Con esta clasecita, publicamos el Web Service cuyo SIB es TimeServerImpl en la dirección 127.0.0.1 (localhost) y el puerto 9876, con ruta de publicación /ts. Utilizamos a la clase Endpoint y a su método publish, donde el primer argumento es el URL de publicación y el segundo una instancia del SIB del servicio. Para el ver el contrato del servicio (documento WSDL), bastaría compilar y ejecutar esta clase, y luego acceder al este URL : http://127.0.0.1:9876/ts?wsdl mediante un browser. Si todo salió bien, esto sería una parte de lo obtenido:



La sección portType de este documento WSDL (en negrita) agrupa las operaciones ofrecidas por el Web Service (getTimeAsString y getTimeAsElapsed), que se corresponden con los métodos declarados en el SEI e implementados en el SIB. El portType de un WSDL es como una interfaz Java, ya que nos muestra las operaciones del servicio pero no da detalles de la implementación. Cada operación del Web Service consiste en un mensaje input y un mensaje output. En tiempo de ejecución, cada mensaje es un documento SOAP. La otra sección en negrita es service, donde se observa que el atributo location es la URL de publicación. Este URL se denomina service endpoint, e informa a los clientes sobre donde pueden acceder al servicio.

El documento WSDL es útil para crear y ejecutar clientes para un web service. Para generar un cliente en base al WSDL , se puede usar la herramienta wsimport.

Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

Web Services: Fundamentos


(Imagen: Rembrandt - La ronda de noche)

Creo que en los anteriores posts me fui de boca y le puse mucho énfasis a los estándares y a los acrónimos (bueno, en parte era culpa de lo que estaba leyendo xD). En fin, pienso subsanar el error, y cambiar de libro xD. Así que comencemos de nuevo con lo de los Web Services.

Un Web Service es una especie particular de aplicación Web, o sea , que se ejecuta sobre HTTP. Es una aplicación distribuida cuyos componentes pueden ser desplegados y ejecutados en muchos dispositivos.

Existen dos tipos de Web Services: Basados en SOAP y de estilo REST. SOAP significa Simple Object Access Protocol, y es un dialecto XML donde los documentos son los mensajes a transmitir. En un escenario típico, la librería SOAP del cliente envía un mensaje SOAP solicitando un servicio, y la librería SOAP del Web Service envía otro mensaje SOAP como respuesta del servicio. Así:



Por otro lado, REST significa Representational State Transfer. SOAP posee estándares, herramientas y librerías. REST no tiene estándares, pocas herramientas y aún menos librerías. Por eso, se considera a REST como una alternativa simple a la complejidad de SOAP.

Por lo general, un cliente de un Web Service (basado en SOAP o de estilo REST) no es un browser, sino una aplicación sin interfaz de usuario. Este cliente puede estar escrito en cualquier lenguaje, y el Web Service y el cliente no deben estar escritos en el mismo lenguaje necesariamente.

Las tecnologías XML son las que dan soporte a esta interoperabilidad, y permiten el intercambio y procesamiento de documentos estructurados. Así, para un Web Service basado en SOAP, el cliente envía un documento SOAP como request al Web Service, el cual devuelve otro documento SOAP como response. Para servicios de estilo REST, el cliente envía un request HTTP estándar al web Service, y recibe un documento XML como respuesta.

Algunas características de los Web Services:

  • Infraestructura abierta: Los WS se despliegan usando estándares de la industria, y protocolos independientes del vendedor como HTTP y XML.
  • Transparencia de lenguajes: Los Web Services y sus clientes puede interactuar aún si han sido escritos en lenguajes diferentes.
  • Diseño modular: Nuevos servicios pueden ser generados a través de la integración e interacción de servicios existentes.
Basado en el capítulo 1 de Java Web Services: Up and Running de Martin Kalin

WSEE 1.2: Vista Rápida

(Imagen: Rembrandt - Autorretrato)

WSEE 1.2 define una arquitectura y empaquetamiento que asegure la portabilidad de los Web Services a través de servidores de aplicaciones Java EE.

Port Component
Un port component es lo que se empaqueta y despliega en el contenedor para implementar un Web Service.

Un port component define los artefactos que constituyen una aplicación Web Service portable, incluyendo a los Service Implementation Beans (SIB). El SIB es el único artefacto obligatorio . Opcionalmente, se puede incluir dentro del port component: documento WSDL, SEI (Service Endpoint Interface) y el descriptor webservices.xml.

Servlet Endpoints
Según WSEE 1.2, un POJo, siempre y cuando cumpla con los requisitos de WS-Metadata para un SIB, puede ser usado para implementar un Web Service a ser desplegado en un contenedor Web. A este POJO, se le conoce también como servlet endpoint.

EJB Endpoint
Según WSEE 1.2, un stateless session bean puede ser usado para implementar un Web Service a ser desplegado en un contenedor EJB. A este EJB lo llamamos EJB endpoint.

Empaquetamiento simplificado
Para muchos escenarios, no es necesario el uso de descriptores de despliegue.

Modelo de Programación para Handlers
Las anotaciones para Handlers son definidas en WS-Metadata 2.0; pero el modelo de programación y de ejecución es descrito en WSEE 1.2. La anotación @HandlerChain asocia una handler chain con un port component.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen

WS-Metadata 2.0 : Vista Rápida

WS-Metadata 2.0 define las anotaciones estándar utilizadas para desarrollar y desplegar Web Services mediante Java SE 6 o dentro de un contenedor Java EE 5. Para mayor detalle, veamos un ejemplito:



  1. @WebService marca esta clase Java como un Web Service, de modo que la implementación de JWS sepa que debe ser desplegada
  2. @SOAPBinding indica que este Web Service (WS) utiliza el protocolo SOAP
  3. El elemento @SOAPBinding.style indica que el WS debe ser desplegado usando el estilo document. Esta anotación configura el atributo style del elemento soap:binding del WSDL como se observa en el gráfico
  4. El elemento @SOAPBinding.use indica que los mensajes para este WS deben ser enviados usando el formato literal (o sea , no encoded). Esta anotación afecta el atributo use de los elementos soap:body
  5. El elemento @SOAPBinding.parameterStyle indica que los mensajes para este WS deben ser parámetros wrapped. Así, el atributo name del parámetro wrapper se denomina "SubmitPO"
  6. El elemento @WebMethod.operationName especifica que el atributo name de la operación del WSDL debe ser "SubmitPO"
  7. El elemento @WebResult.name especifica que el mensaje de respuesta debe ser un elemento llamado "PurchaseOrderAck"
  8. El elemento @WebParam.name especifica que el nombre del parámetro de request correspondiente al parámetro Java purchaseOrder debe llamarse "PurchaseOrder"

Anotaciones para mapping WSDL
Permiten darle forma al mapping WSDL/Java especificando información como el nombre de operación para un método Java en particular. En el gráfico anterior tenemos : @WebService, @WebResult y @webParam.

Anotaciones para binding SOAP
Nos permiten personalizar el estilo de binding SOAP, así como el uso y el estilo de parámetros. Los valores por defecto de JAX-WS para estilo de binding, uso y estilo de parámetros son document, literal y wrapped, respectivamente. Sin embargo, mediante la anotación @SOAPBinding se pueden especificar otras posibilidades, como rpc/literal o document/literal.

Anotaciones para Handlers
El despliegue de handlers se especifica mediante anotaciones de WS-Metadata. La anotación javax.jws.HandlerChain se utiliza para asociar un Web Service con una handler chain definido en un archivo externo referenciado mediante @HandlerChain.file.

Service Implementation Bean
WS-Metadata define los requisitos para desplegar una clase Java como web Service. Las clases que cumplen estos requerimientos se denomina Service Implementation Beans (SIB's). Tanto los POJOs como los EJB's que cumplan estos requisitos pueden desplegarse como Web Services

Comenzar desde WSDL y Java
El modelo de desarrollo "Comenzar desde WSDL y Java" es soportado mediante anotaciones que mapean componentes de clase Java existente con componentes de un documento WSDL. Por ejemplo la anotación @WebMethod.operation-name se utiliza para asociar un método a una wsdl:operation pre-existente.

Despliegue automático
El despliegue automático es un modelo de despliegue "copiar y pegar", similar al utilizado para páginas JSP. Es parte de la especificación de WS-metadata, pero no es obligatorio. En este modelo, el despliegue en tiempo de ejecución del WS depende totalmente de anotaciones, y no se requieren herramientas del vendedor. Glassfish posee esta funcionalidad.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen

JAXB 2.0 : Vista Rápida (II)


(Imagen: Rembrandt - El rapto de Europa)

Binding run-time framework
Es este framework el que implementa las operaciones de marshal y unmarshal.

Marshaling es el proceso de convertir instancias de clases anotadas en una representación XML (como un documento XML en un archivo o un DOM). Por otro lado, unmarshalling es el proceso de convertir una representación XML en un árbol de objetos.

Veamos como funciona este framework al invocar un Web Service:

  1. La aplicación recibe un mensaje SOAP en el endpoint desplegado
  2. JAX-WS prepara una instancia del contexto del mensaje (javax.xml.ws.handler.soap.SOAPMessageContext), que incluye la representación SAAJ (SOAP with Attachments API for Java) del mensaje.
  3. JAX-WS invoca a los handlers.
  4. Después que los handlers terminan, el runtime de JAXB ejecuta el unmarshal de los contenidos del mensaje SOAP dentro de un bean request.
  5. Este bean request, construido por el runtime de JAXB, contiene los objetos Java que son pasados como parámetros al método Java que implementa el Web Service. JAX-WS invoca al método usando estos parámetros.
  6. JAX-WS obtiene un objeto Java como resultado de la invocación del método, y lo utiliza para crear una instancia de un bean response.
  7. JAXB ejecuta el marshalling del bean response.
  8. Como en el paso 2, JAX-WS actualiza el contexto del mensaje para incluir la representación SAAJ del response.
  9. JAX-WS invoca a los handlers de response.
  10. JAX-WS envía el mensaje response a través de un protocolo de transporte.

Entonces, la deseralización ocurre en dos etapas. Un request SOAP comienza con su formato original (en el gráfico, un paquete MIME). La primera etapa convierte este formato a una representación XML y provee acceso SAAJ. Es en esta forma que llega al handler. La segunda etapa aplica unmarshalling a la representación XML y la convierte en objetos Java. Estos objetos se envían Web Service como parámetros para la invocación de métodos.



Validación:
Para implementar un Web Service, es indispensable controlar como se va a tratar XML inválido, respecto al esquema WSDL/XML que define al Web Service.

A diferencia de JAXB 1.0, el unmarshalling de XML inválido es permitido por JAXB 2.0 (por defecto, pero puede deshabilitarse). Si la validación está activada, el comportamiento por defecto es lanzar una excepción al primer error.

Portabilidad
La portabilidad es lograda mediante las anotaciones para mapping. El marshaller de cualquier implementación JAXB debe ser capaz de serializar una clase anotada de JAXB a una instancia de su esquema XML objetivo. Así, el unmarshaller debe ser capaz de deserializar una inastancia de un esquema a una instancia de una clase anotada JAXB.

Esto implica que uno puede desplegar un Web Service construido de clases anotadas JAXB cualquier plataforma JAXB 2.0 (como SAP Netweaver, JBoss, Glassfish) sin tener que recompilar el esquema y/o reestructurar el Web Service para usar clases de implementación propias del entorno.

Marshal event callback
Los callbacks permiten procesamiento específico de nuestra aplicación al momento de serializar. Estos procesos pueden ocurrir antes de serializar o después de la serialización. Esto es bastante útil cuando queremos asignar valores a propiedades fuera del proceso de serialización.

Binding parcial
La clase javax.xml.bind.Binder soporta el binding parcial de un documento XML. Esto nos permite crear un binding JAXB de una cabecera SOAP sin procesar el cuerpo.

Binary Data Encoding
El modelo de programación JWS soporta la optimización de la transmisión de data binaria dentro de mensajes SOAP. Esto implica codificar la data binaria (como un archivo de imagen como xsd:base64Binary), sacarla del sobre SOAP, adjuntar una versión comprimida al paquete MIME y colocar referencias a las partes codificadas en el sobre SOAP. JAXB nos brinda servicios que desempacan la data bianria antes del unmarshalling, y la empaquetan después del marshalling como parte de la serialización del mensaje SOAP. JAXB 2.0 soporta dos tipos de codificación de data binaria: MTOM/XOP y WSIAP.

MTOM es un estándar de la W3C y significa "SOAP Message Transmission Optimization Mechanism". Describe un proceso estándar para sacar el contenido de la representación XML, comprimirlo, empaquetarlo como un adjunto MIME y reemplazarlo por una referencia en la representación XML. La codificación en paquetes utilizada como MTOM es XOP (XML-binary Optimized Packaging).

WSIAP por otro lado significa WS-I Attachments Profile Version 1.0. Sin embargo, aparentemente MTOM/XOP se está convirtiendo en un estándar de la industria.

JAXB utiliza anotaciones para espedificar que propiedades Java de la clase deber ser serializadas usando MTOM o WSIAP. Para MTOM, la anotación @XmlMimeType nos permite especificar como una propiedad Java binaria (como java.awt.Image) está ligada a un elemento de esquema decorado como un atributo xmime:content-Type.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen

JAXB 2.0 : Vista Rápida (I)

(Imagen: Rembrandt - Cristo en la tormenta en el lago de Galilea)

JAXB 2.0 define un binding para JAVA/XML estándar, para obtener representaciones Java de componentes de esquemas XML y viceversa. Este binding asocia un conjunto de clases Java con un esquema XML, de modo que las instancias de este esquema puedan ser manipuladas mediante métodos Java.

Tenemos dos escenarios para empezar con el binding:

  1. Comenzar por Java: Las clases Java ya existen y se utilizan para generar un esquema XML con el generador de esquemas de JAXB 2.0.
  2. Comenzar de un esquema XML: El esquema ya existe y las clases Java y el binding son creados mediante el compilador de esquemas de JAXB 2.0

Algunas características interesantes de JAXB son:

Binding de esquemas XML a representaciones Java
Las implementaciones de JAXB 2.0 nos proveen de un compilador de esquemas, que nos genera clases Java en función a un esquema XML. Cada clase generada nos permite acceder al contenido del componente del esquema correspondiente mediante getters y setters. Así, los elementos y atributos de un tipo son mapeados a propiedades de una clase Java.

Mapping de tipos Java a esquemas XML
JAX-WS 2.0 hace uso de esta característica de JAXB para generar el WSDL de una clase Java desplegada como Web Service. Para esto, los parámetros y tipos devueltos por el método Java desplegado como una wsdl:operation determinan los componentes del esquema en la sección wsdl:types (esta sección contiene los elementos del esquema XML y las definiciones de tipos usados en el WSDL).

Las implementaciones de JAXB nos proveen de un generados de esquemas, que crea esquemas en base a clases existentes. Este generador examina las propiedades JavaBean y las mapea a atributos y elementos. Se utilizan anotaciones para personalizar este mapeo.

Anotaciones para Mapping
Las anotaciones para mapping son el mecanismo de JAXB 2.0 para personalizar el binding Java/XML. El generador de esquemas descrito en la sección anterior, requiere como inputs un conjunto de clases, y un conjunto de anotaciones para mapping (aunque de no existir, se toman valores por defecto).

Aquí un ejemplito, donde se mapea la propiedad PurchaseOrderNumber al elemento de esquema orderNum:

public class PurchaseOrder{
//...
@XmlElement(name="orderNum")
String getPurchaseOrderNumber();
void setPurchaseOrderNumber(String po);

}



También, al generar clases Java de un esquema XML (la forma inversa), el código Java que genera JAXB contiene anotaciones que documentan los componentes XML de los cuáles fueron mapeados.

Lenguaje de Binding
El lenguaje de binding de JAXB 2.0 nos permite anotaciones en XML que nos permiten personalizar la representación Java de un esquema XML. Se utiliza para darle forma a los tipos Java utilizados como parámetros y tipos de retorno de una SEI (Service Endpoint Interface).

A diferencia de las anotaciones, que siempre van entre líneas dentro del código Java, las personalizaciones de lenguaje de binding pueden estar dentro del esquema XML o en un archivo de configuración separado.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen

JAX-WS 2.0: Vista rápida (II)



(Imagen: Rembrandt - Retrato de Titus como monje)

Supongo que al final la vista no era tan rápida que digamos xD. Seguimos con más características de JAX-WS 2.0

Contexto de Mensajes:
JAX-WS nos permite la manipulación del contexto del mensaje (javax.xml.ws.handler.MessageContext) mediante los handlers, endpoints y clientes. Este contexto viaja junto a los mensajes XML de request y response, y nos permite, entre otras cosas, la comunicación entre request handlers, implementaciones de endpoint y response handlers.

SOAP binding:
JAX-WS 2.0 especifica SOAP binding para el proceso de mensajes SOAP. Incluye el procesamiento de mustUnderstand, y soporta los roles nest y ultimateReceiver de SOAP 1.2. El SOAP binding soporta también el uso de SOAP handlers, para el procesamiento de headers SOAP, y mapea las excepiones handler y servicea mensajes de error SOAP.

Las implementaciones de JAX-WS deben soportar SOAP HTTP binding, SOAP con adjuntos y SOAP MTOM.

HTTP Binding
También disponemos de XML/HTTP binding para el despliegue y consumo de Web Services mediante el framework REST. Estos servicios (llamados RESTful) envían y reciben XML sobre un transporte HTTP sin utilizar SOAP.

Conversión de Excepciones a errores SOAP
JAX-WS realiza un mapeo de java.lang.Exception a mensajes de error SOAP (WSDL faults) que son enviados al cliente, incluso al utilizar servicios RESTful. Este proceso en controlado con la anotación WebFault.

Invocación Asíncrona
JAX-WS añade soporte asíncrono al modelo de invocación definido en JAX-RPC 1.1. La "asincronía" es un requisito fundamental de SOAP.

Operaciones one-way
JAX-WS soporta el mapping entre métodos Java y operaciones one-way de WSDL.

Manejo de Threads del lado del cliente
Para proveer control de threads, se puede utilizar instancias de java.util.concurrent.Executor en un cliente JAX-WS (como una instancia de javax.xml.ws.Service).

Estilos WSDL
JAX-WS le encarga la serialización a JAXB, pero para empacar los parámetros serializados en un mensaje SOAP, JAXB necesita de la asistencia de JAX-WS. Esta guía depende del estilo WSDL usado por el Web Service desplegado. JAX-WS soporta los estilos WSDL rpc/literal y document/literal.

Catálogos XML
JAX-WS soporta el uso de catálogos XML. Es útil para los documentos WSDL el importar esquemas externos para evitar duplicidad.

Pseudoreferencias
Las invocaciones de métodos Java pasan las referencias a objetos por valor. Los Web Services no trabajan así: al pasar un objeto a un Web Service, se envuelve una copia serializada de la instancia en un mensaje SOAP y ésta es enviada.

El pasar referencias es útil para, por ejemplo, invocar un método que cambie el estado de una instancia. JAX-WS nos prevee del mecanismo de pase de "pseudoreferencias", mediante la clase Holder.

Esto se hace posible al usar la clase Holder como referencia a nuestra instancia. Al invocar el Web Service, JAX-WS envía una copia de la instancia al destino, y obtiene una versión modificada de la misma. Detrás de bambalinas, JAX-WS actualiza la instancia de Holder para que referencie a la instancia devuelta por el Web Service.

Run-Time Endpoint Publishing (sólo Java SE)
JAX-WS nos permite publicar endpoints de Web Services en tiempo de ejecución. Existe un API (javax.xml.ws.Endpoint) que nos permite asignar una instancia de una implementación Web Service a una URL.

He aquí un ejemplo:

MyServiceImpl myWebService = ... //contruir Web Service
Endpoint myEndpoint = Endpoint.publish("http://localhost/myService", myWebService);

Eso es todo! El runtime de JAX-WS se encarga de crear la infraestructura necesaria. Eso incluye crear un contexto HTTP y el listening de request SOAP en el URL especificado. Al usar este enfoque, el contrato WSDL para el endpoint se crea dinámicamente en base a anotaciones o en documentos de metadata.

Con eso terminaríamos con JAX-WS... por ahora xD

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen


JAX-WS 2.0: Vista rápida

(Imagen: Rembrandt - Retrato de Saskia van Uylenburg)


JAX-WS 2.0 es la especificación siguiente a JAX-RPC 1.1. Veamos algunas cosas de lo que hace:

Mapping Java/WSDL

JAX-WS 2.0 define un mapping estándar para Java/WSDL. Determina como las operaciones WSDL están relacionadas con métodos Java. En otras palabras, cuando un mensaje SOAP invoca una operación WSDL el mapping Java/WSDL determina que método Java invocar y como el mensaje SOAP contiene los parámetros del método. También, el mapping determina como el resultado del método es encapsulado en el response SOAP.

Este mapping estándar nos permite comenzar con una clase Java, pasársela al procesador JAX-WS (puede ser java2wsdl o wsgen) y generar la descripción WSDL del endpoint del Web Service.

Por otro lado, se puede comenzar por un documento WSDL y generar las clases Java y las interfaces. Las clases Java que obtenemos son clases wrapper y es necesario que implementemos la lógica de negocio. Estas clases generadas ya incluyen anotaciones que describen el mapping Java/WSDL.

Algunos principios del mapping WSDL/Java:

  • Los port types del WSDL se mapean a Interfaces Java: Un elemento wsdl:portType es mapeado a una interfaz Java denominada service endpoint interface (SEI). No se puede mapear operaciones individuales dentro de un port type a interfaces Java diferentes. Para implementar un port type, se debe desplegar una SEI.
  • Los mappings de los parámetros y del valor retornado deben ser compatibles con JAXB: Las clases usadas para los parámetros de la SEI y su valor de retorno deben usar anotaciones JAXB para especificar los componentes del esquema XML al que van a ser mapeados.

WSDL estático

JAX-WS nos permite especificar un documento WSDL estático y omitir la generación automática de WSDL que se basa en los mappings estándar WSDL/Java y XML/Java (JAXB). Esto es útil cuando se necesita publicar un Web Service (WS) que se ajuste a un WSDL específico. También nos permite publicar WSDL basados en esquemas existentes.

Clientes estáticos y dinámicos

Los clientes de Web Services de JAX-WS son instancias de javax.xml.ws.Service. Una instancia de Service se corresponde con el elemento wsdl:service del documento WSDL del WS al que nos queremos conectar. Las instancias de Service pueden ser generadas de manera estática o dinámica. La generación dinámica es realizada en tiempo de ejecución por los métodos Service.create. En lo concerniente a generación estática, es posible generar subclases de Service de un documento WSDL usando herramientas de JAX-WS (como wsimport en GlassFish)

Invocación mediante Interfaces Proxy Java

Las instancias de Service pueden crear un proxy para invocar al WS mediante una SEI. Los métodos Service.getPort se utilizan para crear instancias SEI relacionadas con el wsdl:port a invocar. La SEI generada debe ser consistente con los mappings Java/WSDL (JAX-WS) y XML/Java (JAXB). Es por esto que cuando se utilizan proxys SEI se hacen por lo general con instancias de Service generadas junto al SEI, al compilar el WSDL a invocar mediante herramientas JAX-WS(e.j wsimport).

Invocación con XML

Una instancia de Service puede invocar un WS enviando y recibiendo mensajes XML. Para esto, la clase Service nos provee una instancia de javax.xml.ws.Dispatch al invocar cualquier método createDispatch. Esto nos permite interactuar con el WS directamente via XML sin tener que serializar y deserializar a Java.

XML Service Providers

Es posible desplegar un servicio que simplemente envíe y reciba mensajes XML sin preocuparse en el mapping con clases JAXB, mediante la interfaz javax.xml.ws.Provider. Al crear un servicio mediante Provider, no se hace uso de una SEI; por lo que nos permite crear servicios que trabajen directamente con los request y response en XML.

Framework para Handlers

JAX-WS define handlers de request y de response que pueden ser usados en el lado del cliente o del servidor. Los Handlers nos permite el pre-procesamiento y post-procesamiento de mensajes.

Los handlers tienen acceso de lectura y escriuta al mensaje XML y a su contexto. Los handlers JAX-WS se organizan en listas ordenadas llamadas cadenas de handlers. Las cadenas de handlers se definen a nivel de puerto, por lo que todos los métodos de una SEI usan la misma cadena de handlers.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen

Ahora es más fácil hacer Web Services (se supone)

(Imagen: Edvard Munch - Niña enferma)

Una de las ideas detrás de JWS (Java Web Services) es facilitarle la vida a los desarrolladores en la ya tediosa tarea de crear y desplegar Web Services. Supuestamente, ahora ya es así, por lo que revisaremos a grosso modo las características de JWS que nos harán trabajar menos xD:

Anotaciones en código fuente

Ahora es posible anotar nuestro código fuente para hacer más simple el despliegue de Web Services. Estas anotaciones están definidas en JAX-WS, WS-Metadata y JAXB

Mapping Java/WSDL estándar

JAX-WS define un mapping estándar (sorry, pero no sé como traducir mapping xD) de WSDL a Java y viceversa. Cuando un service implementation bean (SIB : clase que puede ser desplegada como endpoint de WS) es desplegado, el WSDL resultante está basado en este mapping por defecto. Este mapping por defecto le facilita la vida al programador Java que quiere desplegar un Web Service y no conoce mucho de WSDL o XML.

Aunque no todo es perfecto, y este mapping tiene sus limitaciones. Por ejemplo, no se puede mapear mapear dos operaciones en el mismo puerto WSDL que pertenezcan a diferentes SIB's. En cristiano, si tenemos el puerto wsdl:port con las operaciones foo y bar, tanto foo como bar deben pertenecer a las misma clase/interface Java. Además, sólamente es posible mapear un WSDL a un SIB, por lo que es imposible desplegar una clase no anotada como Web Service.

Contexto de Serialización estándar

Ahora ya no es necesario especificar mappings y serializadores al momento del despliegue de un Web Service. Parafraseando, con el contexto de serializacióne estándar no hay que definir el XML al que las clases Java van a ser mapeadas. Y esto es posible debido a que JAXB se encarga de la serialización. JAXB tiene reglas estándar para serializar/deserializar componentes de un esquema XML a/desde objetos Java.

Modelos de desarrollo

El primero es "comienzo por Java". En este modelo, se empieza el desarrollo del Web Service(WS) construyendo una clase Java. Luego, para desplegar esta clase como WS la anotamos con @WebService y listo xD!. Los runtimes de JAX-WS y JAXB se encargan de mapear esta clase a un documento WSDL usando los mappings estándar de WSDL/Java y XML/Java. Si queremos modificar nuestro WSDL lo podemos hacer mediante anotaciones.

El otro modelo es "comienzo por el WSDL". En este caso tomamos un WSDL ya existente y usamos un compilador WSDL (que nos brinda la implementación JAX-WS) para generar las clases Java que implementan ese WSDL. Con este modelo obtenemos una grupo de SEI's (Service Endpoint Interface) que se corresponden con el WSDL. Entonces, nuestro trabajo es implementar las SEI's con lógica de negocio.

Y el último modelo es "comienzo por WSDL y por Java". La idea aquí es referenciar un WSDL pre-existente en la anotación @webService, que usaremos en la clase Java que pretende implementarla. Entonces, utilizamos anotaciones para hacer mapping entre la clase y el WSDL referenciado.

Basado en el capítulo 2 de SOA Using Java Web Services de Mark D. Hansen