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.
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".
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:
GoodDay.java
GoodDayHolder.java
_GoodDayStub.java
GoodDayOperations.java
GoodDayHelper.java
GoodDayPOA.java (no se genera en aproximación ImplBase)
GoodDayPOATie.java (_GoodDayImplBase.java en aproximación ImplBase)
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.
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
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
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
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'