martes, 24 de mayo de 2016

RMI JAVA

El objetivo de esta guía es presentar varios ejemplos muy sencillos que permitan familiarizarse con los aspectos básicos del desarrollo de programas que usan Java RMI.
Bajo ningún concepto se pretende que esta guía constituya un curso sobre cómo programar en este entorno, estando ya disponibles en Internet numerosos cursos sobre este tema (el "oficial"). Simplemente se pretende, de una forma muy directa y aplicada, que un programador familiarizado con Java sea capaz de realizar aplicaciones usando RMI en muy poco tiempo y sin necesidad de revisar mucha documentación.
Esta guía está basada en ejemplos que intentan recoger algunos de los usos más típicos de Java RMI.
  • Un servicio básico: servicio de eco
  • Control de la concurrencia: servicio de log
  • Referencias remotas como parámetros (callbacks): servicio de chat
  • Referencias remotas como valor retornado (fábricas de referencias remotas): servicio simple de banco
  • Usando clases definidas por el usuario y clases complejas: servicio de banco
  • Descarga dinámica de clases: servicio de banco extendido
En este enlace dispone de los ejemplos usados en esta guía.

Un servicio básico: servicio de eco

Dividiremos el desarrollo de este servicio en las siguientes etapas:
  • Definición del servicio
  • Implementación del servicio
  • Desarrollo del servidor
  • Desarrollo del cliente
  • Compilación
  • Ejecución

Definición del servicio

En RMI para crear un servicio remoto es necesario definir una interfaz que derive de la interfaz Remote y que contenga los métodos requeridos por ese servicio, especificando en cada uno de ellos que pueden activar la excepción RemoteException, usada por RMI para notificar errores relacionados con la comunicación.
Este primer servicio ofrece únicamente un método remoto que retorna la cadena de carácteres recibida como argumento pero pasándola a mayúsculas.
A continuación, se muestra el código de esta definición de servicio (fichero ServicioEco.java):

import java.rmi.*;

interface ServicioEco extends Remote {
        String eco (String s) throws RemoteException;
}

Implementación del servicio

Es necesario desarrollar el código que implementa cada uno de los servicios remotos. Ese código debe estar incluido en una clase que implemente la interfaz de servicio definida en la etapa previa. Para permitir que los métodos remotos de esta clase puedan ser invocados externamente, la opción más sencilla es definir esta clase como derivada de la clase UnicastRemoteObject. La principal limitación de esta alternativa es que, debido al modelo de herencia simple de Java, nos impide que esta clase pueda derivar de otra relacionada con la propia esencia de la aplicación (así, por ejemplo, con esta solución no podría crearme una clase que deriva a la vez de Empleado y que implemente un cierto servicio remoto). En esta guía usaremos esta opción. Consulte la referencia previamente citada para estudiar la otra alternativa (basada en usar el método estático exportObject de UnicastRemoteObject).
Recapitulando, desarrollaremos una clase derivada de UnicastRemoteObject y que implemente la interfaz remota ServicioEco (fichero ServicioEcoImpl.java):

import java.rmi.*;
import java.rmi.server.*;

class ServicioEcoImpl extends UnicastRemoteObject implements ServicioEco {
    ServicioEcoImpl() throws RemoteException {
    }
    public String eco(String s) throws RemoteException {
        return s.toUpperCase();
    }
}

Observe la necesidad de hacer explícito el constructor para poder declarar que éste puede generar la excepción RemoteException.
Es importante entender que todos los objetos especificados como parámetros de un método remoto, así como el retornado por el mismo, se pasan por valor, y no por referencia como ocurre cuando se realiza una invocación a un método local. Esta característica tiene como consecuencia que cualquier cambio que se haga en el servidor sobre un objeto recibido como parámetro no afecta al objeto original en el cliente. Por ejemplo, este método remoto no llevará a cabo la labor que se le supone, aunque sí lo haría en caso de haber usado ese mismo código (sin la excepción RemoteException, evidentemente) para definir un método local.

    public void vuelta(StringBuffer s) throws RemoteException {
        s.reverse();
    }

Un último aspecto que conviene resaltar es que la clase que implementa la interfaz remota es a todos los efectos una clase convencional y, por tanto, puede incluir otros métodos, además de los especificados en la interfaz. Sin embargo, esos métodos no podrán ser invocados directamente por los clientes del servicio.

Desarrollo del servidor

El programa que actúe como servidor debe iniciar el servicio remoto y hacerlo públicamente accesible usando, por ejemplo, el rmiregistry (el servicio básico de binding en Java RMI). Nótese que se podría optar por usar la misma clase para implementar el servicio y para activarlo pero se ha preferido mantenerlos en clases separadas por claridad.
A continuación, se muestra el código del servidor (fichero ServidorEco.java):

import java.rmi.*;
import java.rmi.server.*;

class ServidorEco  {
    static public void main (String args[]) {
       if (args.length!=1) {
            System.err.println("Uso: ServidorEco numPuertoRegistro");
            return;
        }
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            ServicioEcoImpl srv = new ServicioEcoImpl();
            Naming.rebind("rmi://localhost:" + args[0] + "/Eco", srv);
        }
        catch (RemoteException e) {
            System.err.println("Error de comunicacion: " + e.toString());
            System.exit(1);
        }
        catch (Exception e) {
            System.err.println("Excepcion en ServidorEco:");
            e.printStackTrace();
            System.exit(1);
        }
    }
}

Resaltamos los siguientes aspectos de ese código:
  • El programa asume que ya está arrancado el rmiregistry previamente (otra opción hubiera sido que lo arrancase el propio programa usando el método estático createRegistry de LocateRegistry). En la sección que explica cómo ejecutar el programa se muestra el procedimiento para arrancar el rmiregistry. Nótese que el programa espera recibir como único argumento el número de puerto por que el que está escuchando el rmiregistry.
  • Un aspecto clave en Java RMI es la seguridad. En el código del servidor se puede apreciar cómo éste instancia un gestor de seguridad (más sobre el tema en la sección dedicada a la ejecución del programa). Para ejemplos sencillos, podría eliminarse esta parte del código del servidor (y del cliente) pero es conveniente su uso para controlar mejor la seguridad y es un requisito en caso de que la aplicación requiera carga dinámica de clases.
  • La parte principal de este programa está incluida en la sentencia try y consiste en crear un objeto de la clase que implementa el servicio remoto y darle de alta en el rmiregistry usando el método estático rebind que permite especificar la operación usando un formato de tipo URL. Nótese que el rmiregistry sólo permite que se den de alta servicios que ejecutan en su misma máquina.

Desarrollo del cliente

El cliente debe obtener una referencia remota (es decir, una referencia que corresponda a un objeto remoto) asociada al servicio para luego simplemente invocar de forma convencional sus métodos, aunque teniendo en cuenta que pueden generar la excepción RemoteException. En este ejemplo, la referencia la obtiene a través del rmiregistry.
A continuación, se muestra el código del cliente (fichero ClienteEco.java):

import java.rmi.*;
import java.rmi.server.*;

class ClienteEco {
    static public void main (String args[]) {
        if (args.length<2) {
            System.err.println("Uso: ClienteEco hostregistro numPuertoRegistro ...");
            return;
        }

        if (System.getSecurityManager() == null)
            System.setSecurityManager(new SecurityManager());

        try {

            ServicioEco srv = (ServicioEco) Naming.lookup("//" + args[0] + ":" + args[1] + "/Eco");

            for (int i=2; i<args.length; i++)
                System.out.println(srv.eco(args[i]));
        }
        catch (RemoteException e) {
            System.err.println("Error de comunicacion: " + e.toString());
        }
        catch (Exception e) {
            System.err.println("Excepcion en ClienteEco:");
            e.printStackTrace();
        }
    }
}

Resaltamos los siguientes aspectos de ese código:
  • El programa espera recibir como argumentos la máquina donde ejecuta rmiregistry, así como el puerto por el que escucha. El resto de los argumentos recibidos por el programa son las cadenas de caracteres que se quieren pasar a mayúsculas.
  • Como ocurre con el servidor, es conveniente, e incluso obligatorio en caso de descarga dinámica de clases, la activación de un gestor de seguridad.
  • El programa usa el método estático lookup para obtener del rmiregistry una referencia remota del servicio. Observe el uso de la operación de cast para adaptar la referencia devuelta por lookup, que corresponde a la interfaz Remote, al tipo de interfaz concreto, derivado de Remote, requerido por el programa (ServicioEco).
  • Una vez obtenida la referencia remota, la invocación del método es convencional, requiriendo el tratamiento de las excepciones que puede generar.

Compilación

El proceso de compilación tanto del cliente como del servidor es el habitual en Java. El único punto que conviene resaltar es que para generar el programa cliente, además de la(s) clase(s) requerida(s) por la funcionalidad del mismo, se debe disponer del fichero class que define la interfaz (en este caso, ServicioEco.class), tanto para la compilación como para la ejecución del cliente. Esto se ha resuelto en este ejemplo creando un enlace simbólico. Si quiere probar el ejemplo usando dos máquinas, lo que recomendamos, deberá copiar el fichero class a la máquina donde se ejecutará el cliente. Obsérvese que no es necesario, ni incluso conveniente, disponer en el cliente de las clases que implementan el servicio.
Hay que resaltar que en la versión actual de Java (realmente, desde la versión 1.5) no es necesario usar ninguna herramienta para generar resguardos ni para el cliente (proxy) ni para el servidor (skeleton). En versiones anteriores, había que utilizar la herramienta rmic para generar las clases que realizan esta labor, pero gracias a la capacidad de reflexión de Java, este proceso ya no es necesario.
En el ejemplo que nos ocupa, dado que, por simplicidad, no se han definido paquetes ni se usan ficheros JAR, para generar el programa cliente y el servidor, basta con entrar en los directorios respectivos y ejecutar directamente:

javac *.java

Ejecución

Antes de ejecutar el programa, hay que arrancar el registro de Java RMI (rmiregistry). Este proceso ejecuta por defecto usando el puerto 1099, pero puede especificarse como argumento al arrancarlo otro número de puerto, lo que puede ser lo más conveniente para evitar colisiones en un entorno donde puede haber varias personas probando aplicaciones Java RMI en la misma máquina.
Hay que tener en cuenta que el rmiregistry tiene que conocer la ubicación de las clases de servicio. Para ello, puede necesitarse definir la variable de entorno CLASSPATH para el rmiregistry de manera que haga referencia a la localización de dichas clases. En cualquier caso, si el rmiregistry se arranca en el mismo directorio donde ejecutará posteriormente el servidor y en la programación del mismo no se han definido nuevos paquetes (todas las clases involucradas se han definido en el paquete por defecto), no es necesario definir esa variable de entorno. Así ocurre en este ejemplo:

cd servidor
rmiregistry 54321 &

Ya estamos a punto de poder ejecutar el servidor y el cliente, y si puede ser, mejor en dos máquinas diferentes. Sin embargo, queda un último aspecto vinculado con la seguridad. Dado que tanto en el cliente como en el servidor se ha activado un gestor de seguridad, si queremos poder definir nuestra propia política de seguridad, con independencia de la que haya definida por defecto en el sistema, debemos crear nuestros propios ficheros de políticas de seguridad.
Dado que estamos trabajando en un entorno de pruebas, lo más razonable es crear ficheros de políticas de seguridad que otorguen todos los permisos posibles tanto para el cliente (fichero que hemos llamadacliente.permisos) como para el servidor (fichero servidor.permisos):

grant  {
    permission java.security.AllPermission;
};

Dada la importancia de esta cuestión, se recomienda que el lector revise más en detalle la misma en las numerosas fuentes disponibles en Internet que tratan este tema.
Procedemos finalmente a la ejecución del servidor:

cd servidor
java -Djava.security.policy=servidor.permisos  ServidorEco 54321

Y la del cliente:

cd cliente
java -Djava.security.policy=cliente.permisos  ClienteEco localhost 54321 hola adios
HOLA
ADIOS

Control de la concurrencia: servicio de log

Un mecanismo de comunicación de tipo RPC o RMI no sólo libera al programador de todos los aspectos relacionados con la mensajería, sino también de todas las cuestiones vinculadas con el diseño de un servicio concurrente.
Java RMI se encarga automáticamente de desplegar los threads requeridos para dotar de concurrencia a un servicio implementado usando esta tecnología. Aunque esta característica es beneficiosa, el programador debe de ser consciente de que los métodos remotos en el servidor se ejecutan de manera concurrente, debiendo establecer mecanismos de sincronización en caso de que sea necesario.
Esta concurrencia automática hace que, como puede observarse en el ejemplo previo, el programa servidor no termine cuando completa su código, sino que se quede esperando indefinidamente la llegada de peticiones. Nótese cómo en el tratamiento de las excepciones se usa una llamada a System.exit para completar explícitamente su ejecución.
Este ejemplo intenta ilustrar esta cuestión creando un hipotético servicio de log que ofrece un método que permite al cliente enviar un mensaje al servidor para que lo almacene de alguna forma (ficheroServicioLog.java):

import java.rmi.*;

interface ServicioLog extends Remote {
        void log (String m) throws RemoteException;
}

Para ilustrar la cuestión que nos ocupa, este método va a enviar el mensaje a dos destinos: a la salida estándar del programa servidor y a un fichero especificado como argumento del programa servidor. Esta duplicidad un poco artificial pretende precisamente mostrar la no atomicidad en la ejecución de los servicios remotos.
A continuación, se muestra la clase que implementa esta interfaz remota (fichero ServicioLogImpl.java):

import java.io.*;
import java.rmi.*;
import java.rmi.server.*;

class ServicioLogImpl extends UnicastRemoteObject implements ServicioLog {
    PrintWriter fd;
    ServicioLogImpl(String f) throws RemoteException {
        try {
             fd = new PrintWriter(f);
        }
        catch (FileNotFoundException e) {
            System.err.println(e);
            System.exit(1);
        }
    }
    public void log(String m) throws RemoteException {
        System.out.println(m);
        fd.println(m);
        fd.flush();  // para asegurarnos de que no hay buffering
    }
}

No se incluye en este documento el código del servidor ni del cliente puesto que no aportan información adicional relevante.
Para ilustrar el carácter concurrente del servicio de Java RMI, se propone arrancar simultáneamente dos clientes que envíen un número elevado de mensajes (se muestra un extracto del ficheroClienteLog.java):

            for (int i=0; i<10000; i++)
                srv.log(args[2] + " " + i);

Se pretende comprobar que los mensajes pueden quedar en orden diferente en la salida estándar y en el fichero precisamente por la ejecución concurrente del método log.
A continuación, se muestra una ejecución donde se aprecia este problema:

cd servidor
remiregistry 54321 &
java -Djava.security.policy=servidor.permisos  ServidorLog 54321 fichero > salida &
cd ../cliente
java -Djava.security.policy=cliente.permisos  ClienteLog localhost 54321 yo &
java -Djava.security.policy=cliente.permisos  ClienteLog localhost 54321 tu
diff fichero salida
2544d2543
< tu 714
2545a2545
> tu 714
9985d9984
< yo 5997
9986a9986
> yo 5997
15325a15326
> yo 8469
15444d15444
< yo 8469
17708a17709
> tu 8229
17985d17985
< tu 8229

La solución en este caso es la habitual en Java: marcar el método como sincronizado:

    public synchronized void log(String m) throws RemoteException {

Referencias remotas como parámetros (callbacks): servicio de chat

En los ejemplos previos, los clientes obtenían las referencias remotas de servicios a través del rmiregistry. Sin embargo, teniendo en cuenta que estas referencias son objetos Java convencionales, éstas se pueden recibir también como parámetros de un método, como se ilustra en esta sección, o como valor de retorno del mismo, tal como se mostrará en la siguiente. De esta forma, se podría decir que elrmiregistry sirve como punto de contacto inicial para obtener la primera referencia remota, pero que, a continuación, los procesos implicados pueden pasarse referencias remotas adicionales entre sí.
Es importante resaltar que cuando se especifica como parámetro de un método RMI una referencia remota, a diferencia de lo que ocurre con el resto de los objetos, que se transfieren por valor, ésta se pasa por referencia (se podría decir que se pasa por valor la propia referencia).
Para ilustrar el uso de referencias remotas como parámetros se plantea en esta sección un servicio de chat. Este servicio permitirá que cuando un usuario, identificado por un apodo, se conecte al mismo, reciba todo lo que escriben el resto de los usuarios conectados y, a su vez, éstos reciban todo lo que escribe el mismo.
Esta aplicación se va a organizar con procesos clientes que atienden a los usuarios y un servidor que gestiona la información sobre los clientes/usuarios conectados.
De manera similar a los ejemplos previos, el servidor ofrecerá un servicio remoto para darse de alta y de baja, así como para enviarle la información que escribe cada usuario.
Sin embargo, en este caso, se requiere, además, que los clientes ofrezcan una interfaz remota para ser notificados de lo que escriben los otros clientes.
A continuación, se muestra la interfaz remota proporcionada por el servidor (fichero ServicioChat):

import java.rmi.*;

interface ServicioChat extends Remote {
    void alta(Cliente c) throws RemoteException;
    void baja(Cliente c) throws RemoteException;
    void envio(Cliente c, String apodo, String m) throws RemoteException;
}

El tipo Cliente que aparece como parámetro de los métodos corresponde a una interfaz remota implementada por el cliente y que permite notificar a un usuario de los mensajes recibidos por otros usuarios (a esta llamada a contracorriente, del servidor al cliente, se le suele denominar callback). Se trata, por tanto, de una referencia remota recibida como parámetro, sin necesidad de involucrar al rmiregistry en el proceso. A continuación, se muestra esa interfaz remota proporcionada por el cliente (fichero Cliente):

import java.rmi.*;

interface Cliente extends Remote {
    void notificacion(String apodo, String m) throws RemoteException;
}

Pasamos a la implementación del servicio de chat (fichero ServicioChatImpl.java) que usa un contenedor de tipo lista para guardar los clientes conectados:

import java.util.*;
import java.io.*;
import java.rmi.*;
import java.rmi.server.*;
    
class ServicioChatImpl extends UnicastRemoteObject implements ServicioChat {
    List<Cliente> l;
    ServicioChatImpl() throws RemoteException {
        l = new LinkedList<Cliente>();
    }
    public void alta(Cliente c) throws RemoteException {
        l.add(c);
    }
    public void baja(Cliente c) throws RemoteException {
        l.remove(l.indexOf(c));
    }
    public void envio(Cliente esc, String apodo, String m)
      throws RemoteException {
        for (Cliente c: l) 
            if (!c.equals(esc))
                c.notificacion(apodo, m);
    }
}

Obsérvese como en envio se invoca el método notificacion de todos los clientes (es decir, de todas las interfaces remotas de cliente) en la lista, exceptuando la del propio escritor.
No se muestra el código del servidor (fichero ServidorChat) puesto que es similar a todos los servidores de los ejemplos previos. Sin embargo, sí es interesante el código del cliente (fichero ClienteChat), puesto que tiene que hacer el doble rol de cliente y de servidor: debe buscar en el rmiregistry el servicio de chat remoto, pero también tiene que instanciar un objeto que implemente la interfaz remota de cliente.

import java.util.*;
import java.rmi.*;
import java.rmi.server.*;

class ClienteChat {
    static public void main (String args[]) {
        if (args.length!=3) {
            System.err.println("Uso: ClienteChat hostregistro numPuertoRegistro apodo");
            return;
        }

       if (System.getSecurityManager() == null)
            System.setSecurityManager(new SecurityManager());

        try {

            ServicioChat srv = (ServicioChat) Naming.lookup("//" + args[0] + ":" + args[1] + "/Chat");
            ClienteImpl c = new ClienteImpl();
            srv.alta(c);
            Scanner ent = new Scanner(System.in);
            String apodo = args[2];
            System.out.print(apodo + "> ");
            while (ent.hasNextLine()) {
                srv.envio(c, apodo, ent.nextLine());
                System.out.print(apodo + "> ");
            }
            srv.baja(c);
            System.exit(0);
        }
        catch (RemoteException e) {
            System.err.println("Error de comunicacion: " + e.toString());
        }
        catch (Exception e) {
            System.err.println("Excepcion en ClienteChat:");
            e.printStackTrace();
        }
    }

Nótese que al tener también un perfil de servidor, es necesario terminar su ejecución explícitamente con System.exit.
Por último, se muestra la implementación (fichero ClienteImpl.java):

import java.rmi.*;
import java.rmi.server.*;

class ClienteImpl extends UnicastRemoteObject implements Cliente {
    ClienteImpl() throws RemoteException {
    }
    public void notificacion(String apodo, String m) throws RemoteException {
        System.out.println("\n" + apodo + "> " + m);
    }
}

Hay que resaltar que el método notificacion se ejecutará de forma asíncrona con respecto al flujo de ejecución del cliente.
Con respecto a la compilación y ejecución de estos programas, en este caso es necesario también disponer en la máquina que ejecuta el servidor del fichero class correspondiente a la interfaz remota de cliente (Cliente.class).

Referencias remotas como valor retornado (fábricas de referencias remotas): servicio simple de banco

Además de poder ser recibidas como parámetros de un método, puede obtenerse una referencia remota como el valor de retorno de un método (al fin y al cabo, eso es lo que hace el método lookup delrmiregistry).
Dentro de este ámbito, es muy frecuente el uso de un esquema de tipo fábrica de referencias remotas. Este esquema se suele usar cuando se requiere ir creando dinámicamente objetos remotos (por ejemplo, un objeto que actúe como cerrojo). Con este modelo, el servidor crea, y registra en el rmiregistry, un servicio remoto que ofrece una operación para crear un nuevo objeto remoto (en el ejemplo, un servicio de fabricación de cerrojos con un método para crear uno nuevo). Esa operación instancia un nuevo objeto remoto y retorna una referencia remota al mismo.
Para ilustrar este escenario típico, vamos a crear un servicio bancario trivial, que permite crear dinámicamente cuentas bancarias.
A continuación, se muestra la interfaz remota correspondiente a la fábrica de cuentas (fichero Banco.java), que será la que se registre en el rmiregistry:

import java.rmi.*;

interface Banco extends Remote {
    Cuenta crearCuenta(String nombre) throws RemoteException;
}

La clase que implementa esta interfaz (fichero BancoImpl) meramente crea un nuevo objeto que implementa la interfaz remota Cuenta:

import java.rmi.*;
import java.rmi.server.*;

class BancoImpl extends UnicastRemoteObject implements Banco {
    BancoImpl() throws RemoteException {
    }
    public Cuenta crearCuenta(String nombre) throws RemoteException {
        return new CuentaImpl(nombre);
    }
}

La interfaz remota correspondiente a una cuenta bancaria (fichero Cuenta) especifica unos métodos para operar, hipotéticamente, con esa cuenta:

import java.rmi.*;

interface Cuenta extends Remote {
    String obtenerNombre() throws RemoteException;
    float obtenerSaldo() throws RemoteException;
    float operacion(float valor) throws RemoteException;
}

Y, a continuación, se incluye la clase (fichero CuentaImpl) que implementa esos métodos:

import java.rmi.*;
import java.rmi.server.*;

class CuentaImpl extends UnicastRemoteObject implements Cuenta {
    private String nombre; 
    private float saldo = 0;
    CuentaImpl(String n) throws RemoteException {
        nombre = n;
    }
    public String obtenerNombre() throws RemoteException {
        return nombre;
    }
    public float obtenerSaldo() throws RemoteException {
        return saldo;
    }
    public float operacion(float valor) throws RemoteException {
        saldo += valor;
        return saldo;
    }
}

Dado que tanto el cliente (fichero ClienteBanco.java) como el servidor (fichero ServidorBanco.java) son similares a los de los ejemplos previos, no se incluye su código en este documento. Simplemente, se muestra un extracto del cliente para mostrar el uso de este servicio:

            Banco srv = (Banco) Naming.lookup("//" + args[0] + ":" + args[1] + "/Banco");
            Cuenta c = srv.crearCuenta(args[2]);
            c.operacion(30);
            System.out.println(c.obtenerNombre() + ": " + c.obtenerSaldo());

Por último, es conveniente hacer algún comentario sobre el ciclo de vida de los objetos remotos creados dinámicamente. Al igual que ocurre con cualquier objeto en Java, el objeto seguirá vivo mientras haya alguna referencia al mismo. En el caso de Java RMI, esto se extiende a toda la aplicación distribuida: el objeto que implementa una interfaz remota seguirá vivo mientras haya una referencia local o remota al mismo. Java RMI, por tanto, implementa un recolector de basura distribuido para poder hacer un seguimiento de la evolución de los objetos remotos.
Si el proceso que ha instanciado un objeto remoto desea saber cuándo no quedan más referencias remotas a ese objeto en el sistema, aunque sí pueda existir alguna referencia local (por ejemplo, porque ese proceso ha incluido el objeto remoto en algún contenedor), puede implementar la interfaz Unreferenced y será notificado, invocándose el método unreferenced de dicha interfaz, cuando ocurra esa circunstancia. Nótese que la detección y notificación de ese estado puede diferirse considerablemente (por defecto, puede retrasarse diez minutos, aunque se puede reducir ese valor cambiando la propiedadjava.rmi.dgc.leaseValue, lo que puede implicar, sin embargo, mayor sobrecarga de mensajes de estado en el sistema).

Usando clases definidas por el usuario y clases complejas: servicio de banco

Tanto los parámetros de un método RMI como el resultado devuelto por el mismo pueden ser objetos de cualquier tipo, siempre que sean serializables (la mayoría de los objetos lo son, excepto aquéllos que por su propia esencia estén vinculados con recursos locales y no tenga sentido transferirlos a otra máquina como, por ejemplo, un descriptor de fichero, un thread o un socket). Por tanto, en un método RMI se pueden usar objetos de clases definidas por el usuario y objetos de clases complejas, como puede ser un contenedor: el proceso de serialización se encarga de empaquetar toda la información vinculada con ese objeto, de manera que luego se pueda recuperar en el mismo estado.
En esta sección, vamos a extender el servicio bancario previo de manera que utilice una clase definida por el usuario, así como una clase compleja, como puede ser una lista de cuentas bancarias.
En esta nueva versión, una cuenta bancaria va a quedar identificada por el nombre y el número de identificación del titular, en lugar de sólo por el nombre. Esta doble identificación va a quedar englobada en una nueva clase que representa al titular de una cuenta (fichero Titular):

import java.io.*;

class Titular implements Serializable {
     private String nombre;
     private String iD;
     Titular(String n, String i) {
         nombre = n;
         iD = i;
     }
     public String obtenerNombre() {
         return nombre;
     }
     public String obtenerID() {
         return iD;
     }
     public String toString() {
         return nombre + " | " + iD;
     }
}

Como se puede observar, se trata de una clase trivial pero que presenta una característica importante: dado que se van a usar objetos de esta clase como parámetros y valores de retorno de métodos RMI, es necesario especificar que esta clase implemente la interfaz Serializable, declarando así el programador que esa clase puede serializarse.
En esta nueva versión, la interfaz que declara una cuenta (fichero Cuenta.java) queda:

import java.rmi.*;

interface Cuenta extends Remote {
    Titular obtenerTitular() throws RemoteException;
    float obtenerSaldo() throws RemoteException;
    float operacion(float valor) throws RemoteException;
}

Y su implementación (fichero CuentaImpl.java):

import java.rmi.*;
import java.rmi.server.*;

class CuentaImpl extends UnicastRemoteObject implements Cuenta {
    private Titular tit;
    private float saldo = 0;
    CuentaImpl(Titular t) throws RemoteException {
        tit = t;
    }
    public Titular obtenerTitular() throws RemoteException {
        return tit;
    }
    public float obtenerSaldo() throws RemoteException {
        return saldo;
    }
    public float operacion(float valor) throws RemoteException {
        saldo += valor;
        return saldo;
    }
}

Por otro lado, se ha modificado el servicio bancario para que almacene las cuentas creadas en un contenedor (concretamente, en una lista enlazada como en el ejemplo del servicio de chat) y ofrezca un nuevo método remoto que devuelta toda la lista de cuentas.
A continuación, se muestra la interfaz remota correspondiente a esta fábrica de cuentas (fichero Banco.java):

import java.rmi.*;
import java.util.*;

interface Banco extends Remote {
    Cuenta crearCuenta(Titular t) throws RemoteException;
    List<Cuenta> obtenerCuentas() throws RemoteException;
}

Y la clase que implementa esta interfaz (fichero BancoImpl):

import java.util.*;
import java.rmi.*;
import java.rmi.server.*;

class BancoImpl extends UnicastRemoteObject implements Banco {
    List<Cuenta> l;
    BancoImpl() throws RemoteException { 
        l = new LinkedList<Cuenta>();
    }
    public Cuenta crearCuenta(Titular t) throws RemoteException {
        Cuenta c = new CuentaImpl(t);
        l.add(c);
        return c;
    }
    public List<Cuenta> obtenerCuentas() throws RemoteException {
       return l;
    }
}

Nótese cómo el nuevo método retorna directamente una lista. El proceso de serialización en el que se apoya RMI reconstruye automáticamente esa lista de referencias remotas en la JVM del cliente.
Nuevamente, no se incluye el código del cliente (fichero ClienteBanco.java) ni del servidor (fichero ServidorBanco.java) puesto que no aportan información novedosa. Simplemente se incluye un extracto del cliente para mostrar el uso del nuevo método para imprimir los datos de todas las cuentas:

            Banco srv = (Banco) Naming.lookup("//" + args[0] + ":" + args[1] + "/Banco");
            Titular tit = new Titular(args[2], args[3]);
            Cuenta c = srv.crearCuenta(tit);
            c.operacion(30);

            List <Cuenta> l;
            l = srv.obtenerCuentas();
            for (Cuenta i: l) {
                Titular t = i.obtenerTitular();
                System.out.println(t + ": " + i.obtenerSaldo());
            }

En este punto se considera conveniente incidir en la diferencia que existe entre cuando se usan en un método RMI referencias a objetos remotos y cuando se utilizan referencias a objetos convencionales.
Vamos a fijarnos en ese extracto previo en la variable c y en la variable t. Ambas almacenan una referencia a un objeto retornado por un método RMI (crearCuenta y obtenerTitular, respectivamente). Sin embargo, hay una diferencia muy importante:
  • La variable c guarda una referencia a un objeto remoto que implementa la interfaz Cuenta. Todas las llamadas a métodos que se hagan usando esa referencia (por ejemplo, c.operacion(30)) se convierten en operaciones remotas que, en caso de que provoquen algún tipo de actualización, repercutirá directamente sobre el objeto en el servidor.
  • La variable t guarda una referencia a un objeto local que es una copia del objeto en el servidor. Todas las llamadas a métodos que se hagan utilizando esa referencia (nótese que la sentenciaprintln(t... invoca automáticamente al método toString del objeto) serán operaciones locales y, por tanto, en caso de que causen algún tipo de actualización, no afectará al objeto en el servidor.
Como comentario final, tenga en cuenta que el fichero class que define el titular de una cuenta (fichero Titular.class) tiene que estar presente en las máquinas donde se generarán y ejecutarán los programas cliente y servidor.

Descarga dinámica de clases: servicio de banco extendido

En los ejemplos planteados hasta el momento no se ha explotado una de las características más interesantes de Java RMI: la descarga dinámica de clases. Este potente mecanismo permite que un proceso pueda usar el código de clases que no estaban presentes en su JVM cuando inició su ejecución, descargándose automáticamente en tiempo de ejecución desde otra JVM. Basándose en este mecanismo se pueden implementar técnicas sofisticadas tales como, por ejemplo, la incorporación automática de nuevos protocolos o la instalación automática en un cliente del manejador de un nuevo dispositivo. Esta técnica está en el corazón de muchas tecnologías distribuidas de Java, como, por ejemplo, Jini, y con ella se podría decir que se extiende al sistema distribuido el poder de la orientación a objetos, la herencia y el polimorfismo.
Podría parecer a priori que en los ejemplos previos ya había descargas automáticas de información entre clientes y servidores, pero se correspondían con transferencias de objetos, no de clases. Para poder usar en un método RMI dentro de una determinada JVM un objeto de un cierto tipo (ya sea clase o interfaz), era necesario disponer de la definición de ese tipo (fichero class) en esa JVM.
Pero entonces, ¿dónde surge la necesidad de la carga dinámica de clases y cómo se artícula?
Pensemos en qué ocurriría si a un método RMI se le pasa un objeto que es de un tipo derivado del especificado como parámetro formal (por ejemplo, el parámetro formal de un método RMI es de tipoFormaGeometrica y el parámetro real es de tipo Circulo). El proceso que implementa ese método remoto debe disponer en su JVM de la clase derivada correspondiente; pero, gracias al mecanismo de descarga dinámica de clases, no es necesario que exista a priori en su JVM, sino que puede descargarlo dinámicamente desde la máquina que posee la definición de ese subtipo. Se podría decir que el servidor vaaprendiendo a hacer nuevas cosas dinámicamente (en el ejemplo geométrico, el servidor aprende a manejar círculos).
Para ilustrar este comportamiento, se ha extendido el ejemplo del banco de manera que un cierto cliente (directorio cliente1) va a extender la clase Titular para el caso de que la persona que posee una cuenta sea menor de edad. La clase derivada (fichero TitularMenor.java) incorpora un nuevo campo para incluir el nombre del tutor responsable, así como un nuevo método para leer este campo y una sobrescritura del método toSting para incorporar la nueva información:

import java.io.*;

class TitularMenor extends Titular {
     private String nombreTutor;
     TitularMenor(String n, String i, String t) {
         super(n, i);
         nombreTutor = t;
     }
     public String obtenerTutor() {
         return nombreTutor;
     }
     public String toString() {
         return super.toString() + " | " + nombreTutor;
     }
}

No ha habido modificaciones en ninguna de las clases del ejemplo de la sección anterior. A continuación, se muestra la única línea del cliente (fichero ClienteBanco.java en el directorio cliente1) que ha cambiado a la hora de crear una nueva cuenta para un menor:

            Titular tit = new TitularMenor(args[2], args[3], args[4]);

Gracias a este mecanismo, el servidor y otros clientes, sin ninguna modificación, pueden gestionar objetos de la clase derivada sin disponer a priori de su código, de manera que cuando invoquen un método sobrescrito en la subclase se ejecute esa nueva versión.
Así, una vez creadas nuevas cuentas bancarias para menores, el cliente original incluido en el directorio cliente2 podrá imprimir correctamente todos sus datos a pesar de que no sabe nada de las cuentas para menores.
Sin embargo, para que esta descarga dinámica funcione, a la hora de ejecutar el programa que reside en la JVM que incluye esa nueva clase, debe especificarse la propiedad java.rmi.server.codebase para indicar la URL dónde está almacenada esa clase:

java -Djava.rmi.server.codebase=file:$PWD/ -Djava.security.policy=cliente.permisos  ClienteBanco localhost 54321  NombreMenor ID NombreTutor

Y habilitar esa descarga en el programa que recibirá la nueva clase poniendo a falso para ello la propiedad java.rmi.server.useCodebaseOnlyen la activación del servidor:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=servidor.permisos ServidorBanco 54321

NOTA: Para que funcionara la descarga dinámica, ha sido necesario modificar la clase base Titular para añadirla un calificador public:

public class Titular implements Serializable {

OBJETOS DISTRIBUIDOS

LA TECNOLOGIA DE OBJETOS DISTRIBUIDOS

4.1 DE LOS MODELOS DE OBJETOS CENTRALIZADOS A LOS MODELOS DE OBJETOS DSITRIBUIDOS.
Los sistemas de bases de datos centralizados son aquellos que se ejecutan en un único sistema informático sin interaccionar con ninguna otra computadora. Tales sistemas comprenden el rango desde los sistemas de bases de datos monousuario ejecutándose en computadoras personales hasta los sistemas de bases de datos de alto rendimiento ejecutándose en grandes sistemas. Por otro lado, los sistemas cliente-servidor tienen su funcionalidad dividida entre el sistema servidor y múltiples sistemas clientes.
OBJETOS DISTRIBUIDOS
Definición:
En los sistemas Cliente/Servidor, un objeto distribuido es aquel que esta gestionado por un servidor y sus clientes invocan sus métodos utilizando un “método de invocación remota”. El cliente
invoca el método mediante un mensaje al servidor que gestiona el objeto, se ejecuta el método del
objeto en el servidor y el resultado se devuelve al cliente en otro mensaje.
Tecnologías orientadas a los objetos distribuidos:
Las tres tecnologías importantes y más usadas en este ámbito son:
1.         RMI.- Remote Invocation Method.- Fue el primer framework para crear sistemas distribuidos de Java. El sistema de Invocación Remota de Métodos (RMI) de Java permite, a un objeto que se está ejecutando en una Máquina Virtual Java (VM), llamar a métodos de otro objeto que está en otra VM diferente. Esta tecnología está asociada al lenguaje de programación Java, es decir, que permite la comunicación entre objetos creados en este lenguaje.
2.         DCOM.- Distributed Component Object Model.- El Modelo de Objeto ComponenteDistribuido, esta incluido en los sistemas operativos de Microsoft. Es un juego deconceptos e interfaces de programa, en el cual los objetos de programa del cliente, pueden solicitar servicios de objetos de programa servidores en otras computadoras dentro de una red. Esta tecnología esta asociada a la plataforma de productos Microsoft.
3.         CORBA.- Common Object Request Broker Architecture.- Tecnología introducida por el Grupo de Administración de Objetos OMG, creada para establecer una plataforma para la gestión de objetos remotos independiente del lenguaje de programación.
Ventajas de las Base de Datos Distribuidas

• Descentralización.- En un sistema centralizado/distribuido, existe un administrador que controla toda la base de datos, por el contrario en un sistema distribuido existe un administrador global que lleva una política general y delega algunas funciones a administradores de cada localidad para que establezcan políticas locales y así un trabajo eficiente.
• Economía: Existen dos aspectos a tener en cuenta.
o El primero son los costes de comunicación; si las bases de datos están muy dispersas y las aplicaciones hacen amplio uso de los datos puede resultar más económico dividir la aplicación y realizarla localmente.
o El segundo aspecto es que cuesta menos crear un sistema de pequeñas computadoras con la misma potencia que un único computador.
• Mejora de rendimiento: Pues los datos serán almacenados y usados donde son generados, lo cual permitirá distribuir la complejidad del sistema en los diferentes sitios de la red, optimizando la labor.
• Mejora de fiabilidad y disponibilidad: La falla de uno o varios lugares o el de un enlace de comunicación no implica la inoperatividad total del sistema, incluso si tenemos datos duplicados puede que exista una disponibilidad total de los servicios.

• Crecimiento: Es más fácil acomodar el incremento del tamaño en un sistema distribuido, por que la expansión se lleva a cabo añadiendo poder de procesamiento y almacenamiento en la red, al añadir un nuevo nodo.
• Flexibilidad: Permite acceso local y remoto de forma transparente.
• Disponibilidad: Pueden estar los datos duplicados con lo que varias personas pueden acceder simultáneamente de forma eficiente. El inconveniente, el sistema administrador de base de datos debe preocuparse de la consistencia de los mismos.
• Control de Concurrencia: El sistema administrador de base de datos local se encarga de manejar la concurrencia de manera eficiente.


4.2 EVOLUCIÓN DE LOS SISTEMAS DISTRIBUIDOS

Sistemas Distribuidos
Definición:
“Sistemas cuyos componentes hardware y software, que están en ordenadores conectados en red, se comunican y coordinan sus acciones mediante el paso de mensajes, para el logro de un objetivo. Se establece la comunicación mediante un protocolo prefijado por un esquema cliente-servidor”.

Características:

Concurrencia.- Esta característica de los sistemas distribuidos permite que los recursos disponibles en la red puedan ser utilizados simultáneamente por los usuarios y/o agentes que interactúan en la red.

Carencia de reloj global.- Las coordinaciones para la transferencia de mensajes entre los diferentes componentes para la realización de una tarea, no tienen una temporización general, esta más bien distribuida a los componentes.

Fallos independientes de los componentes.- Cada componente del sistema puede fallar independientemente, con lo cual los demás pueden continuar ejecutando sus acciones. Esto permite el logro de las tareas con mayor efectividad, pues el sistema en su conjunto continua trabajando.

Motivación
Los sistemas distribuidos suponen un paso más en la evolución de los sistemas informáticos, entendidos desde el punto de vista de las necesidades que las aplicaciones plantean y las posibilidades que la tecnología ofrece. Antes de proporcionar una definición de sistema distribuido resultará interesante presentar, a través de la evolución histórica, los conceptos que han desembocado en los sistemas distribuidos actuales, caracterizados por la distribución física de los recursos en máquinas interconectadas.
Utilizaremos aquí el término recurso con carácter general para referirnos a cualquier dispositivo o servicio, hardware o software, susceptible de ser compartido.

Tipos de sistemas

Desde una perspectiva histórica se puede hablar de diferentes modelos que determinan la funcionalidad y la estructura de un sistema de cómputo, las características del sistema operativo como gestor de los recursos, y su campo de aplicación y uso:

  • · Sistemas de lotes. Son los primeros sistemas operativos, que permitían procesar en diferido y secuencialmente datos suministrados en paquetes de tarjetas perforadas. Hoy en día, sobre sistemas multiprogramados con interfaces de usuario interactivas, el proceso por lotes tiene sentido en aplicaciones de cálculo intensivo, por ejemplo en supercomputación.

  • · Sistemas centralizados de tiempo compartido. Fue el siguiente paso, a mediados de los 60. El objetivo es incrementar la eficiencia en el uso de la CPU, un recurso entonces caro y escaso, disminuyendo los tiempos de respuesta de los usuarios, que operan interactivamente. Los recursos están centralizados y se accede al sistema desde terminales.

  • · Sistemas de teleproceso. Se diferencian del modelo anterior en que los terminales —más recientemente, sistemas personales— son remotos y acceden a un sistema central utilizando una infraestructura de red (por ejemplo la telefónica) y un protocolo de comunicaciones normalmente de tipo propietario. El sistema central monopoliza la gestión de los recursos.
Ejemplos de aplicaciones que resolvía este modelo son los sistemas de reservas y de transacciones bancarias.Sistemas personales. La motivación de este tipo de sistemas estuvo en proporcionar un sistema dedicado para un único usuario, lo que fueposible gracias al abaratamiento del hardware por la irrupción del microprocesador a comienzos de los 80. Precisamente, el coste reducido es su característica fundamental. El sistema operativo de un ordenador personal (PC) es, en un principio, monousuario: carece de mecanismos de protección. Aunque, por simplicidad, los primeros sistemas operativos eran monoprogramados (MS-DOS), la mejora del hardware pronto permitió soportar sistemas multitarea (Macintosh, OS/2, Windows 95/98), e incluso sistemas operativos diseñados para tiempo compartido, como UNIX y Windows NT1. Un sistema personal posee sus propios recursos locales. Inicialmente, éstos eran los únicos recursos accesibles, pero hoy en día la situación ha cambiado. Por otra parte, la evolución hardware ha llevado a los ordenadores personales hacia versiones móviles (PC portátiles y otros dispositivos como PDAs y teléfonos móviles).

Sistemas distribuidos. Los recursos de diferentes máquinas en red se integran de forma que desaparece la dualidad local/remoto. La diferencia fundamental con los sistemas en red es que la ubicación del recurso es transparente a las aplicaciones y usuarios, por lo que, desde este punto de vista, no hay diferencia con un sistema de tiempo compartido. El usuario accede a los recursos del sistema distribuido a través de una interfaz gráfica de usuario desde un terminal, despreocupándose de su localización. Las aplicaciones ejecutan una interfaz de llamadas al sistema como si de un sistema centralizado se tratase, por ejemplo POSIX. Un servicio de invocación remota (por ejemplo a procedimientos, RPC, o a objetos, RMI) resuelve los accesos a los recursos no locales utilizando para ello la interfaz de red. Los sistemas distribuidos proporcionan de forma transparente la compartición de recursos, facilitando el acceso y la gestión, e incrementando la eficiencia y la disponibilidad.


El modelo de sistema distribuido es el más general, por lo que, aunque no se ha alcanzado a nivel comercial la misma integración para todo tipo de recursos, la tendencia es clara a favor de este tipo de sistemas. La otra motivación es la Hoy en día existe un hardware estándar de bajo coste, los ordenadores personales, que son los componentes básicos del sistema. Por otra parte, la red de comunicación, a no ser que se requieran grandes prestaciones, tampoco constituye un gran problema económico, pudiéndose utilizar infraestructura cableada ya existente (Ethernet, la red telefónica, o incluso la red eléctrica) o inalámbrica.


4.3 LA TECNOLOGIA DE OBJETOS PARA EL DESARROLLO DE SISTEMAS DISTRIBUIDOS
El Cliente Stub es una entidad de programa que invoca una operación sobre la implementación de un objeto  remoto a través de un Stub cuyo propósito es lograr que la petición de un cliente llegue hasta el ORB Core. Logrando el acoplamiento entre el lenguaje de programación en que se escribe el cliente y el ORB Core. El stub crea y expide las solicitudes del cliente.
Un Servidor Skeleton es la implementación de un objeto CORBA en algún lenguaje de programación, y define las operaciones que soporta una interface IDL CORBA. Puede escribirse en una gran variedad de lenguajes como C, C++, Java, Ada o Smalltalk. Y a través del skeleton entrega las solicitudes procedentes del ORB a la implementación del objeto CORBA.
La función del ORB consiste en conectar las dos partes: cliente y servidor. Presumiblemente estas partes se ejecutan sobre plataformas distintas y funcionan con diferentes sistemas operativos. Esto significa que pueden existir diferencias en tipos de datos, el orden de los parámetros en una llamada, el orden de los bytes en una palabra según el tipo de procesador, etc. Es misión del ORB efectuar los procesos conocidos como marshaling y unmarshaling. En caso de que el método invocado devuelva un valor de retorno, la función de los ORB del cliente y servidor se invierte. Éste realiza el marshaling de dicho valor y lo envía al ORB del cliente, que será el que realice el unmarshaling y finalmente facilite el valor en formato nativo.

Existe una gran variedad de implementaciones CORBA.
En las siguientes páginas web:
se encuentran implementaciones basadas en CORBA y se describen sus características, algunas son propietarias y otras más son libres.
COM/DCOM/Activex
COM (Component Object Model) es un estándar que permite la creación de objetos que ejecuten tareas que resuelven problemas específicos pero comunes a varias aplicaciones que puedan desear hacer uso de ellos. Estos pueden ser invocados por diferentes programas que los requieran, tanto OLE como ActiveX están basados en esta tecnología.
La idea es tener un mundo de objetos independientes de un lenguaje de programación. Por ello COM proporciona un estándar para las comunicaciones entre componentes, de tal forma, que una aplicación puede utilizar características de cualquier otro objeto de la aplicación, o del sistema operativo, y permite actualizar el software de un componente sin afectar a la operación de la solución global.
COM soporta comunicación entre objetos de equipos de cómputo distintos, en una LAN, WAN, o incluso en Internet.
DCOM extiende el estándar COM de objetos remotos, para su utilización en redes. Inicialmente se desarrolló para Windows NT 4.0, y posteriormente para
Solaris 2.x y Macintosh, así como para diferentes versiones UNIX.
Se encarga de manejar los detalles muy bajos de protocolos de red, por lo que el desarrollador se puede centrar en la realidad de los negocios, proporcionando así mejores soluciones a los clientes.
La arquitectura define cómo los componentes y sus clientes interactúan entre sí. Esta interacción es definida cual traduce la llamada del cliente a un formato neutro, totalmente independiente, que puede transportarse sobre cualquier medio
para el cual exista un protocolo de comunicación.


4.4 LOS ESTANDARES PARA EL DISEÑO DE SISTEMAS DISTRIBUIDOS CON OBJETOS

CORBA
Define servidores estandarizados a través de un modelo de referencia, los patrones de interacción entre clientes y servidores y las especificaciones de las APIs.
Con CORBA se facilita:
􀂄 El diseño de middleware de distribución que facilita el diseño de aplicaciones en plataformas heterogéneas sin necesidad de conocer los detalles de los recursos y servicios que ofrece cada elemento de la plataforma.
􀂄 La capacidad de diseñar aplicaciones desarrolladas en diferentes lenguajes de programación. Supliendo los recursos necesarios para implementar las interfaces entre ellas.
􀂄 La insteroperatividad entre aplicaciones desarrolladas por diferentes fabricantes. Para que un componente sea interoperable sólo se requiere que ofrezcan las interfaces y los patrones de interacción basados en la especificación CORBA.

Beneficios que ofrece CORBA.
Capacidad para que los clientes invoquen métodos de objetos ubicados en cualquier nudo de la plataforma.
Capacidad de invocar los métodos estáticamente (conocidos cuando se compila el cliente) y dinámicamente (desconocidos cuando se compiló el cliente).
Facilita la heterogeneidad de los lenguajes de programación. Los clientes y servidores pueden ser desarrollados en lenguajes diferentes. CORBA proporciona los recursos necesarios para compatibilizarlos.
Capacidad de incorporar información reflectiva que describe en tiempo de ejecución a los clientes las capacidades que ofrecen los servidores instalados.
Transparencia de la ubicación en las invocaciones de los objetos que se invocan.
Incorpora los mecanismos de seguridad en los acceso y de consistencia de las transacciones que se ejecutan.
Polimorfismo en las invocaciones.
Coexistencia con otras tecnologías (EJB, DCOM, etc.) a través de la especificación de los elementos puentes.


4.5 ESTUDIO DETALLADO DE CORBA

CORBA es una arquitectura de objetos distribuidos diseñada para permitir que dichos objetos distribuidos interoperen sobre entornos heterogéneos, donde los objetos pueden estar implementados en diferentes leguajes de programación y/o desplegados sobre diferentes plataformas. CORBA se diferencia de la arquitectura Java RMI en un aspecto significativo: RMI es una tecnología propietaria de Sun Microsystems, Inc. y sólo soporta objetos que se encuentren escritos en el lenguaje Java. Por el contrario, CORBA ha sido desarrollado por OMG (Object Management Group) [corba.org, 1], un consorcio de empresas, y fue diseñado para maximizar el grado de interoperabilidad. Es importante saber que CORBA no es en sí mismo una herramienta para dar soporte a objetos distribuidos; se trata de un conjunto de protocolos. Una herramienta que dé soporte a dichos protocolos se denomina compatible con CORBA (CORBA compliant), y los objetos que se desarrollen sobre ella podrán interoperar con otros objetos desarrollados por otra herramienta compatible con CORBA.

CORBA proporciona un conjunto de protocolos muy rico [Siegel, 4], y el abordar todos ellos está más allá del ámbito de este libro. Sin embargo, se centrará en los conceptos clave de CORBA que estén relacionados con el paradigma de objetos distribuidos. También se estudiará una herramienta basada en CORBA: Interface Definition Language (IDL) de Java.

Arquitectura básica
La Figura 10.0 muestra la arquitectura básica de CORBA [omg.org/gettingstarted, 2]. Como arquitectura de objetos distribuidos, guarda una gran semejanza con la arquitectura RMI. Desde el punto de vista lógico, un cliente de objeto realiza una llamada a un método de un objeto distribuido. El cliente interactúa con un proxy – un stub – mientras que la implementación del objeto interactúa con un proxy de servidor – un skeleton. A diferencia del caso de Java RMI, una capa adicional de software, conocida como ORB (Object Request Broker) es necesaria. En el lado del cliente, la capa del ORB actúa con intermediaria entre el stub y la red y sistema operativo local del cliente. En el lado del servidor, la capa del ORB sirve de intermediaria entre el skeleton y la red y sistema operativo del servidor. Utilizando un protocolo común, las capas ORB de ambos extremos son capaces de resolver las diferencias existentes en lo referente a lenguajes de programación, así como las relativas a las plataformas (red y sistema operativo), para de esta forma ayudar a la comunicación entre los dos extremos. El cliente utiliza un servicio de nombrado para localizar el objeto.
Referencias a objetos CORBA
También como en el caso de Java RMI, un objeto distribuido CORBA se localiza por medio de una referencia a objeto. Ya que CORBA es independiente de lenguaje, una referencia a un objeto CORBA es una entidad abstracta traducida a una referencia de objeto específica de cada lenguaje por medio del ORB, en una representación elegida por el desarrollador del propio ORB.

Por interoperabilidad, OMG especifica un protocolo para referencias abstractas de objetos, conocido como protocolo Interoperable Object Reference (IOR). Un ORB que sea compatible con el protocolo IOR permitirá que una referencia a objeto se registre y se obtenga desde un servicio de directorio compatible con IOR. La referencias a objetos CORBA representadas en este protocolo se denominan también IOR (Interoperable Object References).


4.6 LA CONFLUENCIA DE CORBA Y EL WEB

DESARROLLO WEB
Caso particular de los sistemas Cliente-Servidor con representación remota. En donde se dispone de un protocolo estándar: HTTP y un Middleware denominado WebServer. En la actualidad la aplicación de sistemas informáticos basados en Internet, es una herramienta fundamental para las organizaciones que desean tener cierta presencia competitiva.
Tecnologías de la lógica de la aplicación en el servidor web:
• CGI: Common Gateware Interface..- Son programas que se ejecutan en el servidor, pueden servir como pasarela con una aplicación o base de datos o para generar documentos html de forma automática. Cada petición http ejecuta un proceso, el cual analiza la solicitud y genera un resultado. Son independientes del SO, y presentan la ventaja de que, dado un programa escrito en un lenguaje cualquiera, es fácil adaptarlo a un CGI. Entre los lenguajes que se usan para CGIs, el más popular es el Perl.
• Servlets: Pequeños programas en Java que se ejecutan de forma persistente en el servidor, y que, por lo tanto, tienen una activación muy rápida, y una forma más simple de hacerlo. Estos programas procesan una petición y generan la página de respuesta.
• ASP (Active Server Pages): Una página ASP es un fichero de sólo texto que contiene las secuencias de comandos, junto con el HTML necesario, y que se guarda con la extensión “.asp”. Al ser llamado por el navegador, el motor ASP del
IIS (Internet Information Server) se encarga automáticamente de ejecutarlo como se suele hacer con un programa cualquiera, pero cuya salida siempre será a través del navegador que le invoca. Es un entorno propietario de Microsoft y el lenguaje de secuencia de comandos predeterminado del IIS es el VBScript, aunque puede cambiarse.
• JSP (Java Server Pages), que consisten en pequeños trozos de código en Java que se insertan dentro de páginas web, de forma análoga a los ASPs. Ambas opciones, hoy en día, son muy populares en sitios de comercio electrónico. Frente
a los ASPs, la ventaja que presentan es que son independientes del sistema operativo y del procesador de la máquina.
• PHP es un lenguaje cuyos programas se insertan también dentro de las páginas web, al igual que los ASPs y JSPs; es mucho más simple de usar, y el acceso a bases de datos desde él es muy simple. Es tremendamente popular en sitios de comercio electrónico con poco tráfico, por su facilidad de desarrollo y rapidez de implantación.
Consideraciones a tomar en el desarrollo de un sistema WEB
a. Separar la lógica de la aplicación de la interfase de usuario.
b. Utilizar métodos estándar de comunicación entre la lógica de aplicación y la
interfase de usuario.
c. Herramientas que permitan una fácil adaptación de las aplicaciones a los nuevos dispositivos que irán apareciendo.
d. Definir el coste en comunicaciones que debe asumir la organización.
e. Tener en cuenta los procesos de réplica, periodicidad y el ancho de banda que
consuman.
f. Replantear la idoneidad de la ubicación de cada proceso.
g. Extremar las pruebas al diseñar e implementar los protocolos de comunicación.
El concepto de servicio Web
El servicio Web se ha definido como un componente de software reutilizable y distribuido que ofrece una funcionalidad concreta, independiente tanto del lenguaje de programación en que está implementado como de la plataforma de ejecución. Se puede considerar como aplicaciones autocontenidas que pueden ser descritas, publicadas, localizadas e invocadas sobre la Internet (o cualquier otra red) y basada en estándares del W3C (especialmente XML).
Arquitecturas actuales de sistemas WEB.
A continuación se muestran algunas arquitecturas que se presentan en nuestros días al momento de desarrollar una aplicación distribuida sobre la Web.


4.7 JUSTIFICACION DE UNA ARQUITECTURA CON OBJETOS DISTRIBUIDOS PARA EL WEB
Las necesidades y aplicaciones de negocios del mundo de hoy requieren de un conjunto de componentes distribuidos cada vez más complejos a través de las redes de telecomunicaciones. Aunque la complejidad estriba en el software/hardware que ponen a funcionar a esos componentes, todo esto va enfocado a facilitar a los usuarios sus diversas actividades diarias.
Sin que seamos conscientes de ello, en Internet navegan dos tipos de entes muy distintos: las personas que visitan páginas web o acceden a servicios interactivos, y las aplicaciones distribuidas. En la web hay miles de programas que conversan entre sí, intercambiándose datos de forma automática, sin mediación humana.
Usando servicios web, un programador puede implementar aplicaciones basándose en rutinas y datos proporcionados desde un servidor distante.
Así, por ejemplo, existen servidores que proporcionan rutinas que permiten conocer la previsión meteorológica de una localidad o las cotizaciones en bolsa de una empresa, etc.
Esas rutinas pueden ser usadas, por ejemplo, para simplemente mostrar información en una página web, o pueden ser usadas como los datos de entrada en un programa de predicción o de ayuda a la toma de decisiones. Si el acceso a dichas rutinas y a los datos que generan se hace usando ciertos protocolos estandarizados, entonces es cuando hablamos de servicio web.
De esta manera inicia el estudio a los servicios web topando temas como su surgimiento, conceptos, ciclo de vida, arquitectura, ventajas, etc.

La década de los 80′s fue marcada por el surgimiento de la PC y de la interfase grafica. En la década de los 90′s Internet permitió conectar computadoras en una escala global. En principio la conexión fue entre PCs y servidores por medio del explorador de Internet. A comienzos de este siglo es clara la necesidad de permitir a las computadoras conectadas a Internet comunicarse entre ellas.
Desde entonces se va dando forma al nuevo modelo de computación distribuida llamado servicios Web basados en XML. El objetivo es permitir comunicarse entre sí a sistemas heterogéneos dentro y fuera de una empresa. Esta comunicación es independiente del sistema operativo, lenguaje o modelo de programación.
La simplicidad de las interacciones en el modelo de programación Web posibilita construir sistemas incrementalmente. A diferencia del acoplamiento fuerte de RPC y de los sistemas de objetos distribuidos, que requieren la implantación de todas las piezas de una aplicación de una vez, podemos añadir tantos clientes y servidores a sistemas basados en Web como necesitemos. Podemos establecer fácilmente conexiones a aplicaciones nuevas de un modo descentralizado, sin ninguna coordinación central más allá del registro de nombres DNS, y con un grado de interoperabilidad, escalabilidad y capacidad de gestión extraordinariamente alto.

SERVICIO WEB
Definiciones.-
“Aplicaciones de negocio modulares y autocontenidas que tienen interfaces abiertos, orientados a Internet y basados en interfaces estándares”.
Consorcio UDDI .
“Una aplicación software identificada por un URI, cuyas interfaces se pueden definir, describir y descubrir mediante documentos XML. Un servicio Web soporta interacciones directas con otros agentes software utilizando mensajes XML intercambiados mediante protocolos basados en Internet”.
“Una forma estándar de integrar aplicaciones basadas en Web utilizando los estándares abiertos XML, SOAP, WSDL y UDDI. XML se utiliza para etiquetar los datos, SOAP para transferir los datos, WSDL para describir los servicios disponibles y UDDI para listar que servicios están disponibles”.
Los servicios web (SW) son colecciones de funciones u objetos distribuidos que son presentados como una sola entidad la cual es anunciada en la red para ser usada por otros programas. Con un interfaz definido y conocido, al que se puede acceder a través de Internet, al igual que una página web está definida por un URL (Uniform Resource Locator), un servicio web está definido por un URI (Uniform Resource Identification) y por su interfaz, a través del cual se puede acceder a él.
El concepto de servicio web es un paradigma de la computación distribuida que consiste en un conjunto de protocolos de comunicación que permiten el intercambio de datos entre aplicaciones remotas.
Los Servicios WEB son componentes de software reutilizables, ligeramente acoplados que semánticamente encapsulan funcionalidades discretas que son distribuidas y accesibles a nivel de programación a través de protocolos de Internet.

4.8 ASIMILACION DE UNA IMPLEMENTACION DE CORBA: VISIBROKER FOR JAVA
Visibroker es un ORB que cumple con todos los requerimientos de CORBA. Visibroker se compone de lo siguiente (Los nombres de servicio entre comillas son los nombres de la implementación de Visibroker en particular):
•          Librerías de CORBA.
Las librerías de CORBA le ayudan a los programas a exponer métodos CORBA y a utilizar los métodos desde los programas Clientes.
Las librerías de CORBA pueden ser de dos tipos:
1.         ORB
El ORB es el exportador e importador de interfases. Todos los clientes y servidores deben inicializar el ORB y utilizarlo para obtener interfases. Básicamente el ORB es el que interpreta los punteros de interfases y los traduce a mensajes de red, o recibe llamadas de red y los traduce a punteros locales para su ejecución.
2.         Adaptador de Objetos Básico (BOA)
El adaptador de Objetos Básico (Basic Object Adaptor) es la librería que le ayuda a exportar el puntero de interfase. Se utiliza en el servidor.
Nota: En computación distribuida, los términos “servidor” y “cliente” pueden resultar intercambiables, ya que el cliente puede implementar objetos y exportarlos al servidor. En estos casos, los clientes también necesitan usar el BOA (pero solo si exportan interfases). Esto será mas claro en los ejemplos, pero recuerde que “cliente” y “servidor” son rolesque puede jugar un ejecutable en cualquier momento.
•          Servicio de Nombres “SmartAgent”
El servicio SmartAgent le ayuda a exportar un objeto para su utilización en una red local. Si usted tiene el código de interfase I.O.R. en específico, usted no necesita un servicio de nombres.
•          Servicio de Activación “OAD”
Cuando usted escribe un programa COM que exporta interfases, Windows utiliza uno de sus servicios internos (TODO:Averiguar Nombre del Servicio) para ejecutar el archivo (.exe, .dll, etc) donde la interfase “vive”. CORBA no es una tecnología que dependa en el sistema operativo, así que el servicio de activación tiene una lista de las interfases y los nombres de sus ejecutables para poder iniciar automáticamente el ejecutable. Note que si usted comienza el ejecutable por sí mismo, no necesita registrarlo en el OAD. De esta manera, usted puede tener varios servicios en suAUTOEXEC.BAT (o lista de servicios en Windows NT) que aceptarán peticiones de los clientes.
•          Compilador de IDL
IDL es un lenguaje “neutral” para definición de interfases. Como tal, tiene declaraciones pero no tiene sintaxis de control (for, do while, etc). Bajo CORBA, usted describe su interfase utilizando IDL y utiliza el compilador de IDL para crear clases de apoyo a su implementación, y clases auxiliares para que los clientes puedan crear instancias del objeto.
Visibroker y Delphi
La versión de Visibroker que viene con Delphi 4 es:
prompt> vbver oad.exe Information for: /Program Files/Borland/vbroker/Bin/oad.exeProduct Name: VisiBroker for C++ Version: 03.02.00.C2.02 Copyright: (C) 1996, 1998 Company: Visigenic Software, Inc. Build Date: 03/02/1998 11:57:32
Como podrá ver, Delphi 4 utiliza una versión de Visibroker para C++. Como es el caso con todas las librerías de C++, las llamadas han sido traducidas a archivos .PAS para su uso en Delphi.

4.9 EL WEB DE OBJETOS CON VISIBROKER
En 1989, los altos ejecutivos de varias compañías (desde compañías de Software hasta Bancos) se reunieron para hablar acerca de interfases, y de la importancia que éstas tendrían en el futuro. Estas empresas (que forman el llamado “Open Management Group”) decidieron que, dada la enorme importancia que las interfases tendrían, sería muy util especificar un estándar para que todos los lenguajes de programación pudieran exponer interfases, sin importar el lenguaje, el sistema operativo o el vendedor de servicios de objetos.
CORBA es el resultado de muchos años de trabajo de varias compañías para definir un estándar que satisfaga a todos los lenguajes de todos los sistemas operativos. COM fué el primer transporte de interfases soportado por Delphi, pero CORBA es una tecnología mucho más madura y sólida para tranporte de interfases, por el simple y sencillo motivo de que CORBA ha estado funcionando por mucho tiempo en varios sistemas operativos y lenguajes.
Así que la primera diferencia entre COM y CORBA es que COM nos limita a tecnologías Windows, mientras CORBA nos abre las puertas a la posibilidad de usar e implementar interfases en Unix, Linux, Mainframes de IBM, Solaris, Sillicon Graphics, Windows, etcétera, en lenguajes tan dispares como Java, C, Delphi, COBOL, SmallTalk, FORTRAN, LISP y muchos otros.
Independencia de Lenguaje
¿Cómo se logra la independencia entre lenguajes? Tal como vimos en el capítulo 9, Las interfases CORBA se representan en un Lenguaje unificado llamado IDL (Interface Definition Language). Cada lenguaje (Delphi, Java, C, etc.) cuenta con un traductor (también llamado “compilador”) de IDL, que traduce las sentencias de la definición de la interfase al lenguaje adecuado.
Cuando usted compra librerías de CORBA (también llamadas ORBs) para su lenguaje, las librerías normalmente vienen con programas llamados “idl2XXX”, donde “XXX” es el nombre de su lenguaje. De esta manera, un ORB para java viene con un compilador de IDL llamado “idl2java”, mientras que C++ vendría con un compilador llamado “idl2cpp”. En delphi tenemos un caso especial, porque Delphi puede leer IDL directamente en el editor de librerías de tipos y traducirlo a PAS automáticamente (así que el compilador de IDL viene integrado en las versiones Client/Server y Enterprise).
Independencia de Sistemas Operativos y Lenguajes
Como ORB no es dominado por una sola compañía sino por un grupo, mientras uste pueda compilar su programa para un sistema operativo X y pueda comprar unas librerías ORB para el mismo sistema operativo (y que funcionen con su lenguaje), usted puede hablar con cualquier otro servicio en la red que exponga un ORB, sin importar qué sistema operativo o qué marca de ORB está ejecutándose en la máquina que expone el ORB.
CORBA es una Especificación
Tal como lo hicimos en las interfases, hay que ver lo que CORBA no es:
•          CORBA NO ES un producto
CORBA es una especificación de como debe funcionar un set de librerías específicas para llamarse un ORB. Decir “yo sé usar CORBA” no es suficiente; usted normalmente debe decir cosas como “yo sé CORBA usando Visigenics”, o “yo sé CORBA usando IONA”. Visigenics e IONA son dos productos que implementan la tecnología CORBA. Los productos que implementan CORBA se llaman ORBs.
•          CORBA NO ES un lenguaje
De la misma manera, Visigenics (un ORB) está disponible para varios lenguajes bajo varias plataformas (Visigenics for C++/Linux, Visigenics for C++/Windows, Visigenics for Java). Pero CORBA en sí mismo no es un lenguaje, sino una serie de librerías y servicios. Así que recuerde que cuando explique a otra persona que usted puede programar en CORBA, recuerde que debe decirlo de forma “Yo programo CORBA usando Visibroker para Delphi bajo Windows”, o “Yo programo CORBA usando Visibroker para Java”, etcétera.