Buenas prácticas JAXWS. Utilizando RequestWrapper.

Desde la aparición de la arquitectura SOA (Service-Oriented Architecture) y la posterior llegada de SCA (Service Component Architecture), la utilización de servicios web se ha multiplicado en los desarrollos software de las empresas.

En el mundillo java surgieron diferentes tecnologías para facilitar la creación de los servicios web (Axis1, Axis2, CXF, etcétera), pero a raíz de la llegada de la implementación de la especificación JSR 224 – JAXWS a partir de la JDK 1.5, es cuando la creación de servicios web se ha hecho más inter-operable y gracias a la utilización de anotaciones se ha facilitado enormemente.

En esta entrada me centro en la anotación JAXWS @javax.xml.ws.RequestWrapper, como usarla y la utilidad que tiene.

Un caso de uso.

Cuando se realiza un servicio web con JAX-WS se crea una clase java con anotaciones JSR 181 (@WebService, @WebMethod, etcétera) que describen el contrato WSDL que luego utilizan los clientes para invocar al servicio.

Para explicar esta entrada, voy a utilizar el siguiente ejemplo de servicio web básico, compuesto por un método que recibe una serie de parámetros.

package com.egv.webservices.jaxws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService ( name="WebServiceEjemplo", 
              targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(  style=SOAPBinding.Style.DOCUMENT, 
               use=SOAPBinding.Use.LITERAL, 
               parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class WebServiceEjemplo {
	
	@WebMethod(operationName="metodoEjemplo")
	public void metodoEjemplo(  @WebParam(name="codigo1")String codigo1,
                                @WebParam(name="descripcion1")String descripcion1,
                                @WebParam(name="codigo2")Integer codigo2,
                                @WebParam(name="descripcion2")String descripcion2) {
		System.out.println("codigo1=" + codigo1);
		System.out.println("descripcion1=" + codigo1);
		System.out.println("codigo2=" + codigo2);
		System.out.println("descripcion2=" + descripcion2);
	}

}

Esta clase java genera un método metodoEjemplo en el servicio web con las siguiente Request de tipo SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoEjemplo>
         <!--Optional:-->
         <codigo1>?</codigo1>
         <!--Optional:-->
         <descripcion1>?</descripcion1>
         <!--Optional:-->
         <codigo2>?</codigo2>
         <!--Optional:-->
         <descripcion2>?</descripcion2>
      </jax:metodoEjemplo>
   </soapenv:Body>
</soapenv:Envelope>

Hasta aquí el caso más básico de creación de un servicio web. Pero, qué ocurre si queremos complicarlo haciendo que los parámetros codigo1 y codigo2 se requieran de forma obligatoria.

Para conseguir esta obligatoriedad podemos optar por dos tipos de soluciones: una aproximación a posteriori o una aproximación a priori.

La aproximación a posteriori

La solución a posteriori se introduce una vez que el servicio web se ha invocado y se consigue añadiendo en el código una comprobación de parámetros mediante condicionales. Por ejemplo:

if (codigo1 == null || codigo2 == null) {
throw new ParametrosObligatoriosException();
}

Esta comprobación es funcionalmente correcta pero no es óptima. El motivo es que para realizar la comprobación de parámetros se hace una llamada al servicio web, la ejecución del método y el envío de la respuesta.

La aproximación a priori

La solución a priori se basa en el uso conjunto de la anotación javax.xml.ws.RequestWrapper de JAXWS combinado con el uso de las anotaciones JSR 222 – JAXB. Esto puede sonar complejo, e inicialmente nos va a obligar a codificar más líneas de código, sin embargo, la flexibilidad que proporciona a la hora de jugar con los parámetros, tipo, obligatoriedad, etcétera, es mucho más óptima.

El primer paso para llevar a cabo esta solución es hacer un wrapper para envolver la request del método. Este wrapper debe contener los campos declarados como parámetros del método con la anotación WebParam. Cada campo deber ir anotado con anotaciones JAXB que indican las características del campo concreto.

package com.egv.webservices.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = { "codigo1", "descripcion1", "codigo2", "descripcion2" })
@XmlAccessorType(XmlAccessType.FIELD)
public class WebServiceEjemploWrapperRequest {
	
	@XmlElement(name="codigo1", required=true)
	public String codigo1;

	@XmlElement(name="descripcion1")
	public String descripcion1;

	@XmlElement(name="codigo2", required=true)
	public Integer codigo2;

	@XmlElement(name="descripcion2")
	public String descripcion2;

	[…] //Getter y Setter para cada variable.

La anotación @XmlElement indica el nombre de la variable y en las variables codigo1 y codigo2 además incluye el indicador de obligatoriedad required=true.

El segundo paso es anotar el método del servicio web en su clase con la anotación RequestWrapper

	@WebMethod(operationName="metodoEjemplo")
	@RequestWrapper(localName = "metodoEjemploRequestWrapper", 
    className = "com.egv.webservices.jaxws.WebServiceEjemploWrapperRequest")
	public void metodoEjemplo(  @WebParam(name="codigo1")String codigo1,
                                @WebParam(name="descripcion1")String descripcion1,
                                @WebParam(name="codigo2")Integer codigo2,
                                @WebParam(name="descripcion2")String descripcion2) {

El resultado final podemos observarlo en la request SOAP generada:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoEjemploRequestWrapper>
         <codigo1>?</codigo1>
         <!--Optional:-->
         <descripcion1>?</descripcion1>
         <codigo2>?</codigo2>
         <!--Optional:-->
         <descripcion2>?</descripcion2>
      </jax:metodoEjemploRequestWrapper>
   </soapenv:Body>
</soapenv:Envelope>

Los campos codigo1 y codigo2 han dejado de ser opcionales y ahora han pasado a ser obligatorios. Sin embargo, si ejecutamos desde un cliente y dejamos sin informar alguno de los campos obligatorios, el servicio web se ejecuta y da respuesta.

El método se ejecuta pese a no rellenar los campos obligatorios

La explicación a este supuesto mal funcionamiento es que la validación del esquema del servicio web consume bastantes recursos afectando al rendimiento y por tanto no se activa por defecto. Si se quiere activar la validación del esquema bastará con añadir la anotación com.sun.xml.ws.developer.SchemaValidation a nivel de servicio web.

@WebService (name="WebServiceEjemplo", targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, 
			use=SOAPBinding.Use.LITERAL, 
			parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
@SchemaValidation
public class WebServiceEjemplo {
[…]

Una vez añadida la anotación, al invocar el servicio web sin rellenar el campo codigo1, la respuesta es:

   <S:Body>
      <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
         <faultcode>S:Server</faultcode>
         <faultstring>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'descripcion1'. One of '{codigo1}' is expected.</faultstring>
         <detail>
            <ns2:exception class="javax.xml.ws.WebServiceException" note="To disable this feature, set com.sun.xml.ws.fault.SOAPFaultBuilder.disableCaptureStackTrace system property to false" xmlns:ns2="http://jax-ws.dev.java.net/">
               <message>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'descripcion1'. One of '{codigo1}' is expected.</message>
               <ns2:stackTrace>
                  <ns2:frame class="com.sun.xml.ws.util.pipe.AbstractSchemaValidationTube" file="AbstractSchemaValidationTube.java" line="242" method="doProcess"/>
[…]

La validación del esquema hace también comprobaciones de tipos de datos, de forma que si en el campo Integer introducimos ‘abc’ se lanzará una excepción.

<message>com.sun.istack.XMLStreamException2: org.xml.sax.SAXParseException: cvc-datatype-valid.1.2.1: 'abc' is not a valid value for 'integer'.</message>

Ideas a evitar

Se podría idear una solución intermedia sin utilizar la anotación RequestWrapper. Esta solución intermedia pasaría por incluir los campos de la request en un objeto wrapper, como el objeto WebServiceEjemploWrapperRequest del ejemplo, y hacer que este objeto sea el único parámetro del método.

	@WebMethod(operationName="metodoMalEjemplo")
	public void metodoMalEjemplo(	@WebParam(name="objetoComplejo")WebServiceEjemploWrapperRequest objetoComplejo) {
		System.out.println("codigo1=" + objetoComplejo.getCodigo1());
		System.out.println("descripcion1=" + objetoComplejo.getDescripcion1());
		System.out.println("codigo2=" + objetoComplejo.getCodigo2());
		System.out.println("descripcion2=" + objetoComplejo.getDescripcion2());
	}

Esta programación dará como resultado la siguiente petición SOAP:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://egv.com/webservices/jaxws">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:metodoMalEjemplo>
         <!--Optional:-->
         <objetoComplejo>
            <codigo1>?</codigo1>
            <!--Optional:-->
            <descripcion1>?</descripcion1>
            <codigo2>?</codigo2>
            <!--Optional:-->
            <descripcion2>?</descripcion2>
         </objetoComplejo>
      </jax:metodoMalEjemplo>
   </soapenv:Body>
</soapenv:Envelope>	

El funcionamiento de los clientes que llaman al método con la request configurada de esta manera es similar a la configuración con RequestWrapper. Sin embargo, añadir el encapsulado adicional <objetoComplejo> no es una buena idea ya que cuanto más simple permanezca el modelo más compatible será éste.

Conclusión

La utilización de la anotación RequestWrapper permite utilizar JAXB para configurar las características de cada campo que participa en la request de un método de un servicio web.

Esta anotación permite generar una request lo más simple posible lo que, entre otras cosas, hará que los métodos sean completamente compatibles con sistemas de cacheo que normalmente no funcionan bien con objetos complejos.

Links

Publicar servicios web JAX-WS 2.1 en Tomcat 6

La llegada de la crisis se está dejando ver en todos los ámbitos y la informática no es una excepción. Hasta hace no mucho las empresas no daban excesiva importancia al coste de sus servidores y se pagaban las licencias de software religiosamente. Pero, la crisis ha dado un vuelco a esta situación. El gasto se controla más y allí donde antes se utilizaba un servidor de pago para dar servicio, ahora se busca sustituirlo por versiones de código abierto y gratuitas.

Concretamente, en el cliente para el que trabajo actualmente se ha decido empezar a utilizar Apache Tomcat para aquellas aplicaciones que no necesitan de la pila Enterprise de Java.

Esta entrada recoge cómo publicar servicios web JAX-WS en un Apache Tomcat 6.x.

Requisitos

Antes de comenzar con el código puro y duro voy a hacer un pequeño repaso del entorno necesario para seguir la solución propuesta en la entrada.

La máquina virtual Java es la Jrockit-jdk1.6.0_45-R28.2.7-4.1.0. Utilizo esta versión porque en los entornos productivos es esta máquina virtual la que se instala. El ejemplo puede funcionar igualmente con la HotSpot 1.6. La versión en concreto que estoy utilizando contiene la versión JAX-WS RI 2.1.6. Si quieres averiguar que versión de JAX-WS incorpora tu máquina virtual basta con ejecutar el siguiente comando:

${java-home}/bin/wsimport -version

El servidor Tomcat elegido para las pruebas es la versión 6.0.37. No utilizo la versión 7 de Tomcat porque los entornos productivos están configurados con la versión Tomcat 6. Esta versión admite las especificaciones Servlet/2.5, JSP/2.1 y requiere la versión 1.5 o versiones superiores de la máquina virtual Java.

Instalación básica de Tomcat.

El primer paso es realizar una instalación básica de Tomcat.

El servidor se puede descargar de la página oficial de Apache Tomcat. La instalación básica es muy sencilla, basta con descomprimir el fichero descargado en un directorio que a partir de ahora voy a llamar ${TOMCAT_HOME}.

Antes de arrancar el servidor hay que configurar el usuario administrador para poder acceder a la aplicación de despliegue de Tomcat. Los usuarios de Tomcat se configuran en el fichero ${TOMCAT_HOME}/conf/tomcat-users.xml. En este fichero se añaden las siguientes líneas:

<role rolename="manager"/>
<role rolename="admin"/>
<user username="admin" password="admin" roles="admin,manager"/>

No tendría que decirlo, pero por si acaso, ni que decir tiene que el nombre del usuario administrador así como su password no tiene porque coincidir con el ejemplo, queda a elección de la persona que instala el servidor crear los roles y usuarios que más le convengan.

Para el ejemplo básico creo el rol manager, el rol admin y el usuario admin que estará compuesto por estos dos roles.

Una vez modificado este fichero se puede poner en marcha el servidor. Para arrancar y parar, el propio servidor proporciona dos scripts que facilitan enormemente la tarea. El script de arranque es ${TOMCAT_HOME}/bin/startup.bat (en entornos Windows) o ${TOMCAT_HOME}/bin/startup.sh (en entornos unix). Para parar el servidor el script es ${TOMCAT_HOME}/bin/shutdown.bat (Windows) y ${TOMCAT_HOME}/bin/shutdown.sh (Unix).

Creación de la aplicación de ejemplo con servicios web JAXWS.

La aplicación de ejemplo se crea utilizando Maven 3.x para facilitar el manejo del ciclo de vida en general y de las dependencias en particular.

Desde Eclipse se crea un nuevo Maven Project a partir del arquetipo org.apache.maven.archetypes – maven-archetype-webapp en su versión RELEASE. Esto proporciona la base de una aplicación web dinámica. La estructura se parece a lo que se ve en la siguiente captura:

Arquitectura creada por Maven para una aplicación web.

Para facilitar el desarrollo, publicación y pruebas de la aplicación se incluye en el fichero pom.xml que ha generado el arquetipo la configuración de dos plugins; maven-war-plugin y maven-clean-plugin.

El plugin maven-war-plugin configura el empaquetado WAR de la aplicación con ciertas características:

  • Se va a publicar la aplicación directamente en el directorio de despliegues por defecto de Tomcat. Esto se hace añadiendo el tag <webappDirectory> en las configuración del plugin y permite hacer pruebas inmediatas de la aplicación sin necesidad de estar publicándola constantemente desde el Deployer.
  • Se va a dejar una versión empaquetada en un fichero WAR en el directorio dist de la aplicación. Esto se consigue con el tag <outputDirectory>. La idea es poder tener un WAR independiente para poder enviar la aplicación a otros entornos de forma fácil.
<build>
    […]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <webappDirectory>E:\srv\dominio\aplic\dominio_tomcat\apache-tomcat-6.0.37\webapps\ExampleWS</webappDirectory>
          <outputDirectory>./dist</outputDirectory>
          <warName>ExampleWS-${version}</warName>
        </configuration>
      </plugin>
    </plugins>
	 […]
</build>

El plugin maven-clean-plugin limpia los compilados de la aplicación. Como se ha añadido la generación del fichero war en un directorio específico, se configura este plugin para que lo elimine cuando se ejecute un mvn clean.

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.5</version>
    <configuration>
      <filesets>
         <fileset>
          <directory>./dist</directory>
          <followSymlinks>false</followSymlinks>
          <useDefaultExcludes>true</useDefaultExcludes>
          <includes>
            <include>*.war</include>
          </includes>
        </fileset>
      </filesets>
    </configuration>
  </plugin>

Adicionalmente, se añaden las dependencias necesarias para que la aplicación entienda y publique los servicios web JAXWS:

  • jaxws-rt v2.1.6
  <dependencies>
  […]
    <dependency>
      <groupId>>com.sun.xml.ws</groupId>
      <artifactId>jaxws-rt</artifactId>
      <version>2.1.7</version>
    </dependency>
  […]
  </dependencies>

Creación el servicio web de ejemplo.

Desde que la JDK 1.5 incorporó las anotaciones y JAX-WS hizo uso de ellas, crear un servicio web de ejemplo se ha convertido en una tarea sencilla.

package com.egv.webservices.jaxws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService (name="WebServiceUno", targetNamespace="http://egv.com/webservices/jaxws")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, 
			use=SOAPBinding.Use.LITERAL, 
			parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class WebServiceUno {
	
	@WebMethod(operationName="HolaMundo")
	public void HolaMundo(@WebParam(name="nombre")String nombre) {
		System.out.println("Hola mundo! Te saluda " + nombre);
	}

}  

Este servicio web se llama WebServiceUno y tiene un único método que se llama HolaMundo que recibe un nombre como parámetro y que una vez ejecutado escribe en consola Hola mundo! te saluda ${nombre}. Un servicio web muy simple pero perfecto para probar la publicación de WS JAXWS en Tomcat 6.

Configuración de la aplicación para publicar el servicio web en Tomcat.

Para publicar los servicios web en tomcat es necesario que la aplicación publique un servlet específico de JAXWS en el arranque. Este servlet se encuentra en el paquete com.sun.xml.ws.transport.http.servlet.WSServlet y viaja con la implementación de JAXWS que se ha descargado con la dependencia oportuna. En el fichero web.xml de la aplicación se incluye el siguiente código:

<web-app>
  	[…]
    <servlet>
        <servlet-name>WebServiceUno</servlet-name>
        <servlet-class>
        	com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebServiceUno</servlet-name>
        <url-pattern>/wsUno</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
	[…]  
</web-app>

El servlet WSServlet ejecuta los servicios web que se incluyan en la aplicación. Pero, para que Tomcat sepa que servicios web tiene que publicar hay que incluir el fichero sun-jaxws.xml. Este fichero contiene la configuración de los endpoints de los servicios web.

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="WebServiceUno"
      implementation="com.egv.webservices.jaxws.WebServiceUno"
      url-pattern="/wsUno"/>
</endpoints>

Tomcat entiende este fichero gracias al listener com.sun.xml.ws.transport.http.servlet.WSServletContextListener específico de JAXWS. Este listener es el encargado de parsear el fichero y crear un HttpAdapter por cada endpoint definido. El listener hay que incluirlo en la aplicación añadiendo las siguientes líneas en el fichero web.xml:

<web-app>
[…]
<listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener> 
[…]
</web-app>

El war generado presenta la siguiente estructura:

Estructura del fichero War.

Desplegar y probar.

Una vez que todo está creado, se ejecuta mvm install.

Si la compilación es correcta, ésta ejecución debería dejar en el directorio de despliegue de tomcat la aplicación correctamente publicada. En el deployer de tomcat se verá la aplicación correctamente publicada.

Aplicación correctamente desplegada.

Accediendo a la URI del servicio web se ve el servicio web publicado.

Web Service correctamente desplegado.

Probando la aplicación a través de SOAPUi se comprueba el funcionamiento correcto.

Ejecución desde SOAPUi

En la consola del servidor encontramos la traza escrita.

Consola de salida del servidor.

Conclusión

Como se ha visto a lo largo de la entrada, no es especialmente difícil publicar servicios web JAXWS en Apache Tomcat. La ventaja es que Tomcat cuenta con una licencia de código abierto ahorrando a las empresas bastantes euros en costosas licencias de servidores de aplicación.

Por tanto, si la aplicación que se desea desarrollar no tiene exigencias Enterprise y va a tener una carga normal de trabajo, ésta configuración se puede utilizar sin problemas.

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