El diseño del software tiende a ser cada vez más modular. Las aplicaciones se componen de una serie de componentes (servicios) reutilizables, que pueden encontrarse distribuidos a lo largo de una serie de máquinas conectadas en red.
Los Servicios Web nos permitirán distribuir nuestra aplicación a través de Internet, pudiendo una aplicación utilizar los servicios ofrecidos por cualquier servidor conectado a Internet.
Un Servicio Web es un componente al que podemos acceder mediante protocolos Web estándar, utilizando XML para el intercambio de información.
Normalmente nos referimos con Servicio Web a una colección de procedimientos (métodos) a los que podemos llamar desde cualquier lugar de Internet o de nuestra intranet, siendo este mecanismo de invocación totalmente independiente de la plataforma que utilicemos y del lenguaje de programación en el que se haya implementado internamente el servicio.
Cuando conectamos a un servidor web desde nuestro navegador, el servidor nos devuelve la página web solicitada, que es un documento que se mostrará en el navegador para que lo visualice el usuario, pero es difícilmente entendible por una máquina. Podemos ver esto como web para humanos. En contraposición, los Servicios Web ofrecen información con un formato estándar que puede ser entendido fácilmente por una aplicación. En este caso estaríamos ante una web para máquinas.
Las características deseables de un Servicio Web son:
Podemos ver la arquitectura de los Servicios Web desde dos puntos de vista. En el primero de ellos estudiaremos la función de cada agente en el mecanismo de localización e invocación de Servicios Web, mientras que en el segundo abstraeremos una serie de capas en las que se organizan los protocolos que se utilizan para realizar estas funciones
Arquitectura funcional
Podemos distinguir tres agentes con diferentes funciones:
Proveedor de servicio | Implementa unas determinadas operaciones (servicio). Un cliente podrá solicitar uno de estos servicios a este proveedor. |
Cliente del servicio | Invoca a un proveedor de servicio para la realización de alguna de los operaciones que proporciona. |
Registro de servicios | Mantiene una lista de proveedores de servicios disponibles, junto a sus descripciones. |
El mecanismo básico de invocación de servicios consistirá en que un cliente solicitará un determinado servicio a un proveedor, efectuando el proveedor dicho servicio. El servidor devolverá una respuesta al cliente como resultado del servicio invocado.
Esto podremos hacerlo así si el cliente conoce de antemano el proveedor del cual va a obtener el servicio. Pero hemos de pensar que en Internet encontraremos una gran cantidad de Servicios Web dispersos, lo cual hará difícil localizar el que busquemos. Además, si hemos localizado uno que realiza la función que necesitamos, si dicho servicio no está mantenido por nosotros puede ocurrir que en algún momento este servicio cambie de lugar, de interfaz o simplemente desaparezca, por lo que no podremos confiar en que vayamos a poder utilizar siempre este mismo servicio.
Los registros de servicios nos permiten automatizar la localización de Servicios Web. Un proveedor puede anunciarse en un determinado registro, de forma que figurará en dicho registro la localización de este servicio junto a una descripción de su funcionalidad y de su interfaz, que podrá ser entendida por una aplicación.
Cuando un cliente necesite un determinado servicio, puede acudir directamente a un registro y solicitar el tipo de servicio que necesita. Para ello es importante establecer un determinada semántica sobre las posibles descripciones de funcionalidades de servicios, evitando las posibles ambigüedades.
El registro devolverá entonces una lista de servicios que realicen la función deseada, de los cuales el cliente podrá elegir el más apropiado, analizar su interfaz, e invocarlo.
Arquitectura de capas de protocolos
Los protocolos utilizados en los Servicios Web se organizan en una serie de capas:
Capa | Descripción |
Transporte de servicios | Es la capa que se encarga de transportar los mensajes entre aplicaciones. Normalmente se utiliza el protocolo HTTP para este transporte, aunque los servicios web pueden viajar mediante otros protocolos de transferencia de hipertexto como SMTP, FTP o BEEP. |
Mensajería XML | Es la capa responsable de codificar los mensajes en XML de forma que puedan ser entendidos por cualquier aplicación. Puede implementar los protocolos XML-RPC o SOAP. |
Descripción de servicios | Se encarga de definir la interfaz pública de un determinado servicio. Está definición se realiza mediante WSDL. |
Localización de servicios | Se encarga del registro centralizado de servicios, permitiendo que estos sean anunciados y localizados. Para ello se utiliza el protocolo UDDI. |
Más adelante describiremos cada una de las tecnologías para Servicios Web vistas en las distintas capas.
Tenemos una serie de tecnologías, todas ellas basadas en XML, que son fundamentales para el desarrollo de Servicios Web. Estas tecnologías son independientes tanto del SO como del lenguaje de programación utilizado para implementar dichos servicios. Por lo tanto, serán utilizadas para cualquier Servicio Web, independientemente de la plataforma sobre la que construyamos dichos servicios (como puede ser J2EE o .NET).
SOAP
Se trata de un protocolo derivado de XML que nos sirve para intercambiar información entre aplicaciones.
Normalmente utilizaremos SOAP para conectarnos a un servicio e invocar métodos remotos, aunque puede ser utilizado de forma más genérica para enviar cualquier tipo de contenido. Podemos distinguir dos tipos de mensajes según su contenido:
Cuando hablamos de Servicios Web normalmente nos referimos a RPC, por lo que nos centraremos en este tipo de mensajes.
Puede ser utilizado sobre varios protocolos de transporte, aunque está especialmente diseñado para trabajar sobre HTTP.
Dentro del mensaje SOAP podemos distinguir los siguientes elementos:
Hemos visto como los mensajes SOAP nos sirven para intercambiar cualquier documento XML entre aplicaciones. Pero puede ocurrir que necesitemos enviar en el mensaje datos que no son XML, como puede ser una imagen. En ese caso tendremos que recurrir a la especificación de mensajes SOAP con anexos.
Los mensajes SOAP con anexos añaden un elemento más al mensaje:
Nuestro mensaje podrá contener tantos anexos como queramos.
Un ejemplo de mensaje SOAP es el siguiente:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns:getTemperatura xmlns:ns="http://j2ee.ua.es/ns"> <area>Alicante</area> </ns:getTemperatura> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
En él estamos llamando a nuestro método getTemperatura para obtener información meteorológica, proporcionando como parámetro el área de la que queremos obtener la temperatura.
Podemos encontrar la especificación de SOAP y SOAP con anexos publicada en la página del W3C, en las direcciones http://www.w3.org/TR/SOAP/ y http://www.w3.org/TR/SOAP-attachments respectivamente.
WSDL
Es otro lenguaje derivado de XML, que se utiliza para describir los Servicios Web, de forma que una aplicación pueda conocer de forma automática la función de un Servicio Web, así como la forma de uso de dicho Servicio Web.
El fichero WSDL describirá la interfaz del Servicio Web, con los métodos a los que podemos invocar, los parámetros que debemos proporcionarles y los tipos de datos de dichos parámetros.
Si desarrollamos un Servicio Web, y queremos que otras personas sean capaces de utilizar nuestro servicio para sus aplicaciones, podremos proporcionar un documento WSDL describiendo nuestro servicio. De esta forma, a partir de este documento otros usuarios podrán generar aplicaciones clientes en cualquier plataforma (ya que WSDL se define como un estándar) que se ajusten a nuestro servicio.
El elemento raíz dentro de este fichero es definitions, donde se especifican los espacios de nombres que utilizamos en nuestro servicio. Dentro de este elemento raíz encontramos los siguientes elementos:
Un documento WSDL de ejemplo es el siguiente:
<?xml version="1.0" encoding="utf-8" ?> <definitions xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://j2ee.ua.es/wsdl" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://j2ee.ua.es/wsdl" xmlns="http://schemas.xmlsoap.org/wsdl/"> <message name="getTempRequest"> <part name="string_1" xmlns:partns="http://www.w3.org/2001/XMLSchema" type="partns:string" /> </message> <message name="getTempResponse"> <part name="double_1" xmlns:partns="http://www.w3.org/2001/XMLSchema" type="partns:double" /> </message> <portType name="TempPortType"> <operation name="getTemp"> <input message="tns:getTempRequest" /> <output message="tns:getTempResponse" /> </operation> </portType> <binding name="TempPortSoapBinding" type="tns:TempPortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getTemp"> <soap:operation soapAction="" style="rpc" /> <input> <soap:body use="encoded" namespace="http://j2ee.ua.es/wsdl" encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/" /> </input> <output> <soap:body use="encoded" namespace="http://j2ee.ua.es/wsdl" encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/" /> </output> </operation> </binding> <service name="Temp"> <documentation>Documentacion</documentation> <port name="TempPort" binding="tns:TempPortSoapBinding"> <soap:address location="http://localhost:7001/sw_temp/Temp" /> </port> </service> </definitions>
En el que se define un servicio que proporciona el método getTemp, que toma como parámetro una cadena con el nombre del área que queremos consultar, y nos devuelve un valor real.
En los elementos message vemos que tenemos dos mensajes: los mensajes de entrada y salida de la operación getTemp de nuestro servicio. El mensaje de entrada contiene un dato de tipo string (el parámetro del método), y el de salida es de tipo double (la temperatura que devuelve el servicio).
El elemento portType define la operación getTemp a partir de los mensajes de entrada y salida que la componen, y en binding se establece esta operación como de tipo RPC y se indica la codificación de estos mensajes.
Por último en el apartado service se especifica el puerto al que podemos conectar para usar el servicio, dando la URL a la que nuestro cliente deberá acceder.
Podemos encontrar la especificación de WSDL publicada en la página del W3C, en la dirección http://www.w3.org/TR/wsdl.
UDDI
UDDI nos permite localizar Servicios Web. UDDI define la especificación para construir un directorio distribuido de Servicios Web, donde los datos se almacenan en XML.
Además, UDDI define una API para trabajar con dicho registro, que nos permitirá buscar datos almacenados en él, y publicar datos nuevos.
De esta forma, una aplicación podrá anunciar sus servicios en un registro UDDI, o bien localizar servicios que necesitemos mediante este registro.
Esta capacidad de localizar servicios en tiempo de ejecución, y de que una aplicación pueda saber cómo utilizarlo inmediatamente gracias a la descripción del servicio, nos permitirá realizar una integración débilmente acoplada de nuestra aplicación.
La interfaz de UDDI está basada en SOAP. Para acceder al registro se utilizarán mensajes SOAP, que son transportados mediante protocolo HTTP.
Podemos encontrar la especificación de UDDI, documentación, y más información en la dirección http://www.uddi.org/.
Hemos visto las tecnologías en las que se basan los Servicios Web, y que los hacen independientes de la plataforma y del lenguaje de programación utilizado. Sin embargo, escribir manualmente los mensajes SOAP desde nuestras aplicaciones puede ser una tarea tediosa. Por ello, las distintas plataformas existentes incorporan librerías y utilidades que se encargan de realizar esta tarea por nosotros.
En este tema veremos las librerías que incorpora J2EE para la generación y el procesamiento de código XML, que nos servirán para implementar y utilizar Servicios Web.
JAXP
La API JAXP nos permite procesar cualquier documento XML desde lenguaje Java. Tiene en cuenta los espacios de nombres, lo cual nos permite trabajar con DTDs que podrían tener conflictos de nombres si estos no estuviesen soportados. Además, soporta XSLT, lo cual nos permitirá convertir un documento XML a otro formato, como por ejemplo HTML.
Esta es una librería genérica, para procesar cualquier documento XML. A continuación veremos una serie de librerías, para tareas más especificas, que se apoyan en JAXP para realizar el procesado de diferentes lenguajes como SOAP, WSDL y UDDI, todos ellos derivados de XML. Por lo tanto, todas estas librerías dependerán de JAXP para su correcto funcionamiento.
JAXM
La API JAXM implementa la mensajería XML en Java orientada al documento. Nos permitirá de forma sencilla crear mensajes XML, insertando el contenido que queramos en ellos, y enviarlos a cualquier destinatario, así como extraer el contenido de los mensajes que recibamos. Permite enviar y recibir los mensajes de forma síncrona (modelo petición-respuesta) o asíncrona (envío de mensaje sin esperar respuesta).
Los mensajes XML con los que trabaja JAXM siguen la especificación SOAP y SOAP con anexos. Dentro de JAXM encontramos dos APIs:
JAX-RPC
La API JAX-RPC implementa la infraestructura para realizar llamadas a procedimiento remoto (RPC) mediante XML. En este caso se enviará un mensaje SOAP con el método que queremos invocar junto a los parámetros que le pasamos, y nos devolverá de forma síncrona una respuesta SOAP con el valor devuelto por el método tras su ejecución.
Por lo tanto, JAX-RPC dependerá de SAAJ para construir los mensajes SOAP, para enviarlos, y para extraer la información del mensaje SOAP que nos devuelve como resultado.
Esta API nos permitirá, de forma sencilla, invocar Servicios Web de tipo RPC, así como crear nuestros propios Servicios Web RPC a partir de clases Java que tengamos implementadas. Cuando hablamos de Servicios Web, normalmente nos referimos a este tipo de Servicios Web.
JAXR
La API JAXR nos permitirá acceder a registros XML a través de una API estándar Java. Esta API pretende proporcionar una interfaz única para acceder a distintos tipos de registros, cada uno de los cuales tiene un protocolo distinto.
Actualmente JAXR es capaz de trabajar con registros UDDI y ebXML. Podremos realizar dos tipos de tareas distintas cuando accedamos a un registro mediante JAXR:
JAXB
La API de JAXB (Java API for Binding) nos permite asociar esquemas XML y código Java. A partir de un esquema XML, podremos generar una clase Java que represente dicho esquema.
De esta forma podremos convertir un documento XML a una serie de objetos Java que contendrán la información de dicho documento (unmarshalling). Podremos entonces trabajar desde nuestra aplicación con estos objetos, accediendo y modificando sus valores. Finalmente, podremos volver a obtener un documento XML a partir de los objetos Java (marshalling).
Esto nos va a simplificar la tarea de utilizar tipos de datos propios en llamadas a Servicios Web, ya que utilizando JAXB podremos realizar de forma sencilla la conversión entre nuestra clase Java y un documento XML con la información de dicha clase.
Vamos a crear nuestros propios Servicios Web, que ofrecerán una serie de métodos a los que se podrá llamar mediante RPC desde cualquier lugar de Internet mediante protocolos estándar (mensajes SOAP).
Deberemos por lo tanto ser capaces de interpretar en nuestras aplicaciones los mensajes SOAP entrantes de petición para la invocación de un método. Posteriormente, invocaremos el método solicitado, y con el resultado que nos devuelva deberemos construir un mensaje SOAP de respuesta y devolvérselo al cliente.
Si tuviésemos que introducir nosotros el código para interpretar este mensaje de entrada, y generar manualmente el mensaje de respuesta, el desarrollo de Servicios Web sería una tarea altamente costosa.
Es más, si se forzase al programador a componer el mensaje SOAP manualmente cada vez que desarrolle un Servicio Web, es muy probable que cometa algún error y no respete exactamente el estándar SOAP. Esto sería un grave problema para la interoperabilidad de los Servicios Web, que es una de las características que perseguimos con esta tecnología.
Para evitar estos problemas, utilizaremos librerías que nos permitan leer o generar mensajes SOAP para la invocación de métodos remotos (RPC), como es el caso de la API JAX-RPC.
Además, para facilitar aún más la tarea de desarrollar Servicios Web, normalmente contaremos con herramientas que a partir de las clases que implementan nuestro servicio generen automáticamente todo el código necesario para leer el mensaje SOAP de entrada, invocar el método, escribir el mensaje SOAP de salida, y devolverlo al cliente.
Por lo tanto, nosotros deberemos centrarnos únicamente en la tarea de programar la funcionalidad que implementan nuestros servicios, olvidándonos del mecanismo de invocación de éstos.
En las aplicaciones basadas en JAX-RPC encontramos los siguientes elementos:
Figura 1. Arquitectura de JAX-RPC
Las únicas capas que debemos implementar nosotros son el Cliente y el Servicio. En la implementación de estos componentes el uso de la librería JAX-RPC será totalmente transparente para nosotros. No hará falta que introduzcamos código JAX-RPC dentro de ellas. En el servicio simplemente implementaremos los métodos que queremos que ofrezca nuestro servicio, como si se tratase de cualquier clase Java, y en el cliente podremos invocar los métodos de este servicio como si invocásemos directamente los métodos de la clase Java.
Las capas Stub y Tie, son capas construidas a medida para la interfaz de nuestro servicio. Estás son las capas que utilizarán JAX-RPC para generar y leer los mensajes SOAP que vamos a utilizar para invocar el servicio, y devolver la respuesta al cliente. Generarán o serán capaces de leer los mensajes apropiados para el caso concreto de los métodos que definimos en nuestro servicio, por lo que deberemos generar estas capas para cada servicio que desarrollemos. Afortunadamente, normalmente contaremos con herramientas que generen de forma automática estas capas a partir de la interfaz de nuestro servicio, por lo que no será necesario que el desarrollador de servicios trate directamente con JAX-RPC en ningún momento.
Vamos a ver los tipos de datos que podemos utilizar cuando trabajamos con JAX-RPC como tipo de los parámetros y del valor devuelto por los métodos de nuestro servicio.
Podremos utilizar cualquiera de los tipos básicos de Java:
boolean byte double float int long short char
Además, también podremos utilizar cualquiera de los wrappers de estos tipos básicos:
java.lang.Boolean java.lang.Byte java.lang.Double java.lang.Float java.lang.Integer java.lang.Long java.lang.Short java.lang.Character
Las siguientes clases de Java también son aceptadas como tipos válidos por JAX-RPC:
java.lang.String java.math.BigDecimal java.math.BigInteger java.util.Calendar java.util.Date
Podremos utilizar también gran parte de las clases pertenecientes al marco de colecciones de Java:
Listas: List ArrayList LinkedList Stack Vector Mapas: Map HashMap Hashtable Properties TreeMap Conjuntos: Set HashSet TreeSet
Además de estos datos, se permitirá el uso de arrays, tanto unidimensionales como multidimensionales, cuyos elementos podrán ser de cualquiera de los tipos admitidos.
Las clases desarrolladas por nosotros también podrán ser usadas si cumplen ciertas condiciones:
Si nuestros tipos de datos no cumplen estas características, o bien estamos trabajando con herramientas que no soportan estos tipos, deberemos construir manualmente serializadores y deserializadores para nuestras clases. Su función será realizar la conversión entre nuestra clase Java y su correspondiente formato como documento XML.
Vamos a crear paso a paso un Servicio Web utilizando las herramientas que nos ofrece Java Web Services Developer Pack 1.1.
Implementación del servicio
El primer paso será implementar nuestro servicio. Primero deberemos definir la interfaz de nuestro servicio. Utilizaremos para ello una interfaz remota (RMI) en la que se definen todos los métodos que va a ofrecer nuestro servicio:
package utils; import java.rmi.Remote; import java.rmi.RemoteException; public interface ConversionIF extends Remote { public int euro2ptas(double euro) throws RemoteException; public double ptas2euro(int ptas) throws RemoteException; }
Una vez hecho esto, definiremos una clase Java que implemente dicha interfaz, con un constructor void (si no se especifica constructor, por defecto se creará un constructor void), y con la implementación de todos los métodos públicos definidos en la interfaz:
package utils; public class ConversionImpl implements ConversionIF { public int euro2ptas(double euro) { return (int) (euro * 166.386); } public double ptas2euro(int ptas) { return ((double) ptas) / 166.386; } }
Con esto ya habremos implementado el servicio. Ahora deberemos compilar estas clases de la misma forma que compilamos cualquier clase Java.
Crear el contexto
Una vez implementadas nuestras clases del servicio deberemos crear la estructura de directorios adecuada para Tomcat. Crearemos un directorio WEB-INF, y copiaremos las clases generadas al directorio WEB-INF/classes. En nuestro caso tendremos:
WEB-INF/classes/utils/ConversionImpl.java WEB-INF/classes/utils/ConversionIF.java
Además deberemos definir el descriptor de despliegue WEB-INF/web.xml de la misma forma que lo hacíamos para cualquier Aplicación Web:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <display-name>Servicio Web de conversion</display-name> <description> Servicio web para la conversion de Euros a Ptas </description> </web-app>
Además de este descriptor de despliegue perteneciente a la especificación estándar de J2EE, deberemos generar un fichero de configuración de nuestro Servicio Web perteneciente a nuestra implementación concreta de las herramientas para la generación de servicios. Este fichero no estándar es WEB-INF/jaxrpc-ri.xml, y tendrá la siguiente forma:
<?xml version="1.0" encoding="UTF-8"?>
<webServices
xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
version="1.0"
targetNamespaceBase="http://j2ee.ua.es/wsdl"
typeNamespaceBase="http://j2ee.ua.es/types"
urlPatternBase="/ws">
<endpoint
name="Conversion"
displayName="Conversion Euro-Ptas"
description="Servicio Web de conversion entre Euros y Ptas"
interface="utils.ConversionIF"
implementation="utils.ConversionImpl"/>
<endpointMapping
endpointName="Conversion"
urlPattern="/conversion"/>
</webServices>
Dentro de la etiqueta endpoint especificaremos la información del servicio. Le podemos dar un nombre que lo identifique, un nombre para mostrar al usuario, una descripción detallada, y deberemos especificar tanto la interfaz del servicio como la clase que lo implementa.
En la etiqueta endpointMapping le diremos a que URL mapear un determinado endpoint de los definidos anteriormente. Le diremos el nombre del endpoint (servicio), y la dirección a la que queremos mapearlo. En este caso le decimos que lo mapee a la ruta /conversion, por lo que para acceder a él tendremos que especificar esta ruta dentro del contexto donde lo hayamos desplegado.
Una vez hemos creado la estructura de directorios correcta y los ficheros necesarios, empaquetaremos este servicio en un fichero WAR:
jar cvf conv.war *
Generar la capa Tie y la descripción WSDL
En este fichero WAR que hemos creado, tenemos la implementación de nuestro servicio, y un fichero WEB-INF/jaxrpc-ri.xml que describe dicho servicio, pero faltan componentes para que este servicio pueda funcionar.
Necesitaremos una capa Tie que se encargue de invocar el servicio cuando llegue una petición SOAP al servidor, y que posteriormente genere una respuesta y la envíe de vuelta al cliente. Esta capa se generará con la herramienta wsdeploy, que utilizará el fichero de configuración WEB-INF/jaxrpc-ri.xml para conocer los datos de nuestro servicio. Utilizaremos esta herramienta de la siguiente forma:
wsdeploy -o conv-deploy.war conv.war
Con ello generaremos un nuevo fichero WAR, especificado mediante la opción -o, que ya estará listo para desplegar, con todas las capas necesarias para invocar nuestro servicio. Además, habrá generado un documento WSDL que podremos utilizar para invocar nuestro servicio desde otras plataformas. Podemos ver todo el contenido generado si desempaquetamos este nuevo WAR.
Despliegue del servicio
Una vez tenemos el fichero WAR con todos los componentes necesarios, podemos desplegarlo en Tomcat de la misma forma que cualquier otra Aplicación Web. Podemos simplemente copiar el fichero WAR en el directorio de aplicaciones de Tomcat, que será {jwsdp.home}/webapps. Si el fichero WAR que hemos copiado se llama conv-deploy.war, entonces podremos acceder a información sobre nuestro servicio escribiendo la siguiente dirección en el navegador:
http://localhost:8080/conv-deploy/conversion
Desde esta página podremos ver información sobre el endpoint y el puerto al que conectarnos, además de proporcionarse un enlace al fichero WSDL que describe el servicio. A partir de este fichero otros desarrolladores podrán construir aplicaciones sobre cualquier plataforma que utilicen nuestro servicio.
Elementos generados por wsdeploy
Con el paso anterior habremos terminado de desplegar nuestro servicio, pero podemos estar interesados en conocer qué es lo que ha generado wsdeploy de forma transparente. Podemos ver esto si desempaquetamos el WAR, y vemos el contenido que tiene nuestro servicio, o bien en la invocación de wsdeploy especificar las siguientes opciones:
wsdeploy -o conv-deploy.war conv.war -tmpdir generado -keep
De esta forma le estamos diciendo con -tmpdir que use como directorio temporal para guardar los elementos generados el directorio especificado, y con -keep le decimos que no borre este material una vez haya terminado.
Si accedemos al directorio donde ha generado el código vemos los siguientes elementos:
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Conversion"
targetNamespace="http://j2ee.ua.es/wsdl/Conversion"
xmlns:tns="http://j2ee.ua.es/wsdl/Conversion"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<types/>
<message name="ConversionIF_euro2ptas">
<part name="double_1" type="xsd:double"/></message>
<message name="ConversionIF_euro2ptasResponse">
<part name="result" type="xsd:int"/></message>
<message name="ConversionIF_ptas2euro">
<part name="int_1" type="xsd:int"/></message>
<message name="ConversionIF_ptas2euroResponse">
<part name="result" type="xsd:double"/></message>
<portType name="ConversionIF">
<operation name="euro2ptas" parameterOrder="double_1">
<input message="tns:ConversionIF_euro2ptas"/>
<output message="tns:ConversionIF_euro2ptasResponse"/>
</operation>
<operation name="ptas2euro" parameterOrder="int_1">
<input message="tns:ConversionIF_ptas2euro"/>
<output message="tns:ConversionIF_ptas2euroResponse"/>
</operation>
</portType>
<binding name="ConversionIFBinding" type="tns:ConversionIF">
<operation name="euro2ptas">
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://j2ee.ua.es/wsdl/Conversion"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://j2ee.ua.es/wsdl/Conversion"/>
</output>
<soap:operation soapAction=""/>
</operation>
<operation name="ptas2euro">
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://j2ee.ua.es/wsdl/Conversion"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://j2ee.ua.es/wsdl/Conversion"/>
</output>
<soap:operation soapAction=""/>
</operation>
<soap:binding
transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
</binding>
<service name="Conversion">
<port name="ConversionIFPort" binding="tns:ConversionIFBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <display-name>Servicio Web de conversion</display-name> <description> Servicio web para la conversion de Euros a Ptas </description> <listener> <listener-class> com.sun.xml.rpc.server.http.JAXRPCContextListener </listener-class> </listener> <servlet> <servlet-name>Conversion</servlet-name> <display-name>Conversion</display-name> <description>JAX-RPC endpoint - Conversion</description> <servlet-class> com.sun.xml.rpc.server.http.JAXRPCServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Conversion</servlet-name> <url-pattern>/conversion</url-pattern> </servlet-mapping> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <endpoints xmlns='http://java.sun.com/xml/ns/jax-rpc/ri/runtime' version='1.0'> <endpoint name='Conversion' interface='utils.ConversionIF' implementation='utils.ConversionImpl' tie='utils.ConversionIF_Tie' model='/WEB-INF/Conversion_model.xml.gz' wsdl='/WEB-INF/Conversion.wsdl' service='{http://j2ee.ua.es/wsdl/Conversion}Conversion' port='{http://j2ee.ua.es/wsdl/Conversion}ConversionIFPort' urlpattern='/conversion'/> </endpoints>
Trabajar con ant
Todo lo que hemos visto anteriormente, podemos automatizarlo utilizando la herramienta ant. A continuación se muestra un ejemplo de buildfile de ant para generar un servicio web:
<project name="Conversion" default="service" basedir=".">
<!-- Propiedades --> <property name="jwsdp.home" value="c:\\jwsdp-1.3"/> <property name="app.name" value="Conversion"/>
<property name="app.home" value="${jwsdp.home}/webapps"/> <property name="war.temp.name" value="${app.name}-temp.war"/> <property name="war.name" value="${app.name}.war"/>
<property name="bin.home" value="${basedir}/bin"/> <property name="build.home" value="${basedir}/build"/> <property name="dist.home" value="${basedir}/dist"/> <property name="src.home" value="${basedir}/src"/> <property name="web.home" value="${basedir}/web"/>
<property name="compile.debug" value="true"/> <property name="compile.deprecation" value="false"/> <property name="compile.optimize" value="true"/>
<!-- Classpath -->
<path id="compile.classpath"> <fileset dir="${jwsdp.home}/jwsdp-shared/lib"> <include name="*.jar"/> </fileset> <fileset dir="${jwsdp.home}/jaxp/lib"> <include name="*.jar"/> </fileset> <fileset dir="${jwsdp.home}/jaxp/lib/endorsed"> <include name="*.jar"/> </fileset> <fileset dir="${jwsdp.home}/jaxrpc/lib"> <include name="*.jar"/> </fileset> <fileset dir="${jwsdp.home}/saaj/lib"> <include name="*.jar"/> </fileset> <fileset dir="${jwsdp.home}/apache-ant/lib"> <include name="*.jar"/> </fileset> </path>
<!-- Definicion de tareas -->
<taskdef name="wscompile"
classname="com.sun.xml.rpc.tools.ant.Wscompile"> <classpath refid="compile.classpath"/> </taskdef> <taskdef name="wsdeploy"
classname="com.sun.xml.rpc.tools.ant.Wsdeploy"> <classpath refid="compile.classpath"/> </taskdef> <!-- Objetivos -->
<target name="all" depends="clean,dist" description="Compila todo"/> <target name="clean" description="Borra directorios build y dist"> <delete dir="${build.home}"/> <delete dir="${dist.home}"/> </target>
<target name="compile" description="Compila los fuentes Java"> <javac srcdir="${src.home}" destdir="${bin.home}" debug="${compile.debug}" deprecation="${compile.deprecation}" optimize="${compile.optimize}"> <classpath refid="compile.classpath"/> </javac> </target>
<target name="prepare" depends="compile"
description="Prepara el directorio build"> <mkdir dir="${build.home}"/> <mkdir dir="${build.home}/WEB-INF"/> <mkdir dir="${build.home}/WEB-INF/classes"/> <mkdir dir="${build.home}/WEB-INF/lib"/> <copy todir="${build.home}"> <fileset dir="${web.home}"/> </copy> <copy todir="${build.home}/WEB-INF/classes"> <fileset dir="${bin.home}"/> </copy> </target>
<target name="dist" depends="prepare" description="Crea el fichero WAR de la aplicacion"> <mkdir dir="${dist.home}"/> <jar jarfile="${dist.home}/${war.temp.name}" basedir="${build.home}"/> </target>
<target name="service" depends="dist" description="Genera capas para el servicio"> <wsdeploy keep="true" inWarFile="${dist.home}/${war.temp.name}" outWarFile="${dist.home}/${war.name}" verbose="true"> <classpath refid="compile.classpath"/> </wsdeploy> </target>
<target name="deploy" depends="service" description="Despliega copiando al webapps"> <copy file="${dist.home}/${war.name}" todir="${app.home}"/> </target> </project>
Para utilizar este buildfile deberemos tener nuestro directorio de desarrollo estructurado de la siguiente forma:
src |
Código fuente del servicio web |
web |
Estructura de la aplicación web. Debe contener un directorio WEB-INF
con un fichero web.xml decriptor de la aplicación y
el fichero jaxrpc-ri.xml con la descripción del servicio. |
Tendremos disponibles las siguientes tareas de ant:
clean
: Elimina las clases compiladas para volver
a compilar el sistema desde cero la próxima vez.
compile
: Compila las clases de nuestra aplicación,
cuyos fuentes están ubicados en el directorio src
, produciendo
las clases compiladas en el directorio bin
.
prepare
: Construye en el directorio build
la aplicación completa, juntando la configuración del servicio
de web
con las clases del servicio compiladas de bin
.
dist
: Genera un fichero WAR con el contenido
de build
en el directorio dist
.
all
: Elimina las clases compiladas anteriormente
y vuelve a construir el sistema entero, realizando todas las tareas anteriores.
service
: Genera las capas necesarias para desplegar
nuestro servicio. Se generará un nuevo fichero WAR en el directorio dist
en el que tendremos nuestro servicio web con todas las capas necesarias listo
para ser desplegado.
deploy
: Despliega el servicio automáticamente
copiando el fichero WAR generado con la tarea anterior al directorio webapps
de Tomcat. Posiblemente Tomcat deba ser reiniciado para que reconozca el servicio
desplegado de esta forma.