Continuamos con el tutorial Java RMI. En la última entrada vimos las interfaces que va a publicar el Servidor. En esta entrada vamos a implementar ésas mismas interfaces. Los archivos que contienen la definición de las interfaces de todos los servicios deben ser accesibles por todos los agentes del sistema, por lo que es conveniente crear una carpeta/proyecto y añadirlo a cada uno de los proyectos (Servidor, Cliente, Repositorio). Bueno, vamos con la implementación de las interfaces.
ServicioDatosImpl
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import es.uned.common.ServicioDatosInterface; import es.uned.common.Utils; public class ServicioDatosImpl extends UnicastRemoteObject implements ServicioDatosInterface { private static final long serialVersionUID = 1526643248021674437L; // Conjunto de Maps para almacenar y relacionar los clientes, los // repositorios y los numeros de sesion private Map<Integer, String> sesion_cliente = new HashMap<Integer, String>(); private Map<String, Integer> cliente_sesion = new HashMap<String, Integer>(); private Map<Integer, String> sesion_repositorio = new HashMap<Integer, String>(); private Map<String, Integer> repositorio_sesion = new HashMap<String, Integer>(); private Map<Integer, Integer> cliente_repositorio = new HashMap<Integer, Integer>(); // Nombre del archivo que mantiene el Log del Servidor. private String nombreLog; protected ServicioDatosImpl() throws RemoteException { super(); } protected ServicioDatosImpl(String nombreLog) throws RemoteException { super(); this.nombreLog = nombreLog; } // Meetodo que añade un Cliente al sistema recibiendo como parámetro un // nombre y una id. No se permiten nombres de clientes repetidos. Tambien // asocia al Cliente con un repositorio existente. En el caso de no haber // repositorios no se ingresa el Cliente. @Override public boolean ingresaCliente(String nombre, int id) throws RemoteException { // Comprobamos que el cliente no exista if (cliente_sesion.containsKey(nombre.toUpperCase())) { Utils.escribeLog(nombreLog, "El cliente ya se encuentra dado de alta."); return false; } else { // Recogemos la lista de repositorios disponibles List<Integer> repositorios = new ArrayList<Integer>( repositorio_sesion.values()); if (repositorios.isEmpty()) { Utils.escribeLog(nombreLog, "No hay repositorios disponibles."); return false; } else { // Añadimos el cliente a la "base de datos" sesion_cliente.put(id, nombre.toUpperCase()); cliente_sesion.put(nombre.toUpperCase(), id); // Asociamos al cliente con un repositorio al azar int n = (int) (Math.random() * repositorios.size()); cliente_repositorio.put(id, repositorios.get(n)); Utils.escribeLog(nombreLog, "Autenticando a " + nombre + " con la id " + id); return true; } } } // Método que añade un Repositorio al sistema recibiendo como parámetros un // nombre y una id. No se permiten nombres de Repositorios repetidos. @Override public boolean ingresaRepositorio(String nombre, int id) throws RemoteException { if (repositorio_sesion.containsKey(nombre.toUpperCase())) { Utils.escribeLog(nombreLog, "El repositorio ya se encuentra dado de alta."); return false; } // De ser así lo añadimos a la "base de datos" else { sesion_repositorio.put(id, nombre.toUpperCase()); repositorio_sesion.put(nombre.toUpperCase(), id); Utils.escribeLog(nombreLog, "Autenticando a " + nombre + " con la id " + id); return true; } } // Metodo que devuelve una colección con los nombres de los Clientes. @Override public Collection<String> listaClientes() throws RemoteException { Collection<String> clientes = (Collection<String>) sesion_cliente.values(); return clientes; } // Metodo que devuelve una colección con los nombres de los Repositorios. @Override public Collection<String> listaRepositorios() throws RemoteException { Collection<String> repositorios = (Collection<String>) sesion_repositorio.values(); return repositorios; } // Metodo que devuelve la id del repositorio que le corresponde a un cliente // cuya id que se pasa como parámetro. @Override public int buscaRepositorio(int cliente) throws RemoteException { int repositorio = 0; try { repositorio = cliente_repositorio.get(cliente); } catch (Exception e) { System.out.println("ServicioDatosImpl.buscaRepositorio: " + e.getMessage()); System.out.println(cliente_repositorio.get(cliente)); } return repositorio; } // Método que imprime por pantalla la lista de repositorios junto con los // clientes asociados a los mismos. @Override public void listarRepositoriosClientes() throws RemoteException { ArrayList<Integer> repositorios = new ArrayList<Integer>(repositorio_sesion.values()); ArrayList<Integer> clientes = new ArrayList<Integer>( cliente_repositorio.keySet()); if (repositorios.isEmpty()) { Utils.escribeLog(nombreLog, "No hay repositorios registrados actualmente."); } else if (clientes.isEmpty()) { Utils.escribeLog(nombreLog, "No hay clientes registrados actualmente."); } else { for (int n : repositorios) { System.out.println("REPOSITORIO " + sesion_repositorio.get(n)); System.out.println("==========================================="); System.out.println("CLIENTES"); System.out.println("==========================================="); for (int c : clientes) { if (cliente_repositorio.get(c) == n) { System.out.println(sesion_cliente.get(c)); } } System.out.println("==========================================="); } } } }
Vamos a analizar esta clase. Todas las clases que queramos publicar como objetos remotos deberán extender de UnicastRemoteObject. Hay otras formas de publicar objetos que no extienden de UnicastRemoteObject, pero no los vamos a ver en este ejemplo. La clase además implementa por supuesto ServicioDatosInterface.
UnicastRemoteObject ( http://docs.oracle.com/javase/7/docs/api/java/rmi/server/UnicastRemoteObject.html ) nos permite exportar un objeto remoto y obtener un stub que se comunica con el objeto remoto.
Por lo demás las funciones son bastante autoexplicativas. Se usan una serie de Maps para almacenar los distintos datos de clientes y repositorios y relacionarlos y creamos un constructor que recibe como parámetro un nombre de fichero que se corresponde al log del Servidor en el que podremos escribir distintos mensajes de carácter informativo, luego pondré la función que hace esto.
ServicioAutenticacionImpl
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.util.Random; import es.uned.common.ServicioAutenticacionInterface; import es.uned.common.ServicioDatosInterface; import es.uned.common.Utils; public class ServicioAutenticacionImpl extends UnicastRemoteObject implements ServicioAutenticacionInterface { private static final long serialVersionUID = 593676645816200166L; // Numero aleatorio que nos va a servir para obtener los numeros // de sesion de los Clientes y Repositorios private static int sesion = new Random().nextInt(); private static int miSesion = 0; // Objeto para el acceso al servicio Datos. private static ServicioDatosInterface datos; private String nombreLog; protected ServicioAutenticacionImpl() throws RemoteException { super(); // TODO Auto-generated constructor stub } protected ServicioAutenticacionImpl(String nombreLog) throws RemoteException { super(); this.nombreLog = nombreLog; } // Función que autentica un cliente y devuelve el número de sesión que le ha // sido asignado o 0 en caso de que no se haya podido llevar a cabo la // autenticación. @Override public int autenticarCliente(String nombre) throws RemoteException { Utils.escribeLog(nombreLog, nombre + " esta intentando autenticarse"); int sesionUsuario = getSesion(); try { datos = (ServicioDatosInterface) Naming.lookup("rmi://localhost:1492/datos"); } catch (NotBoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } if (datos.ingresaCliente(nombre, sesionUsuario)) { return sesionUsuario; } else { return 0; } } // Función que autentica un repositorio y devuelve el número de sesión que // le ha sido asignado o 0 en caso de que no se haya podido llevar a cabo la // autenticación. public int autenticarRepositorio(String nombre) throws RemoteException { Utils.escribeLog(nombreLog, nombre + " esta intentando autenticarse"); int sesionUsuario = getSesion(); try { datos = (ServicioDatosInterface) Naming.lookup("rmi://localhost:1492/datos"); } catch (NotBoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } if (datos.ingresaRepositorio(nombre, sesionUsuario)) { return sesionUsuario; } else { return 0; } } public static int getSesion() { return ++sesion; } }
La implementación de esta interfaz publica dos métodos: autenticarCliente y autenticarRepositorio. Ambas reciben el nombre de usuario, generan una id, que va a ser única para cada usuario del sistema y haciendo uso del Servicio de Datos, autentican al cliente, para ello, buscan el servicio de Datos, haciendo uso del método lookup de la clase Naming que es una clase que nos permite almacenar y recuperar referencias a objetos remotos de un registro rmi.
Al método lookup le pasamos como parámetro la URL del objeto que queremos recuperar ( http://docs.oracle.com/javase/7/docs/api/java/rmi/Naming.html ).
El objeto devuelto por lookup es casteado a la interfaz que queremos recuperar, en este caso ServicioDatosInterface y a partir de ese momento ya podemos invocar los métodos expuestos por la interfaz.
ServicioGestorImpl
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import es.uned.common.ServicioDatosInterface; import es.uned.common.ServicioGestorInterface; public class ServicioGestorImpl extends UnicastRemoteObject implements ServicioGestorInterface { private static final long serialVersionUID = 1L; private static String nombreLog; //Haremos uso del servicio de DATOS del Servidor. private static ServicioDatosInterface datos; protected ServicioGestorImpl() throws RemoteException { super(); // TODO Auto-generated constructor stub } protected ServicioGestorImpl(String nombreLog) throws RemoteException{ super(); this.nombreLog = nombreLog; } //Método que obtiene la dirección del servicio ClienteOperador @Override public String obtenerServicioClienteOperador(int id) throws RemoteException, NotBoundException, MalformedURLException { datos = (ServicioDatosInterface) Naming.lookup("rmi://localhost:1492/datos"); int repositorio = datos.buscaRepositorio(id); String URL_repositorio = "rmi://localhost:1492/ServicioClOperador/"+ repositorio; return URL_repositorio; } //Método que obtiene la dirección del servicio ServidorOperador @Override public String obtenerServicioServidorOperador(int id) throws RemoteException, NotBoundException, MalformedURLException { datos = (ServicioDatosInterface) Naming.lookup("rmi://localhost:1492/datos"); int repositorio = datos.buscaRepositorio(id); String URL_repositorio = "rmi://localhost:1492/ServicioSrOperador/"+ repositorio; return URL_repositorio; } }
Esta interfaz exponía dos métodos: obtenerServicioClienteOperador y obtenerServicioServidorOperador. En ambos casos, reciben como parámetro la id de un cliente y a partir de ésta, usando el servicio de Datos, forman una Url del tipo «rmi://localhost:1492/Servicio»+idRepositorio con el repositorio que corresponde al cliente que invoca el método, con ésta Url, los clientes pueden localizar el servicio de su correspondiente Repositorio y llevar a cabo las operaciones de subida, bajada, borrado de ficheros,…
Como se puede ver, los métodos lanzan distintas excepciones (comentaré al final del tutorial las distintas excepciones que aparecen en el proyecto) en lugar de capturarlas en su interior. Cuando implementemos el cliente, veremos que esto nos permite informar en la parte del cliente con una mayor claridad.
Servidor
import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; public class Servidor { // Variable para leer la entrada por teclado private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // Direccion del fichero que contiene el Log de esta sesion private static String nombreLog; // Variable que indica el puerto donde se levantara el registro private static int puertoRegistro = 1492; // Main de la Clase Servidor public static void main(String[] args) throws Exception { if (creaLog()) { System.out.println("Log creado correctamente."); // Iniciamos el registro en el puerto indicado startRegistry(puertoRegistro); // Levantamos el SERVICIO DE DATOS ServicioDatosImpl datos = new ServicioDatosImpl(nombreLog); Naming.rebind("rmi://localhost:" + puertoRegistro + "/datos", datos); System.out.println("Servicio Datos listo"); // Levantamos el SERVICIO DE AUTENTICACION ServicioAutenticacionImpl autenticacion = new ServicioAutenticacionImpl(nombreLog); Naming.rebind("rmi://localhost:" + puertoRegistro + "/autenticacion", autenticacion); System.out.println("Servicio Autenticacion listo"); // Levantamos el SERVICIO DE GESTION ServicioGestorImpl gestor = new ServicioGestorImpl(nombreLog); Naming.rebind("rmi://localhost:" + puertoRegistro + "/gestor", gestor); System.out.println("Servicio Gestor listo"); // Imprimimos el MENU int option = 1; do { System.out.println("1.- Listar Clientes."); System.out.println("2.- Listar Repositorios."); System.out.println("3.- Listar Parejas Repositorio-Cliente."); System.out.println("4.- Salir."); option = Integer.parseInt(reader.readLine()); switch (option) { case 1: Collection<String> clientes = datos.listaClientes(); if (!clientes.isEmpty()) { System.out.println("=================================="); System.out.println("Clientes."); System.out.println("=================================="); for (String nombre : clientes) { System.out.println(nombre); } System.out.println("=================================="); } else { System.out.println("=================================="); System.out.println("No hay clientes registrados."); System.out.println("=================================="); } break; case 2: Collection<String> repositorios = datos.listaRepositorios(); if (!repositorios.isEmpty()) { System.out.println("=================================="); System.out.println("Repositorios."); System.out.println("=================================="); for (String nombre : repositorios) { System.out.println(nombre); } System.out.println("=================================="); } else { System.out.println("=================================="); System.out.println("No hay repositorios registrados."); System.out.println("=================================="); } break; case 3: datos.listarRepositoriosClientes(); break; case 4: // Finalizamos el servicio de Autenticacion Naming.unbind("rmi://localhost:" + puertoRegistro + "/autenticacion"); // Finalizamos el servicio de Datos Naming.unbind("rmi://localhost:" + puertoRegistro + "/datos"); // Finalizamos el servicio Gestor Naming.unbind("rmi://localhost:" + puertoRegistro + "/gestor"); break; default: System.out.println("Opción incorrecta."); break; } } while (option != 4); } else { System.out.println("Error en la creación del log. Saliendo."); } }
Esta clase contiene el main del Servidor.
Lo primero que hacemos es levantar el registro con el método startRegistry que recibe como parámetro un número de puerto (al final del post pondré las funciones auxiliares) y a continuación publicamos los distintos servicios…
Para ello, creamos un objeto de la clase que queramos publicar y a continuación utilizamos el método rebind de la clase Naming que recibe como parámetro dicho objeto (que debe ser de tipo UnicastRemoteObject) y una Url que será la que se usará más tarde para localizar el objeto cuando queramos hacer uso de él. El método rebind sustituye el objeto que hubiera en dicha Url en el caso de existir uno. También podríamos usar bind que no hace nada en el caso de que ya existiera un objeto en dicha dirección, pero todo eso lo podreis ver en la documentación de oracle.
Al finalizar el servidor deberemos «desenlazar» todos estos objetos, para ello usaremos el método unbind de la clase Naming, por lo demás el main es bastante autoexplicativo.
Funciones Auxiliares
private static void startRegistry(int RMIPortNum) throws RemoteException { try { Registry registry = LocateRegistry.getRegistry(RMIPortNum); registry.list(); // This call throws a exception if the registry // does not already exist } catch (RemoteException ex) { // No valid registry at that port.System.out.println("RMI registry cannot be located at port " + RMIPortNum); Registry registry = LocateRegistry.createRegistry(RMIPortNum); System.out.println("RMI registry created at port " + RMIPortNum); } } public static boolean creaLog() { Date date = new Date(); DateFormat hourdateFormat = new SimpleDateFormat("HHmmss_ddMMyyyy"); System.out.println("Hora y fecha: " + hourdateFormat.format(date)); URL rutaca = Servidor.class.getProtectionDomain().getCodeSource().getLocation(); String[] rutaTemporal = rutaca.toString().split("/"); StringBuilder rutaFinal = new StringBuilder(); for (int i = 1; i < rutaTemporal.length - 1; i++) { rutaFinal.append(rutaTemporal[i]); rutaFinal.append("/"); } nombreLog = rutaFinal.toString() + hourdateFormat.format(date).toString() + ".txt"; try { File log = new File(nombreLog); log.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } return true; }
Estas funciones se encargan de iniciar el registro y crear un archivo de Log para el Servidor. Son bastante autoexplicativas, simplemente comentar que el método getRegistry de la clase LocateRegistry nos devuelve una referencia al registro que se encuentre en el puerto pasado como parámetro. El método createRegistry crea el registro en el puerto que se le pasa como parámetro.
Y hasta aquí la implementación del Servidor y de los servicios que publica. En el próximo post nos encargaremos de los repositorios. Un saludo.
Deja un comentario