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.
JAX-RPC es una especificación estándar de Sun Microsystems, pero no todos los servidores de aplicaciones utilizan esta librería para gestionar los Servicios Web. Por ejemplo, es el caso de Weblogic, que define su propia API para implementar los Servicios Web.
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.
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.
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://rvg.ua.es/wsdl"
typeNamespaceBase="http://rvg.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 *
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 deplegar, 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.
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.
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://rvg.ua.es/wsdl/Conversion"
xmlns:tns="http://rvg.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://rvg.ua.es/wsdl/Conversion"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://rvg.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://rvg.ua.es/wsdl/Conversion"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
use="encoded" namespace="http://rvg.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://rvg.ua.es/wsdl/Conversion}Conversion' port='{http://rvg.ua.es/wsdl/Conversion}ConversionIFPort' urlpattern='/conversion'/> </endpoints>
Todo lo que hemos visto anteriormente, podemos automatizarlo utilizando la herramienta ant. Las nuevas versiones de Tomcat nos permiten desplegar una Aplicación Web en tiempo de ejecución utilizando tareas de ant.
Estas tareas no son estándar de ant, sino que las incorpora el Tomcat para interactuar dinámicamente con el servidor. Por lo tanto, para poder usarlas previamente debemos declararlas, indicando la clase que implementa dichas tareas:
<taskdef name="deploy" classname="org.apache.catalina.ant.DeployTask" /> <taskdef name="undeploy" classname="org.apache.catalina.ant.UndeployTask" /> <taskdef name="list" classname="org.apache.catalina.ant.ListTask" /> <taskdef name="start" classname="org.apache.catalina.ant.StartTask" /> <taskdef name="stop" classname="org.apache.catalina.ant.StopTask" />
Las tareas de Tomcat que podemos utilizar son las siguientes:
<deploy url="${url}" username="${username}" password="${password}" path="/${context-path}" war="file:${war-path}" />
<undeploy url="${url}" username="${username}" password="${password}" path="/${context-path}" />
<list url="${url}" username="${username}" password="${password}" />
<start url="${url}" username="${username}" password="${password}" path="/${context-path}" />
<stop url="${url}" username="${username}" password="${password}" path="/${context-path}" />
En todas las plantillas anteriores hemos utilizado como parámetros de las tareas una serie de propiedades a las que previamente deberemos dar valor. Estas propiedades son:
http://localhost:8080/manager
Weblogic también nos proporcionará herramientas para generar las capas necesarias para construir y desplegar nuestros Servicios Web. En este caso, Weblogic no implementa estas capas mediante JAX-RPC, sino que utiliza su propia librería. Afortunadamente no tendremos que preocuparnos por esto, ya que todos los elementos necesarios serán generados automáticamente, y por lo tanto no será necesario que tratemos directamente con la implementación que utiliza Weblogic para los Servicios Web. Para poder utilizar todas estas herramientas, deberemos haber establecido previamente las variables de entorno de Weblogic llamando al comando :
setWLSenv.sh
En este caso, la implementación de nuestro servicio, conocida como backend del servicio, podrá consistir en una clase Java, un EJB o un destino JMS. Tener un servicio implementado mediante un EJB nos dará las ventajas que tiene la utilización de estos componentes. De esta forma podemos ver los Servicios Web como otra forma de acceder a los EJBs desde una aplicación cliente.
A continuación veremos con más detalle la forma de crear un Servicio Web utilizando cada uno de los diferentes elementos que sirven como backend del servicio.
Esta es la forma más rápida y sencilla de implementar Servicios Web, sin embargo tiene ciertas limitaciones. No podremos crear hilos desde esta clase. Además, el código de los métodos del servicio no deberá presentar problemas de concurrencia, ya que sólo se mantendrá una instancia de esta clase, y todas las peticiones de servicio se dirigirán a esta misma instancia, por lo que podremos tener múltiples clientes ejecutando las operaciones del servicio de forma simultánea.
Para crear el backend del servicio, deberemos crear una clase con un constructor void. Los métodos públicos que definamos en esta clase serán las operaciones que ofrecerá el servicio. Por ejemplo, podemos implementar nuestro servicio como:
package utils; public class Conversion { public Conversion() { } public int euro2ptas(double euro) { return (int) (euro * 166.386); } public double ptas2euro(int ptas) { return ((double) ptas) / 166.386; } }
Con esto habremos implementado la funcionalidad del servicio como una clase Java ordinaria, sin necesitar tener conocimientos de ninguna librería adicional. Podremos compilar esta clase como cualquier clase Java. Una vez compilada, podemos utilizar las herramientas que ofrece Weblogic para generar todas las capas necesarias para ofrecer esta clase como un Servicio Web. Utilizaremos la tarea servicegen de ant para este fin:
<servicegen destEar="sw_conv.ear" warName="sw_conv.war"> <service javaClassComponents="utils.Conversion" targetNamespace="http://rvg.ua.es/ws" serviceName="Conversion" serviceURI="/conv" generateTypes="True" expandMethods="True"> </service> </servicegen>
Con esto se creará un fichero EAR desplegable en Weblogic, que contendrá el fichero WAR con la Aplicación Web que contiene los servicios. Además de indicar el fichero EAR y WAR que crearemos, podremos introducir tantos elementos service como servicios queramos incluir. De cada servicio indicaremos la clase Java que implementa dicho servicio, el espacio de nombres en el que lo definimos, y el nombre del servicio. Además deberemos proporcionar la URI relativa al contexto donde lo desplegamos donde estará escuchando el servicio. El atributo generateTypes lo que hará será indicar que esta herramienta genere de forma automática las clases para la serialización y deserialización de los tipos de datos que intervengan en los mensajes del servicio y no estén soportados directamente por la librería. El atributo expandMethods indicará que en la descripción del servicio se consideré la configuración de cada operación por separado.
Podemos utilizar también esta herramienta desde línea de comando, en lugar de como tarea ant, ejecutando el siguiente comando:
java weblogic.webservice.servicegen
Esto nos habrá generado un fichero EAR con el Servicio Web listo para desplegar en Weblogic. Desplegaremos el servicio a través de la consola de administración, de la misma forma que desplegamos cualquier otra aplicación.
Una vez desplegado, podremos acceder al servicio mediante una interfaz HTML que nos genera de forma automática. Podremos encontrar el servicio en la siguiente dirección:
http://localhost:7001/<contexto>/<URI servicio>
Aquí encontraremos una interfaz web para invocar los métodos del servicio. Además podremos acceder a la descripción WSDL del servicio en:
http://localhost:7001/<contexto>/<URI servicio>?WSDL
En el caso concreto de nuestro ejemplo estas direcciones serían:
http://localhost:7001/sw_conv/conv http://localhost:7001/sw_conv/conv?WSDL
Podemos utilizar un EJB como backend de los servicios. Esto nos dará las ventajas que nos ofrecen estos componentes. Podremos crear Servicios Web utilizando EJBs de sesión sin estado.
Si en nuestra aplicación necesitamos que mantenga un estado, podemos hacer que el cliente deba proporcionar un identificador en las llamadas a los métodos, de forma que el EJB pueda determinar en cada momento de que cliente se trata, y acceder a una base de datos o a un EJB de entidad para obtener los datos correspondientes al estado del cliente con dicho identificador.
Las operaciones que ofrecerá el servicio serán los métodos definidos en la interfaz remota del EJB. Por ejemplo, podemos definirla como:
package utils;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface Conversion extends EJBObject {
int euro2ptas(double euro) throws RemoteException;
double ptas2euro(int ptas) throws RemoteException;
}
Por otro lado deberemos definir la interfaz home del EJB, cuyos métodos no se ofrecerán como operaciones del servicio, sino que servirán unicamente para la instanciación del EJB, de la cuál no tendremos que encargarnos nosotros:
package utils;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface ConversionHome extends EJBHome {
Conversion create() throws CreateException, RemoteException;
}
Y por último definiremos la implementación del EJB, que será la implementación de nuestro servicio en este caso:
package utils;
import javax.ejb.CreateException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class ConversionBean implements SessionBean {
private SessionContext ctx;
public void ejbActivate() { }
public void ejbRemove() { }
public void ejbPassivate() { }
public void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
public void ejbCreate () throws CreateException { }
public int euro2ptas(double euro) {
return (int) (euro * 166.386);
}
public double ptas2euro(int ptas) {
return ((double) ptas) / 166.386;
}
}
Una vez tengamos definidas estas clases con el código del EJB que vamos a utilizar, añadiremos a su estructura de directorios un fichero META-INF/ejb-jar.xml con la configuración del EJB:
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <!-- Generated XML! --> <ejb-jar> <enterprise-beans> <session> <ejb-name>Conversion</ejb-name> <home>utils.ConversionHome</home> <remote>utils.Conversion</remote> <ejb-class>utils.ConversionBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>Conversion</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
Una vez tenemos ya todos los elementos necesarios para construir el EJB, compilaremos las clases y los empaquetamos en un fichero JAR, que contendrá los siguientes ficheros:
META-INF/ejb-jar.xml utils/Conversion.class utils/ConversionBean.class utils/ConversionHome.class
Una vez generado este fichero, utilizaremos el compilador de EJBs de Weblogic para que genere dentro de este paquete la información necesaria:
java weblogic.ejbc -noexit <jar>
Una vez tenemos el EJB creado, deberemos crear el servicio que lo utilice. Podremos hacerlo con la tarea servicegen del ant (o su correspondiente comando equivalente), proporcionando la siguiente configuración:
<servicegen destEar="sw_conv_ejb.ear" warName="sw_conv_ejb.war"> <service ejbJar="conv-ejb.jar" targetNamespace="http://rvg.ua.es/ws" serviceName="Conversion" serviceURI="/conv" generateTypes="True" expandMethods="True"> </service> </servicegen>
De esta forma generaremos un fichero EAR que contenga el fichero JAR con el EJB, y un fichero WAR con la Aplicación Web que contiene los servicios generados. Igual que en el caso de la clase Java, podremos tener tantos elementos service como servicios queramos incluir en la Aplicación Web. La configuración proporcionada para cada servicio es la misma que en el caso anterior, con la diferencia de que en lugar de especificar el nombre de la clase Java que implementa el servicio, indicamos el fichero JAR que contiene el EJB que actuará como backend del servicio.
Una vez hayamos generado este fichero EAR, el último paso será desplegarlo en Weblogic mediante la consola de administración. Una vez hecho esto podremos acceder al servicio de la misma forma que en el caso anterior.
Como backend también podremos utilizar un determinado destino JMS, pudiendo ser o bien un productor o bien un consumidor de mensajes JMS. En este caso, la implementación de este backend será normalmente un EJB dirigido por mensajes.
Los servicios podrán o bien enviar datos a un destino JMS, o bien recibirlos de él, pero no puede hacer las dos cosas. Por lo tanto, si necesitamos enviar y recibir datos, deberemos crear dos servicios: uno para enviar los datos mediante JMS, y otro para recibirlos.
Estos datos pueden ser enviados o recibidos a través de una cola (mensajes punto-a-punto), o a través de un tópico (mensajes uno-a-muchos). Deberemos también decidir de cuál de estos dos tipos de destinos vamos a recibir o vamos a mandar los datos.
<servicegen destEar="jms_enviar_ptp.ear" contextURI="sw_jms"> <service JMSDestination="jms.destination.cola1" JMSAction="send" JMSDestinationType="queue" JMSConnectionFactory="jms.connectionFactory.queue" JMSOperationName="envia" JMSMessageType="datos.MisDatos" generateTypes="True" targetNamespace="http://rvg.ua.es/sw" serviceName="EnvioJMS" serviceURI="/enviaJMS" expandMethods="True"> </service> </servicegen>
En este caso especificamos el contexto en el que se desplegará el servicio. En los casos anteriores especificábamos un fichero WAR para empaquetar la Aplicación Web, por lo que el contexto tomaba por defecto el nombre de este fichero WAR, pero en este caso indicamos directamente el nombre del contexto.
Dentro de cada elemento service debemos indicar en JMSConnectionFactory y en JMSDestination el nombre JNDI de la factoría de conexiones y del destino JMS respectivamente. La acción a realizar por el servicio la indicaremos en JMSAction que podrá tomar dos valores: send o receive, según si queramos que sea un servicio para enviar o recibir mensajes respectivamente. JMSDestinationType podrá ser queue o topic, según si queremos que sea un mensaje uno-a-uno (cola) o uno-a-muchos (tópico).
En este caso los servicios sólo definen una operación, bien para enviar o bien para recibir datos. Podemos especificar manualmente este nombre en JMSOperationName. Si no lo especificamos, el nombre de la operación por defecto será send o receive, según la acción especificada. Mediante JMSMessageType especificaremos el tipo de datos que intercambiarán los mensajes. Por defecto este tipo será el tipo cadena (String).
Los handlers de mensajes son un componente que podremos utilizar para interceptar los mensajes de petición y respuesta de un servicio. Su función es similar a la función de los filtros que interceptan las peticiones al servidor web, pero actuando sobre los mensajes SOAP que se utilizan para invocar y dar la respuestas de un servicio.
Posibles usos de los handlers son:
Estos handlers van a poder ser instalados tanto en el cliente como en el servidor. Sin los handlers no seríamos capaces de acceder al mensaje SOAP, ya que es JAX-RPC quien se encarga de componer y analizar el mensaje de forma transparente al usuario. Por lo tanto, necesitaremos utilizar handlers para interceptar este mensaje, ya que de otra forma no podríamos acceder a él y modificarlo si hiciese falta.
Para crear un handler deberemos crear una clase que implemente la interfaz Handler. Esta interfaz nos obligará a definir una serie de métodos con los que interceptaremos los distintos mensajes:
Además, deberemos implementar los métodos de inicialización y destrucción del componente, que controlan su ciclo de vida:
Además tendremos que definir el siguiente método:
Existe una forma más sencilla de crear un handler, que es heredando de GenericHandler. Esta es una clase abstracta que implementa Handler y define la mayoría de sus métodos. De esta forma sólo tendremos que redefinir los métodos para los que queramos personalizar el comportamiento, sin tener que preocuparnos del resto.
Los handlers están contenidos dentro de una cadena de handlers (HandlerChain). A cada handler de la cadena le llegará el resultado producido por el anterior handler. Podremos establecer la ordenación de handlers dentro de esta cadena en el fichero de configuración de Servicios Web. El orden de invocación de los handlers para capturar la respuesta será el inverso del orden en el que se capturó la petición:
Figura 2. Cadena de handlers en el servidor
Los métodos para interceptar la petición, respuesta o error devolverán un valor booleano. Según lo que devuelvan estos métodos tendremos diferentes comportamientos de la cadena:
A estos métodos se les pasa un objeto MessageContext como parámetro. Este objeto contiene una serie de propiedades. Podemos hacer una conversión cast de este objeto a SOAPMessageContext, que es una superclase de MessageContext, y nos proporcionará acceso a más información. En él tendremos definidos los métodos getMessage y setMessage con los que podremos obtener el mensaje interceptado, y sustituirlo en caso de que queramos que el handler modifique el mensaje.
Un posible esqueleto para un handler es el siguiente:
public class MiHandler implements Handler
{
private HandlerInfo handlerInfo; public void init(HandlerInfo hi) { handlerInfo = hi; } public void destroy() {} public QName[] getHeaders() { return handlerInfo.getHeaders(); } public boolean handleRequest(MessageContext context) { try { // Accede al mensaje SOAP de peticion SOAPMessageContext smc = (SOAPMessageContext)context; SOAPMessage msg = smc.getMessage(); // Procesar mensaje SOAP ... // Si hemos hecho cambios, enviamos el mensaje modificado smc.setMessage(msg); } catch(Exception ex) { } return true; } public boolean handleResponse(MessageContext context) { try { // Accede al mensaje SOAP de respuesta SOAPMessageContext smc = (SOAPMessageContext)context; SOAPMessage msg = smc.getMessage(); // Procesar mensaje SOAP ... // Si hemos hecho cambios, enviamos el mensaje modificado smc.setMessage(msg); } catch(Exception ex) { } return true; } public boolean handleFault(MessageContext context) { return true; } }
Donde una vez obtenido el mensaje podemos leerlo y/o modificarlo, según la función que queremos que desempeñe el handler. Los métodos devuelven true para indicar que se siga procesando la cadena de handlers de forma normal.
Una vez hemos creado el handler, deberemos registrarlo en el servidor, de forma que cuando llegue una petición de invocación de un servicio, sea capturada por dicho handler. En el próximo capítulo veremos como registrar los handlers para que sean capturados en el lado del cliente.
En JWSDP, registraremos un handler desde el fichero de configuración de despliegue de los servicios jaxrpc-ri.xml:
<?xml version="1.0" encoding="UTF-8"?> <webServices xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd" version="1.0" targetNamespaceBase="http://rvg.ua.es/wsdl" typeNamespaceBase="http://rvg.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"> <handlerChains> <chain runAt="server"> <handler className="handler.MiHandler"/> </chain> </handlerChains> </endpoint> <endpointMapping endpointName="Conversion" urlPattern="/conversion"/> </webServices>
En este fichero podemos registrar una cadena de handlers (handlerChain) para cada endpoint, de forma que los handlers de esa cadena intercepten las peticiones a dicho endpoint. Dentro de esta cadena podremos registrar una serie de handlers (elemento chain), especificando para cada uno de ellos el nombre de la clase donde se encuentra implementado.
Será necesario copiar esta clase al directorio WEB-INF/classes de la aplicación, de forma que JAX-RPC sea capaz de localizar la clase cuando necesite invocar los métodos del handler para notificarle de la llegada de mensajes SOAP.
En Weblogic deberemos registrar los handlers que queramos que intercepten los mensajes en el servidor en el fichero web-services.xml. Este es un fichero con la configuración de despliegue de los Servicios Web, propio de la implementación de Weblogic (no se trata de un estándar).
Este fichero lo habrá generado automáticamente la herramienta servicegen. Si una vez generado el servicio, desempaquetamos el archivo EAR y el archivo WAR que contiene, podremos ver y editar este fichero.
Para añadir una cadena de handlers deberemos incluir un elemento <handler-chains> dentro del bloque <web-services>:
<web-services> <handler-chains> <handler-chain name="nombre_cadena"> <handler class-name="handler.MiHandler"/> </handler-chain> </handler-chains> ... </web-services>
Dentro de este elemento podremos definir varias cadenas de handlers, y dentro de cada una de ellas podemos incluir varios handlers, indicando para cada uno de ellos la clase que lo implementa, que deberá encontrarse entre las clases de la Aplicación Web.
Por último, deberemos asociar cada cadena de handlers a las operaciones sobre las que queremos que actúen. Esto lo haremos creando un elemento operation dentro de operations, para cada operación para la que queremos registrar un handler que intercepte sus mensajes.
<web-service> <components> <stateless-ejb name="Conversion"> ... </stateless-ejb> </components> <operations> <operation name="ptas2euro" method="ptas2euro" component="Conversion" handler-chain="nombre_cadena" /> </operations> </web-service>
Aquí indicamos el backend que implementa el servicio (component), y el método dentro de ese servicio (method) sobre el que vamos a registrar la cadena de handlers (handler-chain).
Podemos también registrar una cadena de handlers que funcione de forma independiente, sin especificar ningún backend sobre el que actuar:
<web-service> <operations> <operation name="servicio_cadena" handler-chain="nombre_cadena" /> </operations> </web-service>
En este caso sólo debemos especificar la cadena de handlers (handler-chain).
Dado que los Servicios Web se despliegan como una Aplicación Web, podremos añadir seguridad prácticamente de la misma forma en la que lo hacíamos para la aplicaciones.
Autentificación
Podremos restringir el acceso al servicio. Esto lo podremos hacer añadiendo restricciones de seguridad a la URL en la que escucha, o en el caso de tratarse de un servicio implementado mediante un EJB, añadiendo restricciones de seguridad al EJB. Para restringir el acceso podemos añadir estas restricciones directamente al fichero web.xml de la Aplicación Web que contiene el servicio, de la misma forma que las añadíamos a cualquier aplicación.
SSL
Además, podremos utilizar SSL para codificar las llamadas a los servicios. Para ello deberemos haber configurado previamente SSL en Weblogic. De esta forma, podremos invocar los servicios tanto mediante HTTP como HTTPS. Sin embargo, si sólo queremos permitir la comunicación mediante HTTPS, deberemos indicar este protocolo en la descripción del servicio:
<servicegen destEar="sw_conv.ear" warName="sw_conv.war"> <service javaClassComponents="utils.Conversion" targetNamespace="http://rvg.ua.es/ws" serviceName="Conversion" serviceURI="/conv" protocol="https" generateTypes="True" expandMethods="True"> </service> </servicegen>
De esta forma, los clientes sólo podrán acceder al servicio mediante SSL. En el próximo tema veremos como crear clientes que accedan a Servicios Web seguros.