Im Rahmen dieses Unterkapitels werden zwei Anwendungen entwickelt. Zunächst wird das Beispiel aus Kapitel 11.4 (einfache Kommunikation über RMI) in ein CORBA-Beispiel überführt. Anschließend wird erläutert, wie Callbacks in CORBA implementiert werden können. Kommunikation in CORBA Die grundlegenden Schritte bei der Entwicklung und Verwendung eines CORBA-Dienstes sehen wie folgt aus: Erzeugen einer IDL-Datei, die das gewünschte Interface repräsentiert. Übersetzen der IDL-Datei mit dem Compiler idltojava. Übersetzen der generierten Java-Klassen mit dem Compiler javac. Erzeugen der Implementierungsklasse(n). Erzeugen des Implementierungs-Servers. Erzeugen der Client-Anwendung. Übersetzen der Implementierung, des Servers und des Clients. Aufruf des Naming Services tnameserv. Aufruf des Servers, der beim Naming Service registriert wird. Aufruf des Clients. Der wichtigste Schritt ist hierbei der erste, da hier das grundlegende Design des Dienstes implementiert wird. Im Folgenden werden die einzelnen Schritte im Detail erläutert. Erzeugen einer IDL-Datei Die im Folgenden erläuterte Anwendung dient der Aktualisierung einer Datenbank. Hierzu ruft der Client einen CORBA-Dienst auf und übergibt einen Parameter, der angibt, ob er das Spiel gewonnen oder verloren hat. Die IDL-Definition sieht dann wie folgt aus: module DatabaseUpdate { //Aktualisierung des Spielstands interface Functions { long updateDatenbank(in long spiel); }; }; Der Parameter wird hierbei als Call-by-Value übergeben. Übersetzen der IDL-Datei Will man die IDL-Datei, die in diesem Fall DatabaseUpdate.idl heißen könnte, übersetzen, so ist die folgende Anweisung anzugeben: idltojava -fno-cpp DatabaseUpdate.idl Die Option -fno-cpp muss verwendet werden, um den C/C++-Präprozessor auszuschalten. Der IDL-Compiler überprüft die IDL-Datei und erzeugt die entsprechenden Verzeichnisse und Java-Dateien. Hierbei wird der Bezeichner module zur Angabe eines Packages verwendet, der Bezeichner interface zur Angabe der Java-Interfaces. Übersetzen der generierten Java-Klassen Anschließend müssen die erzeugten Java-Klassen übersetzt werden. Hierbei können keine Syntaxfehler auftreten, da der Code automatisch erzeugt wurde. Zur Übersetzung der Dateien muss die folgende Anweisung verwendet werden: javac DatabaseUpdate\*.java Erzeugen der Implementierungsklasse(n) Im nächsten Schritt muss eine Java-Klasse entwickelt werden, die das Interface implementiert, das vom IDL-Compiler erzeugt wurde. Hierbei empfiehlt es sich, zur Speicherung der neu entwickelten Dateien ein anderes Verzeichnis zu wählen als das der automatisch erzeugten Dateien. Die Implementierung muss nun erfolgen, indem die Datei _FunctionsImplBase.java verwendet wird, die der IDL-Compiler angelegt hat. Diese Klasse ist abstrakt und erweitert die Klasse org.omg.CORBA.portable.ObjectImpl. Indem diese Klasse weiterentwickelt wird, können die geeigneten Skeleton-Methoden eingefügt werden, die für ORB-Aufrufe an die Implementierungsmethoden notwendig sind. Die Implementierungsklasse muss einen Rumpf für jede Methode zur Verfügung stellen, die im Interface beschrieben ist: /* * File: ./DATABASEUPDATE/FUNCTIONS.JAVA * From: DATABASEUPDATE.IDL * Date: Thu May 27 15:02:54 1999 * By: idltojava Java IDL 1.2 Aug 18 1998 16:25:34 */ package DatabaseUpdate; public interface Functions extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { int updateDatenbank(int spiel); } Die Implementierungsklasse muss daher die Methode updateDatenbank enthalten. Per Konvention fügt die Implementierungsklasse das Suffix Impl an den Namen des Interfaces an. Die Realisierung dieser Klasse ist im Folgenden angegeben. //Implementierung des Interfaces Functions.java import DatabaseUpdate.*; public class FunctionsImpl extends _FunctionsImplBase { public FunctionsImpl() { } public int updateDatenbank(int spiel){ //Hier muss der Datenbankzugriff erfolgen return 0; } } Erzeugen des Implementierungs-Servers Im nächsten Schritt muss die Server-Klasse erzeugt werden, die das implementierte Objekt beim ORB und beim Naming Service registriert und die die Verbindung zur Implementierungsklasse herstellt. Wie schon die Implementierungsklasse wird auch diese Klasse nicht vom Compiler idltojava erzeugt, sondern muss selbst implementiert werden. //Server-Klasse import DatabaseUpdate.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; public class Server { public static void main (String args[]){ try { //Erzeugen des Server-ORB ORB orb = ORB.init(args, null); //Implementierungsobjekt erstellen FunctionsImpl fimpl = new FunctionsImpl(); orb.connect(fimpl); //Handle fuer Name Server erzeugen org.omg.CORBA.Object oref = orb.resolve_initial_references ("NameService"); NamingContext nc = NamingContextHelper.narrow(oref); //Binden der Objektreferenz NameComponent nco = new NameComponent("DB", ""); NameComponent pfad[]={nco}; nc.rebind(pfad, fimpl); //Aufrufe des Clients erwarten java.lang.Object sync = new java.lang.Object(); synchronized (sync) { sync.wait(); } }catch (Exception e) { System.err.println("Fehler: "+e); e.printStackTrace(); } } } Dieser Server ist ein Beispiel für einen transienten Objekt-Server, da die Objektreferenz und der ORB erfordern, dass die Server-Anwendung lauffähig bleibt, nachdem sie einmal gestartet wurde. Erzeugen der Client-Anwendung Die Client-Anwendung lokalisiert eine Referenz auf das Functions-Objekt, indem der Naming Service verwendet wird. Die zurückgegebene Objektreferenz ist eine CORBA-Referenz, die auf den richtigen Referenztyp eingeschränkt (Narrowing) werden muss. Der Server hat diesen Namen als DB veröffentlicht, so dass der Client in der Folge genau diesen Namen anfordern muss. //Client-Klasse import DatabaseUpdate.*; import org.omg.CosNaming.*; import org.omg.CORBA.*; public class Client { public static void main (String args[]){ try { //Erzeugen des Client-ORB ORB orb = ORB.init(args, null); //Handle fuer Name Server erzeugen org.omg.CORBA.Object oref = orb.resolve_initial_references ("NameService"); NamingContext nc = NamingContextHelper.narrow(oref); //Finden der Objektreferenz NameComponent nco = new NameComponent("DB", ""); NameComponent pfad[]={nco}; //Helper-Klasse verwenden, um Casting vorzunehmen. Functions fun = FunctionsHelper.narrow(nc.resolve(pfad)); //Aufrufe ausfuehren, 0 heisst Spieler hat verloren int gewonnen = fun.updateDatenbank(0); }catch (Exception e) { System.err.println("Fehler: "+e); e.printStackTrace(); } } } Der Aufruf des Naming Services liefert eine generelle CORBA-Referenz zurück, die in den geeigneten Typ umgewandelt werden muss, bevor Methodenaufrufe erfolgen können. Zusätzlich ruft der Client Methoden eines Stubs auf, der die Objektreferenz repräsentiert. Die automatisch erzeugte Helper-Klasse erleichtert diese Aufgabe, da die narrow-Methode dazu verwendet werden kann, eine Referenz auf den Functions-Stub zu erhalten. Mit dieser Referenz kann die Methode updateDatenbank aufgerufen werden. Übersetzen der Java-Klassen Nachdem alle Implementierungen erfolgt sind, müssen die Quelldateien übersetzt werden. Hierzu kann die folgende Anweisung verwendet werden: javac -d . FunctionsImpl.java Server.java Client.java Aufruf des Naming Services tnameserv Der Naming Service tnameserv ist Teil von Java 1.2. Dieser Dienst hört einen Port ab (standardmäßig den Port 900), um Anfragen nach Namen und nach Objektbindungen zu empfangen. Indem ein Argument übergeben wird, kann die Port-Nummer verändert werden. tnameserv -ORBInitialPort 2000 Nach dem Aufruf gibt der Naming Service die sog. Interoperable Object Reference (IOR) und die Port-Nummer zurück, die der Naming Service überwacht. Der IOR-String stellt eine weitere Möglichkeit zur Verfügung, CORBA-Objektreferenzen zu lokalisieren, da der String Informationen über den Speicherort eines Objekts enthält, unter anderem den Host-Namen und die IP-Adresse bzw. welche Dienste ein Objekt anbietet. Ein IOR kann daher bspw. dazu eingesetzt werden, um Objektreferenzen zwischen zwei ORBs zu übergeben, ohne den Naming Service zur Lokalisierung einer Objektreferenz einzusetzen. Hierzu veröffentlicht der Server eine sog. Stringified-Objektreferenz, also eine String-Repräsentation einer CORBA-Objektreferenz, indem die Objektreferenz in einen String umgewandelt wird. Aufruf des Servers Anschließend muss der Server mit der Port-Nummer aufgerufen werden, unter der der Naming Service erreichbar ist. java Server -ORBInitialPort 2000 Aufruf des Clients Auch der Client muss mit der Port-Nummer des Naming Service aufgerufen werden. Nach dem Start des Clients ruft dieser die Aktualisierungsfunktion der Datenbank beim Server auf und wird anschließend beendet. Implementiert man daher bspw. diese Funktionalität beim Server, so kann nach einem gewonnenen oder verlorenen Spiel durch Verwendung von CORBA-Objekten vermerkt werden, wie oft ein Spieler gewonnen oder verloren hat. Zum Aufruf des Clients ist die folgende Anweisung zu verwenden: java Client -ORBInitialPort 2000 Callbacks in CORBA Nachdem die einfache Kommunikation über CORBA erläutert wurde, soll im Folgenden betrachtet werden, wie Callbacks in CORBA erfolgen. Auch hier wird das Passwort-Beispiel eingesetzt. Das vorangegangene Beispiel wird daher so erweitert, dass der Server vor dem Eintrag in die Datenbank eine Autorisierung verlangt. Hierzu verwendet er einen Callback. Auch diese Implementierung läuft in den bereits beschriebenen Schritten ab. Erzeugen einer IDL-Datei Die Erzeugung der IDL-Datei ähnelt dem ersten Beispiel. Wie auch schon in RMI muss nun aber die Menge der Eingabeparameter der Funktion updateDatenbank verändert werden. Hierzu wird zusätzlich eine Objektreferenz übergeben. Zusätzlich wird ein neues Interface definiert, das vom Client implementiert werden muss. Der Server kann anschließend mit dem Client kommunizieren, indem dieser die Methode beim Client aufruft. module DatabaseUpdate2 { //Client-Funktion interface ClientCallback { void callback(out string passwort); }; //Aktualisierung des Spielstands interface Functions { long updateDatenbank(in ClientCallback objRef,in long spiel); }; }; Erzeugen der Implementierungsklasse(n) Die nun folgende Implementierung des Interfaces Functions.java muss die geänderte Anzahl der Parameter umsetzen. import DatabaseUpdate2.*; public class FunctionsImpl extends _FunctionsImplBase { public FunctionsImpl() { } public int updateDatenbank(ClientCallback objref, int spiel){ String pwd = "PWDREQUEST"; objref.callback(pwd); //Pruefe Passwort, das uebergeben wurde //Hier muss der Datenbankzugriff erfolgen return 0; } } Erzeugen des Implementierungs-Servers Im nächsten Schritt muss die Server-Klasse erzeugt werden, die das implementierte Objekt beim ORB und beim Naming Service registriert und die die Verbindung zur Implementierungsklasse herstellt. Diese Klasse entspricht als Verwaltungsklasse genau der Klasse, die im vorherigen Beispiel definiert wurde. //Server-Klasse import DatabaseUpdate2.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; public class Server { public static void main (String args[]){ try { //Erzeugen des Server-ORB ORB orb = ORB.init(args, null); //Implementierungsobjekt erstellen FunctionsImpl fimpl = new FunctionsImpl(); orb.connect(fimpl); //Handle fuer Name Server erzeugen org.omg.CORBA.Object oref = orb.resolve_initial_references ("NameService"); NamingContext nc = NamingContextHelper.narrow(oref); //Binden der Objektreferenz NameComponent nco = new NameComponent("DB", ""); NameComponent pfad[]={nco}; nc.rebind(pfad, fimpl); //Aufrufe des Clients erwarten java.lang.Object sync = new java.lang.Object(); synchronized (sync) { sync.wait(); } }catch (Exception e) { System.err.println("Fehler: "+e); e.printStackTrace(); } } } Erzeugen der Client-Anwendung Die Client-Anwendung muss derart erweitert werden, dass das Callback-Interface implementiert wird. Im Folgenden ist die Realisierung in Form einer inneren Klasse angegeben. Hierbei sei auf die Verwendung der Holder-Klasse aufmerksam gemacht. Diese muss benutzt werden, da der Parameter mittels Call-by-Reference übergeben wird. //Client-Klasse import DatabaseUpdate2.*; import org.omg.CosNaming.*; import org.omg.CORBA.*; class ClientCallback extends _ClientCallbackImplBase{ public void callback(org.omg.CORBA.StringHolder passwort){ //weitere Verarbeitung, Passworteingabe ueber Fenster //nun Schreiben des Passworts in die Variable } } public class Client { public static void main (String args[]){ try { //Erzeugen des Client-ORB ORB orb = ORB.init(args, null); //Handle fuer Name Server erzeugen org.omg.CORBA.Object oref = orb.resolve_initial_references ("NameService"); NamingContext nc = NamingContextHelper.narrow(oref); //Finden der Objektreferenz NameComponent nco = new NameComponent("DB", ""); NameComponent pfad[]={nco}; //Helper-Klasse verwenden, um Casting vorzunehmen. Functions fun = FunctionsHelper.narrow(nc.resolve(pfad)); //Objekt vorbereiten ClientCallback callbackRef = new ClientCallback(); orb.connect(callbackRef); //Aufrufe ausfuehren, 0 heisst Spieler hat verloren //Jetzt wird auch das Callback-Objekt uebergeben int gewonnen = fun.updateDatenbank(callbackRef, 0); }catch (Exception e) { System.err.println("Fehler: "+e); e.printStackTrace(); } } } Aufruf der Komponenten Der Aufruf der Komponenten erfolgt in derselben Art und Weise wie beim ersten Beispiel.