4. Invocación de Servicios Web

Vamos a ver ahora cómo invocar Servicios Web orientados a RPC desde Java. Para ello contamos con la API JAX-RPC, que se apoya en la API SAAJ para gestionar los mensajes SOAP orientados a RPC.

Con JAX-RPC podremos ejecutar procedimientos de forma remota, simplemente haciendo una llamada a dicho procedimiento, sin tener que introducir apenas código adicional. Será JAX-RPC quien se encargue de gestionar internamente la conexión con el servicio y el manejo de los mensajes SOAP de llamada al procedimiento y de respuesta.

Podemos encontrar las clases de la API de JAX-RPC dentro del paquete javax.xml.rpc y en subpaquetes de éste.

Nuestro cliente Java realizado con JAX-RPC será interoperable con prácticamente todos los servicios creados desde otras plataformas. Se pueden ver los resultados de los tests de interoperabilidad de JAX-RPC en la dirección:

http://java.sun.com/wsinterop/sb/index.html

4.1 Tipos de acceso

Tenemos varias posibilidades para acceder a un Servicio Web utilizando JAX-RPC:

4.2 Invocación mediante stub estático

Está será la forma más sencilla de acceder siempre que contemos con una herramienta que genera el stub de forma automática.

De esta forma, una vez generado el stub, sólo tendremos que utilizar este stub como si se tratase de nuestro servicio directamente. En el stub podremos hacer las mismas llamadas a métodos que haríamos directamente en la clase que implemente nuestro servicio, ya que ambos implementarán la misma interfaz.

El stub generado implementará la interfaz Stub, además de la interfaz de nuestro servicio. Vamos a ver ahora cómo generar dicha capa de stub desde diferentes plataformas.

4.2.1 Creación del stub en WSDP

En WSDP podemos crear nuestro stub de forma sencilla a partir de la interfaz Java (RMI) de nuestro servicio, o bien del documento WSDL si no contamos con la interfaz Java. Para ello utilizaremos la herramienta xrpcc, o wscompile en las últimas versiones, para generar la parte del cliente.

Vamos a ver cómo se crearía mediante el ejemplo del servicio Conversion que vimos en el tema anterior, que ofrece métodos para convertir de euros a ptas y viceversa.

A partir de la interfaz Java

Supongamos que tenemos la interfaz de nuestro servicio definida en el fichero ConversionIF.java de la siguiente forma:

package utils;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ConversionIF extends Remote {
  public int euro2ptas(double euro) throws RemoteException;
  public double ptas2euro(int ptas) throws RemoteException;
}

Necesitaremos además un fichero config.xml que defina la configuración del Servicio Web como el que se muestra a continuación:

<?xml version="1.0" encoding="UTF-8"?> 
<configuration 
  xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">    
  <service name="Conversion" 
    targetNamespace="http://rvg.ua.es/wsdl" 
    typeNamespace="http://rvg.ua.es/types" 
    packageName="utils"> 
    <interface name="utils.ConversionIF" 
      servantName="utils.ConversionImpl"/> 
  </service> 
</configuration> 

En este fichero especificamos el nombre de nuestro Servicio Web, los espacios de nombres utilizados, el paquete donde guardaremos las clases del servicio, y el nombre de la interfaz del servicio y de la clase que implementa este servicio (servantName).

Una vez tenemos estos ficheros podemos generar las clases necesarias automáticamente mediante la herramienta xrpcc introduciendo el siguiente comando:

xrpcc.sh config.xml -client -classpath .  LINUX
xrpcc config.xml -client -classpath .     WINDOWS

En las nuevas versiones de WSDP esta herramienta se ha sustituido por la herramienta wscompile, aunque xrpcc todavía se mantenga por cuestiones de compatibilidad. Por ello será recomendable utilizar la nueva herramienta, que es similar a la anterior:

wscompile.sh config.xml -gen:client -classpath .  LINUX
wscompile config.xml -gen:client -classpath .     WINDOWS

NOTA: Si no especificamos el classpath explicitamente en la línea de comando es posible que no consiga localizar las clases necesarias.

A partir de la descripción WSDL

Si nosotros no hemos desarrollado el Servicio Web que queremos usar, lo normal será que no tengamos la interfaz Java de dicho servicio. Es más, puede ocurrir que el Servicio Web ni siquiera esté implementado en Java. En este caso deberemos recurrir al fichero WSDL que describa el servicio para generar nuestro cliente.

En este caso utilizaremos la herramienta xrpcc o wscompile al igual que en el caso anterior. Tendremos un fichero de configuración config.xml en el que deberemos indicar la dirección donde se encuentra la descripción WSDL del servicio, así como el paquete en el que se guardarán las clases generadas para el stub:

<?xml version="1.0" encoding="UTF-8"?> 
<configuration 
  xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> 
  <wsdl location="http://localhost:8080/conv/Conversion.wsdl"    
    packageName="utils"> 
  </wsdl> 
</configuration>

Para generar el stub deberemos introducir el siguiente comando con la antigua herramienta xrpcc:

xrpcc.sh config.xml -client  LINUX
xrpcc config.xml -client     WINDOWS

O utilizar la nueva herramienta wscompile, cosa que será recomendable ya que xrpcc podría desaparecer en próximas versiones:

wscompile.sh config.xml -gen:client		LINUX
wscompile config.xml -gen:client		WINDOWS

Esto nos generará la interfaz RMI del servicio dentro del paquete que hayamos indicado. Además habrá creado las clases auxiliares necesarias para invocar el servicio mediante JAX-RPC. La interfaz del servicio que podremos utilizar desde nuestro programas se habrá generado en una clase cuyo nombre será el nombre del tipo de puerto al que vayamos a acceder. Si utilizamos la opción -keep de wscompile, para que no elimine los fuentes, podremos consultar esta interfaz para conocer los métodos que podremos invocar sobre ella.

Tendremos que obtener un objeto Stub que implemente dicha interfaz, y que será quien nos dé acceso al servicio. Para ello nos habrá generado un fichero con el sufijo <Nombre_del_servicio>_Impl que podremos instanciar desde nuestro cliente mediante un constructor vacío, y a partir de él obtener el Stub para acceder a nuestro servicio. Para ello tendrá definido un método get<Nombre_del_tipo_de_puerto>() que nos devolverá el objeto Stub correspondiente al puerto para acceder al servicio, que podremos referenciar mediante la interfaz <Nombre_del_tipo_de_puerto>.

En el próximo punto veremos con más detalle como utilizar estos objetos en nuestro cliente para obtener el objeto Stub y acceder al servicio.

Implementación del cliente

Con cualquiera de estos dos métodos anteriores habremos generado una serie de clases necesarias para invocar el servicio. Entre ellas, tendremos una clase llamada <Nombre_del_servicio>_Impl que será la que deberemos utilizar dentro de nuestra aplicación cliente para obtener un stub. Esta clase tendrá un método get<Nombre_del_tipo_de_puerto>() que nos devolverá el stub que buscamos.

Stub stub = (Stub)(new Conversion_Impl().getConversionIFPort()); 

Deberemos indicar al stub al menos cual es la dirección donde se encuentra atendiendo el servicio (su endpoint). Esto lo estableceremos como una propiedad del objeto Stub obtenido:

stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, 
   endpoint);  

Para finalizar, tendremos que hacer una conversión cast del stub obtenido a la interfaz de nuestro servicio, para poder invocar de esta forma los métodos de nuestro servicio.

En el caso del ejemplo anterior podremos acceder al stub (o proxy) generado de la siguiente forma:

private static ConversionIF creaProxy(String endpoint) {
  Stub stub = (Stub)(new Conversion_Impl().getConversionIFPort());
  stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, 
    endpoint);
  return (ConversionIF)stub;
}

Este será el único código adicional que debamos insertar para acceder a nuestro servicio. Una vez obtenido este stub podremos invocar los métodos del servicio como si se tratase de un objeto local:

public static void main(String[] args) {
  try {
    ConversionIF conv = creaProxy(args[0]);
    int ptas = conv.euro2ptas(Double.parseDouble(args[1]));
    System.out.println(args[1] + " euros son " + ptas + " ptas");
  } catch (Exception e) {
    e.printStackTrace();
  }
} 

4.2.2 Creación del stub en Weblogic

Weblogic incluye una serie de ficheros JAR que contienen la implementación de JAX-RPC para el cliente. Tenemos los siguientes ficheros:

webserviceclient.jar Implementación de JAX-RPC para el cliente
webserviceclient+ssl.jar Implementación para utilizar SSL
webserviceclient+ssl_pj.jar Implementación para crear un cliente J2ME

El cliente deberá tener acceso a los ficheros necesarios según la implementación que estamos haciendo, por lo que deberemos copiar el fichero JAR adecuado a la máquina cliente.

Ahora deberemos generar las clases de stub en el cliente para nuestro Servicio Web específico. Para ello en la implementación de Weblogic, contamos con una tarea de la herramienta ant que realizará este trabajo por nosotros, se trata de la tarea clientgen.

Igual que ocurría en el caso anterior, podemos crear estas clases a partir de la implementación de nuestro Servicio Web, o a partir del fichero WSDL, si no contamos con la implementación.

A partir de la implementación del servicio

La primera opción es hacerlo a partir del fichero EAR que hemos generado previamente con la implementación de nuestro Servicio Web de Weblogic:

<clientgen 
  ear="conversion.ear" 
  warName="conversion.war"
  packageName="utils" 
  clientJar="cliente.jar" 
/>

Del Servicio Web del cual vamos a generar el cliente especificamos el fichero EAR donde tenemos empaquetada la implementación del servicio, y además el nombre del fichero WAR contenido en este fichero EAR anterior con la aplicación Web. Además debemos especificar los datos sobre las clases del cliente que vamos a generar. Indicaremos el paquete en el que se encontrarán las clases de stub y otras clases auxiliares, y el nombre del fichero JAR donde vamos a empaquetar todas estas clases.

A partir del documento WSDL

Sin embargo, si no contamos con una implementación Weblogic del servicio, deberemos utilizar el documento WSDL:

<clientgen 
	wsdl="http://localhost:8080/conversion/Conversion.wsdl" 
	packageName="utils"    
	clientJar="cliente.jar"
/>

En este caso como datos del servicio únicamente aportamos la URL del documento WSDL del cuál vamos a extraer la información del Servicio Web a invocar. Además también deberemos indicar, al igual que en el caso anterior, el paquete donde ubicaremos las clases generadas, y el fichero JAR en el que las empaquetaremos.

Cliente para J2ME

Además podemos crear clientes J2ME para los servicios de forma sencilla. Simplemente deberemos añadir el atributo j2me="True" a la tarea clientgen. Con esto generaremos un fichero JAR que podrá ser utilizado en nuestras aplicaciones J2ME para invocar el servicio. En este cliente deberemos acompañar este fichero JAR que hemos generado con la librería de Weblogic para clientes de servicios J2ME webserviceclient+ssl_pj.jar.

Implementación del cliente

Con esto habremos generado el stub necesario en el paquete especificado en packageName, y lo empaqueta en el JAR indicado en clientJar. Podremos utilizarlo dentro de nuestra aplicación cliente para acceder al servicio prácticamente igual que en el caso anterior. Podemos implementar un cliente de la siguiente forma:

import utils.*;

public class Cliente { 
  
  public static void main(String[] args) { 
    try { 
      ConversionPort conv = creaProxy(); 
      int ptas = conv.euro2ptas(Double.parseDouble(args[0]));    
      System.out.println(args[0] + " euros son " + ptas + " ptas");    
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 

  private static ConversionPort creaProxy() { 
    String wsdl = "http://localhost:7001/sw_conv/conv?WSDL";
    ConversionPort stub = null; 

    try { 
      stub = (new Conversion_Impl(wsdl)).getConversionPort();	   
    } catch(Exception e) { } 

    return stub; 
  }
}

Para acceder al stub primero debemos crear la implementación generada del servicio correspondiente (<servicio>_Impl). Podremos crearla proporcionando la URL del documento WSDL que describe el servicio. En caso de no especificar este dato, utilizaría por defecto la dirección WSDL que hubiésemos especificado al generar las clases de stub. Una vez tenemos este objeto, podemos obtener a partir de él el stub para acceder a un puerto del servicio, y a partir de este objeto stub acceder al servicio igual que si estuviesemos accediendo a la clase que lo implementa en modo local.

Para poder compilar e invocar esta aplicación cliente deberá poder localizar las clases generadas para el cliente, además de las clases de JAX-RPC en las que se basa. Podemos o bien establecerlas en la variable CLASSPATH, o bien utilizar ant para ejecutar el cliente, haciendo que el fichero build.xml correspondiente establezca el CLASSPATH adecuado, o indicar esta información directamente en la línea de comando como sigue a continuación:

javac Cliente.java -classpath webserviceclient.jar:client.jar

Con esto habremos compilado nuestra aplicación cliente. Ahora deberemos ejecutarla, cosa que haremos de forma similar:

java -classpath webserviceclient.jar:client.jar:. Cliente 250

Lo cual nos dará la conversión a ptas de 250 euros.

4.3 Invocación mediante proxy dinámico

Con este método no tendremos que haber generado previamente de forma estática el stub para nuestro servicio, sino que éste será generado de forma dinámica en tiempo de ejecución.

Para hacer esto, deberemos proporcionar la descripción WSDL del Servicio Web, además de una interfaz Java que implemente los métodos de dicho servicio.

Ahora ya no necesitamos ninguna herramienta para generar las clases del cliente, ya que no vamos a generar ninguna clase. Pasaremos directamente a introducir el código de nuestro cliente.

Deberemos introducir en nuestro programa los nombres que se le ha dado al servicio y a los puertos dentro del XML. Estos nombres normalmente constarán de su nombre local y del espacio de nombres al que pertenezcan, aunque pueden venir dados únicamente por un nombre local. Para representar estos nombres (qualified names) tenemos el objeto QName en java, que se puede construir de dos formas distintas, según si pertenece a un espacio de nombres o no:

QName nombre = new QName(namespace, nombre_local);
QName nombre = new QName(nombre_local);

Lo primero que deberemos hacer es obtener un objeto Service correspondiente al servicio que queremos utilizar. Para ello necesitamos previamente un ServiceFactory que nos permita construir objetos Service:

 ServiceFactory sf = ServiceFactory.newInstance();

Ahora podremos crear nuestro objeto Service indicando la dirección donde se encuentra el documento WSDL, y el nombre del servicio al que queremos acceder:

Service serv = sf.createService(
  new URL("http://localhost:8080/conversion/Conversion.wsdl"), 
  new QName("http://rvg.ua.es/wsdl", "Conversion"));

Una vez tenemos el servicio, ya sólo nos queda acceder al puerto concreto que vayamos a utilizar del servicio. Tendremos que indicar el nombre del puerto, y la clase de una interfaz que se ajuste al puerto que queremos utilizar. En el caso del servicio de Conversion, podemos utilizar la interfaz que hemos definido anteriormente (ConversionIF):

ConversionIF conv = (ConversionIF) serv.getPort(
  new QName("http://rvg.ua.es/wsdl", "ConversionIFPort"), 
  utils.ConversionIF.class); 

Una vez hecho esto podremos acceder al servicio a través de dicho objeto, de la misma forma que si accediésemos directamente al servicio, ya que implementará la misma interfaz:

int ptas = conv.euro2ptas(Double.parseDouble(args[1]));
System.out.println(args[1] + " euros son " + ptas + " ptas");

Este método nos obliga a introducir más código en el cliente para conectar con el servicio, pero tiene la ventaja de no necesitar utilizar herramientas adicionales, y si introducimos algún cambio en el servicio, no tendremos que volver a generar las clases del cliente.

4.4 Interfaz de invocación dinámica (DII)

Mediante esta interfaz, ya no utilizaremos un stub para invocar los métodos del servicio, sino que nos permitirá invocar los métodos de forma dinámica, indicando simplemente el nombre del método que queremos invocar como una cadena de texto, y sus parámetros como un array de objetos.

Esto nos permitirá utilizar servicios que no conocemos previamente. De esta forma podremos implementar por ejemplo un broker de servicios. Un broker es un servicio intermediario, al que podemos solicitar alguna tarea que necesitemos. Entonces el broker intentará localizar el servicio más apropiado para dicha tarea en un registro de servicios, y lo invocará por nosotros. Una vez haya conseguido la información que requerimos, nos la devolverá. De esta forma la localización de servicios se hace totalmente transparente para nosotros.

Podremos acceder con esta interfaz tanto si contamos con un documento WSDL como si no contamos con él, pero en el caso de que no tengamos el WSDL deberemos especificar en el código todos los datos incluidos en estos documentos que necesitemos y de los que en este caso no disponemos (endpoint, parámetros y tipos, etc).

A partir de un documento WSDL

Vamos a ver el caso en el que contamos con el documento WSDL que describe el servicio. El primer paso será conseguir el objeto Service igual que hicimos en el caso anterior:

ServiceFactory sf = ServiceFactory.newInstance(); 
Service serv = sf.createService(
  new URL("http://localhost:8080/conversion/Conversion.wsdl"), 
  new QName("http://rvg.ua.es/wsdl", "Conversion"));

Utilizaremos el objeto Call para hacer las llamadas dinámicas a los métodos del servicio. Deberemos crear un objeto Call correspondiente a un determinado puerto y operación de nuestro servicio:

Call call = serv.createCall(
  new QName("http://rvg.ua.es/wsdl", "ConversionIFPort"),
  new QName("http://rvg.ua.es/wsdl", "euro2ptas"));

El último paso será invocar la llamada que hemos creado:

Integer result = (Integer) call.invoke(
                 new Object[] { new Double(30.0) });

A este método le debemos proporcionar un array de objetos como parámetro, ya que debe poder utilizarse para cualquier operación, con diferente número y tipo de parámetros. Como tampoco se conoce a priori el valor devuelto por la llamada, deberemos hacer una conversión cast al tipo que corresponda, ya que nos devuelve un Object genérico.

Sin un documento WSDL

Si no contamos con el WSDL del servicio, crearemos un objeto Service proporcionando únicamente el nombre del servicio:

ServiceFactory sf = ServiceFactory.newInstance(); 
Service serv = sf.createService( 
  new QName("http://rvg.ua.es/wsdl", "Conversion"));

A partir de este objeto podremos obtener un objeto Call para realizar una llamada al servicio de la misma forma que vimos en el caso anterior:

Call call = serv.createCall(
  new QName("http://rvg.ua.es/wsdl", "ConversionIFPort"),
  new QName("http://rvg.ua.es/wsdl", "euro2ptas"));

En este caso el objeto Call no tendrá ninguna información sobre las características del servicio, ya que no tiene acceso al documento WSDL que lo describe, por lo que deberemos proporcionárselas nosotros explícitamente.

En primer lugar, deberemos especificar el endpoint del servicio, para que sepa a qué dirección debe conectarse para acceder a dicho servicio:

call.setTargetEndpointAddress(endpoint);

Una vez especificada esta información, deberemos indicar el tipo de datos que nos devuelve la llamada a la operación que vamos a invocar (en nuestro ejemplo euro2ptas):

QName t_int = 
  new QName("http://www.w3.org/2001/XMLSchema", "int");
call.setReturnType(t_int);

Por último, indicaremos los parámetros de entrada que toma la operación y sus tipos:

QName t_double = 
  new QName("http://www.w3.org/2001/XMLSchema", "double");
call.addParameter("double_1", t_double, ParameterMode.IN);

Una vez hecho esto, podremos invocar dicha operación igual que en el caso anterior:

Integer result = (Integer) call.invoke(
                 new Object[] { new Double(30.0) });

4.5. Invocación de Servicios Web seguros

Si hemos restringido el acceso a nuestro servicio mediante autentificación HTTP básica deberemos especificar el login y password del usuario para poder conectarnos. Esto lo haremos estableciendo las siguientes propiedades de JAX-RPC en el objeto Stub:

stub._setProperty(
  "javax.xml.rpc.security.auth.username", "mylogin");
stub._setProperty(
  "javax.xml.rpc.security.auth.password", "mypassword");

En caso de realizar la invocación mediante DII, podemos establecer estas propiedades en el objeto Call.

Para acceder mediante SSL a un Servicio Web de Weblogic, deberemos añadir al CLASSPATH el fichero webserviceclient+ssl.jar. Además deberemos establecer dentro de nuestro programa el fichero que contiene el certificado CA dentro de un objeto WLSSLAdapter perteneciente al paquete weblogic.webservice.client:

WLSSLAdapter adapter = new WLSSLAdapter(); 
adapter.setTrustedCertifcatesFile("certificado.pem");

Una vez hemos creado el objeto, para que sea utilizado en la invocación deberemos establecer la siguiente propiedad en el objeto Stub o Call, según el tipo de invocación:

stub.setProperty("weblogic.webservice.client.ssladapter", adapter);
call.setProperty("weblogic.webservice.client.ssladapter", adapter);

En la invocación del cliente deberemos especificar las siguiente propiedades:

java 
  -Dbea.home=<dir_bea> 
  -Djava.protocol.handler.pkgs=com.certicom.net.ssl 
  MiCliente

Donde bea.home se refiere al directorio donde se encuentra la licencia de BEA (license.bea).

4.6 Instalación de handlers en el cliente

En el tema anterior vimos como crear handlers para interceptar los mensajes SOAP de la petición y la respuesta que se envían a través de la red. Mediante estos handlers podremos interceptar el mensaje antes de que éste sea enviado a través de la red, en el caso de los mensajes de entrada, y antes de que sea recibido por la aplicación, en el caso de los mensajes salientes, y leerlo o modificarlo en la forma que queramos.

Mediante estos componentes podremos realizar tareas como encriptar estos mensajes, para que viajen de forma segura a través de la red. El extremo que envía el mensaje tendrá un handler que encripte el mensaje, y el extremo que lo reciba deberá tener un handler que lo desencripte, para que esta encriptación sea transparente para JAX-RPC, y sea capaz de leer correctamente el mensaje SOAP en el destino.

Por lo tanto, en este caso vemos claramente la necesidad de que los handlers puedan intervenir en ambos extremos de la comunicación. En el tema anterior vimos como instalar un handler en el servidor. A continuación veremos como instalar este componente en nuestra aplicación cliente que invoca al servicio.

4.6.1 Creación del handler

El primer paso será implementar el objeto Handler que vamos a introducir en nuestra aplicación. Esto lo haremos de la misma forma que para el caso de los handlers del servidor. Por ejemplo, podemos crear un handler que muestre por la salida estándar los mensajes de petición y respuesta que está intercambiando el cliente:

public class HandlerEspia extends GenericHandler
{
  public HandlerEspia() { }

  public QName [] getHeaders() {
    return null;
  }

  public boolean handleRequest(MessageContext context) {
    System.out.println("PETICION:");
    try {
      // Accede al mensaje SOAP
      SOAPMessageContext smc = (SOAPMessageContext)context;
      SOAPMessage msg = smc.getMessage();

      // Lee el contenido del mensaje y lo imprime
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      msg.writeTo(baos);
      System.out.println(baos.toString());
    } catch(Exception ex) { }
    return true;
  }

  public boolean handleResponse(MessageContext context) {
    System.out.println("RESPUESTA:");
    try {
      // Accede al mensaje SOAP
      SOAPMessageContext smc = (SOAPMessageContext)context;
      SOAPMessage msg = smc.getMessage();

      // Lee el contenido del mensaje y lo imprime
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      msg.writeTo(baos);
      System.out.println(baos.toString());
    } catch(Exception ex) { }
    return true;
  }
}

En este caso, al tratarse del cliente, la petición (request) será el mensaje saliente y la respuesta (response) será el mensaje entrante, que nos devuelve el servidor como resultado de la llamada a un método.

4.6.2 Registrar el handler en el cliente

Una vez hemos creado nuestro handler, deberemos registrarlo en el cliente del servicio, de forma que cuando entre o salga un mensaje sea interceptado por nuestro handler. El handler deberá ser registrado dentro del objeto Service correspondiente al servicio que estemos invocando:

Service serv = ... // Obtener servicio

En el caso en el que accedamos mediante un stub estático, no se usa directamente un objeto Service, sino una subclase del mismo, adaptada al caso concreto de nuestro servicio. En este caso podremos utilizar este objeto para registrarlo, ya que al ser subclase de Service podremos utilizarlo de la misma forma.

A través de este objeto Service podremos acceder al registro de handlers (HandlerRegistry) de la siguiente forma:

HandlerRegistry hr = serv.getHandlerRegistry();

A partir de este registro podremos obtener la cadena (chain) de handlers instalados en este servicio. Proporcionaremos el nombre del puerto para el que queremos registrar el handler, mediante un objeto QName:

List chain = hr.getHandlerChain(
             new QName("http://rvg.ua.es/wsdl/Conversion", 
                       "ConversionIFPort"));

Esto nos devolverá una lista con información sobre todos los handlers instalados en el cliente para el servicio y puerto indicados. Esta lista nos dará el orden de invocación de la cadena de handlers en el cliente, tal como se muestra en la siguiente figura:

Figura 2. Cadena de handlers en el cliente

Cada elemento de esta lista será un objeto HandlerInfo, que contendrá información sobre cada handler instalado. Para registrar un nuevo handler deberemos crearnos un objeto HandlerInfo que haga referencia a nuestro handler. En dicho objeto deberemos indicar la clase de nuestro handler, y además podemos proporcionar una serie de parámetros de configuración genéricos que se enviarán al handler en el momento de su creación.

HandlerInfo hi = new HandlerInfo(HandlerEspia.class,null,null);

Una vez hecho esto, el último paso será añadir este objeto a la cadena de handlers:

chain.add(hi);

Con esto tendremos ya nuestro handler registrado. A continuación deberemos acceder al puerto correspondiente para hacer la llamada al servicio, con cualquiera de los tres métodos que hemos visto anteriormente (stub estático, proxy dinámico, o DII). En el momento en el que se envíe el mensaje SOAP con la petición, nuestro handler lo interceptará justo antes de que salga a través de la red. Cuando llegue la respuesta, el handler también la interceptará antes de que la lea nuestra aplicación cliente.

4.7 Acceso a la descripción del servicio

En ocasiones puede interesarnos que una aplicación pueda "interrogar" a un servicio para averiguar los métodos que ofrece, los parámetros que se le deben proporcionar a estos métodos, y el tipo de datos que devuelve. Está descripción del servicio podemos encontrarla en un fichero WSDL, que puede haber sido publicado por el desarrollador de dicho servicio.

Por ejemplo, si queremos que una aplicación sea capaz de localizar un servicio (por ejemplo en un registro UDDI), y además que sea capaz de invocarlo de forma automática sin tenerle que decir el usuario manualmente los métodos que debe invocar, y los parámetros que debe proporcionar, podremos utilizar la información de este documento WSDL. Un uso posible sería el caso de una aplicación que a partir de un fichero WSDL, muestre al usuario todos los métodos disponibles en el servicio, y permita que el usuario los invoque, mostrándole en cada caso un campo de texto para cada parámetro que se deba proporcionar al método.

Para que una aplicación pueda tener esta información sobre la interfaz del servicio, deberá ser capaz de analizar y entender un documento WSDL. Para ello deberemos contar con un parser de documentos WSDL. Afortunadamente, contamos con librerías que realizarán esta tarea, como es el caso de Java API for WSDL.

4.7.1 Java API for WSDL

La API Java API for WSDL (JSR-110) nos permitirá analizar documentos WSDL de forma sencilla, pudiendo obtener de este documento toda la información contenida en él: operaciones, mensajes, tipos, enlaces, puertos, etc.

Las clases de está librería están contenidas en el paquete javax.wsdl, por lo que deberemos importar este paquete y los subpaquetes necesarios. Para comenzar, deberemos obtener un objeto WSDLFactory que nos permita crear objetos que manipulen documentos WSDL. Crearemos este objeto de la siguiente forma:

WSDLFactory wsdl_f = WSDLFactory.newInstance();

Una vez hemos obtenido este objeto, podremos crear a partir de él objetos para analizar (leer) o componer (escribir) documentos WSDL:

WSDLReader wsdl_reader = wsdl_f.newWSDLReader();
WSDLWriter wsdl_writer = wsdl_f.newWSDLWriter();

De esta forma vemos que podemos obtener los siguientes objetos:

4.7.2 Análisis de documentos WSDL

Para analizar un documento WSDL deberemos utilizar el objeto WSDLReader que hemos obtenido en el apartado anterior. Lo primero que deberemos hacer es leer el documento WSDL, llamando al siguiente método:

Definition def = wsdl_reader.readWSDL(
           "http://www.rvg.ua.es:7001/sw/MiServicio?WSDL");

Tras leer el documento, nos devuelve un objeto Definition, donde tendremos todos los datos del documento WSDL leído organizados en una estructura de clases Java. Dentro de esta clase podremos encontrar una serie de métodos get* que nos permitirán obtener todos los componentes del documento WSDL. Estos componentes se representan mediante diferentes clases Java:

Vamos a ver un ejemplo de un programa que lee un documento WSDL, obteniendo una lista de las operaciones definidas en él, y para cada operación sus parámetros y sus tipos. Como primer paso deberemos leer el fichero en forma de objeto Definition:

WSDLReader wsdlReader = WSDLFactory.newInstance().newWSDLReader();
Definition wsdlDefinition = wsdlReader.readWSDL(args[0]);

Ahora obtendremos todos los tipos de puertos definidos en el fichero y un iterador para recorrer esta lista:

Map pts = wsdlDefinition.getPortTypes();
Iterator ptIterator = pts.values().iterator();

Recorremos la lista de tipos de puertos, imprimiendo el nombre de cada uno:

while (ptIterator.hasNext()) {
  // Get next service element
  PortType pt = (PortType) ptIterator.next();
  System.out.println("Tipo de puerto: " + 
                  pt.getQName().getLocalPart());
 

Obtenemos las operaciones definidas para el tipo de puerto actual, y un iterador para recorrerlas:

  List ops = pt.getOperations();
  Iterator opsIterator = ops.iterator();

Recorremos todas las operaciones (métodos) definidas en el puerto actual, imprimiendo el nombre de cada una de ellas:

  while(opsIterator.hasNext()) {
    Operation op = (Operation)opsIterator.next();
    System.out.println("Operacion = " + op.getName());

De cada operación obtendremos sus parámetros de entrada, obteniendo el mensaje que representa la entrada de dicha operación:

    Input input = op.getInput();
    Message mess = input.getMessage();

De este mensaje obtenemos una lista ordenada de los nombres de los parámetros definidos en el método. A partir de estos nombres obtendremos las partes del mensaje de entrada que representen cada parámetro, y de cada uno de estos parámetro imprimiremos su tipo y su nombre:

    Iterator partIterator = mess.getOrderedParts(null).iterator();
while(partIterator.hasNext()) {
Part part = (Part)partIterator.next();
System.out.println("Tipo = " + part.getTypeName() + ", Nombre = " + part.getName()); } } }

4.7.3 Creación de documentos WSDL

Para crear un nuevo documento WSDL deberemos utilizar el objeto WSDLWriter que hemos obtenido anteriormente. Además, previamente deberemos crear la definición del servicio en un objeto Definition. Para ello primero obtendremos una definición vacía mediante el WSDLFactory:

Definition def = wsdl_f.newDefinition();

Una vez obtenido este objeto, deberemos introducir en él los datos de la definición del servicio. Dentro del objeto Definition tenemos una serie de métodos create*, con los que podremos crear los objetos que representan los diferentes elementos que podemos tener dentro del documento WSDL. Una vez creados estos objetos, podremos añadirlos a la estructura del documento WSDL mediante el método add* adecuado.

Una vez creada la definición como una estructura de objetos Java, podremos escribirla en forma de un documento WSDL mediante el objeto WSDLWriter:

wsdl_writer.writeWSDL(def,out);

Donde out puede ser un objeto OutputStream o Writer, donde escribiremos el documento WSDL.