package engine;
import java.rmi.*;
import java.rmi.server.*;
import compute.*;
public class ComputeEngine extends UnicastRemoteObject
implements Compute
{
public ComputeEngine() throws RemoteException {
super();
}
public Object executeTask(Task t) {
return t.execute();
}
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
String name = "//localhost/Compute";
try {
Compute engine = new ComputeEngine();
Naming.rebind(name, engine);
System.out.println("ComputeEngine bound");
} catch (Exception e) {
System.err.println("ComputeEngine exception: " + e.getMessage());
e.printStackTrace();
}
}
}
Ahora echaremos una mirada más cercana a cada uno de los componentes de la implementación del motor de cálculo.
public class ComputeEngine extends UnicastRemoteObject implements ComputeEsta declaración indica que la clase implementa el interface remoto Compute (y, por lo tanto, define un objeto remoto) y extiende la clase java.rmi.server.UnicastRemoteObject.
UnicastRemoteObject es una clase de conveniencia, definida en el API público del RMI, que puede ser utilizada como superclase para la implementación de objetos remotos. La superclase UnicastRemoteObject suministra implementación para un gran número de métodos de java.lang.Object (equals, hashCode, toString) para que estén definidos apropiadamente para objetos remotos. UnicastRemoteObject también incluye constructores y métodos estáticos utilizados para exportar un objeto remoto, es decir, hacer que el objeto remoto pueda recibir llamadas de los clientes.
Una implementación de objeto remoto no tiene porque extender UnicastRemoteObject, y ninguna implementación que lo haga debe suministrar las implementaciones apropiadas de los métodos de java.lang.Object. Además, una implementación de un objeto remoto debe hacer una llamada explícita a uno de los métodos exportObject de UnicastRemoteObject para que el entorno RMI se de cuenta del objeto remoto para que éste pueda aceptar llamadas.
Al extender UnicastRemoteObject, la ComputeEngine puede ser utilizada pra crear un sólo objeto remoto que soporte comunicación remota (punto a punto) y que utilice el transporte de comunicación basado en sockets que tiene por defecto el RMI.
Si elegimos extender un objeto remoto de otra clase distinta de UnicastRemoteObject, o, alternativamente, los extendemos de la nueva clase java.rmi.activation.Activatable del JDK 1.2 (utilizada pra construir objetos remotos que puedan ser ejecutados sobre demanda), necesitamos exportar explícitamente el objeto remoto llamando a uno de los métodos UnicastRemoteObject.exportObject o Activatable.exportObject desde el constructor de nuestra clase (o cualquier otro método de inicialización, cuando sea apropiado).
El ejemplo del motor de cálculo define un objeto remoto que implementa un sólo interface remoto y ningún otro interface. La clase ComputeEngine también contiene algunos métodos que sólo pueden ser llamados localmente. El primero de ellos es un constructor para objetos ComputeEngine; el segundo es un método main que es utilizado para crear un objeto ComputeEngine y ponerlo a disposición de los clientes.
public ComputeEngine() throws RemoteException {
super();
}
Este constructor sólo llama al constructor de su superclase, que es el constructor sin argumentos de la clase UnicastRemoteObject. Aunque el constructor de la superclase obtiene la llamada incluso si la omitimos en el constructor de ComputeEngine, la hemos incluido por claridad.
Durante la construcción, un objeto UnicastRemoteObject es exportado, lo que significa que está disponible para aceptar peticiones de entrada al escuchar las llamadas de los clientes en un puerto anónimo.
Nota: En el JDK 1.2, podríamos indicar el puerto específico que un objeto remoto utiliza para aceptar peticiones.
El constructor sin argumentos de la superclase,UnicastRemoteObject, declara la excepción RemoteException en su clausula throws, por eso el constructor de ComputeEngine también debe declarar que lanza una RemoteException. Esta excepción puede ocurrir durante la construcción si falla el intento de exportar el objeto (debido a que, por ejemplo, no están disponibles los recursos de comunicación o a que la clase stub apropiada no se encuentra).
public Object executeTask(Task t) {
return t.execute();
}
Este método implementa el protocolo entre el ComputeEngine y sus clientes. Los clientes proporcionan al ComputeEngine un objeto Task, que tiene una implementación del método
execute de task. El ComputeEngine ejecuta la tarea y devuelve el resultado del método directamente a su llamador.
El método executeTask no necesita saber nada más sobre el resultado del método execute sólo que es un Object. El llamador presumiblemente sabe algo más sobre el tipo preciso del Object devuelto y puede tipar el resultado al tipo apropiado.
Estas son las reglas que gobiernan el paso y retorno de valores:
Pasar un objeto por referencia (como se hace con los objetos remotos) significa que cualquier cambio hecho en el estado del objeto por el método remoto es reflejado en el objeto remoto original. Cuando se pasa un objeto remoto, sólo aquellos interfaces que son interfaces remotos están disponibles para el receptor, cualquier otro método definido en la implementación de la clase o definido en un interface no remoto no estará disponible para el receptor.
Por ejemplo, si pasarámos por referencia un ejemplar de la clase ComputeEngine, el receptor tendría acceso sólo al método executeTask. El receptor no vería ni el constructor ComputeEngine ni su método main ni cualquier otro método de java.lang.Object.
En las llamadas a método remotoss, los objetos -parámetos, valores de retorno y excpeciones - que no son objetos remotos son pasados por valor. Esto significa que se crea una copia del objeto en la máquina virtual del receptor. Cualquier cambio en el estado del objeto en el receptor será reflejado sólo en la copia del receptor, no en el ejemplar original.
Todos los programas que utilicen RMI deben instalar un controlador de seguridad o el RMI no descargará las clases (las que no se encuentren el el path local) para los objetos que se reciban como parámetros. Estas restriciones aseguran que las operaciones realizadas por el código descargado pasarán a través de unas pruebas de seguridad.
El ComputeEngine utiliza un ejemplo de controlador de seguridad suministrado como parte del RMI, el RMISecurityManager. Este controlador de seguridad fuerza una política de seguridad similar al controlador de seguridad típico de los applets (es decir, es muy conservador con los accesos que permite). Una aplicación RMI podría definir y utilizar otra clase SecurityManager que diera un acceso más liberal a los recursos del sistema, o, en el JDK 1.2, utilizar un fichero de vigilancia que ofrezca más permisos.
Aquí temos el código que crea e instala el controlador de seguridad:
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
Compute engine = new ComputeEngine();Como se mencionó anteriormente, este constructor llama al constructor de su superclase UnicastRemoteObject, que exporta el objeto recien creado al sistema RMI. Una vez completada la exportación, el objeto remoto ComputeEngine esta listo para aceptar llamadas de los clientes en un puerto anónimo (elegido por el RMI o por el sistema operativo). Observa que el tipo de la variable engine es Compute, y no ComputeEngine. Esta declaración enfatiza que el interface disponible para los clientes es el interfaceCompute y sus métodos, no la clase ComputeEngine y sus métodos.
Antes de que un llamador pueda invocar un método de un objeto remoto, debe obtener una referencia al objeto remoto. Este puede hacerse de la misma forma que en que se obtiene cualquier otra referencia en un programa Java, que es obteniéndolo como parte del valor de retorno de un método o como parte de una estructura de datos que contenga dicha referencia.
El sistema proporciona un objeto remoto particular, el registro RMI, para encontrar referencias a objetos remotos. El registro RMI es un sencillo servicio de nombrado para objetos remotos que permite a los clientes remotos obtener una referencia a un objeto remoto por su nombre. El registro se utiliza típicamente para localizar el primer objeto remoto que un cliente RMI necesita utilizar. Este primer objeto remoto, luego proporciona soporte para encontrar otros objetos.
El interfce java.rmi.Naming es utilizado como un API final para la entrega (o registrado) y búsqueda de objetos remotos en el registro. Una vez registrado un objeto remoto en el registro RMI en el host local, los llamadores de cualquier host pueden busar el objeto remoto por el nombre, obtener su referencia, y luego llamar a los métodos del objeto. El registro podría ser compartido por todos los servidores ejecútandose en un host, o un proceso servidor individual podría crear y utilizar su propio registro si así lo desea.
La clase ComputeEngine crea un nombre para el objeto con la sentencia:
String name = "//localhost
/Compute";
Este nombre incluye el nombre del host localhost, en el que se están ejecutando el registro y el objeto remoto, y un nombre Compute, que identifica el objeto remoto en el registro. Luego está el código necesario para añadir el nombre al registro RMI que se está ejecutando en el servidor. Esto se hace después (dentro del bloque try con la sentencia:
Naming.rebind(name, engine);
Al llamar al método rebind se hace una llamada remota al registro RMI del host local. Esta llamada puede provocar que se genera une RemoteException, por eso tenemos que manejar la excepción. La clase ComputeEngine maneja la excepción dentro de los bloques try/catch. Si la excepción no fuese manejada de esta manera, tendríamos que añadir RemoteException a la clausula throws (ahora inexistente) del método main.
Observemos lo siguiente sobre los argumentos de la llamada a Naming.rebind:
Una vez que el servidor se ha registrado en el registro RMI local, imprime un mensaje indicando que está listo para empezar a manejar llamadas, y sale del método main. No es necesario tener un thread esperando para mantener vivo el servidor. Siempre que haya una referencia al objeto ComputeEngine en algún lugar de la máquina virtual (local o remota) el objeto ComputeEngine no será eliminado. Como el programa entrega una referencia de ComputeEngine en el registro, éste es alcanzable por un cliente remoto (¡el propio registro!). El sistema RMI tiene cuidado de mantener vivo el proceso ComputeEngine. El ComputeEngine está disponible para aceptar llamadas y no será reclamado hasta que:
La pieza final de código del método ComputeEngine.main maneja cualquier excepción que pudiera producirse. La única excepción que podría ser lanzada en el código es RemoteException, que podría ser lanzada por el constructor de la clase ComputeEngine o por la llamada al registro para entregar el nombre del objeto "Compute". En cualquier caso, el programa no puede hacer nada más que salir e imprimir un mensaje de error. En algunas aplicaciones distribuidas, es posible recuperar un fallo al hacer una llamada remota. Por ejemplo, la aplicación podría elegir otro servidor y continuar con la operación.