Bienvenidos a la tercera entrega del curso de CORBA. Tras cubrir en las dos primeras aspectos teóricos de CORBA vamos a afrontar en esta nueva entrega el desarrollo de una aplicación sencilla utilizando CORBA.
Una vez cubierto este primer ejemplo y mostrados los detalles fundamentales del desarrollo con CORBA, inicialmente utilizando el lenguaje Java en cliente y servidor, pasaremos a enfrentarnos a la aplicación que ha sido analizada en la anterior entrega y que básicamente consiste en implementar un sistema de comunicación para la intranet de una empresa.
// ============================================================
// = Ejemplo de CORBA de JavaORB
// = -------------------------------------------------------- =
// = Una calculadora sencilla =
// ============================================================
//
// Descripcion de una excepcion
//
exception DivisionPorCero
{
float op1;
float op2;
};
//
// Descripcion de la Interfaz de la calculadora
//
interface Calculator
{
// operacion de Suma
float add ( in float nb1, in float nb2 );
// operacion de Division
float div ( in float nb1, in float nb2 )
raises ( DivisionPorCero );
};
Después de la anterior entrega esperamos que el lector no tenga ningún problema en seguir está descripción IDL. En ella hay un sólo interfaz, el "Calculator" con dos operaciones muy sencillas de suma y división.
Quizás la parte más "original" sea el uso de la excepción "DivisionPorCero" que lanzará el servidor en el caso de que el cliente intente realizar un división por cero. Esta excepción del servidor se propagará hasta el cliente a través de CORBA.
Lo mejor para continuar con el ejemplo es que nos creemos un directorio donde ir guardando los diferentes ficheros. A partir de ahora suponemos que dicho directorio es "Ejemplo".
Guardamos dentro de este directorio la interfaz IDL con el nombre "Calculator.idl".
Ya contamos en la anterior entrega como se pasaba esta interfaz IDL a los "cabos" y "esqueletos" Java. Esto se lograba gracias al compilador de JavaORB "idl2java" que se encuentra dentro del directorio "bin" de "JavaORB" y que el lector debería de tener ya en el PATH de su entorno.
La orden a ejecutar dentro del directorio "Ejemplo" es :
idl2java Calculator.idl
Tras ello se nos creará un directorio "corba_pkg" donde se van a almacenar todas las clases Java que constituyen los "cabos y esqueletos" de CORBA, necesarios para que tanto cliente como servidor se "enchufen" al ORB.
// ============================================================
// = Ejemplo de cliente CORBA =
// ============================================================
//
// Tutorial de JavaORB (Paso 2)
//
public class Client
{
public static void main( String args[] )
{
// 1.
// Inicializacion del ORB
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
// 2.
// Localizacion del NamingService
org.omg.CORBA.Object obj = null;
org.omg.CosNaming.NamingContext naming = null;
try
{
obj = orb.resolve_initial_references("NamingService");
naming = org.omg.CosNaming.NamingContextHelper.narrow(obj);
}
catch ( org.omg.CORBA.ORBPackage.InvalidName name )
{
System.out.println("No se ha podido obtener el NamingService");
System.exit(0);
}
// 3.
// Construccion del nombre del objeto Calculator
org.omg.CosNaming.NameComponent [] name = new org.omg.CosNaming.NameComponent[1];
name[0] = new org.omg.CosNaming.NameComponent();
name[0].id = "Calculator";
name[0].kind = "Example";
// 4.
// Localizacion de la referencia al objeto Calculator
// dentro del NamingService
try
{
obj = naming.resolve(name);
}
catch ( org.omg.CosNaming.NamingContextPackage.NotFound ex )
{
System.out.println("Objeto no encontrado en el NamingService");
System.exit(0);
}
catch ( org.omg.CosNaming.NamingContextPackage.CannotProceed ex )
{
System.out.println("No se ha podido continuar");
System.exit(0);
}
catch ( org.omg.CosNaming.NamingContextPackage.InvalidName ex )
{
System.out.println("Nombre invalido");
System.exit(0);
}
// 5.
// Narrow de la referencia al objeto
Calculator calc = CalculatorHelper.narrow(obj);
// 6.
// Utilizamos el objeto Calculator somo si fuera local
try
{
System.out.println("5 + 3 = " + calc.add(5,3) );
System.out.println("5 / 0 = " + calc.div(5,0) );
}
catch ( DivisionPorCero ex )
{
System.out.println("Interceptada intento de divisón por cero");
System.out.println("La division era "+ex.op1+" / "+ex.op2);
}
catch ( org.omg.CORBA.SystemException ex )
{
System.out.println("Interceptada una excepcion CORBA System");
System.out.println(ex.getMessage());
}
}
}
En el primer paso del ejemplo lo que se hace es inicializar el ORB para indicarle que vamos a utilizarle. En este momento se le pueden pasar parámetros al ORB de inicialización como la localización del servidor de nombres, qué puerto debe de utilizar el ORB etc. En nuestro caso no vamos a utilizar esta vía de configuración del ORB.
Una vez inicializado el ORB lo siguiente que hacemos es contactar con el servidor de nombres. No es indispensable tener un servidor de nombres CORBA disponible para la aplicación, pero si suele ser muy útil su uso para centralizar todas las referencias a objetos dentro de un servicio común. Por ello mostramos su uso en este ejemplo ya que en cualquier uso de CORBA real, el servidor de nombres suele ser indispensable.
JavaORB trae en la herramienta un servidor de nombres. Para arrancarlo basta con ir al directorio "bin" de JavaORB y ejecutar "sh naming". Recordar al lector que dentro de la variable CLASSPATH debe estar la librería "JavaORBv1_2_4.jar".
Una vez arrancando el servidor de nombres con la configuración estándar el ORB de JavaORB sabe localizarlo. Para obtener una referencia a dicha objeto utilizamos el método de la API del ORB "resolve_initial_references("NamingService")". En este caso utilizamos esta función para obtener una referencia al servicio de nombres, pero también se utiliza este método para obtener otros objetos del ORB como el adaptador de objetos. Este método nos devuelve un objeto CORBA genérico, pero en realidad nosotros sabemos que es un servidor de nombres (NamingContext) por lo que utilizamos la función "narrow()" del objeto "Helper" del servidor de nombres para transformar de forma segura este objeto genérico CORBA en un servidor de nombres.
Este procedimiento narrow() lo vamos a utilizar de forma constante. Cada objeto CORBA tiene definido una clase de ayuda "Helper" con esta función, que permite comprobar si un objeto genérico CORBA es realmente de su clase.
A lo largo del ejemplo se capturan muchas excepciones que pueden ocurrir a la hora de interactuar con CORBA. Es mucho más seguro programar de esta forma ya que en todo momento podemos estar informados de lo que ha podido ocurrir, siendo nuestro código mucho más robusto.
Una vez que tenemos dentro de "naming" la referencia al objeto CORBA del servidor de nombres, lo que hacemos en el paso 3 es construir el nombre que tiene el objeto CORBA Calculator dentro del servidor de nombres. Este nombre lo habrá puesto allí anteriormente el servidor CORBA que arranque dicho objeto CORBA. Los nombres dentro del servidor de nombres tienen dos campos, un identificador y una clase. De esta forma es más sencillo agrupar a los objetos comunes dentro de una clase.
En el paso 4 es donde realmente se contacta con el servidor de nombres y a través del método "resolve()" obtenemos la referencia al objeto CORBA Calculator. De nuevo en esta llamada se capturan varias excepciones. Hay que recordar al lector que esta llamada ya va a viajar por CORBA a través de los ORB del cliente y del servidor de nombres, ORBs que pueden estar separados por Internet p.e. por lo que pueden ocurrir muchas incidencias en esta llamada. Aunque para nosotros como desarrolladores el trabajo de invocar la función sea como la llamada sobre una función de un objeto local, el proceso es mucho más complejo.
De nuevo tenemos que utilizar la función "narrow()" en el paso 5, pero en este caso utilizando el "Helper" de Calculator, ya que es esta clase la que sabe si un objeto genérico CORBA es o no es un objeto Calculator. En el caso de lo que sea devuelve el objeto CORBA, pero ya como un Calculator. Y una vez que tenemos este objeto CORBA lo podemos utilizar exactamente igual que si fuera un objeto local, tal y como podemos ver en el paso 6 en las operaciones "calc.add(5,3)" y "calc.div(5,0)". Lo más interesante de este paso es como una excepción que se genera en el objeto remoto CORBA viaja a través de los ORBs y es entregada al cliente. De esta forma el uso de excepciones dentro de CORBA también es transparente para el desarrollador. En este caso la excepción capturada es la de "DivisionPorCero" que recordamos al lector que definimos dentro de la interfaz IDL Calculator.
En el siguiente esquema el lector puede observar la arquitectura del ejemplo y los pasos dados para contactar con el objeto servidor: 
// ============================================================
// = Ejemplo de servidor CORBA con JavaORB =
// ============================================================
// Servidor con POA
public class ServerPOA
{
public static void main( String args[] )
{
// 1.
// Inicializamos el ORB
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null);
// 2.
// Resolvemos la referencia al RootPOA
org.omg.CORBA.Object objPoa = null;
org.omg.PortableServer.POA rootPOA = null;
try
{
objPoa = orb.resolve_initial_references("RootPOA");
}
catch ( org.omg.CORBA.ORBPackage.InvalidName ex )
{}
// 3.
// Hacemos un narrow de referencia de objeto a referencia POA
rootPOA = org.omg.PortableServer.POAHelper.narrow(objPoa);
// 4.
// Instanciamos un objeto Calculator
CalculatorPOAImpl calc = new CalculatorPOAImpl();
try
{
// 5.
// Activamos el servant dentro del ORB
byte[] servantId = rootPOA.activate_object(calc);
// 6.
// Obtenemos la referencia al servant
org.omg.CORBA.Object ref = rootPOA.id_to_reference(servantId);
// 6.
// Accedemos al NamingService
org.omg.CORBA.Object obj = null;
org.omg.CosNaming.NamingContext naming = null;
try
{
obj = orb.resolve_initial_references("NamingService");
System.out.println("Locallizado el NamingService");
naming = org.omg.CosNaming.NamingContextHelper.narrow(obj);
System.out.println("Narrow del NamingService");
}
catch ( org.omg.CORBA.ORBPackage.InvalidName name )
{
System.out.println("No se ha podido obtener el NamingService");
System.exit(0);
}
if ( naming == null )
{
System.out.println("No se ha encontrado el NamingService");
System.exit(0);
}
// 7.
// Construccion del nombre del objeto Calculator
org.omg.CosNaming.NameComponent [] name = new org.omg.CosNaming.NameComponent[1];
name[0] = new org.omg.CosNaming.NameComponent();
name[0].id = "Calculator";
name[0].kind = "Example";
try
{
naming.bind(name,ref);
}
catch ( org.omg.CosNaming.NamingContextPackage.NotFound ex )
{
System.out.println("Objeto no encontrado");
System.exit(0);
}
catch ( org.omg.CosNaming.NamingContextPackage.AlreadyBound ex )
{
System.out.println("Ya hay un objeto con ese nombre");
naming.unbind(name);
System.exit(0);
}
catch ( org.omg.CosNaming.NamingContextPackage.InvalidName ex )
{
System.out.println("Nombre inválido");
System.exit(0);
}
catch ( org.omg.CosNaming.NamingContextPackage.CannotProceed ex )
{
System.out.println("No se ha podido continuar");
System.exit(0);
}
// 8.
// Activamos el gestor de invocaciones del POA
rootPOA.the_POAManager().activate();
System.out.println("El servidor está preparado...");
// 9.
// Nos quedamos a la espera de peticiones sobre Calculator
orb.run();
}
catch ( java.lang.Exception ex )
{
System.out.println("Se ha capturado una excepción");
ex.printStackTrace();
}
}
}
El paso 1 es idéntico al del cliente. Se inicializa el ORB. En el paso 2 ya empezamos a trabajar con POA. El adaptador de objetos POA siempre tiene un POA raíz llamado "RootPOA" y que el ORB conoce. Los objetos dentro del ORB se registran dentro de un POA determinado, pudiendo cada POA tener políticas de gestión de dichos objetos diferentes. Dentro del ORB pueden existir varios POAs organizados en una estructura jerárquica en árbol.
En el caso más sencillo utilizamos sólo el "RootPOA", que tiene unas políticas de gestión predefinidas, y en él registramos a todos nuestros objetos. Si nuestra arquitectura es más compleja habrá que utilizar diferentes POAs con características diferentes, aunque la programación de dichos servidores se complica bastante más que cuando sólo utilizamos un único POA.
En la figura 2 podemos observar el POA de nuestra aplicación y un ejemplo de como podría ser una arquitectura de POAs más complejo 
Por lo tanto en el paso 2 obtenemos la referencia al objeto "RootPOA" gracias a la función "resolve_initial_references()" del ORB. Como esta función nos devuelve un objeto CORBA genérico en el paso 3 debemos de pasar este objeto a una referencia real a POA gracias al "narrow()" del POAHelper.
En el paso 4 es donde creamos el objeto CORBA CalculatorPOA. La implementación de este objeto la veremos en el siguiente apartado, aunque ya adelantamos que es muy sencilla. En terminología POA a la implementación de una interfaz IDL, que es lo que hace CalculatorPOA implementando la interfaz Calculator, se la conoce como "servant". En nuestro caso "CalculatorPOA" es un servant.
En el paso 5 registramos este "servant" dentro del "RootPOA" gracias a la función "activate_object()" de la API de POA.
Al activar nuestro objeto dentro del POA del ORB del servidor, recibe un identificador que nos devuelve dicha función y almacenamos en "servantId". En el paso 6 transformamos este identificador en una referencia a objeto, es la misma información vista de otra forma, con el objetivo de introducir esta referencia al objeto dentro del servidor de nombres.
Lo siguiente que tenemos que hacer es registrar esta referencia al objeto Calculator dentro del servidor de nombres, ya que es allí a donde va a acudir el cliente a buscar la referencia al objeto.
Para ello en el paso 7 se accede al servidor de nombres, paso idéntico al 2 del cliente. El paso 8 es también idéntico al 3 del cliente, con la excepción de que aquí en vez de ejecutar "resolve()" sobre el servidor de nombres ejecutamos "bind()", es decir, aqui unimos dentro del servidor de nombres la referencia al objeto "Calculator" con el nombre ("Calculator","Example").
En el paso 9 activamos el "Manager" del RootPOA. Este manager es el que se encarga de recibir las peticiones sobre los objetos registrados en este POA y distribuirlas al "servant" adecuado, es decir, enviarla a la implementación de la interfaz IDL adecuada. En nuestro caso si se recibe una invocación para Calculator, el manager se la enviaría a CalculatorImpl.
Por último en el paso 10 se invoca el método "orb.run()" que cede el control de la ejecución al ORB para que puede comenzar a recibir invocaciones del cliente. De este método solo se vuelve en el caso de que el ORB finalice, bien por salida provocada o por muerte.
Recordar al lector que aunque le haya resultado en algún momento un poco compleja o tediosa la lectura de este apartado, una vez entendidos estos pasos son siempre igual en cualquier servidor CORBA, independientemente del lenguaje que se utilice y de lo complejo de la aplicación. La única parte que se complicaría algo más sería la de creación de POAs y sus políticas asociadas. Todo lo demás es idéntico siempre.
En la figura 3 podemos observar el momento en el que se registra el objeto CORBA CalculatorImpl dentro del adaptador de objetos del ORB, momento a partir del cual es conocido dentro de CORBA y puede ser accedido por cualquier cliente remoto.

// ============================================================
// = Ejemplo de CORBA
// ============================================================
// Implementacion utilizando POA
public class CalculatorImpl extends CalculatorPOA
{
/**
* Operación add
*/
public float add(float nb1, float nb2)
{
System.out.println("Suma = "+nb1+" + "+nb2);
return nb1 + nb2;
}
/**
* Operación div
*/
public float div(float nb1, float nb2)
throws DivisionByZero
{
System.out.println("Division = "+nb1+" / "+nb2);
if ( nb2 == 0 )
throw new DivisionPorCero(nb1,nb2);
return nb1 / nb2;
}
}
De este implementación lo único que hay que destacar es que el objeto "CalculatorImpl" hereda del objeto "CalculatorPOA". Esto lo que provoca es que a "CalculatorImpl" se le añada todo el "skeleton" (esqueleto) utilizando herencia lo que permite que sea accedido el objeto a través de CORBA. Recordemos que este esqueleto se generaba de forma automática de la interfaz OMG/IDL utilizando el compilador "java2idl".
Para nosotros como desarrolladores nos vale con saber que debemos de heredar de "CalculatorPOA" para que nuestro objeto pueda ser accedido por CORBA. Nos podemos olvidar de que existe CORBA a partir de ese momento y trabajar como si todo el sistema fuera local. De hecho, se puede tener un especialista en CORBA dentro del proyecto y que todos los demas desarrolladores no sepan que por debajo se está utilizando CORBA.
float resta ( in float nb1, in float nb2 ); float mult ( in float nb1, in float nb2 );
Como ninguna de las dos levanta excepciones no hace falta utilizarlas.
Vemos con que facilidad se pueden ampliar las interfaces IDL, y vemos que estos cambios no afectan para nada al servidor de CORBA. Y el cliente CORBA solo se ve afectado en el caso de que se elimine alguna operación de la interfaz que el utilice.
Esta facilidad para ampliar las interfaces y el hecho de que los implementadores de las interfaces de IDL no tenga porque saber nada de CORBA son dos características fundamentales de CORBA.