Invocación Remota de Métodos

El API de Invocación Remota de Métodos (RMI) permite las comunicaciones entre cliente y servidor a través de la red entre programas escritos en Java. El servidor de JavaBeans Enterprise implementa de forma transparente el código RMI necesario para que el programa cliente pueda referenciar a los Beans Enterprise que se ejecutan en el servidor y acceder a ellos como si se estuvieran ejecutando localmente en el programa cliente.

El tener el RMi incluido internamente el servidor JavaBeans de Enterprise es muy conveniente y nos ahorra tiempo de codificación, pero si necesitamos usar características avanzadas de RMI o integrar RMI con una aplicación existente, necesitamos sobreescribir la implementación por defecto RMI y escribir nuestro propio código RMI.

El capítulo reemplaza el RegistrationBean manejado por contenedor del Capítulo 2: Beans de Entidad y de Sesión con un servidor de registro basado en RMI. El Bean SellerBean del capítulo 2, también se modifica para llamar al nuevo servidor de registro RMI usando una llamada a lookup de Java 2 RMI.


Sobre RMI

El API RMI nos permite accede a un servidor de objetos remoto desde un programa cliente haciendo sencillas llamadas a métodos del servidor de objetos. Mientras que otras arquitecturas distribuidas para acceder a servidores de objetos remotos como "Distributed Component Object Model" (DCOM) y "Common Object Request Broker Architecture" (CORBA) devuelven referencias al objeto remoto, el API RMI no sólo devuelve referencias, si no que proporciona beneficios adicionales.

Serialización y colocación de Datos

Para transferir ojbjetos, el API RMI usa el API Serialization para empaquetar (colocar) y desempaquetar (descolocar) los objetos. Para colocar un objeto, el API Serialization convierte el objeto a un Stream de bytes, y para descolocar el objeto, el API Serialization convierte un stream de bytes en un objeto.

RMI sobre IIOP

Una de las desventajas iniciales del RMI era que la única relación con la plataforma Java para escribir interfaces hacen díficil la intregración con sistemas legales existentes. Sin embargo, RMI sobre "Internet Inter-ORB Protocol" (IIOP) explicado en el Capítulo 4: Servicios de Búsqueda permite a RMI comunicarse con cualquier sistema o lenguaje que soporte CORBA.

Si combinamos la integración mejorada con la habilidad de RMI para trabajar a través de firewalls usando proxies HTTP, podríamos encontrar distribuciones para la lógica de nuestro negocio usando RMI más fáciles que una solución basada en sockets.


Nota: La transferencia de código y datos son partes clave de la especificación JINI. De hecho, si añadieramos un servicio de uniones a los servicios RMI crearíamos algo muy similar a los que obtenemos con la arquitectura JINI.

RMI en la aplicación de Subastas

El RegistrationServer basado en RMI tiene los siguientes métodos nuevos: La nueva búsqueda personalizada sevuelve los resultados al cliente llamante mediante una llamada a un Callbak RMI. Está búsqueda es similar a los métodos finder usados en los Beans de ejemplos usados en los capítulos 2 y 3, excepto en que la versión RMI, puede tardar más tiempo en generar los resultados porque el sevidor de registros remoto llama al método remoto exportado por el cliente SellerBean basado en RMI.

Si el cliente llamante está escrito en Java, y no es, por ejemplo, una página web, el servidor puede actualizar el cliente tan pronto como los resultados estuvieran listos. Pero, el protocolo HTTP usado en la mayoría de los navegadores no permite que los resultados sean enviados sin que haya una petición. Esto significa que el resultado de una página web no se crea hasta que los resultados estén listos, lo que añade un pequeño retraso.

Introducción a las Clases

Las dos clases principales en la implementación de la subasta basada en RMI son SellerBean y el remoto RegistrationServer. SellerBean es llamado desde AuctionServlet para insertar un ítem para la subasta en la base de datos, y chequear balances negativos en las cuentas.

Los modelos de ejemplo de la arquitectura JavaBeans Enterprise en los que los detalles de registro del usuario se han separado del código para crear y encontrar detalles de registro. Es decir, los detalles de registro de usuario proporcionados por la clase Registration.java se separan del código para crear y encontrar un objeto Registration, que está en la clase RegistrationHome.java.

La implementación del interface remoto de RegistrationHome.java está unida al rmiregistry. Cuando un programa cliente quiere manipular detalles del registro del usuario, primero tiene que buscar la referencia al objeto RegistrationHome.java en el rmiregistry.

Sumario de Ficheros

Todo los fciheros de código fuente para el ejemplo basado en RMI se describen en la siguiente lista. También necesitaremos un fichero de policía java.policy para conceder los permisos necesarios para ejecutar el ejemplo en plataformas Java 2.

La mayoría de las aplicaciones RMI necesitan dos permisos socket, para accesos a los socket y a HTTP para especificar los puertos. Los dos permisos de threads fueron listados en una pila cuando sea necesario por la clase RegistrationImpl para crear un thread interno.

En la plataforma Java 2, cuando un programa no tiene todos los permisos que necesita, la "Máquina Virtual Java" genera una pila de seguimiento que lista los permisos que necesitan ser añadidos al fichero de policía de seguridad.

grant {
  permission java.net.SocketPermission 
     "*:1024-65535", "connect,accept,resolve";
  permission java.net.SocketPermission "*:80", 
                        "connect";
  permission java.lang.RuntimePermission 
                         "modifyThreadGroup";
  permission java.lang.RuntimePermission 
                         "modifyThread";
};

Compilar el Ejemplo

Antes de describir el código basado en RMI de las clases anteriores, aquí está la secuencia de comandos para compilar el ejemplo en las plataformas Unix y Win32:
Unix:
javac registration/Registration.java
javac registration/RegistrationPK.java
javac registration/RegistrationServer.java
javac registration/ReturnResults.java
javac seller/SellerBean.java
rmic -d . registration.RegistrationServer
rmic -d . registration.RegistrationImpl
rmic -d . seller.SellerBean

Win32:
javac registration\Registration.java
javac registration\RegistrationPK.java
javac registration\RegistrationServer.java
javac registration\ReturnResults.java
javac seller\SellerBean.java
rmic -d . registration.RegistrationServer
rmic -d . registration.RegistrationImpl
rmic -d . seller.SellerBean

Arrancar el Registro RMI

Como estamos usando nuestro propio código RMI, tenemos que arrancar explícitamente el RMI Registry para que el objeto SellerBean pueda encontrar los Beans remotos de Enterprise. El RegistrationServer usa el registro RMI para registrar o unir los Beans enterprise que pueden ser llamados de forma remota. El cliente SellerBean contacta con el registro para buscar y obtener las referencias a los Beans AuctionItem y Registration.

Como RMI permite que el código y los datos sean transferidos, debemos asegurarnos que el sistema classloader no carga clases extras que puedan ser enviadas erróneamente al cliente. En este ejemplo, las clases extras podrían ser las clases Stub y Skel, y las clases RegistrationSever y RegistrationImpl, y para evitar que lo sean cuando arrancamos el registro RMI. Como el path actual podría ser incluido automáticamente, necesitamos arrancar el RMI Registry desde fuera del espacio de trabajo.

Los sigueintes comandos evitan el envío de clases extras, desconfigurando la variable CLASSPATH antes de arrancar el Registro RMI en el puerto 1099. Podemos especificar un puerto diferente añadiendo el número de puerto de esta forma: rmiregistry 4321 &. Si cambiamos el número de puerto debemos poner el mismo número en las llamadas al cliente <lookup y al servidor rebind.

Unix:
export CLASSPATH=""
rmiregistry &

Win32:
unset CLASSPATH
start rmiregistry

Arrancar el Servidor Remoto

Una vez que rmiregistry se está ejecutando, podemos arrancar el servidor remoto, RegistrationServer. El programa RegistrationServer registra el nombre registration2 con el servidor de nombres rmiregistry, y cualquier cliente puede usar este nombre para recuperar una referencia al objeto remoto, RegistrationHome.

Para ejecutar el ejemplo, copiamos las clasesRegistrationServer y RegistrationImpl y las clases stub asociadas a un área accesible de forma remota y arrancamos el programa servidor.

Unix:
cp *_Stub.class 
	/home/zelda/public_html/registration
cp RegistrationImpl.class 
/home/zelda/public_html/registration
cd /home/zelda/public_html/registration
java -Djava.server.hostname=
	phoenix.eng.sun.com RegistrationServer

Windows:
copy *_Stub.class 
	\home\zelda\public_html\registration
copy RegistrationImpl.class 
	\home\zelda\public_html\registration
cd \home\zelda\public_html\registration
java -Djava.server.hostname=
	phoenix.eng.sun.com RegistrationServer
Las siguientes propiedades se usan para configurar los clientes y servidores RMI. Estas propiedades pueden seleccionarse dentro del programa o suministrarlas como propiedades en la línea de comandos para la JVM.

Establecer Comunicaciones Remotas

Los programas clientes se comunican unos con otros a través del servidor. El programa servidor consiste en tres ficheros. Los ficheros de interfaces remotos Registration.java y RegistrationHome.java definen los métodos que pueden ser llamados de forma remota, y el fichero RegistrationServer.java de clase define las clases RegistrationServer y RegistrationImpl que implementan los métodos.

Para establecer comunicaciones remotas, tanto el programa cliente como el servidor necesitan acceder a las clases del interface remoto. El servidor necesita las clases del interface para generar la implementación del interface, y el cliente usa el interface remoto para llamar a las implementaciones de los métodos del servidor remoto.

Por ejemplo, SellerBean crea una referencia a el interface RegistrationHome, y no RegistrationServer, la implementación, cuando necesita crear un regisro de usuario.

Junto con los interfaces del servidor y las clases, necesitamos las clases Stub y Skel para establecer comunicaciones remotas. Estas clases se generan cuando ejecutamos el comando del compilador rmic sobre las clases RegistrationServer y SellerBean.

Las clases SellerBean, SellerBean_Stub.class y SellerBean_Skel.class generadas son necesarias para la llamada desde el servidor hasta el cliente SellerBean. Es el fichero _Stub.class en el cliente que coloca y descoloca los datos desde el servidor, mientras que la clase _Skel.class hace los mismo en el servidor.


Nota: En la plataforma Java 2, el fichero del lado delservidor, _Skel.class ya no es necesario porque sus funciones han sido reemplazadas por las clases de la "Java Virtual Machine".

Colocar Datos

Colocar y descolocar los datos significa que cuando llamamos al método RegistrationHome.create desde SellerBean, esta llamada es reenviada al método RegistrationServer_Stub.create. El método RegistrationServer_Stub.create envuelve los argumentos del método y los envía a un stream serializado de bytes para el método RegistrationServer_Skel.create.

El método RegistrationServer_Skel.create desenvuelve el stream de bytes serializado, re-crea los argumentos de la llamada original a RegistrationHome.create, y devuelve el resultado de la llamada real RegistraionServer.create de vuelta, junto con la misma ruta, pero esta vez, se empaquetan los datos en el lado del servidor.

Colocar y descolocar los datos tiene sus complicaciones. El primer problema son los objetos serializados que podrían ser incompatiles entre versiones del JDK. Un objeto serializado tiene un identificador almacenado con el objeto que enlaza el objeto serializado con su versión. Si el cliente RMI y el servidor son incompativles con su ID de serie, podríamos necesitar generar Stubs y Skels compatibles usando la opción -vcompat del compilador rmic.

Otro problema es que no todos los objetos son serializables por defecto. El objeto inicial RegistrationBean está basado en la devolución de un objeto Enumeration que contiene elementos Registration en un Vector. Devolver la lista desde el método remoto, funciona bien, pero cuando intentamos envíar un vector como un parámetro a un objeto remoto, obtendremos una excepción en tiempo de ejecución en la plataforma Java 2.

Afortunadamente, en el API Collections, la plataforma Java ofrece alternativas a la descolocación de objetos anterior. En este ejemplo, un ArrayList del API Collections reemplaza el Vector. Si el API Collections no es una opción, podemos crear una clase envoltura que extienda Serializable y proporcione implementaciones para los métodos readObject y writeObject para convertir el objeto en un stream de bytes.

La clase RegistrationServer

La clase RegistrationServer extiende java.rmi.server.UnicastRemoteObject e implementa los métodos create, findByPrimaryKey y findLowCreditAccounts declarados en el interface RegistrationHome. El fichero fuente RegistrationServer.java también incluye la implementación del interface remoto Registration como la clase RegistrationImpl. RegistrationImpl también extiende UnicastRemoteObject.

Exportar un Objeto Remoto

Cualquier objeto que querramos que se accesible remotamente necesita extender el interface java.rmi.server.UnicastRemoteObject o usar el método exportObject de la clase UnicastRemoteObject. Si extendemos UnicastRemoteObject, también obtendremos los métodos equals, toString y hashCode para el objeto exportado.

Pasar por Valor y por Referencia

Aunque la clase RegistrationImpl no está unida al registro, todavía está referenciada remotamente porque está asociada con los resultados devueltos por RegistrationHome. RegistrationImpl extiende UnicastRemoteObject, sus resultados son pasados por referencia, y sólo una copia del Bean de registro del usuario existente en la Java VM a la vez.

En el caso de reportar resultados como en el método RegistrationServer.findLowCreditAccounts, la clase RegistrationImpl se puede usar una copia del objeto remoto. Si no extendemos la clase UnicastRemoteObject en la definición de la clase RegistrationImpl, se devolverá un nuevo objeto Registration en cada petición. En efecto los valores son pasados pero no la referencia al objeto en el servidor.

Recolección de Basura Distribuida

Al usar referencias remotas a objetos en el servidor desde fuera del cliente el recolector de basura del servidor introduce algunos problemas potenciales con la memoria. ¿Cómo conoce el servidor cuando se mantiene una referencia a un objeto Registration que no está siendo usado por ningún cliente porque abortó o se cayó la conexión de red?

Para evitar bloqueos de memoria en el servidor desde los clientes, RMI usa un mecanismo de alquiler cuando ofrecen las referencias a los objetos exportados. Cuando se exporta un objeto, la JVM incrementa la cuenta del número de referencias a este objeto y configura el tiempo de expiración, o tiempo de préstamo, por el número de referencias del objeto.

Cuando el alquiler expira, la cuenta de referencias de este objeto se decrementa y si alcanza 0, el objeto es seleccionado para la recolección de basura por la JVM. Hay que configurar el cliente que mantiene un pico de referencia al objeto remoto a que renueve el alquiler si necesita el objeto más alla del tiempo de alquiler. Este pico de referencia es una forma de referirse a un objeto en la memoria sin mantenerlo lejos del recolector de basura.

Este tiempo de alquiler es una propiedad configurable medida en segundos. Si tenemos una red rápida, podríamos acortar el valor por defecto, y crear un gran número de referencias a objetos transitorias.

El siguiente código selecciona el tiempo de alquiler a 2 minutos.

    Property prop = System.getProperties();
    prop.put("java.rmi.dgc.leaseValue", 120000);
Los métodos create y findByPrimaryKey son prácticamente idénticos a las otras versiones del servidor Registration. La principal diferecia es que en el lado del servidor, el registro registration es referenciado como RegistrationImpl, que es la implementación de Registration. En el lado del cliente, se usa Registration en su lugar.

El método findLowCreditAccounts cosntruye un ArrayList de objetos RegistrationImpl serializables y llama al método remoto en la clase SellerBean para pasar el resultado de vuelta. Los resultados on generado por una clase Thread interna porque el método retorna antes de que el resultado esté completo. El objeto SellerBean espera a que sea llamado el método updateAccounts antes de mostrar la página HTML. En un cliente escrito en Java, no sería necesario esperar, podríamos mostrar la actualización en tiempo real.

public class RegistrationServer 
	extends UnicastRemoteObject 
	implements RegistrationHome {

  public registration.RegistrationPK 
    create(String theuser, 
	String password, 
        String emailaddress, 
        String creditcard) 
	  throws registration.CreateException{
    // code to insert database record
  }

  public registration.Registration 
    findByPrimaryKey(registration.RegistrationPK pk) 
	throws registration.FinderException {
      if ((pk == null) || (pk.getUser() == null)) {
            throw new FinderException ();
      }
      return(refresh(pk));
  }

  private Registration refresh(RegistrationPK pk)
	throws FinderException {

    if(pk == null) {
      throw new FinderException ();
    }

    Connection con = null;
    PreparedStatement ps = null;
    try{
      con=getConnection();
      ps=con.prepareStatement("select password, 
	emailaddress, 
	creditcard, 
	balance from registration where theuser = ?");
      ps.setString(1, pk.getUser());
      ps.executeQuery();
      ResultSet rs = ps.getResultSet();
      if(rs.next()) {
        RegistrationImpl reg=null;
        try{
          reg= new RegistrationImpl();
        }catch (RemoteException e) {}
          reg.theuser = pk.getUser();
          reg.password = rs.getString(1);
          reg.emailaddress = rs.getString(2);
          reg.creditcard = rs.getString(3);
          reg.balance = rs.getDouble(4);
          return reg;
      }else{
          throw new FinderException ();
      }
    }catch (SQLException sqe) {
           throw new FinderException();
    }finally {
      try{
        ps.close();
        con.close();
      }catch (Exception ignore) {}
      }
  }

  public void findLowCreditAccounts(
                final ReturnResults client)  
	throws FinderException {
    Runnable bgthread = new Runnable() {
      public void run() {
        Connection con = null;
        ResultSet rs = null;
        PreparedStatement ps = null;
        ArrayList ar = new ArrayList();

        try{
          con=getConnection();
          ps=con.prepareStatement("select theuser, 
	     balance from registration 
	     where balance < ?");
          ps.setDouble(1, 3.00);
          ps.executeQuery();
          rs = ps.getResultSet();
          RegistrationImpl reg=null;
          while (rs.next()) {
            try{
              reg= new RegistrationImpl();
            }catch (RemoteException e) {}
              reg.theuser = rs.getString(1);
              reg.balance = rs.getDouble(2);
              ar.add(reg);
            }
          rs.close();
          client.updateResults(ar);
          }catch (Exception e) {
            System.out.println("findLowCreditAccounts: "+e);
            return;
          }
          finally {
            try{
              if(rs != null) {
                rs.close();
              }
              if(ps != null) {
                ps.close();
                       }
              if(con != null) {
                con.close();
              }
	  }catch (Exception ignore) {}
        }
      } //run
    };
    Thread t = new Thread(bgthread);
    t.start();
  }
}
El método main carga el driver JDBC. Esta versión usa la base de datos Postgres, instala el RMISecurityManager, y contacta con el registro RMI para unir el objeto remoto RegistrationHome al nombre registration2. No necesita unir el interface remoto, Registration porque la clase es cargada cuando es referenciada por RegistrationHome.

Por defecto, el servidor de nombres usa el puerto 1099. Si queremos usar un número de puerto diferente, podemos añadirlo con dos puntos de esta forma: kq6py:4321. Si cambiamos aquí el número de puerto, debemos arrancar el RMI Registry con el mismo número de puerto.

El método main también instala un RMIFailureHandler. Si el servidor falla al crear el socket servidor, el manejador de fallos devuelve true que instruye al servidor RMI para que reintente la operación.

  public static void main(String[] args){
     try {
        new pool.JDCConnectionDriver(
                "postgresql.Driver", 
		"jdbc:postgresql:ejbdemo",
		"postgres", "pass");
        } catch (Exception e){ 
           System.out.println(
             "error in loading JDBC driver");
           System.exit(1);
        }
        try {
           Properties env=System.getProperties();
           env.put("java.rmi.server.codebase", 
             "http://phoenix.eng.sun.com/registration");
           RegistrationServer rs= 
             new RegistrationServer();
           if (System.getSecurityManager() == null )  {
               System.setSecurityManager(
		new RMISecurityManager());
           }
           RMISocketFactory.setFailureHandler(
		new RMIFailureHandlerImpl());

           Naming.rebind("
		//phoenix.eng.sun.com/registration2",rs);
        }catch (Exception e) {
           System.out.println("Exception thrown "+e);
        }
   }
}

class RMIFailureHandlerImpl 
        implements RMIFailureHandler {
   public boolean failure(Exception ex ){
      System.out.println("exception "+ex+" caught");
      return true;
   }
}

Interface Registration

El interface Registration declara los métodos implementados por RegistrationImpl en el fichero fuente RegistrationServer.java.
package registration;

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

public interface Registration extends Remote {
   boolean verifyPassword(String password) 
		throws RemoteException;
   String getEmailAddress() throws RemoteException;
   String getUser() throws RemoteException;
   int adjustAccount(double amount) 
         throws RemoteException;
   double getBalance() throws RemoteException;
}

Interface RegistrationHome

El interface RegistrationHome declara los métodos implementados por la clase RegistrationServer. Estos métodos reflejan el interface Home definido en el ejemplo JavaBeans de Enterprise. El método findLowCreditAccounts toma un interface remoto como su único parámetro.
package registration;

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

public interface RegistrationHome extends Remote {
  RegistrationPK create(String theuser, 
	String password, 
	String emailaddress, 
	String creditcard) 
		throws CreateException, 
		RemoteException;

  Registration findByPrimaryKey(RegistrationPK theuser) 
		throws FinderException, RemoteException;

  public void findLowCreditAccounts(ReturnResults rr) 
		throws FinderException, RemoteException;
}

Interface ReturnResults

El interface ReturnResults declara el método implementado por la clase SellerBean. El método updateResults es llamado desde RegistrationServer.
package registration;

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

public interface ReturnResults extends Remote {
    public void updateResults(ArrayList results) 
	throws FinderException, RemoteException;
}

La Clase SellerBean

La clase SellerBean incluye la implementación del método callback y llama al objeto RegistrationServer usando RMI. El método updateAccounts se hace accesible mediante una llamada a UnicastRemoteObject.exportObject(this);. El método auditAccounts espera un objeto method Boolean.

El método updateAccounts envía una notificación a todos los métodos que esperan el objeto Boolean cuando ha sido llamado desde el servidor y recibe los resultados.

package seller;

import java.rmi.RemoteException;
import java.rmi.*;
import javax.ejb.*;
import java.util.*;
import java.text.NumberFormat;
import java.io.Serializable;
import javax.naming.*;
import auction.*;
import registration.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;


public class SellerBean 
	implements SessionBean, ReturnResults {

    protected SessionContext ctx;
    javax.naming.Context ectx;
    Hashtable env = new Hashtable();
    AuctionServlet callee=null;
    Boolean ready=new Boolean("false");
    ArrayList returned;

    public int insertItem(String seller, 
	String password, 
	String description, 
	int auctiondays, 
	double startprice, 
	String summary) 
		throws RemoteException { 

      try{
        RegistrationHome regRef = (
		RegistrationHome)Naming.lookup(
		"//phoenix.eng.sun.com/registration2");
        RegistrationPK rpk= new RegistrationPK();
        rpk.setUser(seller);
        Registration newseller = (
	      Registration)regRef.findByPrimaryKey(rpk);
	if((newseller == null) || 
              (!newseller.verifyPassword(password))) {
            return(Auction.INVALID_USER);	
  	}

        AuctionItemHome home = (
		AuctionItemHome) ectx.lookup(
		                   "auctionitems");
        AuctionItem ai= home.create(seller, 
		description, 
		auctiondays, 
		startprice, 
		summary);
	if(ai == null) {
          return Auction.INVALID_ITEM;
	}else{
          return(ai.getId()); 
	}
        }catch(Exception e){ 
          System.out.println("insert problem="+e);
          return Auction.INVALID_ITEM;
        }
    }

    public void updateResults(java.util.ArrayList ar) 
		throws RemoteException {
       returned=ar; 
       synchronized(ready) {
          ready.notifyAll();
       }
    }

    public ArrayList auditAccounts() {
        this.callee=callee;
        try {
           RegistrationHome regRef = (
		RegistrationHome)Naming.lookup(
		"//phoenix.eng.sun.com/registration2");
           regRef.findLowCreditAccounts(this);
           synchronized(ready) {
              try {
                 ready.wait();
              } catch (InterruptedException e){}
           }
        return (returned);
       }catch (Exception e) {
          System.out.println("error in creditAudit "+e);
       }
      return null;
    }

    public void ejbCreate() 
		throws javax.ejb.CreateException, 
		RemoteException {
      env.put(
        javax.naming.Context.INITIAL_CONTEXT_FACTORY, 
        "weblogic.jndi.TengahInitialContextFactory");
      try{
        ectx = new InitialContext(env);
      } catch (NamingException e) {
        System.out.println(
          "problem contacting EJB server");
        throw new javax.ejb.CreateException();
      }
        Properties env=System.getProperties();
        env.put("java.rmi.server.codebase", 
	  "http://phoenix.eng.sun.com/registration");
        env.put("java.security.policy","java.policy");
        UnicastRemoteObject.exportObject(this);
    }

    public void setSessionContext(SessionContext ctx) 
		throws RemoteException {
      this.ctx = ctx;
    }

    public void unsetSessionContext() 
		throws RemoteException {   
        ctx = null;  
    } 

    public void ejbRemove() {}
    public void ejbActivate() throws RemoteException {
	System.out.println("activating seller bean");
    }
    public void ejbPassivate() throws RemoteException { 
	System.out.println("passivating seller bean");
    }
}

_______
1 Cuando se usan en toda esta site, los términos, "Java virtual machine" o "JVM" significan una máquina virtual de la plataforma Java.


Ozito