Common Object Request Broker Architecture (CORBA)

Las implementaciones de RMI y de JavaBeans Enterprise de la aplicación de subasta usan el lenguaje Java para implementar los distintos servicios de la subasta. Sin embargo, podríamos necesitar intergrarlo con aplicaciones escritas en C, C++ u otros lenguajes y ejecutarlo en un millar de sistemas operativos y máquinas distintas.

Una forma de integración con otras aplicciones es transmitir datos en un formato común como caracteres de 8 bits sobre sockets TCP/IP. La desventaja es tener que gastar mucho tiempo en derivar un mensaje de protocolo y mapeado de varias estructuras de datos hacia y desde el formato de transmisión común para que los datos puedan ser enviados y recibidos sobre la conexión TCP/IP.

Aquí es donde pueden ayudar el "Common Object Request Broker Architecture" (CORBA) y su "Interface Definition Language" (IDL). IDL proporciona un formato común para representar un objeto que puede ser distribuido a otras aplicaciones. Las otras aplicaciones podrían incluso no entender de objetos, pero mientras puedan proporcionar un mapeado entre el formato común IDL y sus propia representación de datos, la aplicación podrá compartir los datos.

Este capítulo describe el esquema de mapeo de IDL a lenguaje Java, y cómo reemplazar el original RegistrationBean basado en contenedor controlador por su equivalente servidor CORBA. Los programas SellerBean.java y AuctionServlet.java también se modifican para interoperar con el programa CORBA RegistrationServer.

Esquema de Mapeo IDL

Muchos lenguajes de programación proporcionan un mapeo entre sus tipos de datos y el formato común denominado IDL, y el lenguaje Java no es una excepción. El lenguaje Java puede enviar objetos definidos por IDL a otras aplicaciones distribuidas CORBA y recibir objetos definidos mediante IDL desde otras aplicaciones distribuidas CORBA.

Esta sección descrbie el esquema de mapedo de Java a IDL y, cuando sea necesario, presenta problemas que debemos tener en consideración.

Referencia Rápida

Aquí tenemos una tabla de referencia rápida de los tipos de datos del lenguaje Java y los de IDL CORBA, y las excepciones de tiempo de ejecución que se pueden lanzar cuando la conversión falla. Los tipos de datos de esta tabla que necesitan explicación se cubren más abajo.

Tipo de Dato Java Formato IDL Exception
byte octet
boolean boolean
char char DATA_CONVERSION
char wchar
double double
float float
int long
int unsigned long
long long long
long unsigned long long
short short
short unsigned short
java.lang.String string DATA_CONVERSION
java.lang.String wstring MARSHAL

Valores sin Signo: Los tipos de datos Java: byte, short, int, y long están representados por entereros de complemento a dos de 8, 16, 32 y 64 bits. Esto significa que un valor short Java representa un rango desde -215 hasta 215 - 1 ó desde -32768 hasta 32767 inclusives. El tipo con signo equivalente IDL para un short, short, es igual en el rango, pero el tipo short IDL sin signo usa el rango desde 0 hata 215 ó desde 0 hasta 65535.

Esto significa que en el caso de short, si un valor short sin signo mayor de 32767 es pasado a un programa escrito en Java, el valor short será representado como un número negativo. Esto puede causar confusión en los límites de test para valores mayores que 32767 o menores que 0.

Tipos char IDL: El lenguaje Java usa un unicode de 16 Bits, pero los tipos char y string de IDL son carcateres de 8 bits. Podemos mapear un char Java a un char IDL de 8 bits para transmitir caracteres multi-byte si usamos un array para hacerlo. Sin embargo, el tipo de caracter ancho de IDL wchar está especialmente diseñado para lenguajes con caracteres multi-bytes y aloja el número fijo de bytes que sea necesario para contener el conjunto del lenguaje para cada una de las letras.

Cuando se mapea desde el tipo char de Java al tipo char de IDL, se puede lanzar la excepción DATA_CONVERSION si el caracter no entra en los 8 bits.

Tipos string IDL: El tipo string IDL puede ser lanzado como una secuencia de tipos char IDL, también lanza la excepción DATA_CONVERSION. El tipo wstring IDL es equivalente a una secuencua de wchars terminada por un wchar NULL.

Un tipo string y un tipo wstring de IDL pueden tener un tamaño fijo o sin máximo definido. Si intentamos mapear un java.lang.String a un string IDL de tamaño fijo y el java.lang.String es demasidado largo, se lanzará una excepción MARSHAL.

Configurar el Mapeo IDL

El mapeo del lenguaje Java a IDL se sitúa en un fichero con extensión .idl. El fichero es compilado para que pueda ser accedido por los programas CORBA que necesitan enviar y recibir datos. Esta sección explica cómo construir los mapeos para las sentencias de paquete y los tipos de datos Java. La siguiente sección en Implementación CORBA de RegistrationServer describe cómo usar esta información para configurar el fichero de mapeo IDL para el servidor Registration CORBA.

Paquetes e Interfaces Java: Las sentencias de paquete Java son equivalentes al tipo module de IDL. Este tipo puede ser anidado, lo que resulta en que las clases Java generadas se crean en subdirectorios anidados.

Por ejemplo, si un programa CORBA contiene esta sentencia de paquete:

  package registration;
el fichero de mapeo debería tener este mapeo a módulo IDL para ella:
  module registration {
  };
Si un programa CORBA contiene una herencia de paquete como esta:
  package registration.corba;
su mapeo IDL de módulo será este:
  module registration {
    module corba {
    };
  };
Las clases distribuidas están definidas como interfaces Java y se mapean al tipo interface de IDL. IDL no define accesos como public o private que podríamos encontrar en el lenguaje Java, pero permite descender desde otros interfaces.

Este ejemplo añade el interface Java Registration a un registration module IDL.

  module registration {
     interface Registration {
     };
  }
Este ejemplo añade el interface Java Registration a un registration module IDL, e indica que el interface Registration desciende del interface User.
  module registration {
     interface Registration: User {
     };
  }
Métodos Java: Los métodos Java se mapean a operaciones IDL. Las operaciones IDL son similares a los métodos Java excepto en que no hay el concepto de control de acceso. También tenemos que ayudar al compilador IDL especificando qué parámetros son de entrada in, de entrada/salida inout o de salida out, definidos de esta forma: Este mapeo IDL incluye los métodos de los interfaces Registration y RegistrationHome a operaciones IDL usando un tipo módulo IDL.
module registration {

  interface Registration {
    boolean verifyPassword(in string password);
    string getEmailAddress();
    string getUser();
    long adjustAccount(in double amount);
    double getBalance();
  };

  interface RegistrationHome {
    Registration findByPrimaryKey(
		   in RegistrationPK theuser) 
		   raises (FinderException);
  }
}
Arrays Java: Los Arrays Java son mapeados a los tipos array o sequence IDL usando una definición de tipo.

Este ejemplo mapea el array Java double balances[10] a un tipo array IDL del mismo tamaño.

typedef double balances[10];
Estos ejemplo mapean el array Java double balances[10] a un tipo sequence IDL. El primer typedef sequence es un ejemplo de secuencia sin límite, y el segundo tiene el mismo tamaño que el array.
typedef sequence<double> balances;
typedef sequence<double,10> balances;
Excepciones Java: Las excepciones Java son mapeadas a excepciones IDL. Las operaciones usan exceptions IDL especificándolas como del tipo raises.

Este ejemplo mapea la CreateException desde la aplicación subastas al tipo exception IDL, y le añade el tipo raises a la operación. Las excepciones IDL siguen las sintaxis C++, por eso en lugar de lanzar una excepción (como se haría en lenguaje Java), la operación alcanza (raise) una excepción.

exception CreateException {
};

interface RegistrationHome {
  RegistrationPK create(
	           in string theuser, 
	           in string password, 
	           in string emailaddress, 
	           in string creditcard) 
	           raises (CreateException);
}

Otros Tipos IDL

Estos otros tipos básicos IDL no tienen un equivalente exacto en el lenguaje Java. Muchos de estos deberían sernos familiares si hemos usado C ó C++. El lenguaje Java proporciona mapeo para estos tipos porque los programas escritos en Java pueden recibir datos desde programas escritos en C ó C++.

atributo IDL: El tipo attribute IDL es similiar a los métodos get y set usados para acceder a los campos en el software de JavaBeans.

En el caso de un valor declarado como un atributo IDL, el compilador IDL genera dos métodos con el mismo nombre que el atributo IDL. Un método devuelve el campo y otro lo selecciona. Por ejemplo, este tipo attribute:

interface RegistrationPK {
  attribute string theuser;
};
define estos métodos:
//return user
  String theuser(); 
//set user
  void theuser(String arg); 
enum IDL: El lenguaje Java tiene una clase Enumeration para representar una colección de datos. El tipo enum IDL es diferente porque es declarado como un tipo de dato y no una colección de datos.

El tipo enum IDL es una lista de valores que pueden se referenciados por un nombre en vez de por su posición en la lista. En el ejemplo, podemos ver que referirnos al código de estado de un enum IDL por un nombre es mucho más legible que hacerlo por su número. Esta línea mapea los valores static final int de la clase final LoginError. Podemos referirnos a estos valores como lo haríamos con un campo estático:LoginError.INVALID_USER.

enum LoginError {
  INVALID_USER, WRONG_PASSWORD, TIMEOUT};
Aquí hay una versión del tipo enum que incluye un subrayado anterior para que pueda ser usado en sentencias switch:
switch (problem) {
  case LoginError._INVALID_USER:
    System.out.println("please login again");
    break;
}
struct IDL: Un tipo struct IDL puede ser comparado con una clase Java que sólo tiene campos, que es cómo lo mapea el compilador IDL.

Este ejemplo declara una struct IDL. Observamos que los tipos IDL pueden referenciar otros tipos IDL. En este ejemplo LoginError viene del tipo enum declarado arriba.

struct ErrorHandler {
  LoginError errortype;
  short retries;
};
union IDL: Una union IDL puede representar un tipo de una lista de tipos definidos para esa unión. La union mapea a una clase Java del mismo nombre con un método discriminator usado para determinar el tipo de esa unión.

Este ejemplo mapea la unión GlobalErrors a una clase Java con el nombre GlobalErrors. Se podría añadir un case por defecto case: DEFAULT para manejar cualquier elemento que podría estar en el tipo LoginErrors enum, y no está especificado con una sentencia case aquí.

  union GlobalErrors switch (LoginErrors) {
     case: INVALID_USER: string message;
     case: WRONG_PASSWORD: long attempts;
     case: TIMEOUT: long timeout;
  };
En un programa escrito en lenguaje Java, la clase unión GlobalErrors se crea de esta forma:
  GlobalErrors ge = new GlobalErrors();
  ge.message("please login again");
El valor INVALID_USER se recupera de esta forma:
  switch (ge.discriminator().value()) {
    case: LoginError._INVALID_USER:
      System.out.println(ge.message);
      break;
  }
Tipo Any: si no sabemos que tipo está siento pasado o devuelto desde una operación, podemos usar el tipo Any, que representa cualquier tipo IDL. La siguiente operación retorna y pasa un tipo desconocido:
  interface RegistrationHome {
    Any customSearch(Any searchField, out count);
  };
Para crear un tipo Any, se pide el tipo al "Object Request Broker" (ORB). Para seleccionar un valor de un tipo Any, usamos un método insert_<type>. Para recuperar un valor, usamos el método extract_<type>.

Este ejemplo pide un objeto del tipo Any, y usa el método insert_type para seleccionar un valor.

  Any sfield = orb.create_any();
  sfield.insert_long(34);
El tipo Any tiene un valor TypeCode asignado que puede consultarse usando type().kind().value() sobre el objeto. El siguiente ejemplo muestra una prueba del TypeCode double. Este ejemplo incluye una referencia al TypeCode IDL encontrado que contiene el objeto Any. El TypeCode se usa para todos los objetos. Podemos analizar el tipo de un objeto CORBA usando los métodos _type() o type().
public Any customSearch(Any searchField, IntHolder count){
  if(searchField.type().kind().value() == TCKind._tk_double){
// return number of balances greater than supplied amount 
    double findBalance=searchField.extract_double();
Principal: El tipo Principal identifica al propietario de un objeto CORBA, por ejemplo, un nombre de usuario. El valor puede consultarse desde el campo request_principal de la clase RequestHeader para hacer la identificación. Object: El tipo Object es un objeto CORBA. Si necesitamos enviar objetos Java, tenemos que traducirlos a un tipo IDL o usar un mecanismo para serializarlos cuando sean transferidos.

CORBA en la Aplicación de Subasta

El RegistrationBean controlado por contenedor de la aplicación subasta es totalmente reemplazado con un RegistrationServer solitario CORBA que implementa el servicio de registro. El RegistrationServer CORBA está construido creando y compilando ficheros de mapeo IDL para que los programas clientes se puedan comunicar con el servidor de registros.

Los ficheros SellerBean.java y AuctionServlet.java se han actualizado para que busquen el servidor de registro CORBA.

Implementación del RegistrationServer CORBA

Esta sección describe el fichero Registration.idl, que mapea los interfaces remotos RegistrationHome y Registration desde la aplicación de subastas de JavaBeans de Enterprise a sus equivalentes IDL y muestra como compilar el fichero Registration.idl en las clases del servidor de registos CORBA.

El servidor de registros CORBA implementa los métodos create y findByPrimaryKey desdel el fichero RegistrationBean.java original, y lo amplía con los dos métodos siguientes para ilustrar las retrollamadas CORBA, y como usar el tipo Any.

Fichero de Mapeos IDL

Aquí está el fichero Registration.idl que mapea los tipos de datos y métodos usados en los programas RegistrationHome y Registration a sus equivalentes IDL.
module registration {

interface Registration {
   boolean verifyPassword(in string password);
   string getEmailAddress();
   string getUser();
   long adjustAccount(in double amount);
   double getBalance();
};

interface RegistrationPK {
   attribute string theuser;
};

enum LoginError {INVALIDUSER, WRONGPASSWORD, TIMEOUT};

exception CreateException {
};

exception FinderException {
};

typedef sequence<Registration> IDLArrayList;

interface ReturnResults  {
  void updateResults(in IDLArrayList results) 
	raises (FinderException);
};

interface RegistrationHome {
  RegistrationPK create(in string theuser, 
			in string password, 
                   	in string emailaddress, 
			in string creditcard) 
                           raises (CreateException);

  Registration findByPrimaryKey(
                 in RegistrationPK theuser) 
		 raises (FinderException);
  void findLowCreditAccounts(in ReturnResults rr) 
		 raises (FinderException);
  any customSearch(in any searchfield, out long count);
};
};

Compilar el Fichero de Mapeos IDL

El fichero IDL tiene que ser convertido en clases Java que puedan ser usadas en una red distribuida CORBA. La plataforma Java 2 compila los ficheros .idl usando el programa idltojava. Este programa será reemplazado eventualmente con el comando idlj.

Los argumentos -fno-cpp indican que no hay compilador C++ instalado.

  idltojava -fno-cpp Registration.idl
Otros compiladores Java IDL también deberían funcionar, por ejemplo, jidl de ORBacus puede generar clases que pueden ser usadas por el ORB de Java 2.

Stubs y Skeletons

Corba y RMI son similares en que la compilación genera un fichero stub para el cliente y un fichero skeleton para el servidor. El stub (o proxy), y el skeleton (o sirviente) se usan para envolver o desenvolver datos entre el cliente y el servidor. El skeleton (o sirviente) está implementado mediante el servidor. En este ejemplo, el interface RegistrationHome genera una clase _RegistrationHomeImplBase (la clase skeleton o sirviente) que extiende la clase RegistrationServer generada.

Cuando se solicita un objeto CORBA remoto o se llama a un método remoto, la llamada del cliente pasa a través de la clase stub antes de alcanzar el servidor. Este clase proxy invoca la petición CORBA para el programa cliente. El siguiente ejemplo es el código generado automáticamente por la clase RegistrationHomeStub.java.

  org.omg.CORBA.Request r = _request("create");
  r.set_return_type(
        registration.RegistrationPKHelper.type());
  org.omg.CORBA.Any _theuser = r.add_in_arg();

Object Request Broker

El centro de una red distribuida CORBA es el "Object Request Broker" o ORB. El ORB se encarga de empaquetar y desempaquetar los objetos entre el cliente y el servidor. Otros servicios como Servicios de Nombres y Servicios de Eventos funcionan con el ORB.

La plataforma Java 2 incluye un ORB en la distribución llamado el IDL ORB. Este ORB es diferente de otros muchos ORBs porque no incluye un distintivo de "Basic Object Adapter" (BOA) o "Portable Object Adapter" (POA).

Una adaptador de objetos maneja la creacción y ciclo de vida de los objetos en un espacio distribuido CORBA. Esto puede ser comparado con el contenedor del servidor de JavaBeans Enterprise que maneja el ciclo de vida de los beans de entidad y de sesión.

Los programas AuctionServlet y SellerBean crean e inicializan un ORB de Java 2 de esta forma:

  ORB orb = ORB.init(args, null);
En el programa RegistrationServer, el objeto servidor puede ser distribuido en unión con el ORB usando el método connect:
  RegistrationServer rs = new RegistrationServer();
  orb.connect(rs);
Un objeto conectado a un ORB puede ser eleminado con el método disconnect:
  orb.disconnect(rs);
Una vez conectado a un objeto servidor CORBA, el ORB Java2 mantiene vivo el servidor y espera peticiones del cliente para el servidor CORBA.
  java.lang.Object sync = new java.lang.Object();
  synchronized(sync) {
    sync.wait();
  }

Poner Disponible el Servidor CORBA

Aunque este objeto está ahora siendo mapeado por el ORB, los clientes todavía no tienen el mecanismo para encontrar el objeto remoto. Esto puede resolverse uniendo el objeto servidor CORBA a un servicio de nombres.

El servicio de nombres Java 2 llamado tnameserv, por defecto usa el puerto 900; sin embargo, este valor puede modificarse seleccionado el argumento -ORBInitialPort portnumber cuando se arranca tnameserv o seleccionando la propiedad org.omg.CORBA.ORBInitialPort cuando arrancamos los procesos cliente y servidor.

Las siguientes secciones describen el método main de la clase RegistrationServer.

  java.util.Properties props=System.getProperties();
  props.put("org.omg.CORBA.ORBInitialPort", "1050");
  System.setProperties(props);
  ORB orb = ORB.init(args, props);
Las siguientes líneas muestran que la referencia inicial de nombres es inicializada por la petición del servicio llamado NameService. El NamingContext es recuperado y el nombre construido y unido al servicio de nombres como elementos NameComponent. El nombre de este ejemplo tiene una raíz llamada auction que es este objeto que se está uniendo como RegistrationBean desde la raíz auction. El nombre podría ser comparado por una clase mediante el nombre de auction.RegistrationBean.
  org.omg.CORBA.Object nameServiceObj = 
	orb.resolve_initial_references("NameService") ;
  NamingContext nctx = 
	NamingContextHelper.narrow(nameServiceObj);
  NameComponent[] fullname = new NameComponent[2];
  fullname[0] = new NameComponent("auction", "");
  fullname[1] = new NameComponent(
                      "RegistrationBean", "");

  NameComponent[] tempComponent = new NameComponent[1];
  for(int i=0; i < fullname.length-1; i++ ) {
      tempComponent[0]= fullname[i];
      try{
       nctx=nctx.bind_new_context(tempComponent);
      }catch (Exception e){ 
        System.out.println("bind new"+e);}
  }
  tempComponent[0]=fullname[fullname.length-1];
  try{
   nctx.rebind(tempComponent, rs);
  }catch (Exception e){
   System.out.println("rebind"+e);
  }

Conectar un nuevo ORB

El ORB IDL de Java 2 realmente no incluye ninguno de los servicios disponibles en muchos otros ORBs comerciales como los servicios de seguridad o eventos (notificación). Podemos usar otro ORB en el runtime de Java 2 configurando dos propiedades e incluyendo cualquier codigo de objeto adaptador que sea necesario.

Usar un nuevo ORB en el servidor de registros requiere que las propiedades org.omg.CORBA.ORBClass y org.omg.CORBA.ORBSingletonClass apunten a las clases ORB apropiadas. En este ejemplo se usa el ORB ORBacus en lugar del ORB IDL de Java 2. Para usar otro ORB, el código de abajo debería conectarse dentro del método RegistrationServer.main.

En el código de ejemplo, se usa un ORB SingletonClass. Este ORB no es un ORB completo, y su uso primario es como factoría para TypeCodes. La llamada a ORB.init() en la última línea crea el ORB Singleton.

  Properties props= System.getProperties();
  props.put("org.omg.CORBA.ORBClass", 
            "com.ooc.CORBA.ORB");
  props.put("org.omg.CORBA.ORBSingletonClass",
	"com.ooc.CORBA.ORBSingleton");
  System.setProperties(props);
  ORB orb = ORB.init(args, props) ;
En el IDL de Java 2, no hay un objeto adaptador distinto. Como se muestra en el segmento de código inferior, usar el "Basic Object Adapter" desde ORBacus requiere un conversión explícita al ORB ORBacus, El "Broker Object Architecture" (BOA) es notificado de que el objeto ya está distribuido llamando al método impl_is_ready(null).
  BOA boa = ((com.ooc.CORBA.ORB)orb).BOA_init(
                                       args, props);
 ...
  boa.impl_is_ready(null);
Aunque los dos ORBs ORBSingletonClass y ORBClass construyen el nombre del objeto usando NameComponent, tenemos que usar un diferente servicio de nombres ORBacus. El servicio CosNaming.Server se arranca de la siguiente forma, donde el parámetro -OAhost es opcional:
  java com.ooc.CosNaming.Server -OAhost localhost -OAport 1060
Una vez arrancado el servicio de nombres, los programas cliente y servidor pueden encontrar el servicio de nombres usando el protocolo IIOP hacia el host y el puerto nombrados cuando se arrancó el servicio de nombrado:
  java registration.RegistrationServer 
	-ORBservice NameService 
	iiop://localhost:1060/DefaultNamingContext

Acceso al Servicio de Nombres por los Clientes CORBA

Los cliente CORBA acceden al servicio de nombres de una forma similar a como lo hace el servidor, excepto que en lugar de unir un nombre, el cliente resuelve el nombre construido desde el NameComponents.

Las clases AuctionServlet y SellerBean usan el siguiente código para buscar el servidor CORBA:

  NameComponent[] fullname = new NameComponent[2];
  fullname[0] = new NameComponent("auction", "");
  fullname[1] = new NameComponent(
                      "RegistrationBean", "");

  RegistrationHome regRef = 
	RegistrationHomeHelper.narrow(
	                  nctx.resolve(fullname));
En el caso del ORB ORBacus, los clientes también necesitan un "Basic Object Adapter" si se usan retrollamadas en el método SellerBean.auditAccounts. El contexto de nombrado también se configura de forma diferente para el servidor ORBacus arrancado anteriormente:
  Object obj = 
	((com.ooc.CORBA.ORB)orb).get_inet_object (
		"localhost", 
		1060, 
		"DefaultNamingContext"); 
  NamingContext nctx = NamingContextHelper.narrow(obj);

Clases Helper y Holder

Las referencias a objetos remotos en CORBA usan una clase Helper para recuperar un valor desde ese objeto. Un método usado comunmente es el método Helper, que asegura que el objeto está encastado correctamente.

Las clases Holder contienen valores devueltos cuando se usan parámetros inout o out en un método. El llamador primero ejemplariza la clase Holder apropiada para ese tipo y recupera el valor desde la clase cuando la llamada retorna. En el siguiente ejemplo, el valor del contador customSearch se configura y recupera después de que se haya llamado a customSearch. En el lado del servidor el valor del contador se selecciona llamando a count.value=newvalue.

  IntHolder count= new IntHolder();
  sfield=regRef.customSearch(sfield,count);
  System.out.println("count now set to "+count.value);

Recolección de Basura

Al contrario que RMI, CORBA no tiene un mecanismo de recolección de basura distribuido. Las referencias a un objeto son locales al proxy del cliente y al sirviente del servidor. Esto significa que cada Máquina Virtual Java1 (JVM) es libre de reclamar un objeto y recoger la basura si no tiene más referencias sobre él. Si un objeto no es necesario en el servidor, necesitamos llamar a orb.disconnect(object) para permitir que el objeto sea recolectado.

Retrollamadas (Callbacks) CORBA

El nuevo método findLowCreditAccounts es llamado desde el AuctionServlet usando la URL http://localhost:7001/AuctionServlet?action=auditAccounts.

El método AuctionServlet.auditAccounts llama al método SellerBean.auditAccounts, que devuelve un ArrayList de registros de Registration.

//AuctionServlet.java
  private void auditAccounts(ServletOutputStream out,
    HttpServletRequest request) throws IOException{

//   ...

   SellerHome home = (SellerHome) ctx.lookup("seller");
   Seller si= home.create();

   if(si != null) {
     ArrayList ar=si.auditAccounts();
     for(Iterator i=ar.iterator(); i.hasNext();) {
       Registration user=(Registration)(i.next());
       addLine("<TD>"+user.getUser() +
	 "<TD><TD>"+user.getBalance()+
	 "<TD><TR>", out);
     }
          addLine("<TABLE>", out);
    }
El objeto SellerBean llama al método CORBA RegistrationHome.findLowCreditAccounts implementado en el fichero RegistrationServer.java, y se pasa una referencia a sí mismo. La referencia es pasada siempre que la clase SellerBean implemente el interface ReturnResults declarado en el Registration.idl.
//SellerBean.java
  public ArrayList auditAccounts() {
    try{
      NameComponent[] fullname = new NameComponent[2];
      fullname[0] = new NameComponent("auction", "");
      fullname[1] = new NameComponent(
                          "RegistrationBean", "");

      RegistrationHome regRef = 
	  RegistrationHomeHelper.narrow(
	                     nctx.resolve(fullname));
        regRef.findLowCreditAccounts(this);
        synchronized(ready) {
          try{
           ready.wait();
          }catch (InterruptedException e){}
        }
        return (returned);
      }catch (Exception e) {
       System.out.println("error in auditAccounts "+e);
      }
      return null;
  }
El método RegistrationServer.findLowCreditAccounts recupera los registros de usuario desde la tabla Registration de la base de datos que tengan un valor de crédito menor de tres. Entonces devuelve la lista de registros Registration en un ArrayList llamando al método SellerBean.updateResults que tiene una referencia a ella.
//RegistrationServer.java
  public void findLowCreditAccounts(
                final ReturnResults client)  
		throws Finder Exception {
    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 (Exception e) {
             System.out.println("creating reg"+e);
            }
            reg.theuser = rs.getString(1);
            reg.balance = rs.getDouble(2);
            ar.add(reg);
          }
          rs.close();

          RegistrationImpl[] regarray = 
		(RegistrationImpl [])ar.toArray(
		new RegistrationImpl[0]);
          client.updateResults(regarray);
        }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 SellerBean.updateResults actualiza el ArrayList global de registros de Registration devuelto por el objeto RegistrationServer y notifica al método SellerBean/auditAccounts que puede devolver este ArrayList de registros Registration al AuctionServlet.
  public void updateResults(Registration[] ar) 
	throws registration.FinderException {
    if(ar == null) {
          throw new registration.FinderException();
    }
    try{
      for(int i=0; i< ar.length; i++) {
        returned.add(ar[i]);
      }
    }catch (Exception e) {
     System.out.println("updateResults="+e);
     throw new registration.FinderException();
    }
     synchronized(ready) {
     ready.notifyAll();
    }
  }

Usar el Tipo Any

El método RegistrationServer.customSearch usa el tipo Any de IDL para pasar y devolver resultados. El customSearch es llamado por el AuctionServlet de esta forma:
  http://localhost.eng.sun.com:7001/
     AuctionServlet?action=customSearch&searchfield=2
El parámetro searchfield puede ser seleccionado como un número o un string. El método AuctionServlet.customFind pasa el campo de búsqueda directamente al método SellerBean.customFind que recupera un String que luego es mostrado al usuario:
  private void customSearch(ServletOutputStream out,
               HttpServletRequest request) 
               throws IOException{

    String text = "Custom Search";
    String searchField=request.getParameter(
                                 "searchfield");

    setTitle(out, "Custom Search");
    if(searchField == null ) {
      addLine("Error: SearchField was empty", out);
      out.flush();
      return;
    }
    try{
      addLine("<BR>"+text, out);
      SellerHome home = (SellerHome) 
                           ctx.lookup("seller");
      Seller si= home.create();
      if(si != null) {
        String displayMessage=si.customFind(
                                   searchField);
        if(displayMessage != null ) {
          addLine(displayMessage+"<BR>", out);
        }
      }
    }catch (Exception e) {
     addLine("AuctionServlet customFind error",out);
     System.out.println("AuctionServlet " + 
                        "<customFind>:"+e);
    }
    out.flush();
  }
El método SellerBean.customFind llama al objeto RegistrationHome implementado en la clase RegistrationServer.java, y dependiendo de si el searchField puede ser convertido a un double o a un string, inserta este valor dentro de un objeto del tipo Any. El objeto Any se crea mediante una llamada al ORB, orb.create_any();

El método customFind también usa un parámetro out, count, del tipo int que devuelve el número de registros encontrados. El valor de count se recupera usando count.value cuando la llamada retorna:

//SellerBean.java
  public String customFind(String searchField) 
	   throws javax.ejb.FinderException, 
	   RemoteException{

  int total=-1;
  IntHolder count= new IntHolder();

  try{
      NameComponent[] fullname = new NameComponent[2];
      fullname[0] = new NameComponent("auction", "");
      fullname[1] = new NameComponent(
                          "RegistrationBean", "");

      RegistrationHome regRef = 
        RegistrationHomeHelper.narrow(
                            nctx.resolve(fullname));
      if(regRef == null ) {
        System.out.println(
                     "cannot contact RegistrationHome");
        throw new javax.ejb.FinderException();
      }
      Any sfield=orb.create_any();
      Double balance;
      try{
        balance=Double.valueOf(searchField);
        try {
            sfield.insert_double(balance.doubleValue());
        }catch (Exception e) {
         return("Problem with search value"+balance);
        }
       sfield=regRef.customSearch(sfield,count);
       if(sfield != null ) {
         total=sfield.extract_long();
       }
       return(total+" 
	accounts are below optimal level from" +
	count.value+" records");
     }catch (NumberFormatException e) {
      sfield.insert_string(searchField);
      Registration reg;
      if((reg=RegistrationHelper.extract(
	  	regRef.customSearch(
	  	         sfield,count))) 
	  	         != null ) {
        return("Found user "+reg.getUser() +" 
		who has email address "+
		reg.getEmailAddress());
      }else {
       return("No users found who have email address " +
		searchField);
      }
     }
    }catch(Exception e){
        System.out.println("customFind problem="+e);
        throw new javax.ejb.FinderException();
    }
  }
El valor devuelto desde la llamada a customFind se extrae dentro de un objeto del tipo Any y se construye un String con la salida mostrada al usuario. Para los tipos sencillos se puede usar el método extract_<type> de Any. Sin embargo, para el tipo Registration, se usa la clase RegistrationHelper.
  Registration reg =
    RegistrationHelper.extract(
                 regRef.customSearch(sfield,count))
El método RegistrationServer.customSearch determina el tipo del objeto que está siendo pasado en el parámetro searchField chequeando el .type().kind().value() del objeto Any.
 if(searchField.type().kind().value() == 
                         TCKind._tk_double)
Finalmente, como el método customSearch devuelve un objeto del tipo Any, se requiere una llamada a orb.create_any(). Para tipos sencillos como double, se usa el método insert_<type>. Para el tipo Registration , se usa la clase RegistrationHelper: RegistrationHelper.insert(returnResults, regarray[0]).
//RegistrationServer.java
  public Any customSearch(Any searchField, 
                          IntHolder count){
    Any returnResults= orb.create_any();

    int tmpcount=count.value;
    if(searchField.type().kind().value() == 
                          TCKind._tk_double){
// return number of balances greater 
// than supplied amount
      double findBalance=searchField.extract_double();
      Connection con = null;
      ResultSet rs = null;
      PreparedStatement ps = null;
      try{
        con=getConnection();
        ps=con.prepareStatement("select count(*) from 
                      registration where balance < ?");
        ps.setDouble(1, findBalance);
        ps.executeQuery();
        rs = ps.getResultSet();
        if(rs.next()) {
          tmpcount = rs.getInt(1);
        }
        count.value=tmpcount;
        rs.close();
       }catch (Exception e) {
                 System.out.println("custom search: "+e);
                 returnResults.insert_long(-1);
                 return(returnResults);
       }
       finally {
         try{
           if(rs != null) { rs.close(); }
           if(ps != null) { ps.close(); }
           if(con != null) { con.close(); }
         } catch (Exception ignore) {}
       }
         returnResults.insert_long(tmpcount);
         return(returnResults);
     }else if(searchField.type().kind().value() == 
		TCKind._tk_string) {
      // return email addresses that match supplied address
      String findEmail=searchField.extract_string();
      Connection con = null;
      ResultSet rs = null;
      PreparedStatement ps = null;
      ArrayList ar = new ArrayList();
      RegistrationImpl reg=null;
      try{
        con=getConnection();
        ps=con.prepareStatement("select theuser, 
           emailaddress from registration 
	   where emailaddress like ?");
        ps.setString(1, findEmail);
        ps.executeQuery();
        rs = ps.getResultSet();
        while (rs.next()) {
          reg= new RegistrationImpl();
          reg.theuser = rs.getString(1);
          reg.emailaddress = rs.getString(2);
          ar.add(reg);
        }
        rs.close();

        RegistrationImpl[] regarray = 
             (RegistrationImpl [])ar.toArray(
	     new RegistrationImpl[0]);
          RegistrationHelper.insert(
                               returnResults, 
                               regarray[0]);
          return(returnResults);
       }catch (Exception e) {
        System.out.println("custom search: "+e);
        return(returnResults);
       }
       finally {
        try{
          if(rs != null) { rs.close(); }
          if(ps != null) { ps.close(); }
          if(con != null) { con.close(); }
        } catch (Exception ignore) {}
      }
    }
    return(returnResults);
  }

Conclusión

Como hemos podido ver, convertir una aplicación para que use RMI o CORBA requiere muy pocos cambios en el corazón del programa. La principal diferencia ha sido la inicialización y el servicio de nombres. Mediante la abstracción de estas dos áreas en nuestra aplicación fuera de la lógica del negocio podemos migrar fácilmente entre diferentes arquitecturas de objetos distribuidos.

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


Ozito