Definición de colecciones de Java en xml (xsd, wsdl)


12
¡votar!

 Introducción a los Servicios Web en Java


Vistazo en Profundidad a SOAP

En esta página veremos algunos tópicos más avanzados incluyendo el manejo de tipos complejos en SOAP, el procesamiento de errores y las referencias remotas.

SOAP y los Tipos Complejos

Hasta ahora, nuestros Servicios Web sólo intercambiaban tipos de datos primitivos como strings, int o double. Ahora veremos cómo se traducen los tipos complejos en el mensaje SOAP.
El protocolo SOAP recomienda el uso de la llamada codificación SOAP para traducir tipos complejos a XML. Normalmente, las siguientes traduciones ocurren automáticamente:
  • Tipos primitivos de Java 2.
  • Clases personalizadas, que son mapeadas usando un patrón de JavaBean bien-conocido. Todos los campos públicos y los campos con métodos get/set son traducidos a XML mediante el serializador de reflexión Java.
  • Colecciones de Java 2 (HashMap, Hashtable, Vector, List etc.).
La siguiente demostración muestra el patrón de serialización JavaBean por defecto y la serialización de collection de Java 2.
Pasaremos una simple estructura OrderRequest al Servicio Web. Esta estructura está implementada con el JavaBean más simple posible, con métodos get y set para symbol, limitPrice y volume. El método de servicio processOrder acepta la clase Java OrderRequest como único parámetro. Mostraremos como la estructura OrderRequest se representa en el mensaje SOAP. También codificaremos el método getOrders que devuelve la colección de todos los pedidos aceptados desde el Servicio Web para el cliente. Usaremos el contenedor java.util.Hashtable como el valor de retorno para el método getOrders y veremos su representación XML.
En este ejemplo, continuamos con nuestro servicio de mercado de stocks añadiendo un sencillo servicio de pedidos:
package com.systinet.demos.mapping;

public class OrderService {

    private java.util.HashMap orders = new java.util.HashMap();
    
    public String processOrder(OrderRequest order) {
        String result = "PROCESSING ORDER";
        
        Long id = new Long(System.currentTimeMillis());
        
        result       += "\n----------------------------";
        result       += "\nID:             "+id;
        result       += "\nTYPE:           "+
                         ((order.getType()==order.ORDER_TYPE_SELL)?("SELL"):("BUY"));
        result       += "\nSYMBOL:         "+order.getSymbol();
        result       += "\nLIMIT PRICE:    "+order.getLimitPrice();
        result       += "\nVOLUME:         "+order.getVolume();
        
        this.orders.put(id,order);
        
        return result;
    }
    
    public java.util.HashMap getOrders() {
     return this.orders;
    }

}
Encontrarás todos los scripts en el subdirectorio bin del archivo de las fuentes
Compilaremos y desplegaremos el servicio de pedidos ejecutando el script deployMapping.bat. La aplicación del lado del cliente simplemente crea dos solicitudes de pedido y las envía al Servicio Web. Luego recupera el Hashtable relleno con estas dos solicitudes y las muestra en la consola. Veamos el código de la aplicación cliente, donde de nuevo estamos especulando con stocks de tecnología:
package com.systinet.demos.mapping;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;

public final class TradingClient {

    public static void main( String[] args ) throws Exception {
        
      WebServiceLookup lookup = 
        (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);
      OrderServiceProxy service = 
        (OrderServiceProxy)lookup.lookup("http://localhost:6060/MappingService/",
          OrderServiceProxy.class);

      com.systinet.demos.mapping.struct.OrderRequest order = 
        new com.systinet.demos.mapping.struct.OrderRequest();
      order.symbol = "SUNW";
      order.type = com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_BUY;
      order.limitPrice = 10;
      order.volume = 100000;
      String result = service.processOrder(order);
      
      System.out.println(result);
      
      order = new com.systinet.demos.mapping.struct.OrderRequest();
      order.symbol = "BEAS";
      order.type = com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_BUY;
      order.limitPrice = 13;
      order.volume = 213000;
      result = service.processOrder(order);
      
      System.out.println(result);
      
      java.util.HashMap orders = service.getOrders();
      
      java.util.Iterator iter = orders.keySet().iterator();
      
      while(iter.hasNext()) {
       Long id = (Long)iter.next();
       OrderRequest req = (OrderRequest)orders.get(id);
       System.out.println("\n----------------------------");
        System.out.println("\nID:             "+id);
        System.out.println("\nTYPE:           "+
((req.getType()==com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_SELL)?("SELL"):("BUY")));
        System.out.println("\nSYMBOL:         "+req.getSymbol());
        System.out.println("\nLIMIT PRICE:    "+req.getLimitPrice());
        System.out.println("\nVOLUME:         "+req.getVolume());
      }
      
    }

}

Mapeo de Tipos Complejos -- Mirada en Profundidad

La primero a descubrir es el fichero WSDL que se generó en tiempo de despliegue. Si hemos desplegado el servicio mapeado, podemos ver el fichero WSDL completo en http://localhost:6060/MappingService/
Como recordarás de la página anterior, el WSDL describe qué funcionalidad ofrece el Servicio Web, cómo se comunica, y dónde se puede acceder. WSDL proporciona un mecanismo estructurado para describir las operaciones que puede realizar un Servicio Web. En nuestro ejemplo, la parte más importante es la definición del mapeo del tipo Java OrderRequest:

  
    
    
    
    
  

El mapeo de OrderRequest a XML está definido como un conjunto de campos de tipos primitivos. El HashMap devuelto por el mapeo getOrders se importa como el tipo de dato http://idoox.com/containers:HashMap. Nuestro fichero WSDL importa la siguiente definición:

  
    
      
        
           
           
        
      
    
  

Ahora veamos los mensajes SOAP intercambiados entre el cliente y el Servicio Web. Primero arrancaremos el trazado de mensajes en el servidor SOAP abriendo la consola de administración en http://localhost:6060/admin/console en el navegador HTTP, pulsando en el botón Refresh y pulsando sobre el enlace enable de la sección MappingService de la consola. Luego ejecutaremos la aplicación cliente, llamando al script runMappingClient.bat y veremos los mensajes SOAP. El mensaje de abajo es una invocación del método processOrder con un ejemplar de OrderRequest como parámetro:
  
    
      
        
          10.0
          SUNW
          1
          100000
        
      
    
  
El siguiente mensaje representa el valor de retorno (un HashMap relleno con las solicitudes de pedido procesadas) del método getOrders:
  
    
        
          
            
              1006209071080
              
                100000
                SUNW
                10.0
                1
              
            
          
        1006209071130
        
          213000
          BEAS
          13.0
          1
          
      
    
  
El mapeo de Java a XML es sencillo, Podemos ver la definición exterior de HashMap con los elementos clave y valor. Observa que hay una definición XML interna del tipo de dato OrderRequest.
Como paso final eliminaremos el servicio web del servidor ejecutando el script undeployMapping.bat.

 Procesamiento de Errores SOAP

Cuando las cosas van mal, SOAP define una construción llamada SOAP Fault XML que representa un error que ha ocurrido en el lado del servidor. Brevemente presentamos estos mensajes de error en la página anterior. Aquí los examinaremos en más detalle. El Fallo SOAP contiene tres elementos básicos:
  • FAULTCODE con contiene el código del error o ID.
  • FAULTSTRING que contiene una breve descripción del error.
  • DETAIL que describe el error en más detalle.
Para ilustrar el procesamiento de errores añadiremos algunas excepciones a nuestro ejemplo del servicio de stocks. Primero, haremos que el método getQuote lance una StockNotFoundException si no puede encontrar unos de nuestros tres stocks:
package com.systinet.demos.fault;

public class StockQuoteService {

    public double getQuote(String symbol) throws StockNotFoundException {
        if(symbol!=null && symbol.equalsIgnoreCase("SUNW"))
            return 10;
        if(symbol!=null && symbol.equalsIgnoreCase("MSFT"))
            return 50;
        if(symbol!=null && symbol.equalsIgnoreCase("BEAS"))
            return 11;
        throw new StockNotFoundException("Stock symbol "+symbol+" not found.");    
    }
    
    public java.util.LinkedList getAvailableStocks() {
        java.util.LinkedList list = new java.util.LinkedList();
        list.add("SUNW");
        list.add("MSFT");
        list.add("BEAS");
        return list;
    }
}
Luego desplegaremos el Servicio Web usando el script de línea de comandos deployFault.bat.
Usaremos el trazado de mensajes SOAP en el servidor siguiendo los pasos que hemos visto anteriormente. Ahora chequearemos el fichero WSDL generado por el servidor SOAP. Arimos la URL http://localhost:6060/StockQuoteService/ en nuestro navegador.
Observa la definición del mensaje de fallo SOAP en el fichero WSDL:

  

El mensaje de fallo es referenciado en la operación getQuote en el elemento portype del WSDL:

  
  
  

y en el elemento binding:

  
  
    
  
  
    
  
  
    
  

Sumarizando, la excepción Java del lado del servidor se representa con un simple mensaje SOAP que se devuelve al lado del cliente si ocurre la excepción.
El siguiente paso es crear una aplicación cliente del Servicio Web:
package com.systinet.demos.fault;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;

public final class StockClient {

    public static void main( String[] args ) throws Exception {
        
      // lookup service
      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(
                Context.WEBSERVICE_LOOKUP);
      // bind to StockQuoteService
      StockQuoteServiceProxy quoteService = (StockQuoteServiceProxy)lookup.lookup(
        "http://localhost:6060/StockQuoteService/",
        StockQuoteServiceProxy.class
      );
      
      // use StockQuoteService
      System.out.println("Getting available stocks");
      System.out.println("------------------------");
      java.util.LinkedList list = quoteService.getAvailableStocks();
      java.util.Iterator iter = list.iterator();
      while(iter.hasNext()) {
         System.out.println(iter.next());
      }
      System.out.println("");
      
      System.out.println("Getting SUNW quote");
      System.out.println("------------------------");
      System.out.println("SUNW "+quoteService.getQuote("SUNW"));
      System.out.println("");
      
      System.out.println("Getting IBM quote (warning, this one doesn't exist, 
                                      so we will get an exception)");
      System.out.println("------------------------");
      System.out.println("SUNW "+quoteService.getQuote("IBM"));
      System.out.println("");
    }
}
Necesitaremos generar el interface del cliente Java, compilar todas las clases y ejecutar la aplicación cliente. Todas estás tareas se llevan a cabo en el script runFaultClient.bat.
Nuestra cartera de stocks es bastante pobre y no incluye IBM. El cliente primero debería mostrar todos los simbolos disponibles, recuperar el valor del símbolo SUNW y finalmente lanzar la excepción StockNotFoundException indicando que no se ha encontrado el símbolo IBM. Ahora veamos la consola de adminstración (en http://localhost:6060/admin/console) y pulsemos en el enlace show SOAP conversation. En una nueva ventana se mostrará el siguiente mensaje (las partes importantes están en negrita):
==== INPUT ==== http://localhost:6060/StockQuoteService/ 
==== 11/14/01 4:44 PM =


    
      
          IBM
        
    

==== CLOSE 
=====================================================================

==== 
OUTPUT ==== http://localhost:6060/StockQuoteService/ 
======================

 
    
      
            ns0:Server 
            Stock symbol IBM not found. 
             
               
                   
                     com.systinet.demos.fault.StockNotFoundException: 
                        Stock symbol IBM not found.
                     at com.systinet.demos.fault.StockQuoteService.
                        getQuote(StockQuoteService.java:24) 
                     at java.lang.reflect.Method.invoke(Native Method) 
                     at com.idoox.wasp.server.adaptor.JavaAdaptorInvoker.
                        invokeService(JavaAdaptorInvoker.java:387)
                     at com.idoox.wasp.server.adaptor.JavaAdaptorInvoker.
                        invoke(JavaAdaptorInvoker.java:239) 
                     at com.idoox.wasp.server.adaptor.JavaAdaptorImpl.
                        dispatch(JavaAdaptorImpl.java:164) 
                     at com.idoox.wasp.server.AdaptorTemplate.
                        dispatch(AdaptorTemplate.java:178) 
                     at com.idoox.wasp.server.ServiceConnector.
                        dispatch(ServiceConnector.java:217) 
                     at com.idoox.wasp.server.ServiceManager.
                        dispatch(ServiceManager.java:231) 
                     at com.idoox.wasp.server.ServiceManager$DispatcherConnHandler.
                        handlePost(ServiceManager.java:1359)
                     at com.idoox.transport.http.server.Jetty$WaspHttpHandler.
                        handle(Jetty.java:94) 
                     at com.mortbay.HTTP.HandlerContext.
                        handle(HandlerContext.java:1087) 
                     at com.mortbay.HTTP.HttpServer.
                        service(HttpServer.java:675) 
                     at com.mortbay.HTTP.HttpConnection.
                        service(HttpConnection.java:457) 
                     at com.mortbay.HTTP.HttpConnection.
                        handle(HttpConnection.java:317) 
                     at com.mortbay.HTTP.SocketListener.
                        handleConnection(SocketListener.java:99) 
                     at com.mortbay.Util.ThreadedServer.
                        handle(ThreadedServer.java:254) 
                     at com.mortbay.Util.ThreadPool$PoolThreadRunnable.
                        run(ThreadPool.java:601) 
                     at java.lang.Thread.run(Thread.java:484) 
               
             
          
       
   

==== CLOSE 
=====================================================================
Observa el mensaje SOAP de salida y en particular la construcción FAULT. El FAULTCODE contiene el código de error genérico, el elemento FAULTSTRING contiene el mensaje de excepción y el elemento DETAIL contiene todo el seguimiento de pila de la excepción.
Finalmente podemos eliminar el servicio usando el script undeployFault.bat.

Referencias Remotas

Las referencias remotas son una construcción usada en la mayoría de sistemas de objetos distribuidos, como RMI, CORBA y DCOM. Asume que tenemos un cliente que está invocando métodos de un objeto servidor. Aquí está como trabajan. Digamos que el objeto servidor crea otro objeto, y que tiene alguna forma de pasar el nuevo objeto al cliente (ver la siguiente figura). Puede pasar el nuevo objeto por "valor" o por "referencia". Si pasamos el objeto por valor, estamos pasando todo el objeto. Si lo pasamos por referencia, sólo pasamos un puntero al objeto. Una referencia remota es un referencia que funciona a través de la red. Las referencias remotas son críticas para muchos patrones de diseño en la programación distribuida, particularmente en el patrón Factory. A pesar de ser una característica crítica para muchas aplicaciones distribuidas, no todas las implementaciones SOAP la soportan.
Por ejemplo, podríamos definir un método createLineItem en un Servicio Web de Pedidos. Este método creará un nuevo objeto LineItem que contendrá el número de catálogo y el precio del producto pedido y la cantidad pedida. El Pedido potencialmente referenciará muchos objetos LineItem. El objeto LineItem debería ser devuelto a la aplicación cliente como una referencia remota para poder especificar todos los datos necesarios.

Implementar Sencillas Referencias Remotas

Para ilustrar la característica de la referencias remotas vamos a crear un nuevo ejemplo, y gastaremos nuestras ganancias del mercado de stocks pidiendo algunos productos. Empezaremos con la definición de dos interfaces Java: Order y LineItem que usaremos para referenciar el Servicio Web en el lado del cliente:
package com.systinet.demos.interref;

public interface LineItem extends java.rmi.Remote {

   public String getID();
   
   public long getCount();
   
   public String getProductID();
   
   public void close();

}
y
package com.systinet.demos.interref;

public interface Order {

   public LineItem addItem(String productID, long count);
   
   public LineItem getItem(String id);
   
   public void removeItem(String id);
   
}
Observa que el interface LineItem extiende el interface java.rmi.Remote. Este es el método más simple de manejar referencias remotas usando WASP. Por otro lado, el interface LineItem es bastante obvio. El método addItem del interface Order crea y devuelve un nuevo ítem de pedido. El método getItem devuelve un ítem existente y removeItem elimina el ítem especificado del pedido.
Ahora implementaremos los dos interfaces LineItem y Order:
package com.systinet.demos.interref;

import org.idoox.webservice.server.WebServiceContext;
import org.idoox.webservice.server.LifeCycleService;

public class LineItemImpl implements LineItem {

    private String pid;
    private String id;
    private long count;
    
    
    public LineItemImpl(String pid, long count) {
        System.err.println("Creating new LineItem.");
        this.id = pid+System.currentTimeMillis();
        this.pid = pid;
        this.count = count;
    }
    
    public void close() {
        System.err.println("close()");
        WebServiceContext context = WebServiceContext.getInstance();
        LifeCycleService lc = context.getLifeCycleService();
        lc.disposeServiceInstance(this);
    }
    
    public long getCount() {
        System.err.println("getCount()");
        return this.count;
    }
    
    public String getProductID() {
        System.err.println("getProductID()");
        return this.pid;
    }
    
    public String getID() {
        System.err.println("getID()");
        return this.id;
    }
    
}
y
public class OrderImpl implements Order {

    private java.util.HashMap items = new java.util.HashMap();

    public LineItem getItem(String id) {
        return (LineItem)this.items.get(id);
    }

    public LineItem addItem(java.lang.String pid, long count) {
        LineItem item = new LineItemImpl(pid, count);
        this.items.put(item.getID(), item);
        return item;
    }

    public void removeItem(java.lang.String id) {
        LineItem item = (LineItem)this.items.remove(id);
        item.close();
    }

}
Una implementación estándar, sin sorpresas. Observa que LineItem imprime mensajes de seguimiento de llamadas a métodos. El código del cliente también es bastante estándar:
package com.systinet.demos.interref;

import javax.wsdl.QName;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;

public final class OrderClient {

    public static void main( String[] args ) throws Exception {
        
      // lookup service
      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);
      
      Order order = (Order)lookup.lookup("http://localhost:6060/OrderService/", 
                new QName("http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/interref/", 
                "OrderService"),
                "OrderImpl", Order.class);
      
      String id1 = order.addItem("THNKPDT23", 2).getID();
      String id2 = order.addItem("THNKPDT22", 2).getID();
      
      System.out.println("ID1 "+id1);
      System.out.println("ID2 "+id2);
      
      LineItem item = order.getItem(id1);
      
      System.out.println("Line ITEM");
      System.out.println("---------");
      System.out.println("ID:         "+item.getID());
      System.out.println("Product ID: "+item.getProductID());
      System.out.println("Count:      "+item.getCount());
     
      item = order.getItem(id2);
      
      System.out.println("Line ITEM");
      System.out.println("---------");
      System.out.println("ID:         "+item.getID());
      System.out.println("Product ID: "+item.getProductID());
      System.out.println("Count:      "+item.getCount());
    }

}
Esta simple aplicación cliente crea el proxy dinámico para el Servicio Web de pedidos y luego crea dos ítems de pedido, THNKPDT23 y THNKPDT22. Deberíamos ver en la consola del servidor que todas las llamadas a métodos de estos ítems de pedidos tienen lugar en el lado del servidor. Porque estos dos ítems se han creado dinámicamente en el servidor, mientras que el cliente sólo obtiene sus referencias. Este es un buen ejemplo de patrón Factory mencionado anteriormente. En nuestro caso, el servicio de pedidos actúa como la factoría para los dos ítems del pedido.
Observa que las líneas de ítem tienen estado, por lo que pueden mantener datos particulares del ítem.

Borrar Referencias Remotas

Al contrario que los Servicio Web sin estado, los servicios web con estado requieren código de manejo especiales para borrar elementos. Nosotros usamos explicitamente la eliminación de ejemplares en nuestro ejemplo. Esto se hace llamando al método disposeServiceInstance sobre el Servicio Web LifeCycle del sistema. Puedes ver el método close de LineItemImpl para ver como borrar explícitamente un servicio:
public void close() {
  System.err.println("close()");
  WebServiceContext context = WebServiceContext.getInstance();
  LifeCycleService lc = context.getLifeCycleService();
  lc.disposeServiceInstance(this);
}
El último paso es elimiar el servicio usando el script undeployInterref.bat.