Para ilustrar la construcción de aplicaciones mediante CORBA utilizaremos un ejemplo sencillo de reserva de habitaciones. El desarrollo de una aplicación incluye la especificación de la interfaz, la implementación de los objetos, la implementación del servidor, la implementación de una factory, el inicio de los servidores y de los clientes como aplicaciones y como applets.
El sistema de reserva de habitaciones permite tanto la reserva de habitaciones como la cancelación de las mismas. Opera sobre una franja horaria de 9 A.M. hasta las 4 P.M. con unidades de una hora (no se consideran dias ni semanas). Las habitaciones disponibles no son fijas, el número y nombres de las habitaciones puede cambiar. Cuando se reserva una habitación se debe indicar el nombre y el propósito de la persona que la reserva. Todos los objetos creados no tienen un estado persistente. Tampoco vamos a considerar cuestiones de seguridad y cualquiera puede cancelar cualquier reserva.
Vamos a tener en cuenta las siguientes cuestiones sobre el diseño:
La figura 6.1 ilustra la configuración de nuestro sistema de reservas. Hay tres servidores de habitaciones (room servers), que tienen una implementación de objeto Room. También hay un servidor Meeting Factory que crea un objeto Meeting Factory. La Meeting Factory crea varios objetos Meeting que utilizan en mismo espacio de direcciones, y que se corresponden con una máquina virtual Java (en el caso de Java). Un servicio Naming tiene varios objetos Naming Context formando un árbol de contexto. Las implementaciones de los objetos Room y Meeting Factory se registran en el servicio de nombres (Naming service).
Figura 6.1. Sistema de reserva de habitaciones.
La especificación IDL contiene las especificaciones para Meeting,
MeetingFactory, y Room. La interfaz Meeting tiene dos atributos:
purpose
y participants
. Los atributos describen la
semántica de una reserva. La interfaz tiene una operación destroy()
para completar su ciclo de vida.
Los objetos reserva (meeting) son creados en tiempo de ejecución
por la Meeting Factory. Proporciona una única operación,
createMeeting()
, cuyos parámetros se corresponden con los
atributos del objeto Meeting y devuelve una referencia de objeto al nuevo
objeto Meeting creado.
module roomBooking { interface Meeting { // Una reserva tiene dos atributos de solo lectura que describen // el propósito y los participantes en dicha reserva readonly attribute string purpose; readonly attribute string participants; oneway void destroy(); }; interface MeetingFactory { // Una meeting factory crea objetos reserva Meeting createMeeting( in string purpose, in string participants); }; interface Room { // Una habitación proporciona operaciones para mostrar, realizar y // cancelar reservas // Realizar una reserva implica asociar una reserva con un slot // de tiempo (para dicha habitación particular). // Las reservas pueden realizarse en horario de oficina // Por simplicidad disponemos de 8 slots en los que pueden // realizarse las reservas enum Slot { am9, am10, am11, pm12, pm1, pm2, pm3, pm4 }; // ya que IDL does no proporciona la forma de determinar la // cardinalidad de un enum, defnimos la constante MaxSlots const short MaxSlots = 8; // Las reservas asocian todas las reservas (de un dia) // con slots temporales para cada habitación typedef Meeting Meetings[ MaxSlots ]; exception NoMeetingInThisSlot {}; exception SlotAlreadyTaken {}; // El atributo name da nombre a una habitación readonly attribute string name; // view devuelve las reservas de una habitación // Por simplicidad, la implementación maneja solamente // reservas para un día Meetings view (); void book( in Slot a_slot, in Meeting a_meeting ) raises(SlotAlreadyTaken); void cancelBooking( in Slot a_slot ) raises(NoMeetingInThisSlot); };
Para la interfaz Room, definimos algunos tipos y constantes, junto con
dos excepciones: NoMeetingInThisSlot
y SlotAlreadyTaken
.
Hay tres operaciones definidas: view()
devuelve Meetings
(vector de objetos Meeting). Una referencia a objeto nil significa
que el slot correspondiente está libre. La operación book()
realiza una reserva en el slot a_slot
del objeto Room sobre
el que se invoca la operación. La operación cancelBooking()
elimina la reserva del slot a_slot
.
Para compilar la especificación IDL, asumiendo que la especificación
se llama RoomBooking.idl
utilizamos el compilador idlj
:
idlj -fall RoomBooking.idl
Se generan los siguientes ficheros con interfaces java:
MeetingFactory.java
y MeetingFactoryOperations.java
Meeting.java
y MeetingOperations.java
Room.java
y RoomOperations.java
También se generan clases holder. Entre otros métodos,
las clases helper contienen los métodos narrow()
para cada interfaz:
MeetingFactoryHelper.java
MeetingHelper.java
RoomHelper.java
Se generan las clases stub y skeleton para cada interfaz:
MeetingFactoryPOA.java
y _MeetingFactoryStub.java
MeetingPOA.java
y _MeetingStub.java
RoomPOA.java
y _RoomStub.java
Adicionalmente, hay clases para los tipos de datos y las excepciones definidas en la interface IDL.
MeetingsHelper.java
y _MeetingsHolder.java
Slot.java
, SlotHolder.java
y SlotHelper.java
SlotAlreadyTaken.java
, SlotAlreadyTakenHolder.java
y SlotAlreadyTakenHelper.java
NoMeetingInThisSlot.java
, NoMeetingInThisSlotHolder.java
y NoMeetingInThisSlotHelper.java
Las clases que tenemos que implementar utilizan las interfaces Meeting y Room. Para ello extienden las clases POA skeleton generadas.
Implementación del objeto Meeting
import org.omg.CORBA.*; class MeetingImpl extends MeetingPOA { private String purpose; private String participants; /** constructor */ MeetingImpl( String purpose, String participants) { // initialise private variables this.purpose = purpose; this.participants = participants; } // attributes public String purpose() { return purpose; } public String participants() { return participants; } /** deactivates the object */ public void destroy() { try { _poa().deactivate_object( _poa().servant_to_id(this)); } catch( Exception e ) { // ignore } } }
El método destroy()
desactiva el objeto para que puede ser "recogido"
por el recolector de basuras.
El método _poa()
se hereda de la clase base skeleton
org.omg.PortableServer.Servant
. Recupera la instancia POA del contexto
de ejecución actual que es responsable de este objeto CORBA.
Implementación del objeto Room
import org.omg.CORBA.*; public class RoomImpl extends RoomPOA { private String name; private Meeting[] meetings; // constructor public RoomImpl( String name ) { this.name = name; meetings = new Meeting[ Room.MaxSlots ]; } // attributes public String name() { return name; } // operations public Meeting[] view() { return meetings; } public void book( Slot slot, Meeting meeting ) throws SlotAlreadyTaken { if( meetings[ slot.value() ] == null ) { meetings[ slot.value() ] = meeting; } else { throw new SlotAlreadyTaken(); } return; } public void cancelBooking( Slot slot ) throws NoMeetingInThisSlot { System.err.println("cancel " + slot ); if( meetings[slot.value()] != null ) { meetings[slot.value()].destroy(); meetings[slot.value()] = null; } else { throw new NoMeetingInThisSlot(); } } }
La variable name
almacena el nombre del objeto Room
,
y meetings
contiene el vector de habitaciones reservadas.
Implementamos los métodos name(), view(), book(), y cancelBooking().
El método book() comprueba si el slot está vacío,
en cuyo caso asignamos la reserva a dicho slot; en otro caso, lanzamos
la excepción SlotAlreadyTaken
. La clase de dicha excepción
se define en el paquete RoomPackage
debido a que la correspondiente
excepción IDL se define en la interfaz Room
.
Para instanciar las implementaciones de los objetos y hacer que estén disponibles para los clientes debemos implementar un servidor. Dicho servidor se ejecuta como una tarea (o proceso) del sistema operativo. En el caso de Java se trata de una JVM en la que las instancias de los objetos se ejecutan. Puede habar un servidor por objeto, o un servidor puede manejar múltiples objetos. Un servidor tiene cuatro tareas fundamentales:
Servidores de tareas adicionales pueden incluir el registro de los objetos con el servicio Naming o el servicio Trading.
El servidor RoomServer
lleva a cabo estas cuatro tareas fundamentales
y registra cada nueva habitación creada en el servicio Naming.
Para ello se define una clase RoomServer
y se implementa su método
main()
. Definimos dos strings que se usan cuando registramos
el objeto Room
en el servicio Naming. A continuación
chequeamos que el número de argumentos sea correcto y abandonamos el
programa en caso de que no sea así. Se espera un argumento que determine
el nombre del objeto Room
.
Para usar el servicio Naming de forma exitosa, los objetos que queremos que
compartan información via dicho servicio tienen que utilizar una misma
convención de nombrado. Para este ejemplo utilizaremos la siguiente convención,
ilustrada en la Figura 6.2: debajo del contexto raiz tenemos un contexto "Building
Applications", que contiene un contexto denominado "Rooms"
y el objeto Meeting Factory
en el contexto "Building Applications".
Siguiendo esta convención aseguramos que los clientes pueden localizar
a los objetos apropiados.
Figura 6.2. Convención de nombrado.
De acuerdo con esta convención de nombres inicicializamos la variable
context_name
con su versión en forma de cadena de caraceres
el nombre de contexto Room.
import java.io.*; import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; public class RoomServer { public static void main(String[] args) { String context_name, str_name; if( args.length != 1 ) { System.out.println("Usage: vbj RoomServer room_name"); System.exit( 1 ); } context_name = new String("BuildingApplications/Rooms"); try { //init ORB orb = ORB.init( args, null ); POA poa = POAHelper.narrow( orb.resolve_initial_references( "RootPOA")); poa.the_POAManager().activate(); // create the Room object and // export the object reference org.omg.CORBA.Object room_obj = poa.servant_to_reference( new RoomImpl( args[0] ) ); // register with naming service str_name = context_name + "/" + args[0]; NamingContextExt root = NamingContextExtHelper.narrow( orb.resolve_initial_references("NameService") ); try { // make sure the "rooms" context is bound root.bind_new_context( root.to_name( context_name )); } catch( AlreadyBound ab ) { // does not matter . } catch( NotFound nf ) { System.err.println("Context " + context_name + " not found, start MeetingFactoryServer first."); System.err.println("exiting ..."); } root.bind( root.to_name( str_name), room_obj ); // wait for requests orb.run(); } catch( AlreadyBound already_bound ) { System.err.println("Room " + context_name + args[0] + " already bound."); System.err.println("exiting ..."); } catch(UserException ue) { ue.printStackTrace(); } catch(SystemException se) { System.err.println(se); } } }
Para inicializar el ORB y obtener una referencia a POA llamamos al método
de clase init()
de la clase CORBA.ORB, y a resolve_initial_references("RootPOA")
sobre el pseudo-objeto orb
.
La segunda tarea es crear el objeto Room. La tercera tarea consiste en activar el servant como una réplica del objeto Room en el RootPOA. La ctivación del objeto se realiza de forma implícita (como efecto lateral resultante de crear al referencia al objeto para el objeto Room).
El siguiente paso es registrar el objeto en el servicio Naming. Primero obtenemos una referencia a un contexto inicial de un servicio Naming mediante los mecanismos proporcionados por ORB. El servicio Naming maneja nombres incluyendo contextos de forma similar a la notación de nombres de ficheros en varios sistemas operativos:
/<context1>/<context2>/.../<contextn>/name
La cuarta tarea del servidor es crear un bucle para "despachar" invocaciones
mediante la llamada al método run()
del ORB.
Una factory es una implementación de objeto con un particular
patrón de diseño. La diferencia con los objetos ordinarios es
que las factories proporcionan métodos para crear de forma dinámica
nuevos objetos. Realizan la misma inicialización de objetos nuevos que
el método main()
de un servidor. El proceso de la construcción
de factories está formado por los mismos pasos que la construcción
de cualquier otro servidor: implementación del objeto e implementación
del servidor.
Implementación de Meeting Factory
La implementación de Meeting Factory, la clase MeetingFactoryImpl
,
es una extensión de la correspondiente clase skeleton MeetingFactoryPOA.
import org.omg.CORBA.*; class MeetingFactoryImpl extends MeetingFactoryPOA { // // constructor // MeetingFactoryImpl(ORB orb) // { // this.orb = orb; // } // method public Meeting createMeeting(String purpose, String participants ) { MeetingImpl meetingImpl = new MeetingImpl(purpose, participants); try { org.omg.CORBA.Object obj = _poa().servant_to_reference(meetingImpl); Meeting meeting = MeetingHelper.narrow(obj); return meeting; } catch( SystemException se ) { } catch( UserException ue ) {} return null; } }
El único método de la Meeting Factory, createMeeting()
,
tiene como parámetros los correspondientes al constructor del objeto
Meeting, MeetingImpl()
.
Implementación de Meeting Factory Server
El servidor de la Factory Meeting sigue el mismo patrón que el servidor Room. Inicializamos el ORB y POA, creamos el objeto Meeting Factory, y lo activamos.
import java.io.*; import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; public class MeetingFactoryServer { public static void main(String[] args) { String str_name, context_name; if( args.length != 0 ) { System.out.println("Usage: java MeetingFactoryServer"); System.exit( 1 ); } // the stringified names we want to use context_name = "BuildingApplications"; str_name = context_name + "/MeetingFactory"; try { //initialise ORB ORB orb = ORB.init( args, null ); // initialise and activate POA POA poa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); poa.the_POAManager().activate(); // create the MeetingFactory object MeetingFactoryImpl meeting_factory = new MeetingFactoryImpl(); // export the object reference org.omg.CORBA.Object meeting_factory_obj = poa.servant_to_reference(meeting_factory); // register with the CORBA Naming Service NamingContextExt root = NamingContextExtHelper.narrow( orb.resolve_initial_references("NameService") ); try { // make sure our context exists, if the // name server has been running for a while // someone may have already set up the context earlier root.bind_new_context( root.to_name( context_name )); } catch( AlreadyBound ab ) { // ok, we ignore that } // bind our new meeting factory try { root.bind( root.to_name(str_name), meeting_factory_obj ); } catch( AlreadyBound ab ) { System.err.println("A meeting factory is already bound, exiting..."); System.exit(1); } // enter event loop orb.run(); } catch(Exception e) { e.printStackTrace(); } } }
Finalmente llamamos a orb.run() para esperar la llegada de invocaciones y capturamos las excepciones.
Para iniciar los servidores se requieren los siguientes pasos:
> tnameserv -ORBInitialHost= host -ORBInitialPort 4711 &
> java roomBooking.MeetingFactoryServer -ORBInitialPort 4711 &
> java roomBooking.RoomServer RoomServer "Board room" -ORB.initialPort 4711 & > java roomBooking.RoomServer RoomServer "Training room" -ORB.initialPort 4711 & > java roomBooking.RoomServer RoomServer "Meeting room" -ORB.initialPort 4711 & > java roomBooking.RoomServer RoomServer "Andreas' Office" -ORB.initialPort 4711 & > java roomBooking.RoomServer RoomServer "Keith's Office" -ORB.initialPort 4711 &
Los clientes pueden implementarse como aplicaciones Java o applets. Las diferencias entre estos dos tipos de clientes son:
Un cliente como un Applet
Lo primero que debemos hacer es escribir la página HTML que lo contiene.
<html><header> <title> Room Booking Applet </title> <body> <center> <h1> Room Booking Applet <h1> <applet code = ClientApplet.class width=600 height=300> </applet> </center> </body></html>
A continuación mostramos el código del applet:
import java.awt.*; public class ClientApplet extends java.applet.Applet { private RoomBookingClient client; /** override init method of Class Applet */ public void init() { // create a RoomBookingClient client - // using the applet constructor client = new RoomBookingClient( this ); // initialiase the GUI client.init_GUI( this ); // initialise the Naming Service client.init_from_ns(); // view existing bookings client.view(); } }
Inicializamos la interfaz gráfica con el método init_GUI()
sobre
el objeto client. Comenzamos llamando al método init_from_ns()
sobre el objeto client, que contiene las referencias a la Meeting Factory
y Room del servicio Naming. Entonces invocamos al método
view()
, que obtiene las habitaciones disponibles del servicio Naming
e invoca la operación view()
sobre cada uno de dichos objetos
Room.
Un cliente como una aplicación
Implementamos la clase clientApplication
que extiende la clase
java.awt.Frame
. También implementamos el método main()
de la clase, que es similar al método init()
de la clase
applet.
import java.awt.*; import java.awt.event.*; import org.omg.CORBA.*; public class ClientApplication extends Frame implements WindowListener { private static RoomBookingClient client; /** constructor */ ClientApplication() { super( "Room Booking System" ); addWindowListener( this ); setSize( 350, 250 ); } public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) { System.exit(0); } public void windowClosing(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} /** override to reposition frame (for appearances sake only) */ public synchronized void setVisible(boolean vis) { if(vis == true) { setLocation(50, 50); } super.setVisible(vis); } public static void main(String args[]) { // create an object of its own class ClientApplication gui = new ClientApplication(); gui.setVisible(true); // create a RoomBookingClient object - // using the application constructor client = new RoomBookingClient(); // initialise the GUI client.init_GUI( gui ); // initialise the Naming Service client.init_from_ns(); // view existing bookings client.view(); } }
Código independiente del tipo de cliente
A continuación explicamos el código cliente que es independiente de los detalles del applet o aplicación, es decir, el código cliente que realiza la llamada a las implementaciones de los objetos.
La clase RoomBookingClient
implementa los siguientes métodos:
public void init_GUI(java.awt.Container gui)
: inicializa la
interfaz gráficapublic void init_from_ns()
: obtiene el contexto room desde
el contexto raiz y obtiene una referencia a la Meeting Factory
a partir de un nombre predefinidopublic boolean view()
: este método consulta todas las
habitaciones y visualiza el resultado en la interfaz de usuariopublic boolean cancel()
: cancela una habitación seleccionadapublic boolean process_slot(int selected_room, int selected_slot)
:
procesa el evento de "pinchar" un botón de una habitación
o una reserva. Decide si la habitación está libre y la reserva
puede realizarse, o si los detalles de la reserva deben visualizarsepublic boolean meeting_details()
: consulta y visualiza los
detalles de una reservapublic void booking_form()
: produce un formulario de reserva
para un usuariopublic boolean book()
: crea una reserva y la almacena en el
slot seleccionadopublic boolean actionPerformed()
: este método captura
y procesa eventos del usuario