In der Object Management Group (OMG) sind Ingenieure in einem Konsortium vereinigt, deren Ziel die Entwicklung einer Architektur ist, in der verschiedene Programmier- und Betriebssystemumgebungen transparent zusammenarbeiten können. Die OMG entwickelte hierzu eine ganze Reihe von Spezifikationen, die ein Framework wiederverwendbarer Komponenten definieren. Diese Dokumente beschreiben vor allem Architekturelemente, die notwendig sind, damit Hardware- und Software-Systeme miteinander kommunizieren können. Resultat der Arbeit der OMG ist die Common Object Request Broker Architecture (CORBA). CORBA liegt nicht in Form eines Produkts vor, sondern als Standard, der definiert, wie Firmen Implementierungen des Standards anzufertigen haben. Seit 1989 beschäftigte sich die OMG mit der Spezifikation einer Architektur für einen offenen Software-Bus, dem sog. Object Request Broker (ORB), auf dem Objektkomponenten, die von verschiedenen Herstellern geschrieben wurden, über Netzwerke und Betriebssysteme hinweg zusammenarbeiten können. Durch diesen Standard können CORBA-Objekte andere Objekte aufrufen, ohne zu wissen, wo die aufgerufenen Objekte gespeichert sind oder in welcher Sprache sie implementiert sind. Die von der OMG spezifizierte Interface Definition Language (IDL) wird dazu verwendet, Interfaces zu CORBA-Objekten zu definieren. CORBA-Objekte unterscheiden sich von Objekten anderer Programmiersprachen hinsichtlich der folgenden Aspekte: CORBA-Objekte können sich an beliebiger Stelle eines Netzwerks befinden. CORBA-Objekte können mit Objekten anderer Plattformen zusammenarbeiten. CORBA-Objekte können in einer beliebigen Programmiersprache geschrieben sein, für die eine Abbildung von der OMG-IDL auf die jeweilige Programmiersprache existiert. Beispiele hierfür sind Java, C++, C, Smalltalk, Cobol und ADA. Im Folgenden werden zunächst die Komponenten von CORBA vorgestellt. CORBA-Komponenten Eine CORBA-Implementierung besteht aus mehreren Teilen, abhängig von der individuellen Anwendung des Standards durch den Hersteller. Üblicherweise werden die folgenden Komponenten ausgeliefert: Eine Implementierung des Object Request Brokers (ORB). Ein Compiler der Interface Definition Language (IDL). Eine Implementierung der Common Object Services (COS), die manchmal auch als CORBAServices bezeichnet werden. Anwendungsabhängige Umgebungen, die auch unter dem Namen CORBAFacilities bekannt sind. Seit CORBA in der Version 2.0 verfügbat ist, ist auch das standardisierte Internet-Inter-ORB-Protokoll (IIOP) verfügbar, das angibt, wie ORBs über das Netzwerk kommunizieren. Object Request Broker (ORB) Aufgabe des Object Request Brokers (ORB) ist es, Objekte getrennter Adressräume miteinander zu verbinden. Ein ORB ist deshalb eine Art Bus. Möchten zwei CORBA-Systeme mit getrennten Adressräumen miteinander kommunizieren, so stellt der ORB sicher, dass unabhängig von der verwendeten Hardware bzw. vom verwendeten Betriebssystem und von der Programmiersprache Aufrufe entfernter Objekte erfolgreich sind (siehe auch Abb. 12-1). Abb. 12.1: Object Request Broker Wie auch bereits bei RMI wird beim ORB die Technik des Marshaling dazu eingesetzt, Parameter, Rückgabewerte und Exceptions zu bündeln, damit sie in einer Einheit beim Aufruf entfernter Methoden zwischen Systemen ausgetauscht werden können. Allgemein werden beim Marshaling Daten von einem plattformspezifischen in ein plattformneutrales Netzwerkformat umgewandelt, bzw. beim Empfänger wieder von einer plattformneutralen Netzrepräsentation in das empfängerspezifische Datenformat. Beispiele für ORBs sind: Residenter ORB in Client- und Objektimplementierung ORB-Funktionen residieren in Client- und Objektimplementierung. Probleme ergeben sich hier aber mit „schwergewichtigen" Objekten bzw. mit der Transparenz Server-basierter ORB Hierbei kommunizieren Client- und Objektimplementierung mit Server(n). Server leiten also Requests von einer Client- zu einer Objektimplementierung und zurück. System-basierter ORB ORB-Funktionen sind Teil des verteilten Betriebssystems. Vorteile dieses Ansatzes sind ein besserer Schutz, eine große Robustheit und eine erweiterte Leistungsfähigkeit. Laufzeitbibliothek-basierter ORB Methoden und Daten befinden sich bei diesem Ansatz in einer gemeinsam benutzten Laufzeitbibliothek. Hierbei werden sog. Lightweight-Objekte verwendet, aber eine Synchronisation der Zugriffe ist erforderlich. Common Object Services (COS) Die Common Object Services (COS) unterstützen den ORB im Hinblick auf die folgende Funktionalität: Namensdienst Mittels des Namensdienstes können Clients und Server Objekte im Netzwerk finden. Ein Server registriert hierbei ein Objekt bei einem Namensdienst, indem eine hierarchische Repräsentation verwendet wird, die einer Darstellung aus einem Pfad und einem Dateinamen ähnelt. Clients können eine Objektreferenz anfordern, indem der Namensdienst nach dem Namen des Objekts gefragt wird. Eine ähnliche Funktion hat der Leser bereits mit der RMI-Registry kennengelernt. Event-Dienst Der Event-Dienst erlaubt es einem Client oder einem Server, eine Nachricht in Form eines Event-Objekts an einen oder mehrere Empfänger zu schicken. Objekte können das Abhören eines bestimmten Event-Kanals veranlassen. Der Event-Dienst benachrichtigt die Objekte dann, wenn ein Event auf einem derartigen Kanal aufgetreten ist. Der Event-Dienst ist weiterhin dafür zuständig, Events vor der Auslieferung zu speichern. Clients und Server müssen daher nicht permanent verbunden sein, um das Auftreten von Events zu überwachen. Security-Dienst Der Security-Dienst ermöglicht es, Nachrichten zu authentifizieren, den Objektzugriff zu autorisieren und damit eine sichere Kommunikation umzusetzen. Abb. 12.2: CORBA und COS Transaktionsdienst Der Transaktionsdienst definiert eine Möglichkeit, Aktionen einer Datenbank oder eines vergleichbaren Subsystems beweisbar zu kontrollieren. Der Transaktionsdienst erlaubt es Clients und Servern, eine Transaktion zu beenden (commit) oder sie zurückzusetzen (abort), selbst wenn die Transaktion mehrere Datenbanken betrifft. Insgesamt stehen eine Reihe von Diensten zur Verfügung, bspw. Dienste zur Verarbeitung persistenter Objekte, zur Parallelitätskontrolle, zur Verwaltung des Lebenszyklus von Objekten, zur Verarbeitung von Relationen, zur Externalisierung, zur Abfrage von Objekten, zur Lizenzierung, zur Feststellung von Objekteigenschaften, zur Verarbeitung von Timern, zur Verwendung von Objekt-Tradern und zur Verarbeitung von Objekt-Collections. Neben den Common Object Services existieren weiterhin die Common Facilities, die anwendungsspezifische Dienste definieren. Common Facilities sind Sammlungen von Klassen und Objekten, die allgemein nützliche höherwertige Dienste für unterschiedliche Anwendungsarten enthalten. Sie müssen nicht in jedem OMG-konformen Produkt enthalten sein. Allgemein klassifiziert man die Common Facilities in horizontale (allgemein benutzte) und in vertikale (für spezielle Anwendungsdomänen, bspw. Business Objects) Dienste. Das Zusammenspiel dieser Dienste ist in Abb. 12-2 dargestellt. Interface Definition Language (IDL) Aufgabe der Interface Definition Language (IDL) ist es, eine gemeinsame Schnittstelle für ORB, COS und Common Facilities zu definieren. Schnittstellendefinitionen werden in CORBA mittels einer Menge von Sprachkonstrukten erzeugt, die durch die IDL beschrieben sind. IDL stellt eine Möglichkeit zur Verfügung, progammiersprachenneutral Dienstimplementierungen zu beschreiben. Die Konstrukte, aus denen IDL besteht, ähneln in der Syntax Java, können aber nicht direkt in ein Binärprogramm übersetzt werden. Anstelle dessen stellt IDL eine Zwischensprache dar, die die Interfaces definiert, die ein Client verwendet und die ein Server implementiert. Ein Entwickler, der ein CORBA-System erarbeitet, modelliert ein System, indem er IDL zur Definition der Schnittstellen einsetzt, die ein System unterstützt. Das Modell ist hierbei eine abstrakte Repräsentation des aktuellen Systems. Hierbei werden sowohl einfache Datentypen (sog. Basic Values) als auch zusammengesetzte Datentypen (sog. Constructed Values) unterstützt. Beispiele für einfache Datentypen sind Short, Long, UShort, ULong, Float, Double, Char, String, Boolean, Octet, Enum und Any. Beispiele für zusammengesetzte Datentypen sind Struct, Sequence, Union und Array. In IDL werden alle Aufruf- und Rückgabeparameter mittels dieser Datentypen beschrieben. Objektreferenzen werden zur Bezugnahme auf Objekte verwendet. Eine IDL-Spezifikation sieht bspw. folgendermaßen aus: module demo { interface test { readonly attribute string message; long connect(in long id); }; }; Diese IDL-Spezifikation beschreibt ein Attribut und eine Funktion. Die Definition der Funktion ist abstrakt, da kein Code angegeben wird, der die Implementierung der Funktion näher angibt. Auch die Sprache, die zur Implementierung verwendet wird, wird hier nicht spezifiziert. Die IDL-Spezifikation wird anschließend übersetzt, indem Werkzeuge verwendet werden, die Code für das Betriebssystem und für die Programmiersprache generieren, die der Anwender benutzt. IDL-Dateien werden in diesem Zusammenhang aber nicht von einem Compiler übersetzt, sondern eher in generelle Konstrukte überführt, die die Abbildung auf eine Programmiersprache ermöglichen. Die übersetzten Dateien sind allerdings nicht vollständig, da der Entwickler hier noch Implementierungsdetails integrieren muss. Internet-Inter-ORB-Protokoll CORBA in der Version 1.0 macht keine Angaben über das vom ORB verwendete Transportprotokoll oder über das Protokollformat. Hiermit können zwar portable, aber keine interoperablen Lösungen realisiert werden. CORBA in der Version 2.0 schreibt die Unterstützung des Internet-Inter-ORB-Protokolls (IIOP) vor. Basisidee von IIOP ist die Bereitstellung von sog. Universal Networked Objects (UNO), die TCP/IP als Transportprotokoll zur Umsetzung des General-Inter-ORB-Protokolls (GIOP) verwenden. Das GIOP definiert, wie ORBs miteinander kommunizieren, also bspw. wie Nachrichten gesendet werden bzw. wie Parameter zum Aufruf entfernter Objekte verpackt werden (Marshaling). UNOs verwenden ein interoperables Nachrichtenformat, die Common Data Representation (CDR). In CDR wird bspw. angegeben, wie IDL-Typen in ein Netzwerkformat überführt werden müssen und wie die Byte-Reihenfolge aussieht. Im Unterschied zur External Data Representation (XDR) besteht auch die Möglichkeit zur Kommunikation über die „Variable Byte"-Anordnung zwischen Systemen mit gleichem Wortformat. In anderen Systemen wird die Anordnung durch den Absender festgelegt und das verwendete Format über ein Flag mitgeteilt. Die eventuelle Konvertierung ist dann eine Aufgabe des Empfängers. Durch IIOP können Objekte anderer ORBs gefunden und verwendet werden. IIOP ermöglicht weiterhin, in OSF-DCE-basierten Umgebungen das DCE-Common-Inter-ORB-Protokoll (DCE CIOP) zu verwenden. Grundsätzlich ist es möglich, Client-Anwendungen zu entwickeln, die den ORB und den IDL-Compiler eines Herstellers verwenden, und gleichzeitig Server- oder Objektimplementierungen mit Hilfe des ORBs und des IDL-Compilers eines anderen Herstellers vorzunehmen. Die COS für Client und Server können hierbei theoretisch sogar mit dem ORB und dem IDL-Compiler eines dritten Herstellers erzeugt werden. Mittels IIOP können die Produkte der drei Hersteller miteinander kommunizieren, indem eine Standardmenge von Protokollsemantiken verwendet wird. Ein Spezialfall liegt dann vor, wenn alle Anwendungen (Client, Server und COS) in verschiedenen Programmiersprachen auf unterschiedlichen Hardware-Plattformen und Betriebssystemen geschrieben werden. Selbst dann können alle Programme mittels IIOP kommunizieren. Dieses Szenario beschreibt zudem hervorragend, in welchen Gebieten die Stärken von CORBA liegen. Abb. 12-3 illustriert das Zusammenspiel verschiedener Anwendungen mittels IIOP. Abb. 12.3: Verwendung von IIOP Funktionsweise von CORBA Bevor ein CORBA-System implementiert werden kann, muss zuerst ein Design entwickelt werden, das festlegt, welche Funktionalität das System haben soll. Dieses Vorgehen wurde bereits im Zusammenhang mit der Remote Method Invocation (RMI) betrachtet. Das Design wird anschließend in Objekte übersetzt, die, mit IDL-Schnittstellen versehen, in Modulen gruppiert werden. Im Anschluss daran werden die IDL-Dateien übersetzt, um Stubs und Skeletons zu erzeugen. Stubs und Skeletons werden hier in derselben Art und Weise verwendet, wie in Kapitel 11.2 bereits erläutert wurde. Stubs sind daher die Schnittstellen, mit denen der Client kommuniziert, während Skeletons die Schnittstellen der Server zu Objekten bezeichnen. Nachdem die Interface-Definitionen durch Aufruf des Compilers erzeugt wurden, müssen die Implementierungen erfolgen. Anschließend wird der Server gestartet, der Objektreferenzen mit Hilfe des Objektnamens über den sog. Naming Service im Netz bekannt macht. Diese Funktion ähnelt der Rmiregistry. Die Client-Anwendung erfragt nun eine Objektreferenz, indem der Name im Naming Service angefordert wird. Der Naming Service liefert anschließend eine Referenz auf ein generisches CORBA-Objekt zurück. Diese Objektreferenz entspricht der Stub-Repräsentation des entfernten Objekts. Der Aufbau eines derartigen Systems ist in Abb. 12-4 angegeben. Bisher noch nicht erläuterte Komponenten (bspw. dynamische Skeletons) werden in der Folge dieses Kapitels erklärt. Abb. 12.4: CORBA-System CORBA-Objekte CORBA-Objekte werden zunächst in abstrakter Form in einer IDL-Datei beschrieben, die einen Objekttyp definiert. Ein Interface kann hierbei Funktionen von einem oder von mehreren anderen Interfaces erben. Die IDL-Syntax ähnelt der von Java oder C++. Eine IDL-Datei ähnelt daher auch in ihrer Funktionalität dem Analogon einer C++-Header-Datei, wenn auch in einer programmiersprachenunabhängigen Art. Ein IDL-Interface deklariert eine Menge von Operationen, Exceptions und Typattributen (Werte), die dem Client zugänglich sind. Jede Operation besteht aus einer Signatur, die ihren Namen, die Parameter, das Ergebnis und die Exceptions definiert. module OpenJavaApp { interface OJ{ string gutenTag(); }; }; Ein CORBA-Objekt wird oft auch als Dienst bezeichnet. CORBA-Dienste können Rückgabewerte erzeugen oder aber auch Aufgaben wahrnehmen, bei denen keine Rückgaben vorgesehen sind. Hierbei sind die IDL-Definitionen von Diensten vollständig objektorientiert, Daten werden also niemals direkt sichtbar, sondern können nur über Zugriffsmethoden abgefragt werden. Jede IDL-Definition wird auf eine Programmiersprache abgebildet, um Zugang zu Objektschnittstellen der jeweiligen Sprache zu gewinnen. Mittels Java-IDL können IDL-Definitionen auf Java abgebildet werden, indem der Compiler idltojava verwendet wird. Dieser Compiler generiert für jede IDL-Schnittstelle ein Java-Interface und andere notwendige Dateien, wie bspw. Client-Stubs und das Server-Skeleton. Stubs und Skeletons Die Namen der Dateien, die der IDL-Compiler erzeugt, hängen von den Inhalten der IDL-Dateien und vom verwendeten Compiler ab. Übersetzt man die oben angegebene IDL-Datei, so wird ein Package mit Namen OpenJavaApp erzeugt, sowie eine Interface-Datei OJ.java im Verzeichnis OpenJavaApp. Die Interface-Datei enthält eine abstrakte Methodendeklaration der im Interface definierten Methode. Abb. 12.5: Verwendung entfernter Objekte in CORBA Üblicherweise werden Stub- und Skeleton-Dateien erzeugt. Stub-Dateien werden vom Client-Code zur Auflösung von Referenzen auf entfernte CORBA-Objekte verwendet. Skeleton-Dateien werden vom Server-Code, also von der Objektimplementierung, verwendet. Sowohl Stub als auch Skeleton erben von einer gemeinsamen ORB-Klasse, die die Kommunikation der Objekte ermöglicht. Weiterhin verwenden sowohl Client als auch Server den Naming Service, um Informationen über das entferne Objekt zur Verfügung zu stellen, das jeweils angefordert bzw. zurückgegeben wird. Abb. 12-5 stellt die Verwendung von Stubs und Skeletons grafisch dar. Es sei angemerkt, dass dieses Schaubild dem Bild ähnelt, das zur Verdeutlichung der Funktionsweise von RMI angeführt wurde. Wird ein Server gestartet, so erzeugt er ein Objekt (oder gibt dieses weiter), das referenziert werden soll. Das Objekt implementiert die Methoden, die in der IDL-Schnittstelle abstrakt angegeben sind. Der Server registriert die Referenz auf dieses Objekt anschließend beim Naming Service. Anschließend fordert ein Client eine Referenz auf ein entferntes Objekt an, indem er eine Abfrage des Naming Services ausführt. Die zurückgegebene Referenz wird über ein Stub-Objekt übergeben. Über die Stub-Referenz kann der Client nun Methoden aufrufen, als ob sich das Objekt in der lokalen Umgebung des Clients befinden würde. Der Stub reicht die Aufrufe an die Skeleton-Referenz weiter, die vom Naming Service zurückgegeben wurde. CORBA-Client Eine Client-Anwendung ruft Methoden von CORBA-Objekten auf. Hierzu muss der Client wissen, welche Methoden verfügbar sind und welche Argumente eine Methode erwartet. Ein Client kann mit statischen oder mit dynamischen Methodenaufrufen arbeiten. Statische Methodenaufrufe werden zur Übersetzungszeit generiert und auf Typkonsistenz geprüft. Diese Art der Methodenaufrufe verwendet die Methoden, die in den Java-Interfaces deklariert sind, die aus den IDL-Definitionen erzeugt wurden. Abb. 12.6: Client in CORBA Dynamische Methodenaufrufe sind flexibler, aber auch komplexer, da der Client hier die Objektdefinition zur Laufzeit feststellt. Diese Art der Methodenaufrufe führt keine Typprüfung der Argumente durch. Es liegt daher in der Verantwortung des Clients, sicherzustellen, dass die Argumente korrekt übergeben werden und konsistent sind. Die dynamische Form der Methodenaufrufe erfordert weiterhin, dass der Server ein sog. Interface Repository unterstützt. Ein Interface Repository wird verwendet, um dem Client Methodennamen, Typ- und Argumentlisten zur Verfügung zu stellen. Die Implementierung beim Client ist in Abb. 12-6 dargestellt. CORBA-Server Mit Hilfe des Naming Services können Referenzen auf CORBA-Objekte, die ein Server anbietet, zur Verfügung gestellt werden. Man unterscheidet hierbei zwischen transienten und zwischen persistenten Objekten. Transiente Objekte haben grundsätzlich dieselbe Lebensdauer wie der Server, der sie erzeugte. Ein Objekt steht daher genau solange zur Verfügung, wie der Server läuft. Persistente Objekte erfordern hingegen keinen laufenden Server. Wenn eine Anforderung an ein nicht verfügbares Objekt generiert wird, startet ein ORB-Dämon den geeigneten Server, der dann das Objekt erzeugt und eine Referenz zurückgibt. In Java-IDL stehen allerdings nur transiente Objekte zur Verfügung. Ein Zugriff auf persistente Objekte ist allerdings möglich, wenn diese mit Produkten anderer Hersteller realisiert wurden. Abb. 12.7: Server in CORBA CORBA-Server können eine Referenz auf eine Objektimplementierung entweder statisch oder dynamisch erzeugen. Statische Skeletons werden direkt aus der Interface-Deklaration der IDL abgeleitet. Diese Skeleton-Art kann leicht verwendet werden, da die Reihenfolge der Argumente und deren Typ bereits zur Übersetzungszeit bekannt ist. Dynamische Skeletons sind flexibler, da sie Methodenaufrufe eines Objekts gestatten, das dynamisch zur Laufzeit verarbeitet wird. Standardmäßig erzeugt der IDL-Compiler von Java Skeletons, die das Dynamic Skeleton Interface von Java-IDL verwenden. Java-IDL verbindet die Skeleton-Interfaces mit der tatsächlichen Objektimplementierung auf die folgenden alternativen Arten: Direkte Vererbung von Skeleton-Methoden, indem die vom IDL-Compiler erzeugte Skeleton-Klasse vererbt wird. Hierbei entspricht jede Methode des IDL-Interfaces direkt einer Skeleton-Methode. Wie bereits erläutert, kann eine Klasse in Java Eigenschaften nur von einer Superklasse erben. Oftmals ist es daher ein Nachteil, wenn die Vererbungsmöglichkeit durch das Skeleton bereits ausgeschöpft wird. Problematisch ist hierbei vor allem, dass die Klassen, auf die die Skeleton-Klassen abgebildet werden sollen, bereits existieren. In diesem Fall sollte die zweite Alternative verwendet werden. Verwendung einer weiteren Klasse zwischen dem Skeleton und der Implementierung, die Methodenaufrufe an die korrekte Implementierungsklasse weiterleitet. Skeletons, die diese Art der Abbildung verwenden, bezeichnet man auch als Tie-Skeletons. Java-IDL enthält einen Mechanismus, um Tie-Implementierungen zu erzeugen, indem dem Compiler ein spezielles Flag übergeben wird. Bei statischen und bei dynamischen Skeletons werden Anfragen von Clients nicht direkt zur Implementierung eines Objekts weitergeleitet, sondern über das Skeleton übergeben. Das Skeleton stellt Methoden zur Verfügung, mit deren Hilfe die Reihenfolge der Argumente, die für den Methodenaufruf des repräsentierten Objekts übergeben wurden, hergestellt werden können. Weiterhin können Resultate des Methodenaufrufs zur Weitergabe an den Stub gepackt werden (Marshaling). Die Implementierung von Objekten mit dynamischen Methodenaufrufen beim Server ist in Abb. 12-7 dargestellt. Objektadapter Einige CORBA-Implementierungen unterstützen das Konzept eines Objektadapters. Ein Objekt-Adapter erzeugt Server-Objekte und gibt eine Objektreferenz-ID zurück. Ein Objekt-Adapter wird daher immer nur auf der Server-Seite verwendet. CORBA verlangt in der Spezifikation, dass mindestens ein Objekt-Adapter, der sog. Basic Object Adapter (BOA) unterstützt wird. Da die Spezifikation des BOAs allerdings eher ungenau ist, existieren derzeit Implementierungen, deren Semantik von Hersteller zu Hersteller variiert. Es ist offensichtlich, dass diese Unterschiede dazu führen, dass die Portierung von Server-seitigem Code Schwierigkeiten macht. Um dieses Problem zu lösen, wurde von der Object Management Group (OMG) eine neue Spezifikation veröffentlicht, der sog. Portable Object Adapter (POA). Diese Spezifikation ist wesentlich präziser und ermöglicht daher auch die Portierung von Server-seitigem Code. Der POA ist in IDL beschrieben und wird als CORBA-Objekt instantiiert. Zum jetzigen Zeitpunkt ist der POA noch kein Teil von Java-IDL. Aus diesem Grund werden derzeit in Java nur transiente Objekt-Server unterstützt. In der ORB-Klasse von Java steht allerdings ein vereinfachter Objekt-Adapter für transiente Objekte zur Verfügung. In Abb. 12-8 ist abschließend das gesamte Zusammenspiel der CORBA-Komponenten dargestellt. Abb. 12.8: Zusammenspiel der CORBA-Komponenten