Ejercicios de invocación de Servicios Web


1. Vamos a ver como implementar un cliente para el servicio HolaMundo paso a paso. Vamos a generar el stub para el cliente a partir del archivo EAR donde tenemos implementado el servicio.

ant service

De esta forma tendremos ya implementado el servicio, como vimos en la sesión anterior. Ahora vamos a generar el cliente a partir del fichero sw_hola.ear que nos ha generado con la implementación del servicio.

<target name="client">
  <clientgen ear="sw_hola.ear"
    warName="sw_hola.war"
    packageName="utils"
    clientJar="client.jar" />
</target>
ant client

Esto habrá generado el fichero JAR client.jar con las clases de stub necesarias para ejecutar el servicio.

Estas clases que ha generado se apoyan en las clases de la librería JAX-RPC. Weblogic nos proporciona un paquete con estas clases que deberemos distribuir junto a nuestro cliente, ya que serán necesarias para que compile. Estas clases están en el fichero:

${dir.inst}/bea/weblogic700/server/lib/webserviceclient.jar
package cliente;
   
import utils.*;
   
public class Cliente {
  public static void main(String[] args) {
    if(args.length < 2) {
      System.out.println("Uso: java cliente.Cliente <wsdl> <nombre>");
      System.exit(0);
    }
   
    try {
      HolaPort serv = creaProxy(args[0]);
      System.out.println("Devuelve: " +  serv.saluda(args[1]));
   
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
   
  private static HolaPort creaProxy(String wsdl) {
    Hola_Impl serv = null;
   
    try {
      serv = new Hola_Impl(wsdl);
    } catch(Exception e) {}
   
    return serv.getHolaPort();
  }
}

La función creaProxy() es la que se encarga de obtener el stub para acceder al servicio. Como hemos generado el servicio a partir de un fichero EAR sin desplegar, el stub no conoce donde se encuentra desplegado realmente el servicio, por lo que se lo tendremos que decir explicitamente proporcionando como parámetro la URL del documento WSDL de dicho servicio ya desplegado.

El resto del código lo único que hace es acceder al servicio mediante el stub, de igual forma que si estuviese accediendo a un método de una clase Java de forma ordinaria.

javac -classpath ../comun/webserviceclient.jar:client.jar cliente/*.java

Debemos especificar en el CLASSPATH el fichero JAR generado para el cliente (client.jar) y el JAR con las clases necesarias para los clientes que nos proporciona Weblogic (webserviceclient.jar).

java -cp client.jar:../comun/webserviceclient.jar:. cliente.Cliente 
      http://localhost:7001/sw_hola/hola?WSDL  Miguel

Esto nos dará como resultado:

Devuelve: Hola Miguel
<target name="compile">
  <javac srcdir="."
    destdir="."
    includes="cliente/*.java">
    <classpath>
      <pathelement location="client.jar"/>
      <pathelement location="../comun/webserviceclient.jar"/>
    </classpath>
  </javac>
</target>

Utilizaremos por lo tanto el comando:

ant compile
<target name="run">
  <java classname="cliente.Cliente"
    fork="true">
    <arg value="http://localhost:7001/sw_hola/hola?WSDL"/>
    <arg value="${nombre}"/>
    <classpath>
      <pathelement location="client.jar"/>
      <pathelement location="../comun/webserviceclient.jar"/>
      <pathelement location="."/>
    </classpath>
  </java>
</target>

De esta forma, al ejecutar con ant deberemos proporcionar el valor de la propiedad ${nombre} que se utilizará como parámetro al ejecutar la aplicación:

ant run -Dnombre=Miguel

2. Realizar un cliente para el servicio de conversión euro-ptas. Seguir lo mismos pasos que en el ejercicio anterior, esta vez para desarrollar un cliente para el servicio de conversión entre euros y ptas desarrollado en la sesión anterior.

El cliente deberá tomar como parámetros el documento WSDL y un valor numérico en euros. Como resultado nos deberá mostrar a cuantas ptas corresponde dicha cantidad utilizando nuestro Servicio Web.

3. Vamos a desarrollar clientes para varios servicios de demostración ofrecidos por XMethods. Para ello deberemos acceder a

http://www.xmethods.net

Para consultar la información sobre los servicios que ofrece y obtener el documento WSDL que necesitamos para crear nuestros clientes.

a) Cliente para el servicio de cambio de moneda (Currency Exchange Rate). Tenemos los ficheros correspondientes a este cliente en el directorio currency de los ejercicios de la sesión. Para construirlo hemos seguido los siguientes pasos:

http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl

En nuestro caso hemos guardado este documento en el subdirectorio wsdl.

<target name="client">
  <clientgen
    wsdl="http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl"
    packageName="utils"
    clientJar="client.jar">
  </clientgen>
</target>
ant client

Con esto habremos generado el JAR client.jar con las clases de stub necesarias.

package cliente;
   
import utils.*;
   
public class Cliente {
  public static void main(String[] args) {
    if(args.length < 2) {
      System.out.println(
        "Uso: java cliente.Cliente pais_origen pais_destino");
      System.exit(0);
    }
   
    try {
      CurrencyExchangePortType    serv = creaProxy();
      float divisa = serv.getRate(args[0], args[1]);
      System.out.println("Cambio de " + args[0] + " a " + 
                          args[1] + " = " + divisa);
   
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
   
  private static CurrencyExchangePortType creaProxy() {
    CurrencyExchangeService_Impl serv = null;
   
    try {
      serv = new CurrencyExchangeService_Impl();
    } catch(Exception e) {}
   
    return serv.getCurrencyExchangePortType();
  }
}
javac -classpath client.jar:../comun/webserviceclient.jar 
    cliente/Cliente.java
java -cp client.jar:../comun/webserviceclient.jar:. 
    cliente.Cliente usa  euro

Nos devolverá algo similar a:

Cambio de usa a euro = 0.9424
<target name="compile">
  <javac srcdir="."
    destdir="."
    includes="cliente/*.java">
    <classpath>
      <pathelement location="client.jar"/>
      <pathelement location="../comun/webserviceclient.jar"/>
    </classpath>
  </javac>
</target>
ant compile
<target name="run">
  <java classname="cliente.Cliente"
    fork="true">
    <arg value="${pais1}"/>
    <arg value="${pais2}"/>
    <classpath>
      <pathelement location="client.jar"/>
      <pathelement location="../comun/webserviceclient.jar"/>
      <pathelement location="."/>
    </classpath>
  </java>
</target>
ant run -Dpais1=usa -Dpais2=euro

b) Consulta de la temperatura en EEUU, a partir del código postal (Weather - Temperature). Seguir los pasos del punto anterior para desarrollar este cliente. Como parámetro deberá tomar el código postal del área de la cual queremos obtener la temperatura. Por ejemplo:

ant run -Dzip=54124

Nos debería devolver algo como:

La temperatura en 54124 es 60.0

c) Construir un traductor español-inglés. Utilizar para ello el servicio que nos ofrece BabelFish. Proporcionaremos como parámetro una palabra en castellano, y nos deberá devolver la traducción al inglés.

Si nos fijamos en la documentación del servicio vemos que tiene una operación BabelFish que toma dos parámetros. El primero indica entre qué idiomas estamos traduciendo. En nuestro caso utilizaremos valor "es_en", que significa de español a inglés. Al pasarlo a Java, el nombre del método comenzará por minúscula, por lo que tendremos que invocar el método babelFish, en lugar de BabelFish.

Haremos que la aplicación tome como argumento de entrada la palabra que quedemos traducir. Por ejemplo:

ant run -Dpalabra=hola

Nos deberá devolver algo como:

La palabra hola en ingles es hello

4. Cliente para Amazon.com. Vamos a realizar un cliente que acceda a los Servicios Web que ofrece Amazon.com para realizar búsquedas de productos en esta tienda.

Este es un servicio bastante más complejo que los anteriores, por lo que necesitaremos documentación adicional para crear nuestro cliente. Podemos descargar un kit de desarrollo en la dirección:

http://associates.amazon.com/exec/panama/associates/join/developer/kit.html

Este kit incluye documentación y ejemplos, aunque no es necesario para desarrollar los servicios. Si que necesitaremos registrarnos como desarrolladores de servicios, y obtener un código (token) que nos identifique cuando utilizamos los servicios de Amazon. Este código puede ser obtenido en la página anterior.

Para las pruebas que vamos a realizar no es necesario descargar el kit ni obtener dicho código, ya que esto se proporciona implementado dentro de los ejercicios de la sesión, en el directorio amazon.

Utilizaremos directamente el documento WSDL que define los servicios de Amazon.com para generar las clases necesarias para nuestro cliente. Este documento se proporciona con los ejercicios, aunque también podremos descargarlo directamente desde Amazon en la dirección:

http://soap.amazon.com/schemas2/AmazonWebServices.wsdl

Si consultamos este documento, veremos que se definen tipos de datos propios. Por lo tanto, se deberán generar clases para representar estos tipos de datos.

ant client
mkdir temp
cd temp
jar xvf ../client.jar

Vemos que además de las clases de stub generadas para nuestro cliente, nos ha generado una serie de clases en el paquete com.amazon.soap, que encapsulan los distintos tipos de datos que deberemos utilizar al invocar sus métodos.

import com.amazon.soap.*;

Por ejemplo, para buscar películas por director podemos utilizar el siguiente código:

DirectorRequest dsr = new DirectorRequest(
  nombre_director,  
  pagina,    // La busqueda devuelve los productos en paginas
  modo,      // (DVD, VHS, etc)
  dev_tag,   // Token del desarrollador de servicios
  tipo,      // Puede devolver informacion reducida o detallada
  tag,       // Token del desarrollador de servicios
  ordenacion,// Tipo de ordenación del resultado
  locale);

Una vez hemos especificado en este objeto la búsqueda que vamos a realizar, podemos invocar el servicio con:

ProductInfo pi = serv.directorSearchRequest(dsr);

Esto nos devolverá un objeto ProductInfo del cual podemos obtener la información de los productos devueltos con:

Details [] d = pi.getDetails();

for(int i=0;i<d.length;i++) {
  System.out.println(d[i].getProductName() + ", " + d[i].getOurPrice());
}

A continuación se muestra el código de la aplicación cliente completo:

package cliente;
  
import utils.*;
import java.util.*;
import javax.xml.rpc.handler.*;
import javax.xml.rpc.Stub;
import javax.xml.namespace.QName;
import com.amazon.soap.*;
  
public class Cliente {
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println("Uso: ant run -Ddirector=nombre_director");
      System.exit(0);
    }
  
    System.out.println("Buscando peliculas de [" + args[0]  + "]");
  
    try {
      AmazonSearchPort  serv = creaProxy();

      DirectorRequest dsr = new DirectorRequest(args[0],  
                          "1", "dvd", "D3JV7SSUMSD7I5", "lite", 
                          "D3JV7SSUMSD7I5", null, null);
  
      ProductInfo pi = serv.directorSearchRequest(dsr);
      Details [] d = pi.getDetails();
  
      for(int i=0;i<d.length;i++) {
        System.out.println(d[i].getProductName());
      }
  
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  private static AmazonSearchPort creaProxy() {
    AmazonSearchService_Impl serv = null;
  
    try {
      serv = new AmazonSearchService_Impl();
    } catch(Exception e) {}
  
    return serv.getAmazonSearchPort();
  }
}
ant compile
ant run -Ddirector=lynch

Esto nos mostrará una lista de DVDs de películas dirigidas por David Lynch.

5. Recordemos la tienda de venta de DVDs que vimos en la sesión anterior. A partir de ahora vamos a empezar a dedicarnos a la importación de DVDs. Nuestro proveedor será Amazon.com, por lo que podremos ofrecer a nuestros clientes cualquier título que nos ofrezca esta compañía.

Vamos a hacer que nuestro servicio de búsqueda de películas coja directamente la información de Amazon.com, de forma que en todo momento ofreceremos un catálogo totalmente actualizado sin necesidad de tener que hacer nosotros este trabajo.

a) Adaptar el servicio de búsqueda de películas que vimos en la sesión anterior, para que en lugar de coger los datos de nuestra BD, se comporte como cliente de los servicios de Amazon.com para extraer la información directamente de allí. Este apartado se encuentra ya implementado en los ejercicios del curso, y vamos a ver como construirlo paso a paso.

<target name="amazon-client">
  <clientgen
    wsdl="wsdl/AmazonWebServices.wsdl"
    packageName="utils"
    clientJar="amazon.jar">
  </clientgen>
</target>
ant amazon-client
package cliente;
 
import java.util.*;
import javax.xml.rpc.handler.*;
import javax.xml.rpc.Stub;
import javax.xml.namespace.QName;
import com.amazon.soap.*;
import utils.*;
import tienda.*;
 
public class ClienteAmazon {
  public static DatosPelicula [] busca(String director) {
 
    ArrayList lista = new ArrayList();
   
    try {
      AmazonSearchPort serv = creaProxy();
 
      DirectorRequest dsr = new DirectorRequest(director, 
                           "1", "dvd", "D3JV7SSUMSD7I5", "lite", 
                           "D3JV7SSUMSD7I5", null, null);
 
      ProductInfo pi = serv.directorSearchRequest(dsr);
      Details [] d = pi.getDetails();
 
      for(int i=0;i<d.length;i++) {
        float precio = 0.0f;
 
        String sprecio = d[i].getOurPrice();
        if(sprecio!=null) {
          sprecio = sprecio.substring(1,sprecio.length());
 
          try {
            precio = Float.parseFloat(sprecio);
          } catch(Exception e) {}
        }
 
        lista.add(new DatosPelicula(d[i].getProductName(), 
                                         director, precio));
      }

    } catch (Exception e) { }
 
    DatosPelicula [] result = new DatosPelicula[lista.size()];
    lista.toArray(result);
 
    return result;
  }
 
  private static AmazonSearchPort creaProxy() {
    AmazonSearchService_Impl serv = null;
 
    try {
      serv = new AmazonSearchService_Impl();
    } catch(Exception e) {}
 
    return serv.getAmazonSearchPort();
  }
}
<target name="compile-client">
  <javac srcdir="."
    destdir="."
    includes="cliente/*.java">
    <classpath>
      <pathelement location="amazon.jar"/>
      <pathelement location="../comun/webserviceclient.jar"/>
    </classpath>
  </javac>
</target>
ant compile-client
package tienda;
 
import java.util.*;
import cliente.*;
 
public class BuscaDVD {
 
  public DatosPelicula [] buscaPorDirector(String director) {
    DatosPelicula [] result = ClienteAmazon.busca(director);
    return result;
  }
}

Vemos que ahora esta clase se apoya en la clase cliente generada anteriormente para acceder a los servicios de Amazon.

export CLASSPATH=$CLASSPATH:${dir.ejercicios}/ejercicios_3/tienda/
<target name="service" depends="compile_service">
<servicegen
  destEar="sw_tienda.ear"
  warName="sw_tienda.war">
    <service
      javaClassComponents="tienda.BuscaDVD"
      targetNamespace="http://www.rvg.ua.es/ws"
      serviceName="BuscaDVD"
      serviceURI="/busca"
      generateTypes="True"
      expandMethods="True">
    </service>
  </servicegen>
</target>
 
<target name="compile_service">
  <javac srcdir="."
    destdir="."
    includes="tienda/*.java">
  </javac>
</target>
ant service

Con esto habremos generado un fichero sw_tienda.ear, pero tenemos el problema de que sólo contiene la clase que implementa el servicio y los tipos de datos que se intercambian en la comunicación. Sin embargo, no tenemos las clases generadas para los clientes, por lo que deberemos incluirlas manualmente, ya que el servicio depende de ellas.

mkdir build-ear
mkdir build-war
cd build-ear
jar xvf ../sw_tienda.ear
cd ../build-war
jar xvf ../build-ear/sw_tienda.war
cp ../cliente WEB-INF/classes -R
mkdir WEB-INF/lib
cp ../amazon.jar WEB-INF/lib
jar cvf sw_tienda.war *
cp sw_tienda.war ../build-ear
cd ../build-ear
jar cvf sw_tienda.ear * 
cp sw_tienda.ear ..
rm -R build-war
rm -R build-ear

b) Necesitaremos tener un cierto margen de beneficios, por lo que deberemos ofrecer las películas a un precio algo superior al que las compramos en Amazon. Por lo tanto, los precios que mostraremos en nuestro catálogo serán un 10% más altos de los que ofrece Amazon. Introducir el código necesario en el servicio, para que realice de forma automática este aumento de precio.

c) Tenemos el problema de que nosotros compramos los DVDs de EEUU, donde los precios figuran en dólares, mientras que nuestros potenciales clientes se encuentran en España, donde se utiliza el euro. Por lo tanto, deberemos convertir los precios de dólares a euros. Podríamos establecer un valor fijo, y modificarlo manualmente según varien las divisas, pero como varían continuamente esto nos forzaría a estar actualizando este valor casi a diario. Para solucionar este problema podemos utilizar el servicio CurrencyExchangeService que nos ofrece XMethods para realizar la conversión.

Hacer que nuestro servicio de búsqueda se comporte como cliente del servicio de cambio de divisas para obtener este valor de cambio de dolar a euro actualizado en cada momento.

6. Análisis de documentos WSDL. Si queremos ser capaces de integrar servicios dinámicamente en nuestras aplicaciones, deberemos contar con algún método que nos permita obtener la información necesaria para invocar el servicio. Esta información está contenida en los documentos WSDL, por lo que podremos conseguirla si somos capaces de hacer que nuestra aplicación entienda estos documentos.

Utilizaremos la librería Java API for WSDL (WSDL4J) para analizar los documentos WSDL, y extraer de ellos la información necesaria.

a) Hacer una aplicación a la que se le pase como parámetro la URL del documento WSDL, y nos muestre como salida la lista de operaciones que define el servicio definido en este documento. Podemos encontrar una plantilla en el fichero AnalizaWSDL.java, dentro del directorio wsdl.

Para que compile deberemos tener en el CLASSPATH la librería wsdl4j.jar. Podemos:

javac -classpath wsdl4j.jar AnalizaWSDL.java
java -cp wsdl4j.jar:. AnalizaWSDL <URL_del_documento_WSDL>

NOTA: Para que la librería WSDL4J funcione correctamente es necesario utilizar JDK 1.4 o posteriores. Comprueba la versión de JDK antes de intentar compilar la aplicación para asegurarte.

b) Además de los nombres de las operaciones, mostrar para cada operación los tipos de datos de sus parámetros y el tipo del valor devuelto.

c) Si quisiesemos invocar estos servicios descubiertos de forma dinámica, mientras se ejecuta la aplicación, ¿qué método de invocación deberíamos usar?

d) La aplicación de ejemplo WSE (Web Services Explorer) nos permite ejecutar servicios de forma dinámica. Descargamos la aplicación desde la sección de recursos y la descomprimimos.

ant compile
ant run

Puedes consultar el código fuente como ejemplo de análisis de un documento WSDL e invocación de servicios mediante interfaz dinámica (DII).