2. Proceso de desarrollo CORBA

Para ilustrar el proceso de desarrollo CORBA vamos a utilizar un ejemplo sencillo que llamaremos "Hello world". La Figura 2.1 muestra la estructura de dicho ejemplo.

Figura 2.1. Aplicación HelloWorld.

Los pasos a seguir para crear objetos distribuidos usando CORBA son:

La Figura 2.2 muestra el uso del lenguaje IDL y del compilador correspondiente cuando se construye la aplicación.

Figura 2.2. Construcción de la aplicación HelloWorld.

Cuando se ejecuta el compilador IDL para el ORB de Java que se tenga instalado, se generan dos conjuntos de ficheros código Java: (a )código stub para crear objetos proxy que un cliente puede usar para realizar invocaciones sobre las referencias de objetos de los tipos de interfaz definidos en el fichero IDL, y (b) código skeleton para acceder a los objetos que soportan dichas interfaces.

2.1. Descripción de la interfaz

Para cualquier aplicación CORBA se debe escribir una especificación IDL que define los tipos de datos e interfaces, incluyendo atributos y operaciones. En este caso, un cliente invoca una operación hello() sobre la interfaz de un objeto potencialmente remoto de un objeto de tipo GoodDay. El resultado de la invocación es un mensaje que se muestra al cliente.

La interfaz se define en un fichero que denominamos HelloWorld.idl, en el directorio ejemplo/sesion2/holamundo, que se muestra a continuación:

//HelloWorld.idl
module ejemplo {
 module sesion2 {
  module holamundo {
    interface GoodDay {
      string hello();
    };
  };
 };
};

El fichero contiene la especificación de una jerarquía de módulos. Es un buen estilo de especificación el uso de módulos para separar los espacios de nombres para una aplicación o sus principales componentes y seguir la mismas convenciones introducidas para los paquetes Java. Se recomienda utilizar nombres de módulos que comiencen por letras minúsculas, e interfaes que comiencen por mayúscula.

Dentro del módulo helloWorld definimos una interfaz: GoodDay. La interfaz no presenta ninguna relación de herencia, y proporciona una operación, hello(). Dicha operación devuelve un mensaje de la forma: "Hello world, from location".

2.2 Compilación del IDL

El siguiente paso en el desarrollo de la aplicación es compilar el IDL para general el código stub y skeleton, mediante la orden:

idlj -fallTIE HelloWorld.idl

La opción -fall indica al compilador que debe generar el código stub y el skeleton. Para crear solamente uno de ellos se utilizan las opciones -fclient o -fserver.

Hay dos formas de crear implementaciones de objetos: la aproximación ImplBase, y la aproximación Tie.

La aproximación ImplBase implica utilizar una clase generada por el compilador IDL como clase base para la implementación del objeto. Por ejemplo, dada la interfaz denominada HelloWorld, el compilador IDL de Java generará una clase denominada _HelloWorldImplBase, una clase abstracta que podemos usar como base para la implementación del objeto. Esta clase base implementa una interfaz Java creada a partir del interfaz IDL, asegurando así que el objeto porporcionará una implementación de cada método especificado en la interfaz del objeto CORBA.

La aproximación Tie es útil cuando nuestra implementación del objeto ya extiende otra clase, ya que Java no soporta herencia múltiple. Esta aproximación crea un objeto secundario que extiende la clase skeleton generada al igual que lo hace la aproximación ImplBase. Todas las invocaciones CORBA se realizarán sobre el objeto tie, asociando dicho objeto tie con nuestra implementación del objeto. El objeto tie, a su vez, delegará las invocaciones en nuestra implementación real del objeto. Para generar las clases tie correspondientes se debe usar la opción -ftie en el copilador IDL. A lo largo del tema utilizaremos la aproximación Tie.

El compilador IDL "mapea" cada módulo en un paquete Java y usa las convenciones Java para escribir los paquetes en los directorios. Ambos, el nombre del paquete y el directorio se nombran después del módulo IDL. El paquete Java contiene las interfaces Java y las clases que implementan el stub, skeleton y código adicional para soportar la aplicación distribuida.

Mientras que el nombre de las opciones del compilador dependen del vendedor del mismo, los ficheros generados por cualquier compilador conforme con IDL son siempre los mismos:

La interfaz GoodDay se "mapea" a una interfaz Java del mismo nombre en el fichero GoodDay.java. La clase GoodDayHolder proporciona soporte para manejar los parámetros de entrada/salida de IDL. La clase GoodDayHelper contiene varios métodos estáticos, de los cuales el más importante es el método narrow().

Los siguientes ficheros contienen clases con funcionalidades generales. La clase _GoodDayStub contiene el código stub que permite crear un proxy en el lado del cliente para la implementación del objeto. La clase GoodDayPOA contiene el código skeleton usado por POA. La interfaz GoodDayOperations y la clase GoodDayPOATie son usadas por el mecanismo Tie en la parte del servidor.

2.3. Un cliente como una aplicación Java

Una vez que se crea el skeleton IDL, podemos construir nuestra implementación de objeto CORBA y aplicaciones servidor. La implementación de un cliente como una aplicación Java sigue los siguientes pasos:

Antes de inicializar el entorno CORBA, veamos cuál es la interfaz Java correspondiente a la implementación IDL. Ésta es una interfaz vacía que extiende dos clases bases para objetos CORBA y entidades IDL, así como la interfaz Java GoodDayOperations, que contiene las signaturas reales de las operaciones:

// generated Java - GoodDay.java 
package ejemplo.sesion2.holamundo;
public interface GoodDay extends GoodDayOperations,
                                 org.omg.CORBA.object,
                                 org.omg.CORBA.portable.IDLEntity
{
}

La interfaz GoodDayOperations define un método Java hello() que devuelve un String Java. La razón de esta división de trabajo entre GoodDay y GoodDayOperations es que en algunos casos es necesario usar una interfaz de operaciones que no extienda org.omg.CORBA.object.

// generated Java - GoodDayOperations.java 
package ejemplo.sesion2.holamundo;
public interface GoodDayOperations {
   public java.lang.String hello();
}

Para inicializar el ORB, definimos una clase Java Client en nuestra implementación y un método main() para dicha clase. Inicializar el ORB significa obtener una ferencia al pseudo-objeto ORB. El ORB se llama pseudo-objeto debido a que sus métodos serán proporcionados por una librería en comunicación con el soporte de ejecución, y la referencia a dicho pseudo-objeto no uede pasarse como parámetro a las operaciones de la interfaz de CORBA. Excepto esta restricción, una referencia a un ORB es similar a cualquier otra referencia a objeto.

import java.io.*;
import org.omg.CORBA.*;
 
public class Client {
  public static void main(String args[]) {
    try {
      //inicializar el ORB
      ORB orb = ORB.init (args,null);
     //resto de sentencias
  }
}

El método estático init() en la clase org.omg.CORBA.ORB devuelve una instancia de un ORB.

Las referencias a objetos pueden obtenerse de varias maneras, usaremos una bastante sencilla. Las referencias a objetos son estructuras de datos opacas; sin embargo, una referencia a un objeto puede convertirse en un String. El resultado se denomina stringfied object reference. Este tipo de referencia puede ser "reconvertida" a una referencia a objeto "viva". Para ello se utilizan las operaciones object_to_string() y string_to_object().

//get object reference from command-line argument
orb.omg.CORBA.Object obj = orb.string_to_object(args[0]);

En este ejemplo asumimos que la stringfied object reference se proporciona como argumento del método string_to_object(). Dicho método devuelve una referencia a objeto de tipo CORBA::object, el tipo base de todos los objetos CORBA. Para hacer uso del objeto, éste necesita ser "reducido" (narrowed) al tipo apropiado. El método narrow se define en la clase GoodDayHelper.

GoodDay goodDay = goodDayHelper.narrow(obj);
if (goodDay == null) {
  System.err.println( "stringfied object reference is of wrong type");
  System.exit(-1);
}

Una vez que el ORB es inicializado y se obtiene una referencia a un objeto, la programación de CORBA es muy parecida a la programación estándar orientada a objetos. La invocación de métodos sobre objetos se realiza de forma idéntica para objetos locales y remotos.

System.out.println(goodDay.hello());

Nuestro cliente invoca el método hello() sobre el objeto goodDay, y el resultado se imprime en la salida estándar.

Lo último a considerar es el manejo de excepciones. Debido a que no hay excepciones lanzadas por el usuario, solamente tendremos que tratar y procesar las excepciones del sistema que pueden provocarse por cualquier método CORBA incluyendo la inicialización del ORB, la llamada a narrow, y el método hello().

catch (SystemException ex) {
         System.err.println(ex);
}  

La clase SystemException se define en el paquete org.omg.CORBA.

Para compilar y ejecutar el cliente utilizaremos el compilador de Java

javac Client.java

Para ejecutar el cliente utilizaremos dos argumentos: el nombre de la clase cliente y una referencia a objeto stringfied, que vamos a llamar IOR. Veremos como generar dicho String cuando hablemos del servidor.

java ejemplo/sesion2/holamundo.Client IOR

El cliente entonces imprime el mensaje esperado.

Hello World, from Alicante

2.4. Un cliente como un Applet

Cuando se escribe un cliente como un applet se siguen los mismos pasos que cuando se trata de una aplicación clinete, excepto por lo siguiente:

Para hacer que un applet sea accesible a través de la web, éste necesita anclarse en una página HTML. Un ejemplo de fichero HTML es:

<html>
 <head>
   <title> Hello world example </title>
 </head>
 <body>
   <h1>Hello world example</h1>
     <center>
       <applet code= ejemplo/sesion2/holamundo/Applet.class width=400 height=80>
       </applet>
     </center>
 </body>
</html>

Para inicializar el applet, definimos nuestra clase Applet, cuyo código es el siguiente:

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import org.omg.CORBA.*;
 
public class Applet extends java.applet.Applet
                    implements ActionListener {

  private ORB orb;
  private GoodDay goodDay;
  private Button helloWorldButton;
  private TextField textField;
   
  public void init() {
    helloworldButton = new Button("Invoke remote method");
    helloworldButton.setFont( new Font ("Helvetica", Font.BOLD, 20));
    helloworldButton.setActionCommand("invoke");
    helloworldButton.addActionListener( (ActionListener)this);
    textField= new textField();
    textField.setEditable(false);
    textField.setFont( new Font ("Helvetica", Font.BOLD, 14));
    setLayout (new GridLayout(2,1));
    add(helloWorldButton);
    add(textField);
    //resto de sentencias
  }
}

Para localizar los objetos, en el apartado 2.3 hemos utilizado una stringfied object reference. En este caso, y puesto que dicha referencia no se puede usar como parámetro de un applet, vamos a considerar que se proporciona un fichero que contiene el IOR destino en el mismo directorio en el que se encuentra la página HTML que contiene al applet.

Para inicializar el ORB de nuevo utilizamos el método init(), con el objeto applet como argumento. Para obtener una referencia al objeto remoto usamos el método readIOR(). Dicho método construye una URL para la ubicación en donde se espera el fichero IOR. A continuación abre una conexión con dicho recurso y lee una línea de texto que se asume contiene la stringfied object reference.

private Sring readIOR() {
  try {
    URL iorURL = new URL (getCodeBase().toString()+"ior");
    BufferedReader in = new BufferedReader(
      new InputStreamReader(iorURL.openStream() ));
    String line = in.readLine();
    in.close();
    return line;
  } catch (Exception ex) {
     System.err.println(ex);
  }
  return null;
}

La inicialización del applet se completa con la captura y procesamiento de excepciones:

try {
  orb = ORB.init(this,null);
  org.omg.CORBA.Object obj = orb.string_to_object(readIOR());;
  goodDay = GoodDayHelper.narrow(obj);
}
catch (SystemException ex) {
  System.err.println("ORB is not initialized");
  System.err.println(ex);
}

Para manejar los eventos desde la interfaz gráfica, implementamos el método ActionPerformed() de la interfaz java.applet.awt.event.ActionListener.

public void actionPerformed(ActionEvent e) {
  if (e.getActionCommand().equals("invoke")) {
   //invoque the operation
   try { textField.setText(goodDay.hello()); }
   catch (SystemException ex) {
    System.err.println(ex);
   }
  }
}

Para compilar el applet se utiliza el compilador de java.

javac -d . Applet.java

2.5 Implementación del objeto

Volviendo a la implementación del objeto cuya interfaz se ha especificado en IDL, dicha implementación se conoce también como servant class. La especificación de mapeado IDL/Java define una clase (servant base class) que se nombra después del nombre de la interfaz IDL: NombreInterfazPOA (en nuestro ejemplo GoodDayPOA). Esta clase base es un skeleton generado por el compilador IDL. Como se ha comentado en el apartado anterior hay dos formas de asociar las clases de implementación del objeto con una clase skeleton: por herencia (o ImplBase), o por delegación (Tie).

En nuestro ejemplo, tenemos una clase de implementación GoodDayImpl que extiende la clase base servant GoodDayPOA, y que tiene una variable privada location conteniendo la localización geográfica.

import org.omg.CORBA.*;
 
public class GoodDayImpl extends GoodDayPOA {
 private String location;
 GoodDayImpl (String location) {
   this.location = location;
 }
 public String hello() {
  return "Hello World, from " + location;
 }
}

Implementamos el método hello() que devuelve un String compuesto por el mensaje "Hello world, from" y el valor de la variable location.

Para compilar el programa se utiliza el compilador de Java.

javac -d . GoodDayImpl.java

2.6. Creación del servidor

Finalmente tenemos que implementar la clase servidor. Esta clase:

La clase servidor de nuestro ejemplo se llama Server. Solamente implementamos el método main() en dicha clase. En el método main comprobamos el número correcto de argumentos, uno de los cuales indica la localización del servidor.

import java.io.*
import org.omg.CORBA.*;
import org.omg.PortableServer.*;

 
public class Server {
  public static void main(String[] args) {
    if (args.length != 1) {
      System.out.println(
      "Usage: java ejemplo/sesion2/holamundo.Client <location> ");
      Systen,exit(1);
    }
    try {
    //inicializar ORB
    ORB orb = ORB.init (args,null);
    //crear un objeto GoodDay
    GoodDayImpl goodDayImpl = new GoodDayImpl(args[0]);
    //inicializar POA
    POA poa = POAHelper.narrow(
         orb.resolve_initial_references("RootPOA"));
    poa.the_POAManager().activate();
    //crear la referencia al objeto
    org.omg.CORBA.Object obj =
      poa.servant_to_reference(goodDayImpl);
    //imprimir la stringfied object reference
    System.out.println(orb.object_to_string(obj));
    //esperar peticiones
    orb.run()
    }
    catch (InvalidName e) {
     System.err.println(e);
    }
    catch (UserException e) {
     System.err.println(e);
    }
    catch (SystemException e) {
     System.err.println(e);
    }
  }
}

Para permitir que el objeto goodDayImpl actue como un servant y reciba invocaciones de operaciones CORBA tenemos que utilizar un object adapter (en este caso se trata del objeto poa). Para crear una referencia a un objeto CORBA que podamos exportar, necesitamos primero obtener una instancia a un POA e inicializarlo. Para ello es suficiente con usar el ORB de la raiz POA, mediante la llamada al método resolve_initial_references("RootPOA"). A continuación activamos el object adapter mediante la llamada al método activate().

A continuación ya podemos crear una referencia al objeto mediante el método servant_to_reference().

Después de crear la referencia al objeto, imprimimos dicha referencia stringfied en la salida estándar. Destacar que hemos activado implícitamente el nuevo objeto CORBA con la llamada a servant_to_reference(), para que esté preparado para recibir peticiones. Finalmente llamamos al método run() que bloquea el hilo prinicipal del servidor esperando peticiones.

Para compilar el servidor:

javac -d . Server.java

Para ejecutar el servidor:

java ejemplo.sesion2.holamundo.Server Alicante

Esto muestra por pantalla el stringfied IOR que se asemejará a:

IOR:00000000002143243c4c4cc46757456c34c4c4cbc767c6c5b72736362bdbdbdb0000
00000000004c33400000000000000434300000000000000000000fdevbbdf00000000000
c34c4c4cbc767c6c5b727363600000000043430000022

Probablemente sea mejor redireccionar la salida estándar a un fichero. En nuestro ejemplo podríamos utilizar el fichero shw.ior:

java ejemplo.sesion2.holamundo.Server Alicante > shw.ior

Ahora podríamos ejecutar los clientes tal y como se ha ilustrado en los apartados anteriores. Un cliente puede comenzar convenientemente utilizando el fichero IOR:

java ejemplo.sesion2.holamundo.Cliente 'cat shw.ior'