Web Services con Estado

1
26509

Web Services con Estado

  1. Web Services con Estado
    1. Introducción
    2. Requisitos
    3. Creación del web service con estado
    4. Creación del cliente del web service con estado
    5. Conclusión

Introducción

Este tutorial está motivado por la pregunta de un alumno de un curso, acerca de mantener una sesión de diálogo entre un web service y su cliente. Veamos un ejemplo de lo que se viene al caso a denominar web services con estado (stateful webservices).

El código fuente del tutorial puede descargarse desde aquí.

Requisitos

Partimos del siguiente software

  • Implementación de referencia del estándar JAX-WS 2.0/2.1 (https://jax-ws.dev.java.net/), versión de distribución 2.1.4, que podemos descargar de aquí: https://jax-ws.dev.java.net/2.1.4/. JAX-WS es el núcleo de METRO (conjunto de componentes que forman una WS stack, o pila de web services, de Sun).
  • Por comodidad, usaré Eclipse como IDE.
  • Servidor web Apache Tomcat 6.0.18, que podremos descargar desde http://tomcat.apache.org/download-60.cgi.

Si estás con Windows vista, la manera más adecuada de instalar la distribución JAX-WS es mediante el comando indicado en la web de descarga, es decir:

C:\TutorialWS>java -jar JAXWS2.1.4-20080502.jar

Creará la carpeta C:\TutorialWS\jaxws-ri. Crearemos una variable de entorno llamada JAXWS_HOME con el valor C:\TutorialWS\jaxws-ri y añadiremos JAXWS_HOME\bin al PATH del sistema.

Creación del web service con estado

Vamos a hacer un ejemplo relacionado con los tiempos que corren: un broker hace especulaciones en bolsa utilizando las operaciones expuestas por un web service con estado. El broker se validará, realizará operaciones y finalizará la ‘sesión’. Un web service habitual (sin estado y habitualmente síncrono) no sabría llevar la cuenta de lo ganado por el broker entre operación y operación, la información se pierde. Pero el que vamos a crear, sí mantiene memoria de lo acumulado, sin utilizar ningún tipo de persistencia en la lógica implementada.

En Eclipse creamos un nuevo proyecto web dinámico para facilitarnos posteriormente la generación del WAR, y agregamos las dependencias necesarias al Build Path, que serán los .jar existentes en JAXWS_HOME\lib.

Creamos un paquete llamado com.autentia.ws.bolsa, y ahí creamos las siguientes dos clases:

CuentaInversion.java:

package com.autentia.ws.bolsa;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.soap.Addressing;

import com.sun.xml.ws.developer.Stateful;
import com.sun.xml.ws.developer.StatefulWebServiceManager;

/**
 * <p>
 * Cuenta de inversion de un broker. Trabaja con un conjunto de titulos
 * conocidos
 * </p>
 * <p>
 * El broker puede manejar su cuenta de inversion desde cualquier parte ya que
 * se ofrece como un web service con estado
 * </p>
 * 
 * @author Ivan Garcia Puebla - www.autentia.com
 * @version 1.0
 */
@Stateful
@WebService
@Addressing
public class CuentaInversion {

	// cartera de titulos de companias
	private Map<String, Activo> cartera;

	public final static String IENEGE = "IENEGE";
	public final static String BEBEUVA = "BEBEUVA";
	public final static String TECNOGUAY = "TECNOGUAY";
	public final static String INVESTIS = "INVESTIS";
	public final static String CASARIS = "CASARIS";

	/**
	 * Alta de una cuenta de inversion
	 */
	public CuentaInversion() {
		cartera = new Hashtable<String, Activo>(5);

		cartera.put(IENEGE, new Activo(IENEGE));
		cartera.put(BEBEUVA, new Activo(BEBEUVA));
		cartera.put(TECNOGUAY, new Activo(TECNOGUAY));
		cartera.put(INVESTIS, new Activo(INVESTIS));
		cartera.put(CASARIS, new Activo(CASARIS));
	}

	/**
	 * Metodo que compra titulos sobre un precio minimo
	 * 
	 * @param precioMinimo
	 *            Precio minimo de compra
	 * @param cantidad
	 *            Cantidad de titulos
	 */
	public void comprarPrecio(int precioMinimo, int cantidad) {

		if (precioMinimo < 0 || cantidad < 0)
			return;

		// compramos titulos sobre el precio minimo
		Set<String> set = cartera.keySet();
		Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			Activo a = cartera.get(it.next());
			if (a.getCotizacion() >= precioMinimo)
				a.adquirir(cantidad);
		}
	}

	/**
	 * Adquirir titulos de una compania
	 * 
	 * @param ticker
	 *            Ticker de la compania
	 * @param cantidad
	 *            Cantidad de titulos
	 */
	public void comprarCompania(String ticker, int cantidad) {
		Activo a = cartera.get(ticker);
		if (a != null) {
			a.getCotizacion();
			a.adquirir(cantidad);
		}
	}

	/**
	 * Obtiene los ingresos del broker
	 * 
	 * @return Ingreso de la inversion actua
	 */
	public int getIngresos() {
		int total = 0;

		Set<String> set = cartera.keySet();
		Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			total += ((Activo) cartera.get(it.next())).getMontante();
		}

		return total;
	}

	/**
	 * 
	 * Este objeto es inyectado por la implementacion de referencia JAX-WS, y
	 * ofrece varios metodos para manejar web services con estado.
	 * 
	 */
	public static StatefulWebServiceManager<CuentaInversion> manager;

	/**
	 * Fin de sesion explicito para liberar memoria
	 */
	public void finSesion() {
		manager.unexport(this);
	}

	/**
	 * Entidad Activo Financiero
	 * 
	 * @author Ivan Garcia Puebla - www.autentia.com
	 * @version 1.0
	 */
	class Activo {

		private String ticker; // id de empresa que cotiza
		private int numero; // cantidad de titulos adquiridos
		private int montante; // capital de la inversion
		private int cotizacion; // precio de cotizacion

		/**
		 * Constructor
		 */
		public Activo(String ticker) {
			this.ticker = ticker;
			numero = 0;
			montante = 0;
			cotizacion = 0;
		}

		/**
		 * @return el ticker del activo
		 */
		public String getTicker() {
			return ticker;
		}

		/**
		 * @param ticker
		 *            establece el ticker del activo
		 */
		public void setTicker(String ticker) {
			this.ticker = ticker;
		}

		/**
		 * @return cantidad de titulos adquiridos
		 */
		public int getNumero() {
			return numero;
		}

		/**
		 * @return El montante
		 */
		public int getMontante() {
			return montante;
		}

		/**
		 * Obtiene el precio de cotizacion actual
		 * 
		 * @return Precio actual
		 */
		public int getCotizacion() {

			Random random = new Random();
			cotizacion = random.nextInt(10);

			return cotizacion;
		}

		/**
		 * Método que efectua la compra de acciones al precio actual de
		 * cotizacion
		 * 
		 * @param numero
		 *            de acciones a comprar
		 */
		public void adquirir(int cantidad) {

			numero += cantidad;
			montante += (cantidad * cotizacion);

		}

	}

}

El código es sencillo de entender. Disponemos de una clase Activo.java, que representa una entidad de tipo activo financiero. Este activo tiene unas variables de clase que almacenan su estado y un método para comprar determinadas cantidades de ellos.

La clase CuentaInversion.java es la que nos interesa, pues simplemente anotándola con @WebService, @Stateful y @Addressing la estamos convirtiendo en un web service con estado al que podemos invocar sea cual sea el protocolo de transporte. ¡Todo eso con 3 palabras! Small is Beauty 😉

Destacamos dos métodos:

  • StatefulWebServiceManager, método que JAX-WS implementará por nosotros, encargado de manejar el estado. Es recomendable consultar el JAX-WS javadoc.
  • finSesion(). Sino invocamos el unexport del manager, tendremos pérdida de memoria, al no librear recursos tras dejar de utilizar el web service. Otra solución es establecer un timeout, como indica la documentación del punto anterior.

La otra clase que compondrá la lógica de nuestro web service es Cartera.java:

package com.autentia.ws.bolsa;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

/**
 * Este es un servicio web sin estado endpoint del que tiene estado
 * 
 * @author Ivan Garcia Puebla - www.autentia.com
 * 
 */
@WebService
public class Cartera {

	/**
	 * Metodo que proporciona al cliente una referencia a una cuenta de
	 * inversion
	 * 
	 * @param usuario
	 *            Usuario
	 * @param password
	 *            Clave
	 * @return Referencia a una instancia cuenta de inversion
	 */
	@WebMethod
	public synchronized W3CEndpointReference accesoCartera(String usuario,
			String password) {

		CuentaInversion cuenta = null;

		if (!validar(usuario, password))
			return null;

		cuenta = new CuentaInversion();
		return CuentaInversion.manager.export(cuenta);

	}

	

	// TODO refactorizar este metodo
	/**
	 * Metodo de validacion
	 * 
	 * @param usuario
	 *            Usuario
	 * @param password
	 *            Clave
	 * @return Acceso valido (true) o denegado (false)
	 * 
	 */
	protected static boolean validar(String usuario, String password) {

		final String usuarioSecreto = "Alberto";
		final String passwordSecreta = "emc2";

		return (usuarioSecreto.equals(usuario) && passwordSecreta
				.equals(password));

	}

}

Esta clase también es un web service pero sin estado. Mediante el método accesoCartera() estamos validando un usuario y devolviendo al otro extremo de la invocación (el cliente), una referencia al web service con estado que realmente nos interesa.

Teniendo estas clases, podemos generar el web service con los comandos:

wsgen -cp . -keep com.autentia.ws.bolsa.CuentaInversion

y

wsgen -cp . -keep com.autentia.ws.bolsa.Cartera

Nos habrá generado el conjunto de clases necesarias para el funcionamiento del web service, en un nuevo paquete llamado com.autentia.ws.bolsa.jaxws.

Asimismo implementamos los ficheros web.xml y sun-jaxws.xml (más información en: Metro: pila de webservices de Sun). Puedes ver su contenido en la descarga de los fuentes del tutorial.

En este punto, el proyecto tiene el siguiente aspecto:

Estado del proyecto del web service broker en Eclipse
Proyecto del web service en Eclipse

A continuación generamos el ensamblado war: BrokerWS.war y lo copiamos en el directorio webapps de Tomcat para su despliegue. Podremos acceder al estado del web service en la dirección http://localhost:8080/BrokerWS/ws/cartera:

Estado de los web services con estado en Tomcat
Estado de los web services cartera y cuentainversion en Tomcat

Pasamos a generar el cliente.

Creación del cliente del web service con estado

Creamos un nuevo proyecto de tipo Java en Eclipse, y agregamos las librerias de JAX-WS, como en el caso del proyecto del servidor.

Para generar las clases necesarias para la comunicación con el web service ya publicado, ejecutamos los comandos:

wsimport -s src -p com.autentia.broker -Xnocompile http://localhost:8080/BrokerWS/ws/cartera?wsdl 

y

wsimport -s src -p com.autentia.broker -Xnocompile http://localhost:8080/BrokerWS/ws/cuentainversion?wsdl

y se habrán creado en el paquete que hemos especificado, com.autentia.broker. En un paquete com.autentia.broker.client creamos una clase llamada TestBroker.java donde implementaremos la lógica del cliente:

package com.autentia.broker.client;

import com.autentia.broker.Cartera;
import com.autentia.broker.CarteraService;
import com.autentia.broker.CuentaInversion;
import com.autentia.broker.CuentaInversionService;

/**
 * 
 * <p>
 * Cliente del web service de sesion de bolsa de un broker
 * </p>
 * <p>
 * Ejemplo de uso de un web service con estado
 * </p>
 * 
 * 
 * @author Ivan Garcia Puebla - www.autentia.com
 * @version 1.0
 * 
 */
public class TestBroker {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		// creamos los objetos necesarios para el dialogo
		Cartera cartera = new CarteraService().getCarteraPort();
		CuentaInversionService cuentaService = new CuentaInversionService();

		try {

			// nos validamos ante el servicio:
			CuentaInversion cuenta = cuentaService.getPort(cartera
					.accesoCartera("Alberto", "emc2"), CuentaInversion.class);

			// realizamos operaciones sobre nuestra cuenta de inversion

			// compramos 25 titulos a mayor precio que 1$
			cuenta.comprarPrecio(3, 25);
			cuenta.getIngresos();
			System.out.println("Montante acumulado: " + cuenta.getIngresos());

			// compramos 100 titulos de una compania
			cuenta.comprarCompania("BEBEUVA", 100);
			System.out.println("Montante acumulado: " + cuenta.getIngresos());

			// compramos 33 titulos de una compania que no es del broker
			cuenta.comprarCompania("ssd", 33);
			System.out.println("Montante acumulado: " + cuenta.getIngresos());
			
			// compramos 1850 titulos de una compania
			cuenta.comprarCompania("CASARIS", 1850);
			System.out.println("Montante acumulado: " + cuenta.getIngresos());

			// finalizamos la sesion
			cuenta.finSesion();

		} catch (NullPointerException excepcionUsuarioNoAutorizado) {
			System.out.println("broker ID o password incorrectos");
		}

	}

}

El código se explica por sí solo.

En este punto el estado del proyecto en Eclipse es:

Proyecto del cliente del web service en Eclipse
Proyecto del cliente del web service en Eclipse

Ejecutamos la clase Test.java y obtenemos lo siguiente:

Output de la ejecucion del web service
Resultado de la inversión de nuestro broker

Todo esto puedes ejecutarlo tú mismo con el código fuente del tutorial, preparado para ser importado como ‘proyecto existente‘ en Eclipse (JEE). Sólo tendrás que añadir las librerías de JAXWS_HOME\lib a ambos proyectos.

Conclusión

Este tutorial muestra otra de las capacidades de los servicios web. Resulta cuando menos interesante seguir aprendiendo sobre ellos, ¿verdad David? 🙂

 

1 COMENTARIO

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