Creando un servicio web a partir de su interfaz WSDL

12
268677

Creando un servicio web a partir de su interfaz WSDL


En este tutorial se resalta la importancia de definir la interfaz de un servicio web antes
de implementarlo, y cómo hacer todo esto con Eclipse y Apache Axis v1.

Por Javier Cámara (jcamara@softwareag.es)
Arquitecto de software en el BCS Competence Center de Software AG España.

Introducción

Una de las tareas más importantes a la hora de crear una SOA es definir su modelo de servicios:
o sea, qué servicios
hay y qué tareas en concreto hace cada uno. Esto aclara mucho las tareas a realizar por el sistema y
qué elemento del mismo las llevará a cabo, y permite validar que esos elementos implementarán las
necesidades del usuario, previamente definidas.
Lo cual en cualquier sistema es siempre el grueso del diseño arquitectónico
del mismo. Por ello, de la bondad de este resultado depende en gran parte el éxito de la SOA.

Más tarde o más temprano, en una SOA basada en servicios web este modelo se plasmará en
documentos WSDL que definan en detalle
las interfaces de cada servicio: operaciones, datos recibidos, datos devueltos y errores que pueden
ocurrir. Estos WSDLs son casi imprescindibles a la hora de crear los clientes de un servicio, pues
facilitan enormemente la tarea de invocarlo y gestionarlo.

La mayoría de las herramientas de creación de servicios web, como Apache Axis o Visual Studio .Net,
facilitan que primero se implemente el servicio (o un esqueleto del mismo), por ejemplo en Java,
y a partir de él se genere automáticamente el WSDL.
Pero lo cierto es que esta forma de trabajar que promueven estas herramientas es incorrecta
por varias razones. Para empezar, lo normal es crear la interfaz de algo antes de implementarlo, lo que
permite crear los clientes y los servidores en paralelo. Pero sobre todo es que si creamos el WSDL a
partir del código, tenemos bastantes posibilidades de que los detalles de este WSDL dependan de la
herramienta que hemos usado para generarlo. Por tanto, si luego queremos que ese servicio sea implementado
usando una herramienta diferente, o incluso por una versión superior de esa misma herramienta, puede
que ese WSDL cambie, con lo cual tendríamos que cambiar los clientes de ese servicio. Por ello, lo
apropiado es que la herramienta se adapte al WSDL, y no al revés.

Estos posibles problemas no son imaginaciones, y ha habido proyectos en Software AG
que han tenido problemas por esto, por ejemplo para llamar a servicios creados con Axis
desde el Sun Java Web Services Developer Pack (JWSDP). Los WSDLs generados por Axis pueden
contener referencias a tipos de Java (que no funcionan en .Net), o a construcciones propias de Axis
(que no funcionan en otros clientes de servicios web Java), o incluso cuya sintaxis
no sigue siquiera el estándar WSDL.
Eso sí, un cliente Axis no tiene problemas para conectarse a un servicio web Axis. Pero para conseguir
eso no necesitábamos todas estas complejidades; el beneficio clave de los servicios web es la
interoperabilidad, que se fundamenta en la independencia de las plataformas.

Por todo eso, lo aconsejable es
crear el WSDL antes del código, y no al revés. De esta forma el modelo de los servicios
de nuestra SOA no dependerá de las herramientas con las que se implemente, sino al revés, y
los clientes de los servicios
pueden conectarse a ellos independientemente de si está implementado con Axis, con JWSDP, con .Net
o con cualquier otra herramienta. Y eso es lo que vamos a ver en este tutorial, usando Apache Axis,
claro, que para eso es el más popular.
Como todos los entornos de servicios web, Axis trae una herramienta para crear esqueletos de
servicios a partir de WSDL, tanto para los clientes como para los servicios, llamada WSDL2Java.
No es la herramienta definitiva: el código que genera a veces no compila, y cuando compila puede
no cumplir la interfaz definida por el WSDL. Pero bueno, en muchos casos sí funciona bien, y en cualquier
caso cuando no lo hace nos da una aproximación al resultado que podemos luego completar.

En este tutorial vamos a usar todo el rato Java, así que si tu PC no lo tiene, tendrás que
instalarte el Developer Kit de Java Standard Edition descargándotelo desde
http://java.sun.com/javase/downloads/.

El resto del tutorial contiene las siguientes secciones:

Diseñando nuestra interfaz

Como hemos dicho, antes que ponernos a implementarlo primero hay que pensar cuál será la interfaz
de nuestro servicio. En nuestro caso vamos a crear
un servicio que podría ser usado por un banco y que informaría de las sucursales del mismo que están próximas a
un código postal. Por ello, tendría una interfaz como esta:

  • Entrada: código postal (ej. «28760»), o parte del mismo (ej. «28» o «760»)
  • Salida: lista de sucursales cuyo código postal contiene al menos parte del recibido, posiblemente vacía.
    A su vez, para cada sucursal se informará de lo siguiente:

    • Código de la sucursal
    • Dirección
    • Código postal

Ahora tenemos que plasmar eso en WSDL. WSDL es un lenguaje XML y se puede escribir a mano,
pero es más complicado de lo que uno se imaginaría y no es lo más divertido o sencillo de
escribir, así que lo normal es usar una herramienta.
Hay una bastante buena para ello, el XML Spy, pero la versión gratuita del mismo no permite
crear WSDLs; así que usaremos otra gratuita que se puede usar en Eclipse.

Diseñando el WSDL en Eclipse

Para esto vamos a tener que hacer las siguientes cosas que se describen a continuación:

Instalar Eclipse

Para poder manejar WSDLs en Eclipse vamos a usar el Web Tools Project.
En este tutorial utilizaremos la versión 1.5 de Junio de 2006,
que precisa de Eclipse 3.2. Si tienes Eclipse 3.2 puedes instalarte los numerosos plugins
que las webtools contienen y necesitan, pero aquí vamos a tirar por el camino fácil:
vamos a descargar una versión preconfigurada de Eclipse 3.2 con todo lo necesario para el WTP,
o sea el archivo wtp-all-in-one-sdk-R-1.5.0-200606281455-win32.zip,
que ocupa 182Mb.

Una vez descomprimido ese ZIP en un directorio de nuestra elección, ejecutamos el eclipse.exe
que contiene y, después de un ratito, ya tendremos el Eclipse arrancado.

Si tu PC necesita de un proxy para salir a Internet, te aconsejo que configures el Eclipse para
que lo use, pues debido al uso de XML Schema externos, a veces se intentará
conectar al exterior
para recuperar esos esquemas (ej. debido al web.xml a veces intentará recuperar
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd). Si estamos detrás de un proxy,
no podrá hacerlo y tardará un rato, retardando
nuestro trabajo. Aquí no se cuenta cómo se hace, pero está en las preferencias de Eclipse
y es sencillo.

Instalar Tomcat

Para evitarnos problemas futuros como que Eclipse nos cree un proyecto web que luego dice que no se
puede desplegar porque no está soportado por el servidor, o que no podamos cambiar cuál es el servidor
de un proyecto debido a errores esotéricos, lo mejor será que definamos cuanto antes en Eclipse el
servidor de aplicaciones que vamos a usar.
Como de costumbre, en este tutorial utilizaremos Apache Tomcat, que es gratis y conveniente.
Para instalarlo en Eclipse:

  1. Descarga Tomcat desde http://tomcat.apache.org/
    e instálalo (aquí no contamos cómo se hace, pero es fácil)
  2. Una vez instalado, defínelo en Eclipse así:







Para nuestro tutorial esto ya vale, pero si queremos que ese Tomcat pueda ejecutar JSPs, debe tener acceso no sólo
a un JRE sino a un JDK. Si ese es tu caso, además de ésto primero tienes que tener instalado un JDK,
luego lo defines en Eclipse desde ese botón de «Installed JREs», y lo asocias a Tomcat.

Por cierto, un comentario relevante respecto a este Tomcat: las aplicaciones web que ejecutes
con él desde dentro de Eclipse no serán las que estén en el directorio webapps
del propio Tomcat, sino que Eclipse utiliza su propio directorio de despliegue y fichero de
configuración de servidor. Este fichero de configuración lo puedes ver y editar en la ventana
de layout del Eclipse, y ese directorio de despliegue está en tu
(Eclipse workspace)\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\webapps .

Creación de un proyecto web dinámico

Lo siguiente que haremos será crear un nuevo Proyecto web, diciéndole que lo
despliegue en ese servidor que hemos creado:




Lo llamaremos sucursales:

Nos preguntará si queremos cambiar a la perspectiva J2EE. En este tutorial no se ha hecho así,
pero yo creo que da igual.

Una vez creado el proyecto, crearemos un nuevo archivo WSDL dentro de él. Intentaremos
explicar la menor cantidad posible de conceptos de WSDL, pues la versión 1 de este estándar
tiene un modelo innecesariamente complejo (ya ha salido la versión 2, pero aún no la soporta
casi nadie, y he leído alguna crítica demoledora).

Hay diversas variantes para definir nuestro servicio, pero nosotros
no vamos a entrar en ellas y nos quedaremos directamente con la variante document/literal, que
es la que ofrece mayor interoperabilidad. Aunque no entremos en detalles, quiero señalar que
otra opción llamada «RPC/literal», al contrario de lo que mucha gente piensa, es igualmente válida y
estándar en lo referente a interoperabilidad, y más sencilla en muchos casos.
Pero lo cierto es que document/literal es la que se está popularizando más.

El wizard de las web tools ofrece bastantes ayudas para editar el WSDL y nos creará ya un
esqueleto de para que luego lo adaptemos:



¿Cómo llamar a nuestro servicio? A mí esto de llamar a los servicios «AlgoServicio» me parece
tan redundante como llamar a las clases «AlgoClase», así que como nuestro servicio va a manejar
datos de sucursales, lo llamaremos Sucursales.wsdl:

Y cambiaremos el Target namespace y su prefijo por algo adaptado a nuestro
ejemplo:

Así, el wizard ya nos creará un esqueleto de servicio, con una operación llamada
NewOperation:

Y ahora vamos a adaptar ese esqueleto a nuestras necesidades.

El portType de WSDL

«PortType» es un
término poco afortunado de WSDL v1 para referirse a una interfaz independiente del medio de transporte:
define qué operaciones tiene nuestro servicio y qué recibe y qué devuelve
cada una, de forma independiente del mecanismo de comunicación usado (SOAP, HTTP GET, correo-e, etc).

Esta aparente flexibilidad de definir una única interfaz abstracta válida para diferentes
transportes ha demostrado ser inútil en el mundo real, con lo que es una de las complejidades
innecesarias que WSDL v1 incorpora a la vida del desarrollador. Pero es lo que hay.

Esa interfaz abstracta está compuesta de una serie de operaciones, y a su vez
cada una de ellas puede tener una entrada y una salida (también declarar errores, pero
eso no lo vemos aquí). Esas entrada y salida se define cada
una en base a un mensaje.
Cualquiera pensaría que un mensaje es simplemente un documento XML, y en nuestro caso de document/literal
esto es prácticamente así, pero en otros casos no lo es. Pero bueno, el caso es que nosotros tenemos que
definir la estructura XML de esos mensajes de entrada y salida en base a elementos en XML Schema.

En fin,
el wizard ya nos ha creado un portType llamado Sucursales,
con una operación y elementos XML asociados para su entrada y salida. Inicialmente
la operación se llama NewOperation, y pinchando sobre ese nombre podemos cambiarlo
por buscaSucursales, que es el que queremos nosotros:

Ahora vamos a definir exactamente el XML que esa operación recibirá y devolverá.

El XML Schema de nuestros mensajes

En WSDL la definición de nuestros mensajes puede estar incrustada dentro del propio WSDL, o
ser incluida desde documentos externos. Ésto último suena más apropiado para un entorno de
sistemas de información empresariales, pero lo cierto es que más frecuentemente los tipos van
definidos dentro del propio WSDL, y algunas herramientas dan problemas si no es así.

Sea como sea, en nuestro caso para definir ese XML debemos abrir el XML schema incrustado
en nuestro WSDL, lo que hacemos desde la ventanita de Outline:



Como vemos, el wizard ya nos ha creado esqueletos de las estructuras XML de entrada
y salida de nuestra operación (incluso las ha renombrado
cuando la hemos renombrado, siguiendo las conveniciones <operación>Request y
<operación>Response
), pero ahora tenemos que cambiarlas según nuestras necesidades.

En el esqueleto, el elemento XML que define el XML que recibiremos,
buscaSucursalesRequest, es un elemento simple que sólo contiene una string.
Eso podría valernos puesto que sólo pediremos un trozo de código postal, pero va a quedar más claro
si creamos un elemento con un nombre más indicativo para enviar ese trozo de código postal,
como «parteCodPostal»
o algo.

Para eso tenemos que hacer que
buscaSucursalesRequest sea un elemento complejo que contenga un «parteCodPostal». Esto,
que es algo muy frecuente
en XML Schema, por alguna razón el editor de XML Schema de Eclipse no nos lo pone fácil. Según
él tendríamos que crear un tipo con nombre, en vez de crear sólo un elemento complejo con
tipo anónimo, que es el pan nuestro de cada día en XML Schema. Bueno, pues como yo creo que eso
es innecesario, recurriremos al código fuente, que tampoco es tan complicado. Pinchamos en la
pestaña inferior «Source», y cambiamos
un poco la declaración de buscaSucursalesRequest. Donde ponía:

Vamos a poner esto otro:

Si ahora volvemos a la pestaña de «Design», vemos que la declaración buscaSucursalesRequest
ha cambiado y el tipo es ahora «**anonymous**» (que es una opción que lamentablemente no teníamos antes).
Si pinchamos en buscaSucursalesRequest, veremos su estructura interna:

El «(buscaSucursalesRequestType)»
corresponde con ese tipo anónimo. El XML Spy lo muestra mucho más clarito que el Eclipse, pero
así es suficiente. Ahora vamos a añadirle el sub-elemento que representará el
código postal parcial, parteCodPostal:



Pinchando en el icono volveremos a ver el schema.

Ahora vamos a definir el resultado de nuestra operación, buscaSucursalesResponse.
Este elemento debe contener una lista de 0 o más estructuras, cada una con información
sobre una de las sucursales encontradas. Así que lo primero vamos a
definir cuál es la información que vamos a devolver para cada sucursal, y ésto lo haremos
con un nuevo tipo complejo de XML, InfoSucursal, que va a contener una sequence y
dentro tres elementos, codSucursal, direccion y codPostal:









Ahora vamos a definir buscaSucursalesResponse.
Lo podemos hacer al menos de
dos maneras, que resultarían en las dos variantes siguientes de XML que se devolvería:

Opción 1: todas las infoSucursal directamente bajo buscaSucursalesResponse Opción 2: un único elemento sucursales para contener la lista completa
    <buscaSucursalesResponse>
        <sucursal>
            ...
        </sucursal>
        ...
        <sucursal>
            ...
        </sucursal>
    <buscaSucursalesResponse>
    <buscaSucursalesResponse>
      <sucursales>
        <sucursal>
            ...
        </sucursal>
        ...
        <sucursal>
            ...
        </sucursal>
      </sucursales>
    <buscaSucursalesResponse>

Es una cuestión en parte de gusto, pero yo prefiero la segunda porque admite con más facilidad
que haya más cosas junto a las sucursales. Por ejemplo, en un servicio más realista,
posiblemente no se devolviesen todas las sucursales, sino sólo las primeras y se permitiría
luego recuperar las demás, así que la respuesta podría incluir también un flag diciendo si
hay más, y cómo continuar recuperándolas. Y además, como veremos más adelante, WSDL2Java puede
dar problemas con la primera opción.

Así pues escogemos la segunda opción.
De nuevo, el editor de XML Schema de Eclipse no nos
ayuda mucho, así que tiraremos de código fuente para dejar la declaración de
buscaSucursalesResponse así:

El visor gráfico del XML Schema no nos muestra mucho, pero confiemos en que esté bien.

Con esto ya hemos definido las estructuras de entrada y de salida, pero aún nos queda un detallito
relacionado con los namespaces, que son un mecanismo de XML que a menudo causa problemas pero que
en el fondo es útil.

Hasta donde yo sé, en servicios web SOAP lo normal es que todos los elementos de los XML intercambiados
tengan un namespace. Por ejemplo,

Elementos con namespace «uri:myns» Elementos con namespace «uri:myns» (exactamente igual que el anterior) Elementos sin namespace
  <sucursal xmlns="uri:myns">
    <codSucursal>..</codSucursal>
    <direccion>..</direccion>
  </sucursal>
  <bqs:sucursal xmlns:bqs="uri:myns">
    <bqs:codSucursal>..</bqs:codSucursal>
    <bqs:direccion>..</bqs:direccion>
  </bqs:sucursal>
  <sucursal xmlns="">
    <codSucursal>..</codSucursal>
    <direccion>..</direccion>
  </sucursal>

Los dos primeros casos son dos formas diferentes de escribir exactamente lo mismo: elementos
cuyo namespace URI es «uri:myns». Sin embargo, el tercero son elementos sin namespace, o sea
con namespace URI «», lo cual aunque no lo parezca hace una gran diferencia.

En SOAP 1.2 se requiere que el hijo del Body (o sea,
en nuestro caso buscaSucursalesRequest) tenga un namespace, y sobre los hijos de ese
primer elemento se recomienda que también lo tengan. Por eso, yo casi siempre he visto que los
elementos de los mensajes SOAP llevan namespace. Y nuestro servicio no va a ser menos.

Para conseguir esto, la forma más fácil es especificar en nuestro schema un atributo
elementFormDefault="qualified".
Pero resulta que el editor de XML Schema de Eclipse tampoco nos deja establecer este atributo,
lo que es una carencia incluso más importante que la de los elementos complejos de antes. Pero
bueno, recurrimos de nuevo al código fuente y dejamos nuestra declaración <xsd:schema >
como sigue:

Si ahora cerramos y salvamos nuestro «Inline Schema of Sucursales.wsdl»
volveremos al WSDL. Como nuestra operación buscaSucursales ya apuntaba a los elementos
que hemos definido, no tenemos que hacer nada más para definir su entrada y salida.

El binding de WSDL

Éste es otro término poco afortunado de WSDL 1. Un binding es la particularización de un portType
para un transporte particular. O sea, define una interfaz (operaciones, parámetros, etc) para un tipo
de transporte particular.
En nuestro caso, el portType Sucursales tiene ya asociado
un binding SOAP, que podemos ver en el editor de WSDL si pinchamos en el icono correspondiente
,
o navegando hasta él en la ventana de Outline. El esqueleto ya nos lo ha generado, pero
aún así tenemos que
cambiar una cosa que Eclipse olvidó adaptar cuando renombramos la operación: el soapAction.
Su valor es una cabecera HTTP que se usa en SOAP, y supongo que está pensado para permitir redirigir
peticiones sin tener que analizar el XML. No estoy seguro de que sirva de mucho, pero lo apropiado
es ponerlo en cualquier caso. De nuevo, incomprensiblemente Eclipse no nos deja editar este valor,
pero siempre podemos recurrir al fuente para cambiar el valor incorrecto de
http://banquito.com/Sucursales/NewOperation por el correcto de
http://banquito.com/Sucursales/buscaSucursales:

El servicio y puerto en WSDL

Igualmente, el esqueleto ya contiene nuestro servicio, llamado Sucursales.
Dentro de él podemos ver una cosa llamada «SucursalesSOAP», que es lo que en WSDL se
llama port (puerto) y es el punto de entrada que implementa el binding, que a su vez está
asociado al portType. O sea, la interfaz definitiva de nuestro servicio. Lo que vamos
a cambiar ahí es la dirección, poniendo la que luego usará Axis, o sea
http://localhost:8081/sucursales/services/SucursalesSOAP:

Ya que Axis por omisión pone en el URL el nombre del puerto («SucursalesSOAP»), decisión que a mí no me parece
adecuada en absoluto, pero bueno. Se puede cambiar, siempre que te acuerdes de hacerlo
cada vez que generas el servicio web.

Esto de
cablear las direcciones en el WSDL es una de las peores cosas de los servicios web y que más
problemas trae luego, pues gracias a la ocultación que hacen las herramientas luego quedan guardados
en sitios inverosímiles y escondidos que cuesta encontrar y cambiar (porque obviamente «localhost:8081»
no es normalmente una dirección válida para un servicio de producción). Pero
ésto no vamos a comentarlo aquí porque podría dar para varios tutoriales más.

Vale la pena mencionar la importante característica de WSDL de que
un servicio puede implementar varios puertos. Esto es, el mismo servicio puede implementar
a la vez diferentes interfaces (o lo que es lo mismo, diferentes versiones de la misma interfaz),
en direcciones distintas (o iguales, dependiendo de lo listo que sea el servicio).

Generando el esqueleto de nuestro servicio

Una vez tenemos un WSDL, ya podríamos crear el cliente del servicio en cualquier plataforma, pero
nosotros lo que vamos a crear es el servidor. Esto lo podemos hacer fuera de Eclipse invocando
manualmente a la utilidad WSDL2Java de Axis, pero ya que estamos en Eclipse
lo haremos con la opción de crear un nuevo servicio web, que por debajo llama a esa utilidad:





Si al elegir el New teníamos seleccionado el WSDL, por omisión nos sale
como Web service type = Top down Java bean Web Service, que es justo lo que
queremos: crear el Java a partir desde WSDL. El otro tipo (Bottom up Java bean
Web service
) corresponde a crear el WSDL a partir de Java, que es justo lo que Eclipse
y Axis promocionan pero que yo (y más gente, eh) pensamos que es incorrecto.
Una vez seleccionado el tipo Top down Java bean Web Service, si en Service definition
no tenemos nuestro WSDL, deberemos seleccionarlo.

Por omisión, el selector vertical de la izquierda está marcado hasta «Start service». Podemos
dejarlo ahí, o con «Install service», da igual. Luego le vamos
dando a Next (o directamente a Finish, si preferimos)
y después de esperar un ratito, y si todo va bien (copiar el runtime de Axis, crear el Java,
arrancar Tomcat y desplegar el servicio), pues al final tendremos el Tomcat funcionando y
el esqueleto del servicio generado:

El fichero SucursalesSOAPImpl es justo el que implementa las operaciones del servicio
web, así que vamos a editarlo y ponerle una implementación sencilla:

/**
 * SucursalesSOAPImpl.java
 *
 * This file was auto-generated from WSDL
 * by the Apache Axis 1.3 Oct 05, 2005 (05:23:37 EDT) WSDL2Java emitter.
 */

package com.banquito.Sucursales;

import java.util.List;
import java.util.ArrayList;

public class SucursalesSOAPImpl implements com.banquito.Sucursales.Sucursales_PortType{
  protected static List Sucursales=new ArrayList();
  static {
    Sucursales.add(new InfoSucursal("6565","Av. Colmenar, 13", "28760"));
    Sucursales.add(new InfoSucursal("34734","Pº Castellana, 145", "28034"));
    Sucursales.add(new InfoSucursal("9832","C/ Mayor, 12", "19001"));
  }

  public com.banquito.Sucursales.BuscaSucursalesResponse buscaSucursales
      (com.banquito.Sucursales.BuscaSucursalesRequest buscaSucursalesRequest)
          throws java.rmi.RemoteException {

    List results=new ArrayList();
    String parteCodPostal=buscaSucursalesRequest.getParteCodPostal();
    for (InfoSucursal suc:Sucursales)
      if (parteCodPostal==null || suc.getCodPostal().indexOf(parteCodPostal)>=0)
        results.add(suc);
    InfoSucursal[] resultsA=new InfoSucursal[results.size()];
    results.toArray(resultsA);
    BuscaSucursalesResponse resp=new BuscaSucursalesResponse(resultsA);
    	
    return resp;
  }

}

Cuando salvemos el fuente, si todo está bien Eclipse se ocupará de reiniciar lo que haga
falta para que funcione, así que con eso ya debería estar nuestro servicio. Asegúrate
de que Tomcat está arrancado (Status: started), y vamos a probar
el servicio con una utilidad de Eclipse que sirve para invocar a cualquier servicio web:







Parece que ha funcionado. También podemos ver el XML intercambiado con nuestro
servicio, pinchando en el enlace Source:

Ahora podríamos probar con otros clientes de servicios web, que a partir del WSDL
podrían acceder a nuestro servicio. Pero no conozco ningún otro cliente que sea gratis y
relevante, así que esto lo vamos a dejar
para otro tutorial en el que crearemos una aplicación AJAX
que permitirá acceder a nuestro servicio de sucursales.
Además, en
otro tutorial se muestra cómo utilizar el producto AmberPoint Express
para monitorizar la actividad de nuestro servicio web.

Disgresiones adicionales

¿Para qué tengo que ocuparme de todos estos malditos detalles del WSDL y el XML Schema,
si Axis lo puede hacer por mí?

Como ya he dicho, en mi empresa hay ejemplos de problemas de interoperabilidad entre herramientas
por no preocuparse del WSDL.
En mi opinión, la creación de servicios de esa forma «bottom up»
debería estar prohibida
,
excepto para casos muy concretos, pues
esta aproximación causa problemas reales de interoperabilidad, o sea se carga el argumento mismo para usar servicios
web. Imagino que las sucesivas versiones de Axis irán mejorando el asunto, pero aún así
yo considero que el diseño del modelo de servicios de una SOA es una pieza de la misma demasiado
importante como para arriesgarse a que quede limitado por las manías o defectos de una
herramienta, y así dificultar su futura evolución fuera de esa herramienta.

Y demonios, no es tan difícil. La SOA es el concepto más importante en la informática de estos tiempos
(no, no es sólo marketing),
así que aprender a usar unas herramientas, en vez de usar sólo Java no parece fuera de lugar.
Y además, seamos realistas: siempre hay problemas, y cuando ocurren a menudo las herramientas te
dejan tirado y es muy útil conocer cosas tan vitales como estas.

¿Y qué hay de malo en el WSDL2Java?

Bueno, el WSDL2Java de Axis 1 tiene al menos dos problemas que yo conozca:

  1. Por omisión no maneja correctamente los elementos XML repetitivos (arrays)
  2. No maneja bien los tipos simples que extienden xs:string, ej. para poner strings con atributos

Las dos son problemáticas, pero la primera además contribuye a no respetar el modo de trabajo
que Eclipse llama «Top-down», o sea hacer primero los WSDLs y luego el Java. Vamos a ilustrar esto.

Si creamos el XML Schema con la segunda opción para definir buscaSucursalesResponse
que hemos comentado antes
, podemos ver alguno de esos problemas. Por ejemplo,
crea un nuevo proyecto web «sucursales2», copia en él Sucursales.wsdl y luego
cambia la definición de buscaSucursalesResponse así:

Que en el diseño visual se ve así:

También debemos cambiar el URL de nuestro servicio en el WSDL para reflejar el nuevo proyecto,
http://localhost:8081/sucursales2/services/SucursalesSOAP.

Si generamos un nuevo servicio web «Top down» a partir de eso, veremos que el esqueleto de
nuestro servicio se creará diferente. En vez de que el método devuelva una estructura, como antes:

    public com.banquito.Sucursales.BuscaSucursalesResponse buscaSucursales
      (com.banquito.Sucursales.BuscaSucursalesRequest buscaSucursalesRequest)
        throws java.rmi.RemoteException {

ahora tendremos que devuelve directamente un array:

   public com.banquito.Sucursales.InfoSucursal[] buscaSucursales
      (com.banquito.Sucursales.BuscaSucursalesRequest buscaSucursalesRequest)
          throws java.rmi.RemoteException {

Si rellenamos ese esqueleto con código similar al del otro servicio:

package com.banquito.Sucursales;

import java.util.List;
import java.util.ArrayList;

public class SucursalesSOAPImpl implements com.banquito.Sucursales.Sucursales_PortType{
  protected static List Sucursales=new ArrayList();
  static {
    Sucursales.add(new InfoSucursal("6565","Av. Colmenar, 13", "28760"));
    Sucursales.add(new InfoSucursal("34734","Pº Castellana, 145", "28034"));
    Sucursales.add(new InfoSucursal("9832","C/ Mayor, 12", "19001"));
	}

  public com.banquito.Sucursales.InfoSucursal[] buscaSucursales
    (com.banquito.Sucursales.BuscaSucursalesRequest buscaSucursalesRequest)
        throws java.rmi.RemoteException {

      List results=new ArrayList();
      String parteCodPostal=buscaSucursalesRequest.getParteCodPostal();
      for (InfoSucursal suc:Sucursales)
        if (parteCodPostal==null || suc.getCodPostal().indexOf(parteCodPostal)>=0)
          results.add(suc);
      InfoSucursal[] resultsA=new InfoSucursal[results.size()];
      results.toArray(resultsA);
    	
      return resultsA;
  }

}

Después, cuando esté el Tomcat arrancado, probamos el servicio. Todo funciona, pero si miramos
el XML devuelto por el servicio, veremos que contiene cosas inesperadas:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="
  http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <buscaSucursalesResponse xmlns="http://banquito.com/Sucursales/">
      <item xmlns="">
        <ns1:codSucursal xmlns:ns1="http://banquito.com/Sucursales/">6565</ns1:codSucursal>
        <ns2:direccion xmlns:ns2="http://banquito.com/Sucursales/">Av. Colmenar, 13</ns2:direccion>
        <ns3:codPostal xmlns:ns3="http://banquito.com/Sucursales/">28760</ns3:codPostal>
      </item>
      <item xmlns="">
        <ns4:codSucursal xmlns:ns4="http://banquito.com/Sucursales/">34734</ns4:codSucursal>
        <ns5:direccion xmlns:ns5="http://banquito.com/Sucursales/">Pº Castellana, 145</ns5:direccion>
        <ns6:codPostal xmlns:ns6="http://banquito.com/Sucursales/">28034</ns6:codPostal>
      </item>
    </buscaSucursalesResponse>
  </soapenv:Body>
</soapenv:Envelope>

¿»item»? ¿Cómo que «item»? Nosotros no hemos puesto nada sobre «item» en nuestro XML Schema,
así que no deberían aparecer.
Cualquier cliente que utilice nuestro WSDL original no entenderá esa respuesta, pues «item» no
es «sucursal».

Ocurrirá lo mismo si usamos el WSDL original pero, en vez de document/literal, usamos rpc/literal.
WSDL2Java (que es la herramienta de Axis subyacente que nos ha generado ese código) parece tener un
problema con los elementos XML repetitivos (o sea, arrays). En casos como este, el código funciona
pero genera el XML que no es (así como Axis genera luego un WSDL incorrecto), pero en otros casos
directamente el código de WSDL2Java no compila
porque pone clases que extienden de un array o que intentan lanzan arrays como excepción.

Pero bueno,
afortunadamente WSDL2Java tiene una especie de switch que viene a decirle «genera el código correcto».
Lamentablemente, el sencillo wizard de Eclipse no parece permitirnos poner ese switch
ni lo pone él por omisión, así que tendremos que utilizar directamente la utilidad WSDL2Java
desde la línea de comandos. Para ello:

  1. Abrimos una caja de comandos (ejecutamos cmd.exe) y establecemos el classpath de Axis.
    Aquí vamos a usar el mismo Axis que ya tenemos dentro de Eclipse, que
    es la versión 1.3, pero puedes usar cualquier Axis que ya tengas instalado por ahí:

      C:> set p=C:\Archivos de programa\eclipse32\plugins
             En la línea anterior tienes que poner el directorio donde esté el Eclipse en tu máquina
      C:> set a=%p%\org.apache.axis_1.3.0.v200606181221\lib
      C:> set classpath=%a%\axis.jar;%a%\wsdl4j-1.5.1.jar;%a%\commons-discovery-0.2.jar;
            %a%\jaxrpc.jar;%a%\saaj.jar;
            %p%\org.apache.commons_logging_1.0.4.v200606131651\lib\commons-logging-1.0.4.jar
    
  2. Luego nos cambiamos a un directorio temporal y ejecutamos WSDL2Java:

      C:> cd c:\temp\w
      C:\temp\w> java org.apache.axis.wsdl.WSDL2Java --wrapArrays --server-side "
      C:\Trabajo\varios\wsdl2java\eclipsews\sucursales2\Sucursales.wsdl"
             En la línea anterior debes poner el camino correcto hasta tu WSDL modificado, dentro de tu workspace de Eclipse
    

Esa opción --wrapArrays (o -w) es ese switch de «genera el código correcto»
que decía antes: le dice a WSDL2Java que, en vez de usar arrays directamente, les ponga alrededor
una clase. En nuestro caso, eso le da la posibilidad a WSDL2Java de generar la metainformación
del nombre de los elementos individuales del array; o sea «sucursal» en vez de «item». Si tienes
curiosidad, esta metainformación está en el código que WSDL2Java ha generado (ahora) para la clase
BuscaSucursalesResponse:

    // Type metadata
    private static org.apache.axis.description.TypeDesc typeDesc =
        new org.apache.axis.description.TypeDesc(BuscaSucursalesResponse.class, true);

    static {
        typeDesc.setXmlType(new javax.xml.namespace.QName
          ("http://banquito.com/Sucursales/", ">buscaSucursalesResponse"));
        org.apache.axis.description.ElementDesc elemField = new org.apache.axis.description.ElementDesc();
        elemField.setFieldName("sucursal");
        elemField.setXmlName(
            new javax.xml.namespace.QName("http://banquito.com/Sucursales/", "sucursal"));
        elemField.setXmlType(
            new javax.xml.namespace.QName("http://banquito.com/Sucursales/", "InfoSucursal"));
        elemField.setMinOccurs(0);
        elemField.setNillable(false);
        elemField.setMaxOccursUnbounded(true);
        typeDesc.addFieldDesc(elemField);
    }

Que parece que no se puede poner si no se crea esta clase que envuelva al array.

Debo confesar que yo no he conocido este switch -w hasta el otro día (lamentablemente,
después de haber publicado la primera versión de este tutorial), que lo vi casualmente, y hasta
entonces pensaba que WSDL2Java era incapaz de generar este código correcto. No entiendo bien
por qué no hace ésto por omisión, aunque supongo que
se debe a razones históricas, pero a mí me parece que
también se debe a poco respeto a la interoperabilidad que, en mi opinión, destila Axis: como
luego el WSDL que genera Axis es coherente con el código de WSDL2Java, pues da igual que no sea
compatible con el original.

En fin, el código generado lo tenemos en el subdirectorio com de ese directorio temporal.
Ahora lo tenemos que meter en nuestro proyecto Eclipse. Eso es fácil: sólo hay que machacar
los fuentes. Puedes hacerlo desde Eclipse, desde el explorador de archivos o desde la línea
de comandos:

  C:\temp\w> xcopy /s/y com "C:\Trabajo\varios\wsdl2java\eclipsews\sucursales2\src\com"
         Recuerda poner el camino correcto hasta tu proyecto en el workspace de Eclipse

Después de lo cual podemos tener que darle a la opción de Refresh en Eclipse, en el menú
contextual del directorio «src» de nuestro proyecto. Luego veremos que la declaración del método
buscaSucursales de nuestra clase SucursalesSOAPImpl vuelve a devolver
una clase BuscaSucursalesResponse, en vez de un array. Así que le podemos
copiar como implementación del servicio el código de nuestra clase original.

Pero esto no es todo: tenemos también que modificar la información de despliegue de Axis.
Axis trae herramientas de línea de comandos para ello, pero nosotros lo vamos a hacer dentro de Eclipse.
Para ello,

  1. Abrimos el fichero deploy.wsdd que está en el directorio src
    de nuestro proyecto y que también ha sido generado por WSDL2Java:

    Ahí seleccionamos toda la declaración de nuestro servicio, desde <service>
    hasta </service>, y la copiamos en el portapapeles.

  2. Luego, dentro del directorio WebContent/WEB-INF de nuestro proyecto, abrimos el archivo
    server-config.wsdd:

  3. Ahí buscamos el texto Sucursal, y encontraremos la declaración del servicio
    SucursalesSOAP:

  4. Vamos a sustituir toda esa declaración, desde <service>
    hasta </service>, por lo que hemos copiado antes desde deploy.wsdd
    y tenemos ahora en el portapapeles, y salvamos el fichero:

Tras esos cambios, Eclipse redesplegará lo necesario a nuestro Tomcat y, cuando accedamos con
el Web Services Explorer, podremos ver que esta vez el servicio devuelve la respuesta adecuada,
o sea una <buscaSucursalesResponse> con varias etiquetas <sucursal>
dentro.

Ahora bien, si resulta que no usamos WSDL2Java sino sólo Eclipse, o si usamos WSDL2Java
pero no sabemos que para que genere el código correcto hay que darle la opción -w
(como me pasaba a mí),
pues nos vamos a encontrar con que el servicio de Axis no cumple con nuestro WSDL. Es posible
que ni nos demos cuenta, pues en vez de usar el WSDL original (correcto) pasemos a usar el
que nos genera Axis (erróneo). Si todos los demás clientes lo usan también, pues bueno;
pero si no, pues en algún momento futuro encontraremos que no hay interoperabilidad.

El segundo problema de WSDL2Java que mencionaba arriba se ve por ejemplo cuando
uno intenta generar stubs complejos como los necesarios para acceder
a UDDI v3 a partir de su WSDL.
Aún si tenemos en cuenta la opción –wrapArrays,
el resultado no compila, pues a partir de tipos que derivan de xs:string, genera clases
que no extienden de nadie pues no se puede extender de la clase String (que vaya otra idea feliz, ésta)
pero llaman a su super().

Pero al menos, una vez arreglado esto (a mano clase por clase), parece que los mensajes intercambiados con el servidor
son correctos. En fin, al menos por ahora no he encontrado una opción que haga que WSDL2Java trate bien esto,
pero si lo encuentro modificaré el tutorial.

En mi opinión, Axis es una herramienta útil pero tiene un grave problema: está diseñado
desde el principio para funcionar en modo «Bottom-up», o sea primero hacer el Java y luego
que Axis genere el XML. Como ya he dicho, esta facilidad precisamente hay que evitarla.


12 COMENTARIOS

  1. necesito una respuesta urgente de esto se trata mi tema de proyecto en la universidad y no he logrado descubrir en que estoy mal o si tengo que crear la clase y que va dentro de esta

  2. estimado Javier, tenía ilusión de que justo este tutorial me ayudaría a meter a funcionar por lo menos un ejemplo que explica aquí para Web Services, pero me está fallando el server Tomcat 7.0, el error que da es: IWAB0135E An unexpected error has occurred. java.net.ConnectException Connection refused:
    Sabrá el motivo de este error? Gracias de antemano.

  3. Vaya el articulo lo has escrito en el 2006; 6 años después, aun sigue vigente la idea y me parece estupendo; en primera instancia seguí los pasos; empero también me sale el error: \\\» IWAB0135E\\\» bueno hoy es viernes ya el fin de semana con mas calma lo revisare y les comento como me fue; ya que esto tiene que salir si o si.

    Enserio Muchas Gracias, excelente articulo, esta portal me ha sacado de varias.

  4. Cuando un cliente da un \\\»Connection Refused\\\» significa que la máquina a donde se está conectando responde, pero que el puerto al que se está conectando está cerrado. Así que habría que asegurarse de que ambos son los correctos. Podeis conectaros a ellos con un navegador web a ver si os da el mismo error o no, y probar a cambiar la dirección del WSDL por otra (ej. poniendo la IP de la máquina). Suerte

  5. Saludos, he estado siguiendo tu tutorial para crear un WS de consulta a un base de datos y me surgió una duda ¿cómo podría crear una operación sin entradas para mostrar una consulta donde muestre toda la tabla del tipo SELECT e FROM e TABLA?, ¿necesito tener al menos un output y un input?. También tengo algunas dudas sobre la creación de datos complejos, en especial con las estructuras.Gracias att Arturo.

  6. Buenos días soy nuevo en esto de web services y necesito ayuda, como puedo enviar un requerimiento SOAP a un web services teniendo ya un archivo WSDL ??? Espero me puedan ayudar. Saludos.

  7. Hola que tal, gracias por la iniciativa, te hago una consulta, a ver si me puedes ayudar, resulta que a partir de un wsdl existente (en una url), genero el cliente, desde eclipse (neon) el primer detalle que aparece es el aviso «Selection must be a wsdl», evito este aviso descargando el wsdl, luego me indica que schema, no lo ubica en el equipo, por lo que lo ubico a partir de la url del mismo y lo guardo con extensión xsd.

    Luego de sortear esos detalles, eclipse genera el cliente y la respectiva prueba, probado con SoapUi, todas las fucionalidades, en absoluto, trabajan correctamente, pero, no pasa lo mismo desde eclipse, es decir, funcionalidades que van correctamente en Soap, no lo hacen desde eclipse.

    Conoces algún motivo por el cual se dé esta situación, no habia trabajado ocn webServices hasta ahora y no se que tan comun, o irregular pueda ser la situacion que he notado.

    Gracias de antemano, saludos

  8. Saludos, les comento que estoy empezando a probar web service soap y estuve probando varios de la web. El servicio que me esta interesando tiene su definicion «https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl» en esta direccion. Pero cuando quiero agregarlo a mi aplicacion obtengo un error por una clase ya existente. Probé otros servicios del mismo servidor y no tuve inconvenientes ¿A que se debe o como podria resolverlo?

  9. Estimado Javier, le mando un saludo!, el presente es para consultarle acerca de los estándares de comunicación WITSML y la implementación de sus esquemas en java! requiero realizar una aplicación en java que trabaje con los estandares WITSML, cualquier asesoria que me pueda dar (paga o gratuita) sería bienvenida! gracias por su atención!.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad