CustomBinding en ServiceEndpoint con el contrato ‘IServicioWeb’ carece de TransportBindingElement

El otro día generando un cliente .NET para un servicio web JAXWS basado en SOAP 1.2 me topé con el siguiente problema:

System.InvalidOperationException: 
CustomBinding en ServiceEndpoint con el contrato 'IServicioWeb' carece de TransportBindingElement. 
Todos los enlaces deben tener al menos un elemento de enlace que se derive de TransportBindingElement.

O lo que es lo mismo pero en la lengua de Shakespeare:

System.InvalidOperationException: 
The CustomBinding on the ServiceEndpoint with contract 'Contract Name' lacks a TransportBindingElement. 
Every binding must have at least one binding element that derives from TransportBindingElement.

El servicio web se había generado desde Visual Studio utilizando la herramienta Add Service Reference, que por debajo hace uso de SvcUtil.exe, siguiendo los pasos que en su momento dejé recogidos en la entrada «Generando clientes de servicios web JAX-WS desde .NET».

Leyendo detenidamente el mensaje de error, se llega a la conclusión de que la sección customBinding no tiene referencia a ningún TransportBindingElement.

TransportBindingElement es una clase abstracta que representa un tipo de binding (enlace) de transporte. Los tipos de binding de transporte más comunes son:

  • Peer to peer – PeerTransportBindingElement
  • Http – HttpTransportBindingElement
  • Https – HttpsTransportBindingElement
  • TCP – TcpTransportBindingElement
  • Named Pipe – NamedPipeTransportBindingElement
  • MS MQ – MsmqTransportBindingElement
  • MS MQ Integration – MsmqIntegrationBindingElement
  • Connection Oriented – ConnectionOrientedTransportBindingElement

En este caso, el acceso al servicio web se realiza utilizando protocolo HTTP, por lo que hay que añadir una referencia a HttpTransportBindingElement en la configuración del cliente.

La manera simple de incluir esta información es en el fichero app.config de la aplicación.

Tras la generación con la herramienta SvcUtil.exe de Visual Studio el app.config tiene el siguiente aspecto:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8080/ServicioWebWSImpl/ServicioWeb"
                binding="customBinding" bindingConfiguration="ServicioWebPortBinding"
                contract="ServicioWebSR.ServicioWeb" name="ServicioWebPort" />
        </client>
    </system.serviceModel>
</configuration>

La solución al problema es añadir la configuración <httpTransport> en la definición del <CustomBinding> del fichero app.config.

La sintaxis de esta configuración es:

<httpTransport
    allowCookies=Boolean"
    authenticationScheme="Digest/Negotiate/Ntlm/Basic/Anonymous"
    bypassProxyOnLocal=Boolean"
    hostnameComparisonMode="StrongWildcard/Exact/WeakWildcard"
    keepAliveEnabled="Boolean"
    maxBufferSize="Integer"
    proxyAddress="Uri"
    proxyAuthenticationScheme="None/Digest/Negotiate/Ntlm/Basic/Anonymous"
IntegratedWindowsAuthentication: Specifies Windows authentication"
    realm="String"
    transferMode="Buffered/Streamed/StreamedRequest/StreamedResponse"
        unsafeConnectionNtlmAuthentication="Boolean"
        useDefaultWebProxy="Boolean" />  
  • allowCookies – Un valor booleano que especifica si el cliente acepta las cookies y las propaga en solicitudes futuras. El valor predeterminado es false.
  • authenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un agente de escucha HTTP. El valor predeterminado es anónimo. Protocolos de autenticación válidos pueden ser:
    • Digest: especifica la autenticación implícita.
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM.
    • Basic: especifica la autenticación básica.
    • Anonymous: especifica la autenticación anónima.
  • bypassProxyOnLocal – Un valor booleano que indica si se omitirá el servidor proxy para las direcciones locales. El valor predeterminado es false.
  • hostnameComparisonMode – Especifica el modo de comparación de HostName HTTP usado para analizar los URI. El valor predeterminado es StrongWildcard. Para analizar las URIs se puede hacer de tres maneras:
    • StrongWildcard: («+») coincide con todos los posibles nombres del host en el contexto del esquema especificado, puerto y URI relativo.
    • Exact: ningún carácter comodín
    • WeakWildcard: («*») coincide con todo posible nombre del host en el contexto del esquema especificado, puerto y URI relativo con los que no se han coincidido explícitamente o a través del mecanismo del carácter comodín fuerte.
  • keepAliveEnabled – Un valor booleano que especifica si se debe establecer una conexión continua con el recurso de Internet.
  • maxBufferSize – Un entero positivo que especifica el tamaño máximo del búfer. El valor predeterminado es 524288.
  • proxyAddress – Un URI que especifica la dirección del proxy HTTP. Si useSystemWebProxy es true, este valor debe ser null. El valor predeterminado es null.
  • proxyAuthenticationScheme – Especifica el protocolo utilizado para autenticar solicitudes de cliente que son procesadas por un proxy HTTP. La opción por defecto es Anonymous. Las diferentes posibilidades de autenticación son:
    • None: no se lleva a cabo ninguna autenticación.
    • Digest: especifica la autenticación implícita
    • Negotiate: negocia con el cliente para determinar el esquema de autenticación. Si cliente y el servidor son compatibles con Kerberos, se utiliza; de lo contrario, se utiliza NTLM
    • Ntlm: especifica la autenticación NTLM
    • Basic: especifica la autenticación básica
    • Anonymous: especifica la autenticación anónima
    • IntegratedWindowsAuthentication: especifica la autenticación de Windows
  • realm – Una cadena que especifica el dominio kerberos que se utilizará en el proxy/servidor. El valor predeterminado es una cadena vacía.
  • transferMode – Especifica si los mensajes se almacenan en búfer, se transmiten o si son una solicitud o una respuesta. El valor por defecto es Buffered. Las diferentes opciones de transferencia son:
    • Buffered: los mensajes de respuesta y solicitud están almacenados en búfer.
    • Streamed: se transmiten los mensajes de solicitud y respuesta
    • StreamedRequest: se transmite el mensaje de solicitud y el mensaje de respuesta está almacenado en búfer
    • StreamedResponse: se transmite el mensaje de respuesta y el mensaje de solicitud está almacenado en búfer
  • unsafeConnectionNtlmAuthentication – Un valor booleano que especifica si la conexión compartida no segura está habilitada en el servidor. El valor predeterminado es false. Si está habilitado, la autenticación NTLM se realiza una vez en cada conexión TCP.
  • useDefaultWebProxy – Un valor que especifica si se utiliza la configuración del proxy del equipo en lugar de la configuración específica del usuario. El valor predeterminado es true.

Conclusión

Todavía no tengo claro porque la generación automática del cliente no incluye la definición del tipo de transporte a utilizar en la comunicación. Lo que si tengo claro es que la solución es tan simple como añadir esta definición en el fichero de configuración de la aplicación.

        <bindings>
            <customBinding>
                <binding name="ServicioWebPortBinding">
                    <textMessageEncoding messageVersion="Soap12" />
			<httpTransport />
                </binding>
            </customBinding>
        </bindings>

Para este caso, y en vista de que la comunicación se hace por un canal HTTP sin proxy ni ninguna cosa rara, basta con añadir la etiqueta <httpTransport /> para solucionar el problema. Si existiera algún proxy entre el cliente y el servicio web, o algún sistema de autenticación, o se quisiera limitar las llamadas, se pueden ir añadiendo las posibilidades descritas a esta configuración.

Si la comunicación no fuera HTTP habría que generar otro tipo de binding de transporte, con sus propias configuraciones explicadas en la biblioteca MSDN de Microsoft.

Links

Generando clientes de servicios web JAX-WS desde Java.

Hace poco publiqué una entrada donde comentaba la generación de clientes de servicios web JAX-WS desde .NET. Ha llegado el momento de hacer una entrada paralela para explicar el mismo procedimiento pero desde el punto de vista Java.

Existen varios métodos para generar clientes para un servicio web basado en tecnología JAX-WS 2.x, yo me voy a centrar en la herramienta wsimport que viene incluida en la implementación de referencia que Glassfish/Metro proporciona de la especificación.

La implementación de referencia.

La especificación JSR 224 – Java API for XML-Based Web Services establece las bases para trabajar con servicios web que utilizan XML para comunicarse. Como pasa con todas las especificaciones puede haber varias implementaciones, pero siempre hay una que se elige como la implementación de referencia (RI – Reference Implementation).

En el caso de ésta especificación la implementación de referencia la aporta Metro. La RI que yo voy a utilizar es la versión JAX-WS 2.1.9, por que se acerca mucho a la versión 2.1.5 que es la que incluye de saque el servidor de aplicaciones Weblogic 11g (parche 10.3.6), que es el que estoy utilizando para realizar estas pruebas.

En teoría, se podría utilizar cualquier versión de la implementación de referencia teniendo en cuenta que si difiere de la que incorpora weblogic habrá que incluir las librerías correctas en la aplicación y tal vez marcar la opción prefer-web-inf-classes en el descriptor weblogic.xml de nuestra aplicación.

La implementación de referencia se descarga de la página jax-ws.java.net (ver sección Links) y va empaquetada en un fichero JAR que si ejecutamos nos muestra un acuerdo de licencia y descomprime en la carpeta donde se ejecuta el contenido de la implementación.

La sintaxis de la herramienta wsimport

Los ejecutables de esta herramienta son insustancialmente diferentes en windows y en linux. En windows la herramienta está en \bin\wsimport.bat y en linux se puede encontrar en /bin/wsimport.sh.

La sintaxis en ambos sistemas es la misma:

wsimport [options] <wsdl>

<wsdl> indica una url que permita acceder al WSDL del servicio web para el que vamos a generar el cliente. Esta url puede ser tanto un recurso local, como un recurso obtenido mediante protocolo http.

[options] es el apartado donde se pueden incluir diferentes opciones que variarán el comportamiento de la herramienta. A continuación se presenta una lista completa de estas opciones:

  • -d <directory> : Indica el directorio de salida donde se dejan las clases compiladas. Si no se utiliza esta opción las clases compiladas se dejarán en el mismo directorio desde el que se llama a wsimport.
  • -b <path> : Añade ficheros XSD adicionales que se puedan necesitar en los binding jaxws/jaxb del servicio web.
  • -B <jaxbOption>
  • -catalog <file> : Especifica un fichero de catálogo que resuelve las referencias a entidades externas. Los formatos de catálogo soportados son: TR9401, XCatalog y OASIS XML Catalog.
  • -extension : Permite extensiones de terceros. Esta funcionalidad no está soportada por la especificación, por lo que el cliente generado puede no ser portable o permitir la interoperabilidad entre plataformas.
  • -help : Muestra una ayuda con el listado de las opciones.
  • -httpproxy:<host>:<port> : Si para acceder a la URL del WSDL que queremos generar hay que viajar a través de un proxy, con esta opción podremos indicar su configuración. Si no se rellena el puerto, por defecto será el 8080.
  • -keep : Si se incluye esta opción los fuentes que generan los compilados del cliente no se borran.
  • -p : Especifica el paquete java de las clases del cliente generado. Si se indica esta opción no se tendrán en cuenta; ni el nombre de paquete que puede incluirse en el wsdl, ni el nombre de paquete por defecto que se genera cuando no se indica esta opción.
  • -s <directory> : Especifica un directorio donde se guardan los ficheros de código fuente generados.
  • -verbose : Muestra los mensajes del compilador indicando las tareas que está realizando.
  • -version : Muestra un mensaje informativo con la versión de la implementación de referencia que se está utilizando.
  • -wsdllocation <location>
  • -target : Genera el código para la versión JAX-WS indicada. La versión 2.0 genera código compatible con la especificación JAX-WS 2.0.
  • -quiet: Elimina cualquier salida que se pueda generar. Útil para generaciones automatizadas de clientes.

Clases generadas

La herramienta wsimport genera las clases necesarias para poder invocar a las operaciones del servicio web de forma correcta. Las clases que se generan siguen siempre el siguiente criterio:

  • Clase PortType. Una clase que lleva el mismo nombre que el atributo name del elemento porttype del wsdl y contiene un método por cada operación definida con los elementos operation.
  • Clase Service. Una clase que lleva el mismo nombre que el atributo name del elemento service del wsdl. Esta clase accede al servicio web y permite instanciar la clase PortType.
  • Por cada operación definida en el portType
    • Tantas clases como sean necesarias para rellenar los Input
    • Tantas clases como sean necesarias para devolver el resultado de la operación
  • Clase ObjectFactory. Esta clase facilita la instanciación interna de las clases input y response.
  • Clase package-info. Anota el paquete java para que los objetos generados a partir del xsd del wsdl estén correctamente ubicados.

Una generación de ejemplo.

En la entrada Creando un servicio web mediante anotaciones JAX-WS utilizando un enfoque ascendente (bottom-up) generaba un servicio web calculadora que permitía hacer las operaciones aritméticas básicas: suma, resta, multiplicación y división. Este servicio web está publicado en un servidor de aplicaciones weblogic que tengo para pruebas, el WSDL de acceso es: http://localhost:7001/JaxWSEjemploWAR/CalculadoraService?WSDL. El ejemplo que voy a generar se basa en las operaciones que proporciona este servicio web.
Mediante Eclipse creo un proyecto java básico que servirá para contener las clases que van a hacer uso del cliente y las propias clases generadas con la herramienta wsimport. Este proyecto se encuentra en una carpeta local de mi ordenador, E:\srv\entorno\aplic\wk-pruebas-jaxws\JaxWSClientEjemplo\src. Añado la opción -d  informada con este directorio.
A la hora de realizar la generación del cliente es interesante guardar el código fuente generado. Incluyo la opción -keep.
Las clases generadas para mantener un orden quiero que se creen bajo el paquete java egv.jaxws.clientes.calculadora. Añado la opción -p con el paquete indicado.
Finalmente, me gustaría que el compilador me vaya indicando información sobre las operaciones que va realizando. Añado la opción -verbose.
Ejecución del comando wsimport para el servicio web CalculadoraService
Tras la ejecución se puede ver en el proyecto de Eclipse las clases generadas a través de la herramienta.
  • CalculadoraPortType. La clase que contiene los métodos con las operaciones del servicio web.
  • CalculadoraService. La clase que comunica con el servicio web y que permite instanciar la clase CalculadoraPortType.
  • Division y DivisionResponse. Las clases de entrada y salida para la operación division().
  • Multiplicacion y MultiplicacionResponse. Las clases de entrada y salida para la operación multiplicacion().
  • Suma y SumaResponse. Las clases de entrada y salida para la operación suma().
  • Resta y RestaResponse. Las clases de entrada y salida para la operación resta().
  • ObjectFactory. Clase que permite utilizar internamente las clases de entrada y salida.
  • package-info. Clase que indica el paquete sobre el que se han generado las clases del cliente.
Cliente generado por wsimport en proyecto de Eclipse

Finalmente, he generado una clase de ejemplo CalculadoraCompra.java que calcula mediante operaciones aritméticas el resultado de la lista de la compra. Como el servicio web solo permite manejar enteros, las operaciones no manejan decimales y el resultado no es muy exacto, pero es ilustrativo del uso del servicio web.

public static void main(String[] args) {
  Integer pan = new Integer (1);
  Integer leche = new Integer (2);
  Integer carne = new Integer (6);
  Integer lentejas = new Integer (3);

  Integer totalCompra = new Integer(0);

  CalculadoraService cs = new CalculadoraService();
  CalculadoraPortType cpt = cs.getCalculadoraPortTypePort();
  totalCompra = cpt.suma(pan, leche);
  totalCompra = cpt.suma(totalCompra, carne);
  totalCompra = cpt.suma(totalCompra, lentejas);

  Integer ivaDeLaCompra = new Integer(0);
  Integer totalCompraConIva = new Integer(0);
  Integer iva = new Integer(21);

  ivaDeLaCompra = cpt.multiplicacion(totalCompra, iva);
  ivaDeLaCompra = cpt.division(ivaDeLaCompra, new Integer(100));
  totalCompraConIva = cpt.suma(totalCompra, ivaDeLaCompra);

  System.out.println("Pan: " + pan.toString() + "€");
  System.out.println(" + ");
  System.out.println("Leche: " + leche.toString() + "€");
  System.out.println(" + ");
  System.out.println("Carne: " + carne.toString() + "€");
  System.out.println(" + ");
  System.out.println("Lentejas: " + lentejas.toString() + "€");
  System.out.println(" = ");
  System.out.println("Total SIN IVA: " + totalCompra.toString() + "€");
  System.out.println("Total PVP (" + iva.toString() + "%): " + totalCompraConIva.toString() + "€");
}

El único punto en el que voy a hacer incapié es para subrayar los pasos necesarios para invocar a las operaciones del servicio web que se encuentran en la clase CalculadoraPortType. Esta clase es un interfaz por lo que no se puede crear una instancia, la forma correcta de obtenerla es instanciar la clase del servicio web CalculadoraService y posteriormente obtener el porttype con el método correspondiente getCalculadoraPortTypePort().

CalculadoraService cs = new CalculadoraService();
CalculadoraPortType cpt = cs.getCalculadoraPortTypePort();

El resultado de la ejecución del ejemplo se puede ver en la siguiente imagen.

Resultado de la ejecución del ejemplo que hace uso del cliente de servicios web.

Links

Generando clientes de servicios web JAX-WS desde .NET

Es normal en mi trabajo que los servicios web que hacemos estén basados en tecnología Java y que, sin embargo, sean utilizados desde clientes con tecnología .NET. Si no se observan de forma rigurosa los estándares el utilizar diferentes plataformas tecnológicas puede llegar a dar problemas. Por eso es indispensable probar los servicios web en todas aquellas plataformas susceptibles de utilizarlos.

Esta entrada explica las dos herramientas más comunes para generar clientes .NET, que a la postre servirán para probar la compatibilidad entre plataformas; Wsdl.exe y Svcutil.exe.

Web Services Description Language Tool (Wsdl.exe)

Desde la versión .NET Framework 1.1 hasta la versión 2.x, para crear aplicaciones SOA, Microsoft ofrece el framework ASMX. Este framework simplifica la creación tanto de los servicios web como de sus clientes. Los servicios web creados a través de esta tecnología son invocados exclusivamente a través del protocolo HTTP. Es por eso que son conocidos como Web References.

Al crear un cliente de servicio web utilizando Web Reference, estamos utilizando por debajo la herramienta Wsdl.exe proporcionada por Microsoft.

Si en la plataforma donde vamos a ejecutar las aplicaciones estamos limitados a la versión 2.x, entonces el uso de esta herramienta es obligatoria. Sin embargo, si se dispone de la versión 3.x o superiores de .NET Framework, ASMX está obsoleto y conviene reemplazarlo por WCF.

Wsdl.exe genera los objetos .NET necesarios para crear un cliente que invoque las operaciones que publica el servicio web. Esta invocación se puede hacer, entre otras, a través del wizard que proporciona Visual Studio o bien a través de línea de comando.

Visual Studio 2005, al estar basado en .NET Framework 2.0, sólo genera clientes mediante ASMX (wsdl.exe).  Visual Studio 2008 a la hora de crear el cliente permite crearlo tanto con ASMX (wsdl.exe) como con WCF (svcutil.exe). La versión de 2010 directamente ya no permite crearlo con ASMX y se oculta esta opción en un botón de opciones avanzadas.

También se pueden obtener los objetos ASMX ejecutando la herramienta Wsdl.exe desde la consola. Esta herramienta se encuentra en el directorio <Program Files>\Microsoft SDKs\Windows\<version>\Bin\wsdl.exe.

La sintaxis de ejecución es:

wsdl [options] {URL | path}

ServiceModel Metadata Utility Tool (Svcutil.exe)

Windows Communication Foundation, conocido como WCF, es el nuevo framework que a partir de la versión 3.x proporcionó Microsoft para desarrollar aplicaciones SOA.

WCF viene a sustituir al framework ASMX y aunque está disponible desde la versión 3.x del .NET Framework no es sino hasta la versión 4.x donde se simplifica y generaliza su uso.

Los clientes de servicios web que se crean con ésta tecnología son conocidos como Service References.

La herramienta que Microsoft proporciona para la generación de Service References es ServiceModel Metadata Utility Tool (svcutil.exe). Esta herramienta sirve tanto para generar los objetos .NET a partir de un fichero wsdl, como para hacer el proceso inverso, generar un wsdl a partir de objetos .NET.

Svcutil.exe se encuentra en el directorio <Program Files>\Microsoft SDKs\Windows\<version>\Bin\svcutil.exe.

La sintaxis de ejecución es:

 svcutil.exe [/t:code] <metadataDocumentPath>* | <url>* | <epr>

El servicio web de ejemplo.

Para probar las generación del cliente .NET con las herramientas descritas, el servicio web de ejemplo es un viejo conocido del blog, ya que es el servicio CalculadoraService que creé en la entrada «Creando un servicio web mediante anotaciones JAX-WS utilizando un enfoque ascendente (bottom-up)«. En esta entrada se creaba un servicio web JAXWS que proporcionaba cuatro operaciones aritméticas básicas: suma, resta, multiplicación y división.

Este servicio web está publicado en un servidor de Weblogic y se puede acceder a su descriptor a través de la URL: http://localhost:7001/JaxWSEjemploWAR/CalculadoraService?WSDL.

Generando el cliente .NET desde Visual Studio con svcutil.exe.

La generación del cliente .NET utilizando Visual Studio es la manera más gráfica y sencilla. Para realizar esta operación se utiliza una versión de prueba de Visual Studio 2010 y el framework .NET versión 4.0. El lenguaje elegido para la implementación es C# por la similitud que tiene con Java.

Para no complicar en exceso el ejemplo se elige crear una aplicación de consola, ya que lo que importa es ver que la llamada al servicio web se hace correctamente, dejando de lado las dificultades que podrían suponer añadir elementos gráficos.

El nombre de la aplicación de consola será CalculadoraServiceServiceReference.

Console Application para probar el cliente mediante Service References

El siguiente paso es añadir una referencia de servicio web a la aplicación. Para ello en la ventana Solution Explorer se hace click botón derecho en el apartado References y se selecciona Add Service Reference.

Botón derecho sobre References y seleccionar Add Service Reference...

Se abre el wizard para añadir referencias a servicios. En el campo Address se escribe la URL del WSDL del servicio web que se quiere añadir como referencia; http://localhost:7001/JaxWSEjemploWAR/CalculadoraService?WSDL. El nombre de la referencia es CalculadoraServiceReference.

Se pega el WSDL del servicio al que hay que acceder

Automáticamente se generan los objetos .NET que se necesitan para comunicarse con el servicio web. La clase del cliente propiamente dicha y que se instanciará para obtener la funcionalidad del servicio web es CalculadoraPortTypeClient.cs.

Objetos generados por svcutil.exe

Finalmente, en el método Main de la aplicación de consola se instancia esta clase del cliente y se llama a cualquiera de sus operaciones. En el ejemplo, se llama a la operación suma mostrando su resultado por consola.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CalculadoraServiceServiceReference.CalculadoraServiceReference;
namespace CalculadoraServiceServiceReference
{
  class Program
  {
    static void Main(string[] args)
    {
      int operando1 = 10;
      int operando2 = 13;
      CalculadoraPortTypeClient cliente = new CalculadoraPortTypeClient();
      int resultado = cliente.suma(operando1, operando2);
      Console.WriteLine("CalculadoraServiceSvcutil Prueba Empírica");
      Console.WriteLine(operando1.ToString() + "+" + operando2.ToString() + "=" + resultado.ToString());
      Console.ReadKey(true);
    }
  }
}

El resultado es:

Resultado llamada mediante Service Reference

Generando el cliente .NET desde Visual Studio con wsdl.exe.

Para generar el mismo ejemplo, pero utilizando ASMX con la versión de Visual Studio 2010, se accede a la generación Web Reference de las opciones Avanzadas que presenta el wizard que añade referencias de servicio. Esta opción avanzada no hace otra cosa que lanzar el wizard para añadir referencias web que se basa en la herramienta wsdl.exe.

Para realizar este ejemplo, al igual que el anterior se crea una aplicación de consola.

El nombre de la aplicación de consola será CalculadoraServiceWebReference.

Se selecciona una aplicación de consola para crear el cliente mediante web references.

Se añade una referencia de servicio web a la aplicación. Para ello en la ventana Solution Explorer se hace click botón derecho en el apartado References y se selecciona Add Service Reference.

Botón derecho sobre References y se selecciona Add Service Reference...

En el wizard que se abre se pincha directamente en el botón Advanced….

Se pincha el botón Advanced...

Las opciones avanzadas permiten personalizar la creación del cliente, pero todavía basándose en svcutil.exe, que no es lo que se pretende. Para utilizar wsdl.exe se pincha el botón Add Web Reference.

Click en el botón Add Web Reference...

Se abre el wizard antiguo para la generación de clientes mediante Web Reference. En el campo URL se introduce el WSDL del servicio para el que se genera el cliente. En el campo Web reference name se introduce el nombre del objeto .NET que va a encapsular este cliente; CalculadoraServiceWebReference. Click en el botón Add Reference.

Se rellena la URL con el WSDL y automáticamente detecta las operaciones del Web Service. Click en el botón Add Reference.

Automáticamente se generan los objetos .NET que se necesitan para comunicarse con el servicio web. La clase que se instancia para hacer uso del cliente es CalculadoraService.

Objetos .NET del cliente basado en Web Reference.

Finalmente, en el método Main de la aplicación de consola se instancia la clase del cliente y se llama a cualquiera de sus operaciones. En este ejemplo, y para hacer que se parezca lo mejor posible al anterior, también se elige la operación suma.

El cliente generado con wsdl.exe, sin embargo, difiere del generado con la herramienta svcutil.exe. La llamada a la operación suma necesita dos parámetros booleanos, que antes no aparecían, y que indican si se han informado o no los operandos. Además, el resultado no es un retorno de función, si no que se devuelve en un parámetro output de entrada.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CalculadoraServiceWebReference.CalculadoraServiceWebReference;
namespace CalculadoraServiceWebReference
{
  class Program
  {
    static void Main(string[] args)
    {
      int operando1 = 10;
      bool operando1Informado = true;
      int operando2 = 13;
      bool operando2Informado = true;
      int resultado;
      bool resultadoInformado;
      CalculadoraService cliente = new CalculadoraService();
      cliente.suma(operando1, operando1Informado, operando2, operando2Informado, out resultado, out resultadoInformado);
      Console.WriteLine("CalculadoraServiceWebReference Prueba Empírica");
      Console.WriteLine(operando1.ToString() + "+" + operando2.ToString() + "=" + resultado.ToString());
      Console.ReadKey(true);
    }
  }
}

Pese a la diferencia de generación del cliente, el servicio web se invoca correctamente y el resultado de la suma se puede ver por la consola:

Resultado de la invocación al servicio web vía web references (ASMX)

Conclusión

Microsoft dió un paso adelante en la interoperabilidad entre plataformas y en el framework .NET 3.x incluyó WCF para el manejo de servicios web. Esta nueva tecnología sustituye al framework ASMX que ha quedado relegado a ser utilizado en aquellas arquitecturas donde todavía se emplea el framework .NET 1.1 o el 2.x.

De todas formas, ya hemos visto que Microsoft en su plataforma de desarrollo, permite desarrollar todavía con las dos tecnologías ASMX y WCF. Pero, salvo que estemos limitados por la versión de framework .NET, lo recomendable es utilizar WCF, ya que nos aportará más compatibilidad entre plataformas y una aproximación a los estándares mucho más fiable que ASMX.

Links

Creando un servicio web mediante anotaciones JAX-WS utilizando un enfoque ascendente (bottom-up)

Para la programación de servicios web, independientemente de la tecnología, ya sea Java, ya sea .NET, se pueden utilizar dos enfoques distintos; crear el servicio web a partir de un WSDL ya existente (enfoque top-down o descendente), o generar el correspondiente WSDL a partir de código fuente ya existente (enfoque bottom-up o ascendente).

El objetivo de esta entrada es generar un servicio web mediante el enfoque bottom-up o ascendente y utilizando la tecnología JAXWS. En concreto, en esta entrada se utilizan las anotaciones de la especificación JSR-181.

¿Qué necesitas para seguir esta entrada?

Me gustaría aclarar que no voy a utilizar para la generación de este servicio web ninguna ayuda (wizard) de las que proporcionan las IDEs Java que hay en el mercado, por tanto, cualquiera de éstas herramientas sirve para desarrollar el ejemplo.

En este caso, y porque ya lo tengo instalado, voy a utilizar Eclipse 3.7.1 (Helios) con el plugin de Oracle (OEPE) para realizar un rápido despliegue en un dominio básico de Weblogic que he creado para esta ocasión.

El servidor de aplicaciones sobre el que voy a desplegar y publicar el servicio web va a ser el parche 10.3.6 de Weblogic 11g.

El dominio básico de Weblogic creado para estas pruebas tiene una única instancia que nos servirá tanto para publicar la consola administrativa del servidor, como la aplicación de ejemplo que voy a generar.

La JDK instalada es la versión 1.6.0_29 del Hotspot de Oracle.

Los componentes de la prueba

Los servicios web, en java, se publican en aplicaciones web dinámicas y se empaquetan en ficheros WAR. Teniendo esto en cuenta, para esta prueba voy a crear un proyecto Web Dinámico que a su vez voy a incluir en una aplicación JEE empaquetada en un EAR.

El motivo de incluir el módulo WAR en un EAR es simplemente una decisión personal del grupo «porquemeapetece«. En realidad, simplemente con el módulo WAR ya nos bastaría para desplegar y publicar el servicio web en el servidor de Weblogic.

La aplicación EAR la voy a llamar AppEjemploEAR y el módulo WAR lo voy a llamar JaxWSEjemploWAR.

La clase java de ejemplo.

La lógica de negocio que quiero convertir en servicio web es una calculadora muy básica compuesta por cuatro métodos: suma, resta, multiplicacion y division, que realizan las operaciones aritméticas correspondientes entre dos operandos.

La implementación de este servicio es la clase CalculadoraService.java que se encuentra en el directorio de fuentes de la aplicación WAR.

package ejemplo.jaxws; 
/**
 * @author EGV
 * <p>Clase calculadora que proporciona las cuatro operaciones aritméticas 
 * básicas: suma, resta, multiplicación y división.</p> 
 */
public class CalculadoraService {

  /**
   * Realiza la suma de los operandos.
   * @param operando1 Operando uno.
   * @param operando2 Operando dos.
   * @return Operando1 + Operando2.  
   */
  public Integer suma (Integer operando1, Integer operando2)
  {
    return operando1 + operando2;
  }

  /**
   * Realiza la resta de los dos operandos.
   * @param operando1 Operando uno.
   * @param operando2 Operando dos.
   * @return Operando1 - Operando2
   */
  public Integer resta (Integer operando1, Integer operando2)
  {
    return operando1 - operando2; 
  }

  /**
   * Realiza la multiplicación de los dos operandos.
   * @param operando1 Operando uno.
   * @param operando2 Operando dos.
   * @return Operando1 * Operando2
   */
  public Integer multiplicacion (Integer operando1, Integer operando2)
  {
    return operando1 * operando2;
  }

  /**
   * Realiza la división entre los operandos.
   * @param operando1 Operando1
   * @param operando2 Operando2
   * @return Operando1 / Operando2   
   */
  public Integer division (Integer operando1, Integer operando2)  
  {
    return operando1 / operando2;
  }

  /**
  * @param args
  */
  public static void main(String[] args) {
    Integer operando1 = new Integer(10);
    Integer operando2 = new Integer(3);
    Integer resultado;
    CalculadoraService calculadora = new CalculadoraService();
    StringBuffer bsTraza;

    resultado = calculadora.suma(operando1, operando2);
    bsTraza = new StringBuffer();
    bsTraza.append(operando1).append("+").append(operando2).append("=").append(resultado);
    System.out.println(bsTraza.toString());

    resultado = calculadora.resta(operando1, operando2);
    bsTraza = new StringBuffer();
    bsTraza.append(operando1).append("-").append(operando2).append("=").append(resultado);
    System.out.println(bsTraza.toString());

    resultado = calculadora.multiplicacion(operando1, operando2);
    bsTraza = new StringBuffer();
    bsTraza.append(operando1).append("*").append(operando2).append("=").append(resultado);
    System.out.println(bsTraza.toString());

    resultado = calculadora.division(operando1, operando2);
    bsTraza = new StringBuffer();
    bsTraza.append(operando1).append("/").append(operando2).append("=").append(resultado);
    System.out.println(bsTraza.toString());

  }

}

Las anotaciones básicas para definir Servicios Web.

La especificación JSR-181 define una serie de anotaciones que permiten generar, a partir de una clase java, un servicio web. Gracias a estas anotaciones el desarrollo del servicio web se simplifica enormemente, además, las anotaciones de esta especificación están incluidas en la JDK a partir de la versión 1.5 y sucesoras, por tanto, incluidas en mi versión 1.6.0_29.

Las anotaciones que voy a utilizar son las siguientes:

  • @WebService –> javax.jws.WebService
  • @WebMethod –> javax.jws.WebMethod
  • @WebParam –> javax.jws.WebParam
  • @WebResult –> javax.jws.WebResult
  • @SOAPBinding –> javax.jws.soap.SOAPBinding

Las anotaciones @WebParam, @WebResult y @SOAPBinding las incluyo para dejar constancia de su funcionamiento, en verdad para este ejemplo tan básico no serían necesarias ya que la configuración por defecto, es la más adecuada.

La clase java de ejemplo anotada.

La clase CalculadoraService.java con las anotaciones quedaría de la siguiente forma:

package ejemplo.jaxws;
import javax.jws.WebMethod;
 import javax.jws.WebParam;
 import javax.jws.WebResult;
 import javax.jws.WebService;
 import javax.jws.soap.SOAPBinding;
/**
* @author EGV
* <p>Clase calculadora que proporciona las cuatro operaciones aritméticas
* básicas: suma, resta, multiplicación y división.</p>
* <p>Esta clase se anota con la especificación JSR-181 para convertirla
* en un servicio web.</p>
*/
@WebService(serviceName="CalculadoraService",
  name="CalculadoraPortType",
  targetNamespace="http://egv.com/ejemplo-jaxws/calculadora")
@SOAPBinding( style=SOAPBinding.Style.DOCUMENT,
  use=SOAPBinding.Use.LITERAL, 
  parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class CalculadoraService {
  /**
  * Realiza la suma de los operandos.
  * @param operando1 Operando uno.
  * @param operando2 Operando dos.
  * @return Operando1 + Operando2.
  */
  @WebMethod( operationName="suma",
    action="sumaAction")
  @WebResult( name="sumaResponse")
  public Integer suma (
    @WebParam(name="operando1", mode=WebParam.Mode.IN) Integer operando1,
    @WebParam(name="operando2", mode=WebParam.Mode.IN) Integer operando2)
  {
    return operando1 + operando2;
  }

  /**
  * Realiza la resta de los dos operandos.
  * @param operando1 Operando uno.
  * @param operando2 Operando dos.
  * @return Operando1 - Operando2
  */
  @WebMethod( operationName="resta",
    action="restaAction")
  @WebResult( name="restaResponse")
  public Integer resta (
    @WebParam(name="operando1", mode=WebParam.Mode.IN) Integer operando1,
    @WebParam(name="operando2", mode=WebParam.Mode.IN) Integer operando2)
  {
    return operando1 - operando2;
  }

  /**
  * Realiza la multiplicación de los dos operandos.
  * @param operando1 Operando uno.
  * @param operando2 Operando dos.
  * @return Operando1 * Operando2
  */
  @WebMethod( operationName="multiplicacion",
    action="multiplicacionAction")
  @WebResult( name="multiplicacionResponse")
  public Integer multiplicacion (
    @WebParam(name="operando1", mode=WebParam.Mode.IN) Integer operando1,
    @WebParam(name="operando2", mode=WebParam.Mode.IN) Integer operando2)
  {
    return operando1 * operando2;
  }

  /**
  * Realiza la división entre los operandos.
  * @param operando1 Operando1
  * @param operando2 Operando2
  * @return Operando1 / Operando2
  */
  @WebMethod( operationName="division",
    action="divisonAction")
  @WebResult( name="divisionResponse")
  public Integer division (
    @WebParam(name="operando1", mode=WebParam.Mode.IN) Integer operando1,
    @WebParam(name="operando2", mode=WebParam.Mode.IN) Integer operando2)
  {
    return operando1 / operando2;
  }

Las anotaciones @WebService y @SOAPBinding son generales al servicio web generado, por lo que se asocian a la clase. Las anotaciones @WebMethod y @WebResult están relacionadas con el método y por tanto hay una por cada método a publicar. La anotación @WebParam está relacionada a los parámetros, por lo que habrá una por cada parámetro que haya en los métodos.

Desplegando, publicando y probando el servicio web.

Una vez que la clase java que queremos publicar como servicio web está anotada, desplegamos la aplicación EAR en el servidor de Weblogic.

Si hemos hecho correctamente la anotación, el servidor desplegará AppEjemploEAR con el módulo JaxWSEjemploWAR dentro y el servicio web CalculadoraService publicado.

Vista Despliegue AppEjemploEAR

Para probar el servicio web se puede hacer de varias maneras; generando un cliente de web service, utilizando la herramienta de test de servicios web proporcionada por Weblogic, utilizando herramientas externas como SOAPUi, etcétera.

En la imagen se puede ver el cliente que proporciona el servidor de Weblogic. Si en la vista de despliegue se pincha en el servicio web a probar, en este caso CalculadoraService, nos abrirá la pantalla con la configuración propia de este servicio. En la pestaña de Prueba existe un Punto de Prueba que se llama Test 

Client que nos llevará a una página web que nos permitirá probar las diferentes operaciones de CalculadoraService.

Cliente Test de CalculadoraService

El WSDL generado

El WSDL del servicio web es generado a partir de la clase anotada.

Podemos observar como las anotaciones que hemos incluido en el código Java se reflejan en los diferentes apartados del documento WSDL.

<!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is Oracle JAX-WS 2.1.5. -->
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is Oracle JAX-WS 2.1.5. -->
<definitions 
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://egv.com/ejemplo-jaxws/calculadora" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://egv.com/ejemplo-jaxws/calculadora" name="CalculadoraService">
  <types>
    <xsd:schema>
      <xsd:import namespace="http://egv.com/ejemplo-jaxws/calculadora" schemaLocation="http://172.16.204.140:7001/JaxWSEjemploWAR/CalculadoraService?xsd=1"/>
    </xsd:schema>
  </types>
  <message name="suma">
    <part name="parameters" element="tns:suma"/>
  </message>
  <message name="sumaResponse">
    <part name="parameters" element="tns:sumaResponse"/>
  </message>
  <message name="resta">
    <part name="parameters" element="tns:resta"/>
  </message>
  <message name="restaResponse">
    <part name="parameters" element="tns:restaResponse"/>
  </message>
  <message name="multiplicacion">
    <part name="parameters" element="tns:multiplicacion"/>
  </message>
  <message name="multiplicacionResponse">
    <part name="parameters" element="tns:multiplicacionResponse"/>
  </message>
  <message name="division">
    <part name="parameters" element="tns:division"/>
  </message>
  <message name="divisionResponse">
    <part name="parameters" element="tns:divisionResponse"/>
  </message>
  <portType name="CalculadoraPortType">
    <operation name="suma">
      <input message="tns:suma"/>
      <output message="tns:sumaResponse"/>
    </operation>
    <operation name="resta">
      <input message="tns:resta"/>
      <output message="tns:restaResponse"/>
    </operation>
    <operation name="multiplicacion">
      <input message="tns:multiplicacion"/>
      <output message="tns:multiplicacionResponse"/>
    </operation>
    <operation name="division">
      <input message="tns:division"/>
      <output message="tns:divisionResponse"/>
    </operation>
  </portType>
  <binding name="CalculadoraPortTypePortBinding" type="tns:CalculadoraPortType">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <operation name="suma">
      <soap:operation soapAction="sumaAction"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="resta">
      <soap:operation soapAction="restaAction"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="multiplicacion">
      <soap:operation soapAction="multiplicacionAction"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="division">
      <soap:operation soapAction="divisonAction"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="CalculadoraService">
    <port name="CalculadoraPortTypePort" binding="tns:CalculadoraPortTypePortBinding">
      <soap:address location="http://172.16.204.140:7001/JaxWSEjemploWAR/CalculadoraService"/>
    </port>
  </service>
</definitions>

Los XML Request y Response de la operación Suma.

Para invocar a la operación Suma del servicio web CalculadoraService se envía el XML de petición de servicio. Como respuesta, el servicio web enviará otro XML con la información de la operación. Estos XMLs también están influidos por las anotaciones de la clase Java anotada.

XML Request

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header />
  <env:Body>
    <suma xmlns="http://egv.com/ejemplo-jaxws/calculadora">
      <!--Optional:-->
      <operando1 xmlns="">10</operando1>
      <!--Optional:-->
      <operando2 xmlns="">13</operando2>
    </suma>
  </env:Body>
</env:Envelope>

XML Response

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Body>
    <ns2:sumaResponse xmlns:ns2="http://egv.com/ejemplo-jaxws/calculadora">
      <sumaResponse>23</sumaResponse>
    </ns2:sumaResponse>
  </S:Body>
</S:Envelope>

Conclusión

Si disponemos de una clase java, bien sea un POJO, o bien sea un Entreprise Java Bean, que contiene lógica de negocio que quisiéramos publicar para su utilización por otras aplicaciones en una arquitectura SCA (orientada a componentes), una buena idea es convertir de manera muy sencilla esa clase en un servicio web mediante las anotaciones que proporciona la especificación JSR-181.
Esta especificación se incluyó por primera vez en la JDK 1.5 y permite que una aplicación EAR automáticamente despliegue y publique servicios web.
Estas anotaciones se incluyen en la clase Java y definen el contrato WSDL del servicio web.
En siguientes entradas iré comentando con más en detalle el uso de las anotaciones JAX-WS para manipular el WSDL generado, haciendo un uso avanzado además de las anotaciones relacionadas con JAXB para la manipulación de los XML de las operaciones.

Links