Ejemplo sencillo de RMI con Java 5

Actualmente estoy realizando un proyecto para la maestria donde se nos pide el uso de varias tecnologías, entre ellas RMI. Para mi sorpresa veo que a partir de la versión 5 de java, ya no es necesaria la ejecución del compilador de RMI (rmic).

A continuación escribo un ejemplo sencillo, donde utilizo el patrón de diseño de Command para la elaboración del servidor.

Como ingredientes para este ejemplo solo necesitaremos dos clases y una interfaz para tener una comunicación entre servidor y cliente, empezare por la interfaz.

ServerRemote.java

package com.av.rmi.interfaces;

import java.rmi.Remote;
import java.rmi.RemoteException;

import com.av.rmi.Parametro;

/**
 * Servicio o punto de entrada en el servidor para ejecucion de distintas
 * acciones
 *
 * @author electrocucaracha
 *
 */
public interface ServerRemote extends Remote {

	public static final String OBJECT_STUB_REGISTRY_NAME = "ServerRemote";

	/**
	 * Accion generica a ser ejecutada en el servidor remoto
	 * @param accion
	 */
	Parametro ejecutar(Parametro parametro) throws RemoteException;
}//ServerRemote

Como ya sabemos dicha interfaz debe de heredar de java.rmi.Remote y todos sus metodos deben lanzar la excepcion java.rmi.RemoteException.

Para finalizar la parte del servidor tenemos su implementacion

ServerRemoteImpl.java

package com.av.rmi.impl;

import java.io.File;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import org.apache.log4j.Logger;

import com.av.acciones.BaseAccion;
import com.av.exceptions.AvException;
import com.av.rmi.Parametro;
import com.av.rmi.interfaces.ServerRemote;

/**
 * Implementacion de punto de entrada a funciones promovidas en el servidor
 * 
 * @author electrocucaracha
 *
 */
public class ServerRemoteImpl implements ServerRemote {

	private static Logger log = Logger.getLogger(ServerRemoteImpl.class);
	private static final long serialVersionUID = 1L;
	private static final File f = new File("c:/av_server/bin/");
	
	protected ServerRemoteImpl() throws RemoteException {
		super();
		log.info("Inicio - ctor");
		
		log.info("Fin - ctor");
	}//ServerRemoteImpl
	
	@Override
	public Parametro ejecutar(Parametro param) throws RemoteException {
		log.info("Inicio - ejecutar(Accion accion)");
		
		BaseAccion accion = BaseAccion.getAccion(param.getAccion());
		Parametro p = null;
		if(accion != null){
			log.debug("Ejecutar : " + param.getAccion());
			try {
				p = accion.ejecutar(param);
			} catch (AvException e) {
				log.error("Ha ocurrido un error al intentar ejecutar la accion");
				throw new RemoteException("Ha ocurrido un error al intentar " +
						"ejecutar la accion", e);
			}
		}else{
			log.warn("Accion no encontrada");
		}
		
		log.info("Fin - ejecutar(Accion accion)");
		
		return p;
	}//ejecutar
	
	public static void main(String[] args){
		System.out.println("Starting server...");
		
		try {
			LocateRegistry.createRegistry(1099);
		} catch (RemoteException e) {
			log.error("Ha ocurrido un error al ejecutar el comando rmiregistry : " + 
					e.getMessage());
			e.printStackTrace();
		}
		
		System.setProperty ("java.rmi.server.codebase", f.toURI().toString());
		
		try{
			ServerRemoteImpl obj = new ServerRemoteImpl();
			ServerRemote stub = (ServerRemote)UnicastRemoteObject.
				exportObject(obj, 0);
			
			Registry r = LocateRegistry.getRegistry();
			r.bind(OBJECT_STUB_REGISTRY_NAME, stub);
			System.out.println("Server ready");
		}catch(Exception e){
			log.error("Ha ocurrido un error al registrar el servicio : " + 
					e.getMessage());
			e.printStackTrace();
		}
	}//main
}//ServerRemoteImpl

Hay muchos puntos que observar en esta implementación

1. Utilizo una instancia de File para obtener su URL, ya que Windows permite crear nombres de carpetas con espacios.

private static final File f = new File("c:/av_server/bin/");

2. Es necesario escribir el constructor por defecto

protected ServerRemoteImpl() throws RemoteException {

3. Esta linea es necesaria para evitar el tener que ejecutar el comando rmiregistry antes de la ejecución del servidor. Nota: Utilizo el puerto 1099 ya que es el puerto por defecto de para el servicio de rmiregistry de RMI.

LocateRegistry.createRegistry(1099);

4. Registro en codebase el path de los archivos que expondré sus métodos.

System.setProperty ("java.rmi.server.codebase", f.toURI().toString());

5. En caso de que el servicio o clase no herede direcamente de UnicastRemoteObject es necesario dinamicamente crear una instancia que lo realice. Nota: El segundo parámetro especifica el puerto que utilizara java para hacer las llamadas a nuestro servicio:

ServerRemote stub = (ServerRemote)UnicastRemoteObject.
				exportObject(obj, 0);

Para finalizar el ejemplo, basta crear un cliente que consuma los servicios, para esto he creado esta clase

Cliente .java

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import com.av.db.dataobjects.Usuario;
import com.av.rmi.CatalogoAcciones;
import com.av.rmi.Parametro;
import com.av.rmi.Parametro.Tipo;
import com.av.rmi.interfaces.ServerRemote;

/**
 * Clase temporal para probar RMI
 * 
 * @author electrocucaracha
 *
 */
public class Cliente {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String host = (args.length < 1) ? null : args[0];
		Registry registry = null;
		ServerRemote stub = null;
		Parametro p = new Parametro();
		Usuario[] usuarios = null;
		try {
			registry = LocateRegistry.getRegistry(host);
		    stub = (ServerRemote) registry.
		    	lookup(ServerRemote.OBJECT_STUB_REGISTRY_NAME);
		    p.setAccion(CatalogoAcciones.OBTENER_TODOS_USUARIO);
		    
		    p = stub.ejecutar(p);
		    
		    if(p.getValor(Tipo.OUTPUT) != null && 
		    		p.getValor(Tipo.OUTPUT) instanceof Usuario[]){
		    	usuarios = (Usuario[])p.getValor(Tipo.OUTPUT);
		    	for(Usuario u : usuarios){
		    		System.out.println(u);
		    	}
		    }
		} catch (Exception e) {
		    System.err.println("Client exception: " + e.toString());
		    e.printStackTrace();
		}	}
}//Cliente

Una vez compiladas las clases, solo se requiere que se ejecute primero la clase del servidor y después la del cliente, sin algún parámetro adicional.