CORBA Dienste

Ausarbeitung zum Hauptseminar Middleware
WS 2000/2001

von E-MailMarc Layer 2000-11-09

Vorwort

Der vorliegende Text entstand im Rahmen einer Ausarbeitung zum Hauptseminar "Middleware" im Wintersemester 2000/2001. Er ist auch als PostScript-Datei (zip, 136 KB) verfügbar. Die virtuellen "Folien" des zugehörigen Vortrags wurden mit pdfLATEX und PPower4, dem Pdf Presentation Post Processor, erstellt. Sie können als PDF-Dokument (zip, 105 KB) betrachtet werden.

Inhalt

CORBA Dienste
    1  Einführung
    2  Einige wichtige Dienste (Services)
        2.1  Naming Service
        2.2  Event Service
        2.3  Life Cycle Service
    3  Weitere Dienste
        3.1  Relationship Service
        3.2  Trading Service
        3.3  Externalization Service
        3.4  Persistent Object Service
        3.5  Concurrency Control Service
        3.6  Transaction Service
        3.7  Property Service
        3.8  Licensing Service
        3.9  Object Collection Service
        3.10  Query Service
        3.11  Time Service
        3.12  Security Service
    4  Implementierungen von Diensten
    5  Ausblick
Anhang
    A Abbildungen
    B Tabellen
    C Quellen
 

1  CORBA Dienste

1  Einführung

Während die beiden vorangegangenen Vorträge gezeigt haben, wie das grundsätzliche Modell von CORBA und Implementierungen dafür aussehen, soll es in diesem Kapitel darum gehen, die CORBA Dienste - Common Object Services (COS) genannt - vorzustellen.

Die Object Management Group (OMG; ein Konsortium von IT-Firmen) hat bis jetzt fünfzehn verschiedene Dienste spezifiziert, weitere sollen folgen. Als notwendig erachtete Funktionalitäten wurden dabei durch Requests for Proposals gesammelt und sind nach und nach in die einzelnen Dienste eingegangen.

Jeder CORBA Dienst ist auf eine spezielle Aufgabe zugeschnitten und bietet nur die dafür nötige Funktionalität. Untereinander sollen sie völlig orthogonal sein, d. h. kein Dienst Funktionen bereit stellen, die bereits ein anderer Dienst liefert. Dadurch ist die Implementierung eines Dienstes so wenig komplex, wie nötig. Dagegen kann es durchaus sein, dass ein Dienst einen anderen für gewisse Aufgaben in Anspruch nimmt.

Die CORBA-Dienste sind selbst als CORBA-Objekte mit entsprechenden IDL-Interfaces spezifiziert. Es gibt keine Vorgaben, dass ein ORB bestimmte Dienste anbieten muss, denn dies wiederspräche der Modularität von CORBA. Die meisten ORB-Anbieter liefern zwar selbst einige grundlegende Dienste mit, darüberhinausgehende Module müssen jedoch hinzugekauft werden. Die einzelnen Module werden wie viele andere CORBA-Serverobjekte meist als eigenständiger Prozess gestartet.

Der große Vorteil der standardisierten Dienst-Interfaces ist, dass das dahinterstehende Servermodul völig transparent für die Anwendung ausgetauscht werden kann, etwa weil eine performantere Implementierung benötigt wird, oder weil veränderte Quality of Service Eigenschaften gefordert werden. So ist es auch praktikabel, einen Dienst zunächst selbst (evtl. in eingeschränkter Form) zu implementieren, und ihn später gegen ein Kaufmodul auszutauschen. Einige Dienste sind sogar darauf ausgerichtet von den Anwendungsobjekten selbst implementiert zu werden, sind also als Designhilfen zu verstehen.

Die genauen Spezifikationen aller CORBA Services mit ihren IDL-Beschreibungen ist in [2] nachzulesen. Sie sind über die Website der OMG unter

    http://www.omg.org/
als PDF-Dokumente zugänglich.

nach oben

2  Einige wichtige Dienste (Services)

In diesem Abschnitt werde ich einige grundlegende Dienste herausgreifen und etwas ausführlicher darstellen. Es handelt sich dabei um die Services Naming, Event und Life Cycle, die als erste von Plattformherstellern angeboten wurden. Um die verbleibenden Dienste geht es dann im darauf folgenden Abschnitt 3.

2.1  Naming Service

Es gibt verschiedene Wege, um an Referenzen auf CORBA-Objekte zu gelangen. Eine zentrale Rolle spielen dabei die ORB-Methoden object_to_string und string_to_object, die es ermöglichen, eine Stringrepräsentation eines CORBA-Objektes zu erzeugen bzw. diese wieder in eine gültige Objektreferenz umzuwandeln. Zum Austausch der Stringrepräsentation (z. B. zwischen Server und Clients) bieten sich dann verschiedene Möglichkeiten, z. B. Speicherung in einer Datei, Verschickung per E-Mail etc.

Dieses Vorgehen hat jedoch einige entscheidende Nachteile:

Deswegen hat die OMG einen separaten Dienst zum definierten Austausch von CORBA-Objektreferenzen spezifiziert, den Naming Service.

Aufbau des Naming Service

Die zentrale Komponente des Naming Service ist der sog. Namenskontext, dargestellt durch die IDL-Schnittstelle CosNaming::NamingContext. Innerhalb eines Namenskontextes werden Objekte an eindeutige Namen gebunden. Ein Name setzt sich dabei aus einem eindeutigen Identifikator und einem kind-Attribut zusammen. Letzteres wird vom Naming Service nicht interpretiert und kann zur Beschreibung eines Objektes mit beliebigen applikationsspezifischen Semantiken verwendet werden. NamingContext stellt auch eine Methode new_context zur Erzeugung neuer Namenskontexte bereit.

Da Namenskontexte selbst CORBA-Objekte sind, ist es möglich, Graphen von Namenskontexten aufzubauen. Seine Struktur wird nicht durch den Standard vorgeschrieben, es können also sowohl Bäume als auch zyklische und azyklische Graphen aufgebaut werden. Auf diese Weise lässt sich ein Verzeichnis von beispielsweise nach Funktionsgruppen geordneten Kontexten erzeugen. Falls erwünscht, lässt sich auch eine Verteilung des Namensverzeichnisses über mehrere Domänen realisieren, indem entfernt liegende Namenskontextobjekte eingebunden werden.

Ein Beispiel dafür ist in Abb. 1 zu sehen: im Wurzelkontext sind neben "Objekt_1" auch die drei Unterkontexte A, B und C gebunden, B enthält wiederum A. Objekte dürfen auch in verschiedenen Kontexten auftreten, deshalb ist es erlaubt, dass Kontext E ebenfalls "Objekt_1" enthält. Kontext C ist ein Beispiel für einen (noch) leeren Kontext.

Abbildung 1: Mögliches Aussehen eines Naming-Graphen

Um den Naming Service benutzen zu können, benötigt eine Anwendung eine Referenz auf dessen Wurzelkontext. Diese erhält sie über die ORB-Methode resolve_initial_references:

    CORBA::Object_ptr obj =
        orb->resolve_initial_references("NameService");

    // Umwandlung in einen NamingContext
    CosNaming::NamingContext_var rootContext =
        CosNaming::NamingContext::_narrow(obj);
Sie liefert CORBA::Object::_nil(), falls der Naming Service nicht verfügbar ist. Dem ORB wird der Naming Service in der Regel durch einen Konfigurationsparameter bekannt gemacht. Neben der Nutzung dieser ORB-Methode ist es natürlich trotzdem möglich, Namenskontexte in Form der Stringrepräsentation ihrer Objektreferenzen verfügbar zu machen.

Binden von Objekten
Damit auf ein Objekt (genauer: auf seine zugehörige CORBA-Objektreferenz ) über den Naming Service zugegriffen werden kann, muss es zunächst registriert, also an einen Namen gebunden werden. Dafür stellt das NamingContext Interface die Methode bind mit folgender IDL-Definition zur Verfügung:

  void bind(in Name n, in Object obj)
       raises(NotFound, CannotProceed, InvalidName, AlreadyBound);
Der erste Parameter vom Typ Name ist als Sequenz von Namenskomponenten definiert, die einen Pfad durch das Namensverzeichnis bildet:
    typedef Istring string;
    struct NameComponent {
      Istring id;
      Istring kind;
    };

    typedef sequence <NameComponent> Name;
Dies ermöglicht das Ansprechen beliebig verschachtelter Kontexte mit einem einzigen Methodenaufruf, und sorgt für die einfache Handhabbarkeit von Namensverzeichnissen. Alle in der Sequenz enthaltenen Namenskomponenten, bis auf die letzte, müssen dabei Namen von existierenden Namenskontexten sein. Über den durch sie festgelegten Pfad wird der Kontext spezifiziert, in dem das Objekt an den durch die letzte Namenskomponente definierten Namen gebunden wird. Dieser Name darf im Zielkontext noch nicht eingetragen sein, sonst kommt es zu einer AlreadyBound Exception. Um bereits gebundene Objekte zu ersetzen, kann die Methode rebind verwendet werden, die die selbe Signatur wie bind hat, und deren Aufrufe auch vollständig ersetzen kann.

Beispiel:
Um ein Objekt Obj6 an den Namen "Objekt_6" in den Kontext D (siehe Abb. 1) zu binden, ist ausgehend vom bereits erhaltenen Wurzelkontext rootContext folgendes zu tun:

    // Objekt 6, ein CORBA Objekt
    BeispielKlasse_impl Obj6;
    boa->obj_is_ready(&Obj6);

    // Namenssequenz für "A"/"D"/"Objekt_6" anlegen
    // ("kind" Attribut auf "Objekte" setzen)
    CosNaming::Name theName;
    theName.length(3);
    theName[0].id   = (const char*) "A";
    theName[1].id   = (const char*) "D";
    theName[2].id   = (const char*) "Objekt_6";
    theName[2].kind = (const char*) "Objekte";

    // binden
    rootContext->bind(theName, &Obj6);

    // Obj6 zur Verwendung freigeben
    boa->impl_is_ready();
Das Ergebnis ist der erweiterte Namenskontext D, wie in Abb. 2 zu sehen.

Abbildung 2: Erweiterung von Kontext D

Damit der Naming Service erkennen kann, ob ein gebundenes CORBA-Objekt einen Kontext darstellt, der Teil der Verzeichnisstruktur ist, gibt es die leicht von bind abgewandelte Methode bind_context:

  void bind_context(in Name n, in NamingContext nc)
       raises(NotFound, CannotProceed, InvalidName, AlreadyBound);
Nur durch diese Methode gebundene Namenskontexte werden als solche behandelt, wenn dem Dienst eine Sequenz von Namenskomponenten als Pfad übergeben wird. Die Methode zum Ersetzen bereits gebundener Kontexte heißt analog rebind_context.

Auflösen von Namen
Zum Auflösen von im Namensverzeichnis gebundenen Namen kann die Methode resolve verwendet werden:

    Object resolve (in Name n)
           raises (NotFound, CannotProceed, InvalidName);
Der übergegebene Parameter Name n ist selbst wieder eine Sequenz von Namenskomponenten. Da der Naming Service keine Typinformationen über das zurückgegebene Objekts liefert, muss dieses vom Aufrufer mit einer entsprechenden _narrow Methode in den richtigen Typ umgewandelt werden.

Beispiel:
Um an eine Objektreferenz auf das im vorigen Beispiel unter "Objekt_6" gebundene Obj6 zu gelangen, ist (wieder ausgehend vom Wurzelkontext rootContext) folgendes zu tun:

    // Namenssequenz erzeugen ("kind" Attribut irrelevant)
    CosNaming::Name theName;
    theName.length(3);
    theName[0].id = (const char*) "A";
    theName[1].id = (const char*) "D";
    theName[2].id = (const char*) "Objekt_6";

    // auflösen
    CORBA::Object_ptr obj = rootContext->resolve(theName);

    // in den richtigen Typ umwandeln
    BeispielKlasse meinObj6 = BeispielKlasse::_narrow(obj);

Erzeugen von Namenskontexten
Um neue Namenskontexte zu erzeugen, stellt das Interface NamingContext die folgenden beiden Methoden zur Verfügung:

    NamingContext new_context();

    NamingContext bind_new_context(in Name n)
                  raises(NotFound, AlreadyBound, CannotProceed, InvalidName);
Die Methode new_context() erzeugt einen neues Kontextobjekt, das von dem selben Naming Server gehandhabt wird, wie der Kontext, an dem die Methode aufgerufen wurde. Es kann mittels bind_context an einen Namen gebunden werden.

bind_new_context erzeugt ebenfalls ein neues Kontextobjekt und bindet es gleich im angegebenen Kontext. Dessen Naming Server verwaltet gleichzeitig das Kontextobjekt. Wie bei bind_context stellt der Parameter Name n eine Sequenz von Namenskomponenten dar, deren letzte den Namen angibt, unter dem der neue Kontext gebunden werden soll, während die vorangehenden den Pfad zum Zielkontext beschreiben.

Entfernen von Namen und Namenskontexten
Um einen Namen aus dem Namensverzeichnis zu entfernen, wird die unbind Methode mit der entsprechenden Sequenz von Namenskomponenten aufgerufen:

    void unbind(in Name n)
         raises (NotFound, CannotProceed, InvalidName);
Dadurch wird das zu dem Namen gehörige Objekt selbst nicht verändert.

Hat man alle Namen aus einem Kontext entfernt, kann auch dieser durch Aufruf seiner destroy Methode gelöscht werden. Falls noch Namen in ihm gebunden sind, wird eine NotEmpty Exception geworfen. Bevor ein Namenskontext entfernt wird, sollte er selber aus dem übergeordneten Kontext (falls vorhanden) gelöscht werden.

Auflisten von gebundenen Namen
Um dynamisch erzeugte Kontexte zu durchwandern, sei es, um ihren Inhalt auszugeben, sie zu löschen, oder bestimmte Bindungen (beispielsweise über das kind-Attribute gruppierte) zu filtern, wird noch eine Operation zur Auflistung des Kontextinhalts benötigt. Das NamingContext Interface stellt dafür die Methode list bereit:

    enum BindingType {object, ncontext};

    struct Binding {
        Name binding_name;
        BindingType binding_type;
    };

    typedef sequence <Binding> BindingList;

    void list (in unsigned long how_many,
               out BindingList bl, out BindingIterator bi);
Sie liefert in BindingList bl höchstens die durch how_many spezifizierte Anzahl von Bindungen zurück. Sollten noch weitere im Kontext vorhanden sein, wird zusätzlich der BindingIterator bi zurückgeliefert, der ansonsten nil ist.

Für jede Bindung kann anhand des BindingType binding_type festgestellt werden, ob es sich um ein normales Objekt (=object) oder einen Subkontext (=ncontext) handelt. Über das Name binding_name Attribut kann zudem die Art des gebundenen Objekts (kind) ermittelt werden.

Der BindingIterator hat folgendes IDL-Interface:

    interface BindingIterator {
        boolean next_one(out Binding b);
        boolean next_n(in unsigned long how_many,
                       out BindingList bl);
        void destroy();
    };
Seine Methoden haben folgende Aufgaben:

Lokalisieren des Naming Service
Ein Aufgabe, die es nun noch zu lösen gilt, ist die Bekanntmachung eines Naming Service Prozesses bei den ORBs der Anwendungen, die ihn benutzen wollen, damit er deren Methode resolve_initial_references zur Verfügung steht. Üblicherweise lässt sich der Naming Service so konfigurieren, dass er (bzw. sein ORB) an einer bestimmten Adresse nach Verbindungen "lauscht". Diese Adresse muss dann den Anwendungen nur noch bei ihrem Start mitgeteilt werden (über einen Kommandozeilenparamter, eine Konfigurationsdatei,…). Dadurch wird vermieden, dass zur Verwendung des Naming Service wieder zu Hilfskonstrukten wie der Speicherung der Stringrepräsentation der Referenz auf seine Instanz zurückgegriffen werden muss.

nach oben

2.2  Event Service

Der übliche Weg, um Anforderungen an CORBA-Objekte (Server) zu senden, ist der synchrone Aufruf einer entsprechenden Methode dieses Objekts. Dem Aufrufer (Client) muss also das Objekt bekannt sein, und er muss auf irgendeine Weise eine Referenz darauf erlangen, beispielsweise über den Naming Service. Diese Art der Kommunikation wird auch als eng gekoppelt bezeichnet, weil ein direkter Datenaustausch (über CORBA) zwischen Client und Server besteht . Im Falle von nicht mit oneway gekennzeichneten Methoden blockiert der Aufruf, bis der Server mit dessen Abarbeitung fertig ist. Außerdem muss der Client möglicherweise Exceptions, die auf der Serverseite auftreten können, abfangen und entsprechend behandeln.

Für eine Anzahl von Anwendungen ist es jedoch wünschenswert, die Kommunikation stärker zu entkoppeln. Hier einige Beispiele, wo dies sinnvoll sein kann:

Für derartige Anwendungsfälle hat die OMG den Event Service spezifiziert, über den lose gekoppelte oder Ereignis basierte Kommunikation möglich ist. Dabei wird zwischen Objekten unterschieden, die Ereignisse erzeugen (Suppliers) und denen, die Ereignisse verarbeiten (Consumers).

Die OMG sieht zwei grundlegende Modelle für die Ereignis basierte Kommunikation vor:

Der Basismechanismus für den Austausch von Ereignissen ist zunächst noch nicht entkoppelt, er stützt sich auf die Definition von Interfaces im Modul CosEventComm, die zwischen genau einem Supplier und einem Consumer Objekt ausgetauscht werden, und über die dann die Kommunikation abgewickelt wird. Die beiden folgenden Abschnitte beschreiben jeweils kurz das Push und das Pull Modell, die auch in Abb. 3 gegenübergestellt sind.

Abbildung 3: Push und Pull Modell

Push Modell
Um Kommunikation nach dem Push Modell einzurichten, müssen folgende Objektreferenzen zwischen Consumer und Supplier ausgetauscht werden:

Hier die Signatur der push Methode des PushConsumer Interface:

    void push (in any data) raises(Disconnected);
Wie man sieht, wird ihr ein Parameter vom CORBA Typ any übergeben. Dadurch ist es praktisch möglich, beliebige Datenstrukturen als Ereignisdaten zu verwenden, also sowohl einfache Typen, als auch CORBA Objekte.

Pull Modell

Für Kommunikation nach dem Pull Modell müssen ebenfalls zwei Interfaces ausgetauscht werden:

Die beiden Pull Methoden haben folgende Signaturen:

    any pull () raises(Disconnected);

    any try_pull (out boolean has_event)
        raises(Disconnected);
Mit ihnen kann ein Consumer ein Ereignis vom Supplier abholen. pull blockiert dabei so lange, bis der Supplier ein Ereignis bereitstellen kann. Mit try_pull dagegen kann der Consumer prüfen, ob Ereignisse bereitstehen, und falls ja, das nächste von ihnen abholen. Ist kein Ereignis verfügbar, kehrt der Aufruf unmittelbar zurück und liefert false in has_event. Beide Methoden verwenden wieder den Datentyp any für die Ereignisdaten.

Event Channels
Wie bereits eingangs erwähnt, soll es in diesem Abschnitt um die entkoppelte Kommunikation zwischen Objekten gehen. Um dieses Ziel zu erreichen, spezifizierte die OMG den Event Channel , der den eigentlichen Event Service erbringt.

Der Event Channel wird zwischen Supplier und Consumer gestellt. Beide müssen dadurch nicht mehr von der Existenz des jeweils anderen wissen. Der Event Channel tritt gegenüber dem Supplier als Consumer auf, gegenüber dem Consumer als Supplier. Da die eigentlichen Consumer und Supplier nun unabhängig voneinander mit dem Ereigniskanal verbunden sind, ist es auch möglich, dass sie unterschiedliche Kommunikationsmodelle verwenden, wie in Abb. 4 illustriert [2]. Hier sind ein Push Supplier und ein Pull Consumer über den Ereigniskanal miteinander verbunden (die umgekehrte Variante macht keinen Sinn; beide würden ewig auf eine Aktion des anderen warten). Der Ereigniskanal kann nun die Ereignisse des Suppliers solange zwischenzuspeichern, bis sie von einem Consumer mittels Pull abgeholt werden.

Abbildung 4: Gemischte Kommunikation über einen Event Channel

Ein Event Channel kann darüber hinaus auch n:m Kommunikation ermöglichen, also mehrere Consumer auf der einen Seite mit mehreren Suppliern auf der anderen Seite verbinden. Ob von einem Supplier erzeugte Ereignisse in so einem Fall an alle Consumer übermittelt werden, oder nur an einen, schreibt der Standard nicht vor. Dies ist ein Quality of Service Parameter, der von der jeweiligen Implementierung des Event Service abhängt.

Für praktische Anwendungen wird man in der Regel einen Ereigniskanal für einen bestimmten Typ von Ereignissen verwenden, z. B. für die beispielhaft erwähnte änderung von Konfigurationseinstellungen. Diejenigen CORBA-Objekte, die an diesen Ereignissen interessiert sind, müssen sich bei dem Kanal als Consumer registrieren, während sich die CORBA-Objekte, die die Ereignisse erzeugen, sich als Supplier registrieren. Was dabei zu tun ist, wird im nächsten Abschnitt erläutert.

Benutzung von Event Channels
Um einen Event Channel nutzen zu können, muss ein solcher erst einmal erzeugt werden. Wie dies zu geschehen hat, schreibt CORBA nicht vor. In der Regel muss dazu für jeden einzelnen Event Channel ein eigenes Serverprogramm des Event Service Anbieters gestartet werden, das eine IOR (InterOperable Referenz) auf das EventChannel Objekt als String zurück gibt. Eine andere Möglichkeit ist das Schreiben einer eigenen Serverapplikation, die einen Event Channel über ein Factory-Objekt des Anbieters erzeugt (falls vorhanden).

Das Ergebnis ist in beiden Fällen eine Referenz auf einen Event Channel, die beispielsweise mittels des Naming Service an einen Namen gebunden werden kann. Er ist in diesem Zustand weder mit Suppliern noch mit Consumern assoziiert.

Im Folgenden soll anhand zweier Programmfragmente für einen Push Supplier und einen Pull Consumer (siehe Abb. 4) gezeigt werden, wie das soeben erzeugte EventChannel Objekt von beiden genutzt wird. Zunächst das zugehörige IDL-Interface aus dem Modul CosEventChannelAdmin:

    interface EventChannel {
        ConsumerAdmin for_consumers();
        SupplierAdmin for_suppliers();
        void destroy();
    };

Supplier:
Nachdem der Supplier eine Referenz auf das passende EventChannel Objekt erhalten hat, ruft er dessen suggestiv benannte Methode for_suppliers auf und erhält ein SupplierAdmin Objekt zur Verwaltung seines Zugangs zum Kanal.

    interface SupplierAdmin {
        ProxyPushConsumer obtain_push_consumer();
        ProxyPullConsumer obtain_pull_consumer();
    };
Da ein Push Supplier erstellt werden soll, wird als nächstes obtain_push_consumer aufgerufen. Über das dadurch erzeugte ProxyPushConsumer Objekt tritt der Event Channel als Push Consumer gegenüber dem Supplier auf (engl. proxy = Bevollmächtigter). Es ist von dem bereits besprochenen PushConsumer abgeleitet und definiert nur eine weitere Methode:
    void connect_push_supplier(in CosEventComm::PushSupplier push_supplier)
         raises(AlreadyConnected);
Sie wird mit einem Objekt aufgerufen, das PushSupplier implementiert, ähnlich wie beim Push Modell ohne Event Channel; es hat nur die Funktion, den Supplier über die Beendigung der Verbindung zum Event Channel zu informieren. Dadurch wird der Supplier schließlich mit dem EventChannel verbunden und kann die push Methode des ProxyPushConsumer benutzen, um Ereignisdaten zu übermitteln.

Für das folgende Beispielprogramm (-fragment) wird davon ausgegangen, dass der ORB in orb und eine Referenz auf den EventChannel in channel bereits verfügbar sind.

    // SupplierAdmin holen
    CosEventChannelAdmin::SupplierAdmin supplierAdmin = channel.for_suppliers();

    // unseren PushSupplier erzeugen
    SamplePushSupplier_impl* supplier = new SamplePushSupplier_impl();
    orb->connect(supplier);

    // Proxy holen...
    CosEventChannelAdmin::ProxyPushConsumer channelProxy =
        supplierAdmin.obtain_push_consumer();

    // ...und mit unserem PushSupplier verbinden
    channelProxy.connect(supplier);

    try {
        while (busy) {
            /*
             * Arbeiten...
             */

            // Ereignisdaten erzeugen und als any verpacken...
            char* eventMsg = "EinEreignis";
            CORBA::Any event(_tc_string, eventMsg);

            // ...und abschicken
            channelProxy.push(event);
        }
    }
    catch (Disconnected) {
      // Kommunikation auch von unserer Seite beenden
      channelProxy.disconnect_push_consumer();
    }

    // aufräumen...
Das verwendete SamplePushSupplier_impl Objekt könnte im einfachsten Fall aus einem leeren Methodenrumpf für die vorgeschriebene disconnect_push_supplier bestehen.

Consumer:
Für einen Pull Consumer sind ganz ähnliche Schritte durchzuführen. Er ruft for_consumers des EventChannel Objekts auf und erhält ein ConsumerAdmin Objekt zur Verwaltung seines Zugangs zum Kanal, das analog zu SupplierAdmin aufgebaut ist:

    interface ConsumerAdmin {
        ProxyPushSupplier obtain_push_supplier();
        ProxyPullSupplier obtain_pull_supplier();
    };
Über obtain_pull_supplier wird danach ein ProxyPullSupplier Objekt erzeugt, das von PullSupplier abgeleitet ist und den Event Channel dem Consumer gegenüber vertritt. Es definiert wie ProxyPushConsumer eine neue Methode connect_pull_consumer, zum Verbinden eines PullConsumer Objektes mit dem Event Channel , das die Aufgabe hat, den Consumer über die Beendigung der Verbindung mit dem Event Channel zu informieren.
    void connect_pull_consumer(in CosEventComm::PullConsumer pull_consumer)
         raises(AlreadyConnected);
Nach ihrem Aufruf ist auch der Consumer mit dem Event Channel verbunden und kann bei Bedarf nach Ereignissen fragen.

Hier das Beispielprogramm für den Consumer:

    // SupplierAdmin holen
    CosEventChannelAdmin::ConsumerAdmin consumerAdmin = channel.for_consumers();

    // unseren PullConsumer erzeugen
    SamplePullConsumer_impl* consumer = new SamplePullConsumer_impl();
    orb->connect(consumer);

    // Proxy holen...
    CosEventChannelAdmin::ProxyPullSupplier channelProxy =
        consumerAdmin.obtain_pull_supplier();

    // ...und mit unserem PullConsumer verbinden
    channelProxy.connect(consumer);

    try {
        int eventCount = 0;
        bool has_event;
        char* eventMsg;

        while (countEvent < 10) {
            // versuchen, ein Ereignis zu holen
            CORBA:Any event = channelProxy.try_pull(&has_event)

            if (has_event)
                // falls Ereignis da: Meldung ausgeben
                printf("event\%u: \%s", countEvent++, event.value());
            else
                // sonst: warten
                sleep(60000);
        }

        // Kommunikation nach maximaler Zahl von Ereignissen beenden
        channelProxy.disconnect_push_consumer();
    }
    catch (Disconnected) {
      // Kommunikation auch von unserer Seite beenden
      channelProxy.disconnect_push_consumer();
    }

    // aufräumen...
Auch hier besteht ein minimales SamplePullConsumer_impl Objekt lediglich aus einem leeren Methodenrumpf für disconnect_pull_consumer.

andere Kommunikationsmodelle:
Die Verwendung von Push-Supplier/Push-Consumer bzw. Pull-Supplier/Pull-Consumer unterscheidet sich von dem hier gezeigten Beispiel insofern, dass die Methoden push (von PushConsumer) bzw. pull/try_pull (von PullSupplier) implementiert werden müssen, um Ereignisse auszutauschen.

Typed Events
Neben der bisher gezeigten Verwendung von Ereignissen, die auch generische Ereignisse genannt werden, sieht die Spezifikation typisierte Ereignisse (typed events) vor. Dabei werden bestimmte Ereignistypen mit eindeutigen Schlüsselwerten versehen. Consumer können dann nur die Ereignisse empfangen, deren Schlüssel sie bei der Registrierung am Event Channel angegeben haben.

Die Übermittlung der Ereignissen ist in diesem Fall auch nicht mehr an bestimmte Push oder Pull Methoden gebunden, sondern kann, da zwischen Suppliern und Consumern sowieso das gemeinsame Wissen um den Ereignistyp vorhanden sein muss, über selbst geschriebene IDL-Interfaces abgewickelt werden. Diese müssen selbstverständlich ebenfalls beiderseitig bekannt sein. Die einzige Restriktion der die Methoden eines solches Interface unterworfen sind, ist die strikte Verwendung von in Parametern und des Rückgabewerts void. Das liegt daran, dass Ereignis basierte Kommunikation eben asynchron und unidirektional ist.

Auf typisierte Ereignisse werde ich im Rahmen dieser Arbeit jedoch nicht genauer eingehen.

nach oben

2.3  Life Cycle Service

In verteilten Anwendungen kann es erforderlich sein, Objekte von einem Ort an einen anderen zu verschieben oder zu kopieren, entfernte Objekte zu löschen, oder Objekte auf entfernten Plattformen zu erzeugen. Dies ist z. B. der Fall bei Agentensystemen, in denen Agenten von Plattform zu Plattform wandern, um Daten zu sammeln oder Aktionen auszulösen. Ein weiteres Beispiel ist Load Balancing, wo Objekte so auf verschiedene Maschine verteilt werden, dass deren optimale Auslastung gewährleistet wird.

In CORBA gibt es für diese Zwecke den Life Cycle Service, der Operationen zum Kopieren, Verschieben und Löschen definiert, als auch Wege zur Erzeugung von Objekten auf entfernten Plattformen beschreibt. Seine Interfaces und Typen sind im Modul CosLifeCycle zusammengefasst. Er stellt die Sicht des Nutzers (z. B. Client-Objekte) auf den Lebenszyklus eines bestimmten Objekts dar. Dazu gehören:

Objekte erzeugen
Die wichtigste Operation für den Life Cycle Service ist das Erzeugen von Objekten. Dafür sind so genannte Factory Objekte vorgesehen. Diese sind spezifisch für eine bestimmte Objektklasse und müssen an dem Ort liegen, an dem sie ein derartiges Objekt erzeugen sollen. Da es keinen allgemeinen Aufruf gibt, um Objekte beliebiger Klassen zu initialisieren, gibt es auch keine IDL-Definition für Factory-Objekte. Eine Anwendung kann nun Objekte auf einer entfernten Plattform erzeugen, indem sie über eine Referenz auf ein dort gelegenes Factory Objekt für die jeweilige Klasse, die sie beispielsweise über den Naming Service erhält, eine entsprechende Methode aufruft.

Eine standardisierte Verbindung zu einer entfernten Plattform stellt das Interface FactoryFinder eines dort gelegenen CORBA-Objekts dar. Wie der Name sagt, dient es dem Auffinden von Factory Objekten für bestimmte Klassen. Es bietet nur eine Methode find_factories; seine IDL-Definition sieht folgendermaßen aus:

    interface FactoryFinder {
        Factories find_factories(in Key factory_key)
                  raises(NoFactory);
    };
Es macht Gebrauch von diesen Typdefinitionen:
    typedef CosNaming::Name Key;
    typedef Object Factory;
    typedef sequence <Factory> Factories;
Der Typ Key stützt sich dabei auf das vom Naming Service bekannte Name (siehe 2.1) ab. Wie er für diese Aufgabe zu verwenden ist, ist nicht standardisiert und muss somit für jede Anwendung festgelegt werden. Die OMG kündigte an, in Zukunft weitere Dienste (z. B. den Trading Service, siehe 3.2) in die Spezifikation des FactoryFinders aufzunehmen, gegenwärtig ist dies aber nur für den Naming Service der Fall.

find_factories liefert alle Factory Objekte zurück, die in der Lage sind, den gewünschten Objekttyp zu erzeugen (Anm.: auch wenn der Naming Service eine eindeutige Zuordnung eines Namen zu genau einem Objekt vorsieht, ist es möglich, dass mehrere Factory Objekte gefunden werden, wenn beispielsweise der FactoryFinder in mehreren Basiskontexten danach sucht). Ein FactoryFinder kann also relativ einfach selbst implementiert werden.

Da es wie gesagt kein allgemeines IDL-Interface für Factories geben kann, erfolgt die Kommunikation mit einem Factory Objekt auf implementationsspezifische Art und Weise. Der Aufrufer muss also wissen, womit er es zu tun hat; dies wäre aber auch der Fall, wenn er ein Objekt direkt (lokal) instantiieren würde. Factories werden in der Regel zusammen mit den Objekten, die sie erzeugen sollen, bereitgestellt, da beide eng miteinander verkoppelt sind.

Migration
Objekte, die das angesprochene Kopieren bzw. Verschieben an einen anderen Ort über den Life Cycle Service unterstützen wollen, müssen das Interface LifeCycleObject implementieren:

    interface LifeCycleObject {
        LifeCycleObject copy(in FactoryFinder there, in Criteria the_criteria)
             raises(NoFactory, NotCopyable, InvalidCriteria, CannotMeetCriteria);
        void move(in FactoryFinder there, in Criteria the_criteria)
             raises(NoFactory, NotMovable, InvalidCriteria, CannotMeetCriteria);
        void remove()
             raises(NotRemovable);
    };
wobei der Typ Criteria folgende Definition hat:
    typedef struct NVP {
        CosNaming::Istring name;
        any value;
    } NameValuePair;

    typedef sequence <NameValuePair> Criteria;

copy und move:
Die beiden Methoden copy und move sind ähnlich aufgebaut. Beide erhalten vom Aufrufer die Referenz auf einen FactoryFinder, der sich auf der Zielplattform befindet. Diese wird von den Methoden verwendet, um dort eine Factory zu suchen, die den gewünschten Objekttyp erzeugen kann. Wegen der bereits erwähnten engen Verzahnung zwischen Objektklasse und zugehöriger Factory, erfolgt das eigentliche Erzeugen des Objekts auf implementationsspezifische Weise. Es wird als genaue Kopie des bestehenden Objekts angelegt. Um bestimmte Rahmenbedingungen für das zu erzeugende Objekt vorzugeben, kann der Parameter the_criteria verwendet werden (aber muss nicht). Es sind dafür beliebige Sequenzen von Name/Wert-Paaren möglich, deren Bedeutung für das jeweilige Objekt spezifiziert werden muss, da sie nicht durch den Standard festgelegt werden

Nach erfolgreichem Erstellen des entfernten Objekts, gibt copy eine Referenz darauf zurück. Dagegen wird bei move das lokale Objekt gelöscht und das entfernte fortan über die bisherige Referenz angesprochen (dies wird auch als Migration bezeichnet).

Sollte ein bestimmtes Objekt eine der beiden Operationen nicht unterstützen, so kann es eine der Exceptions NotCopyable bzw. NotMoveable werfen.

Ende des Lebenszyklus
Dafür ist die Methode remove des LifeCycleObject Interface zuständig:
Sie beendet den Lebenszyklus des Objekts, an dem sie aufgerufen wird. Im Zuge dessen sollten von ihm belegte Resourcen freigegeben werden. Falls das Objekt nicht entfernt werden kann oder darf, kann die Exception NotRemovable geworfen werden.

Beispiel zum Life Cycle Service
Im folgenden Beispiel werden die notwendigen Schritte zum Kopieren eines Objekts Obj von Plattform A auf eine entfernte Plattform B kurz besprochen. Abb. 5 gibt eine Übersicht darüber.

Abbildung 5: Vorgänge beim Kopieren eines Objekts

  1. Ausgangszustand: Auf Plattform A existieren das Objekt Obj der Klasse LCSample, deren Interface von CosLifeCycle::LifeCycleObject abgeleitet ist, sowie ein client, der eine Referenz auf Obj besitzt. Auf Plattform B steht ein FactoryFinder Objekt bereit, auf den der client ebenfalls eine Referenz besitzt, und eine LCSampleFactory, die im Naming Service unter ihrem Namen eingetragen ist und Objekte der Klasse LCSample erzeugen kann.
  2. Das client Objekt ruft die von LifeCycleObject geerbte Methode copy von Obj auf und übergibt ihr die Referenz auf den FactoryFinder (B) von Plattform B. Als zweiten Parameter wird nil für die Erstellungskriterien angegeben (mit anderen Worten: es werden keine vorgegeben).
  3. Obj ruft nun find_factories des übergebenen Finders mit dem Namen "LCSampleFactory" auf, welcher seinerseits den Naming Service dafür bemüht. Das Ergebnis ist eine Referenz vom Typ CORBA::Object auf das LCSampleFactory Objekt, welche von Obj durch eine _narrow Operation umgewandelt werden muss.
  4. Eine entsprechende Factory Methode (im Beispiel create_LCSample) kann nun verwendet werden, um auf Plattform B die gewünschte Kopie zu erzeugen. Die genaue Aufrufsemantik hierfür ist nicht mehr Teil des Standards. Ist die Kopie (Obj¢) erzeugt, so wird eine Objektreferenz darauf an den Aufrufer - das client Objekt - zurückgegeben.

Weitere Funktionen des Life Cycle Service
Eine Frage, die sich jetzt noch im Zusammenhang mit dem Kopieren und Verschieben zwischen verschiedenen Plattformen stellt, ist die nach dem Vorgehen bei Objekten, die selbst Relationen zu anderen Objekten besitzen, oder verallgemeinert nach dem Vorgehen bei Graphen von Objekten. Als Antwort darauf hat die OMG zahlreiche Interfaces im Modul CosCompoundLifeCycle nachgeliefert, die sich hauptsächlich auf den Relationship Service (siehe 3.1) abstützen. Je nach Beziehungssemantik gibt es verschiedene Möglichkeiten, die Lebenszyklus-Operationen auf zusammengesetzte Objekte anzuwenden:

Dabei wird speziell zwischen Objekten unterschieden werden, die in einer "enthalten in" Relation stehen, und solchen, auf die nur eine Referenz besteht. Erstere müssen mit dem zentralen Objekt mitkopiert werden; bei letzteren wird der Kopie einfach die weiterhin gültige vorhandene CORBA-Objektreferenz mitgegeben. Wenn jedoch auch das referenzierte Objekt selbst auf Grund einer anderen Beziehung mitkopiert wird, werden Referenzen auf diese Kopie auf der Zielplattform verwendet. Für Enthaltenseins-Relationen (Containment) sind die beiden Rollen "enthält" (ContainsRole) und "ist enthalten in" (ContainedInRole) definiert; für Referenzierungs-Relationen die Rollen "referenziert" (ReferencesRole) und die "referenziert von" (ReferencedByRole).

Für tiefere Einblicke in den Compound Life Cycle sei Anhang A der Life Cycle Service Spezifikation empfohlen, die sich in [2] findet.

Zusammenfassung
Wie die vorangegangenen Abschnitte gezeigt haben, handelt es sich beim Life Cycle Service weniger um ein separates Modul, das die benötigten Operationen für Objekte "von außen" durchführt, als vielmehr um eine Infrastruktur, anhand derer Objekte erstellt werden können, die ihre Migration in standardisierter Form und mittels standardisierter Schnittstellen selbst durchführen. Der eigentliche Dienst wird also durch die beteiligten Objekte selbst realisiert.

Der Vorteil bei dieser Vorgehensweise ist, dass alle Objekte bezüglich Migration auf die gleiche Art behandelt werden können:

3  Weitere Dienste

In den folgenden Unterabschnitten werden die übrigen für CORBA definierten Dienste in etwas kürzerer Form vorgestellt.

nach oben

3.1  Relationship Service

In der objektorientierten Programmierung versucht man Objekte aus der realen Welt durch attributierte Klassen zu modellieren, die über gewisse Operationen verfügen. Diese Objekte stehen häufig in bestimmten Beziehungen mit anderen Objekten. So kann eine Person beispielsweise Dokumente besitzen oder es referenzieren, ein Dokument in einem Ordner enthalten sein, ein Ordner wiederum einer Person gehören,…

Solche Relationen zwischen Objekten können mit Hilfe des Relationship Service modelliert werden. Die in Beziehung stehenden Entitäten spielen dabei bestimmte Rollen in den Relationen.

Modell
Im Unterschied zu unidirektionalen Objektreferenzen, die als Attribute gespeichert werden, werden Beziehungen beim Relationship Service durch Relationsobjekte modelliert. Der Vorteil dabei ist, dass Beziehungen auch von außen verändert werden können, sogar ohne dass die beteiligten Objekte davon erfahren müssen. Das Basisinterface aller Beziehungsobjekte ist dabei CosRelationships::Relation. Beziehungen werden in der Regel auf bestimmte Typen von beteiligten Objekten beschränkt.

Die Objekte, zwischen denen eine Beziehung besteht, werden darin durch Rollenobjekte vertreten, deren Basisinterface CosRelationships::Role ist. Eine Rolle nimmt dabei in der Regel nur an einem Beziehungstyp teil. In wie vielen Beziehungen dieses Typs sie stehen kann, wird durch ihre Kardinalität bestimmt. Die Anzahl von durch Rollen vertretene Objekte, die an einer Relation beteiligt sind, wird als deren Grad bezeichnet. Für beide Werte wird bei der Erstellung der entsprechenden Rollen-/Relations-Objekte ein Minimal- und Maximal-Wert angegeben. Beim Verbinden von Rollen durch Beziehungen kann dann die Integrität geprüft werden. Um einen Graphen von Relationen zu durchwandern, ist es durch die Trennung von Objekt und Rolle nicht notwendig, das Objekt selbst zu aktivieren.

Abb. 6 zeigt ein Beispiel für Relationen zwischen und Rollen von Objekten. Kreise stellen dabei die Rollen der Objekte dar, Rauten die Beziehungen.

Abbildung 6: Beziehungen und Rollen

Nebenbei definiert der Relationship Service auch ein Interface für Objektidentität, CosObjectIdentity::IdentifiableObject. Zwei Objekte, die es implementieren können mit Hilfe seiner Methode is_identical auf Gleichheit untersucht werden.

Dienststufen
Im Relationship Service werden drei Dienststufen definiert:

  1. Grundlegende Beziehungen: Es existieren Beziehungen und Rollen. Beziehungen haben Typ- und Grad-Beschränkung, Rollen Kardinalitätsbeschränkungen. Eine Beziehung gibt Auskunft über beteiligte Rollenobjekte. Die durch Rollen vertretenden Objekte müssen selbst keine bestimmten Interfaces implementieren, und können dadurch ohne Anpassungen in ein Beziehungsschema eingefügt werden.
  2. Relationengraphen: das Modul CosGraphs definiert ein abgeleitetes Role Interface, das in der Lage ist, alle seine Kanten zurückzugeben. Daneben gibt es das Node Interface, das von den in Beziehung stehenden Objekten selbst implementiert wird und dazu dient, alle ihre Rollen preiszugeben. Dadurch ist es möglich, einen (gerichteten) Graphen von Objekten zu durchwandern.

    Zusätzlich ist das Traversal Interface definiert, das Operationen zur Durchquerung eines solchen Graphen bietet.

  3. spezielle Relationen: In den Modulen CosContainment und CosReference werden spezielle Interfaces für Enthaltensein- (1:n) und Referenz-Beziehungen (n:m) definiert.

Eine mit dem Standard konforme Implementierung des Relationship Service ist gehalten, entweder nur Level 1, Level 1 und 2 oder Level 1, 2 und 3 zu implementieren.

nach oben

3.2  Trading Service

Neben dem in 2.1 vorgestellten Naming Service, bietet CORBA noch eine weitere Möglichkeit, Objekte zur Laufzeit zu finden, nämlich den Trading Service. Im Gegensatz zum Naming Service werden hier Objekte aber nicht durch einen bestimmten eindeutigen Namen identifiziert, sondern durch den Vergleich von Eingenschaftswerten bezüglich eines bestimmten Diensttyps - das Ergebnis einer solchen Operation können auch mehrere Objekte sein.

Aufbau des Trading Service
Die zentrale Komponente des Trading Service ist der Trader , der Angebot und Nachfrage zusammenbringt. Er wird von den so genannten Exporteuren benutzt, um ihre Dienste anbieten, und von den Importeuren , um diese Dienste zu beziehen. Da er orthogonal zum Naming Service einsetzbar sein soll, hat die OMG vorgesehen, dass eine Objektreferenz auf den Trading Service über die ORB-Methode resolve_initial_references bezogen werden kann (analog zu dem Beispiel für den Naming Service):

    CORBA::Object_var obj =
        orb->resolve_initial_references("TradingService");

    // Umwandlung in Lookup-Interface des Trading Service
    CosTrading::Lookup_var traderLookup =
        CosTrading::Lookup::_narrow(obj);
Erkennbar ist, dass man dadurch ein CosTrading::Lookup Objekt erhält. Um von dieser für die Importeure gedachten Schnittstelle zu den anderen des Trading Service zu gelangen, sind alle Schnittstellen des Moduls CosTrading von CosTrading::TradingComponents abgeleitet, das entsprechende Attribute definiert.

"Gehandelt" wird dabei nicht mit beliebigen Objekttypen, sondern mit so genannten Service Types, die sowohl den Exporteuren als auch den Importeuren bekannt sein müssen. Jedem Service Type liegt ein IDL-Interface zu Grunde, welches von Exporteuren implementiert wird, und von Importeuren zum Zugriff auf das Dienst erbringende Objekt benutzt wird. Damit ein Dienst beim Trader registriert werden kann, muss zunächst in dessen Service Type Repository eine Beschreibung des zu Grunde liegenden Service Types abgelegt werden. Dies geschieht durch den Aufruf der add_type Methode des Interface CosTradingRepos::ServiceTypeRepository, die folgendermaßen aussieht:

    IncarnationNumber add_type(
        in CosTrading::ServiceTypeName name,
        in Identifier if_name,
        in PropStructSeq props,
        in ServiceTypeNameSeq super_types)
    raises (
      CosTrading::IllegalServiceType, ServiceTypeExists,
      InterfaceTypeMismatch, CosTrading::IllegalPropertyName,
      CosTrading::DuplicatePropertyName, ValueTypeRedefinition,
      CosTrading::UnknownServiceType, DuplicateServiceTypeName);
name gibt dabei den Namen an, unter welchem sich Exporteure dieses Diensttyps registrieren lassen müssen und den Importeure zur Dienstsuche verwenden. if_name ist die CORBA Interface Repository ID, unter der das IDL-Interface des Dienstes zu finden ist. Die möglichen Eigenschaften des Dienstes, welche von den Importeuren zur Suche verwendet werden, werden durch props festgelegt. Es handelt sich dabei um eine Liste, in der jeder Eintrag eine Eigenschaft durch Name (ein String), Datentyp und Modus klassifiziert. Mit dem Modus können beispielsweise Eigenschaften als unveränderbar (PROP_READONLY) oder als obligatorisch (PROP_MANDATORY) definiert werden. Der letzte Parameter, super_types gibt an, von welchen bereits im Repository vorhandenen Diensttypen der hinzuzufügende Dienst abgeleitet ist. Dafür muss auch das IDL-Interface des Dienstes von den IDL-Interfaces aller angegebenen Supertypen abgeleitet sein. Solchermaßen abgeleitete Service Types verhalten sich wie polymorphe Objekte, d. h. ein Subtyp kann bei einer Suche nach dem Basisdienst ebenfalls gefunden werden.

Durch die Verknüpfung des Diensttyps mit einer Interface Reopsitory ID ist Typensicherheit zur Laufzeit gewährleistet - es ist nicht möglich, ein Objekt einer fremden Klasse "einzuschmuggeln", der Importeur erhält vom Trader immer Objekte, die das Dienstinterface bereitstellen.

Ist ein Service Type eingetragen, so können sich Exporteure dafür beim Trader über dessen CosTrading::Register Interface registrieren lassen, also ihren Dienst exportieren. Dafür übergeben sie diesem den Namen des angebotenen Dienstes, eine Liste von beschreibenden Eigenschaften - dabei müssen nur solche Eigenschaften angegeben werden, die im Service Type Repository als obligatorisch angegeben sind -, und als wichtigstes, eine Referenz auf das Dienstobjekt. Die Eigenschaftenliste kann bei Bedarf nachträglich modifiziert werden (falls die Implementierung des Trading Service das zulässt). Es gibt aber auch die Option, bestimmte Eigenschaftswerte als dynamisch festzulegen (z. B. die Menge an Papier, über die ein Drucker noch verfügt). Dann kontaktiert der Trader bei jeder Dienstsuche über ein festgelegtes Interface (CosTradingDynamic::DynamicPropEval) den Dienstanbieter, um den aktuellen Wert zu erfragen.

Ein Client, der einen Dienst eines eingetragenen Typs mit bestimmten Eigenschaften sucht (also ein Importeur ), wendet sich an das CosTrading::Lookup Interface des Traders und setzt über dessen query Methode eine Anfrage ab. Dafür sind folgende Parameter notwendig:

Der letzte Punkt zeigt, dass der Client selbst anhand der Eigenschaften der vom Trader gelieferten Dienstanbieter den für ihn geeignetsten auswählen muss. Wie bereits erwähnt, können Exporteure Eigenschaftswerte auch als dynamisch spezifizieren. In solchen Fällen erhält der Client immer den aktuellen Wert, den der Trader beim Dienstanbieter in Erfahrung bringt.

Abb. 7 zeigt als Beispiel das "typische" Trader-Szenario, in dem verschiedene Drucker mit verschiedenen Eigenschaften in verschiedenen Stockwerken/Zimmern ihre Dienste über den zentralen Trading Service anbieten [1].

Abbildung 7: Beispielszenario für den Trading Service

Zu sehen sind zwei Drucker, die sich über das Register Interface beim Trader registriert haben; der eine (HP520) als "Drucker" Dienst, der andere (Lexmark) als davon abgeleiteter "PSDrucker" Dienst, welcher eine zusätzliche Eigenschaft "PSLevel" besitzt. Auf der Gegenseite sitzt ein Druckauftrag Objekt, das einen Drucker sucht, und nur an desssen "Farben" Eigenschaft interessiert ist. Aus den vom Lookup Interface des Traders erhaltenen Druckern könnte es z. B. die Referenz auf Lexmark auswählen, wenn es einen Drucker mit mehr als 64 Farben suchte.

Trader-Verbände

Um verschiedene Trader zu einem großen verteilten Trader-Verband zusammenzuschließen, gibt es das Interface CosTrading::Link. Die für das Verbinden zuständige Methode ist add_link, welche einen eindeutigen Namen für jeden einzelnen eingebundenen Trader, dessen Lookup Interface, sowie einige Konfigurationsparameter entgegen nimmt. Je nach Konfigurationsparametern bzw. vom Importeur spezifizierter Suchstrategie werden dann bei Dienstsuchen auch externe Trader einbezogen, ähnlich den verteilten Namenskontexten aus 2.1.

Lokalisieren des Trading Service
Das Auffinden des Trading Service für die ORB-Methode resolve_initial_references geschieht analog zum Naming Service, siehe 2.1.

nach oben

3.3  Externalization Service

Der Externalization Service beschreibt Interfaces und Protokolle zur Externalisierung (externalization) bzw. Internalisierung (internalization) von Objekten. Darunter versteht man das umwandeln von Objekten in einen Datenstrom bzw. das Erstellen von Objekten aus einem solchen Datenstrom. Die Datenströme können in einer Datei gespeichert werden, über ein Netzwerk verschickt werden, usw. Es ist u. a. möglich, ein Objekt zu externalisieren, und in einer abgetrennten CORBA-Plattform wieder zu internalisieren. Teil der Spezifikation ist ein genaues Protokoll, wie die Daten im Strom zu organisieren und identifizieren sind.

Der Externalization Service macht Gebrauch vom Life Cycle Service (siehe 2.3) und u.U. auch vom Relationship Service (siehe 3.1). Er ist vergleichbar mit dem aus Java bekannten Mechanismus der ObjectInputStream und ObjectOutputStream Objekte.

Modell
Ein Client, der den Dienst nutzen möchte, muss zunächst einen Datenstrom - repräsentiert durch das Interface CosExternalization::Stream - erzeugen oder eine Referenz auf einen solchen erhalten. Um ein externalisierbares Objekt in den Strom zu schreiben ruft er die externalize Methode des Streams auf und übergibt ihr das Objekt. Soll dagegen ein Objekt aus dem Datenstrom gelesen werden, wird internalize aufgerufen. Diese Methode benötigt eine Referenz auf einen FactoryFinder (vom Life Cycle Service bekannt, siehe 2.3), der benötigt wird, um eine Factory zu finden, die eine Objektinstanz des Typs, der als nächster im Strom bereit steht zu erzeugen. Diese wird dann schließlich mit den Stromdaten initialisiert.

Wie der Name Strom nahelegt, werden die Objekte in genau der selben Reihenfolge aus dem Strom geholt, in der sie hineingeschrieben wurden. "Seek"-Operationen sind nicht möglich.

Objekte, die externalisiert und internalisiert werden können sollen, müssen das Interface CosStream::Streamable implementieren, das die beiden Methoden externalize_to_stream und internalize_from_stream definiert. Diese werden vom Service aufgerufen, wenn ein Client das Objekt mittels externalize bzw. internalize in den Strom schreiben will.

externalize_to_stream bekommt dabei eine Referenz auf den Datenstrom, in den das Objekt seinen internen Zustand schreiben soll. Der Strom, dargestellt durch das Interface CosStream::StreamIO, stellt Methoden bereit, um alle in CORBA definierten primitiven Datentypen zu schreiben. Besitzt das Objekt seinerseits Referenzen auf andere Objekte, so kann es für diese ebenfalls externalize Aufrufen, und sie veranlassen, ihre Daten in den Strom zu schreiben (dafür müssen sie ebenfalls das Streamable Interface implementieren). Für welche referenzierten Objekte dies geschieht, liegt im Ermessen des Objektes (bzw. seines Designers/Programmierers). Dabei muss auch Sorge dafür getragen werden, dass keine Deadlocks auftreten, wenn zwei Objekte sich gegenseitig referenzieren.

Bei der Internalisierung wird, wie bereits erwähnt, eine Factory benutzt, um eine Instanz des nächsten im Strom stehenden Objekttyps zu Erzeugen. Danach wird deren internalize_from_stream Methode mit dem Strom und dem Factory Finder als Argumenten aufgerufen. Das Objekt liest nun seine Zustandswerte in genau der selben Reihenfolge wieder ein, in der sie bei der Externalisierung herausgeschrieben wurden. Für mitgespeicherte Objekte wird wiederum internalize des Stroms mit einer Referenz auf den FactoryFinder aufgerufen.

Behandlung von Relationen
Im Modul CosCompoundExternalization sind vom Relationship Service abgeleitete Interfaces für Node, Role und Relationship definiert, die es ermöglichen, ganze Graphen von Objekten zu externalisieren/internalisieren. Dabei sollte es dann genügen, externalize für ein Knotenobjekt aufzurufen, um alle damit verbundenen Objekte (und die Rollen-/Beziehungsobjekte) in den Strom zu schreiben.

nach oben

3.4  Persistent Object Service

Ein ORB kann zwar Objektreferenzen persistent, d. h. dauerhaft verfügbar machen, er kann aber nicht garantieren, dass für den Objektzustand das selbe gilt. Um auch den Zustand von Objekten persistent machen zu können, also in einem nichtflüchtigen Datenspeicher abzulegen, wurde der Persistent Object Service geschaffen. Die Art des Datenspeichers hängt wie immer von der Implementierung ab, z. B. relationale oder OO Datenbanken.

Komponenten
Die teilnehmenden Objekte des Dienstes sind in Abb. 8 dargestellt [2].

Abbildung 8: Komponenten des Persistent Object Service

Client:
Ein Client des durch das Interface CosPersistencePO::PO dargestellten Persistenten Objektes (PO), der dessen Speicherung veranlassen möchte, muss diesem zunächst einen Persistence Identifier (PID) zuweisen. Ein solches Objekt wird i. A. durch eine Factory des Dienstanbieters erstellt, und identifiziert den zu verwendenden Datenspeicher. Es kann darüber hinaus weitere Eigenschaftswerte aufnehmen, die jedoch nicht Teil des Standards sind. Mit dem PID ruft der Client zunächst die Connect Methode des PO auf, um eine Verbindung mit dem Speicher herzustellen. Ist die Verbindung etabliert, können folgende Methoden des PO zur Zustandsspeicherung verwendet werden:

Nach Beendigung der Aktion wird disconnect aufgerufen, um die Verbindung wieder zu beenden. Anstelle eines externen Clients kann das PO bei Bedarf auch selbst Persistenzoperationen ausführen.

PO und POM:
Die Aufrufe des Clients leitet das PO an die gleichnamigen Methoden des Persistent Object Manager (POM) weiter, der anhand des PID die Zuordnung zwischen PO und Datenspeicher vornimmt. Dazu gibt er eine Referenz auf das PO zusammen mit dessen PID an den richtigen Persistent Data Service (PDS) - das Interface für den Datenspeicher - weiter. Bei store und restore Operationen kommuniziert der PDS schließlich direkt mit dem PO, unter Verwendung eines durch den Dienstanbieter festgelegten Protokolls. Die OMG spezifiziert für den Persistent Object Service einige Protokoll-Interfaces, die den Datenaustausch zwischen PO und PDS übernehmen.

nach oben

3.5  Concurrency Control Service

Ein Problem, das in Anwendungen mit mehr als einem Ausführungsstrang genauso wie in verteilten Anwendungen, wie sie durch CORBA unterstützt werden, auftaucht, ist der konkurrierende Zugriff auf Resourcen, die nur beschränkt nutzbar sind. Dies ist der Aufgabenbereich des Concurrency Control Servie. Er führt so genannte Sperren (eine Art von Semaphoren) für Resourcen ein, um den Zugriff darauf zu kontrollieren, und gegebenenfalls zu serialisieren. Diese arbeiten im Prinzip genau so wie die Sperrmechanismen, die in vielen Datenbanken zum Einsatz kommen. Der Dienst ist so ausgelegt, dass er mit dem Transaction Service zusammen verwendet werden kann (siehe 3.6).

Sperrmodi
An Sperrmodi stehen die üblichen R- (Read) und W-Sperren (Write) zur Verfügung, wobei viele gleichzeitige Leser zulässig sind, aber nur ein exklusiver Schreiber. Um Deadlocks zu vermeiden, die entstehen, wenn mehrere Clients zunächst eine Lesesperre setzen, um danach in den Schreibmodus zu wechseln (wobei sie sich unauftrennbar gegenseitig blockieren), gibt es auch U-Sperren (Upgrade), welche wiederum mit anderen U-Sperren unverträglich sind. Clients, die evtl. nach dem Lesen eine Schreiboperation durchführen wollen, setzen anstatt der R-Sperre eine U-Sperre, die das exklusive Lesen erlaubt, und erweitern sie bei Bedarf zur W-Sperre.

Eine weitere Art von Sperrmodi wird angeboten, um die typischen hierarchischen Beziehungen zwischen Sperren verschiedener Granularität auszunutzen. Dies sind IR (Intention Read) und IW (Intention Write). Ein Beispiel, wo derartige Sperren verwendet werden, sind heutige Datenbanksysteme. Um einen Datensatz zu verändern müsste ein Client ohne diese Sperren eine Schreibsperre auf die gesamte Datenbank setzen, denn wenn er sie nur auf den Datensatz legt, könnte ein anderer Client, der beispielsweise die ganze Datenbanktabelle des Datensatzes auslesen möchte eine Lesesperre auf der höheren Hierarchiestufe setzten - ein Konflikt mit der Schreibsperre. Stattdessen legt der Client mit dem Schreibwunsch eine IW Sperre auf die Datenbank, dann auf die Tabelle, und schließlich eine W-Sperre auf den Datensatz. So bemerkt der Client mit dem Lesewunsch frühzeitig, dass er warten muss.

Tabelle 1 gibt einen Überblick über die Verträglichkeit verschiedener Sperrmodi. Mit X gekennzeichnete Matrixelemente stehen für Unverträglichkeit (ein Client, der eine solche Sperre setzen will, muss warten, bis alle unverträglichen Sperren entfernt wurden) [2].

 angeforderter Modus
aktueller Modus IR R U IW W
No Lock (NL)      
Intention Read (IR)     X
Read (R)    X X
Upgrade (U)   X X X
Intention Write (IW)   X  X
Write (W) X X X X X

Tabelle 1: Verträglichkeit von Sperren

Zwei-Phasen Sperrung
Bei Zusammenarbeit mit dem Transaction Service wird üblicherweise das so genannte Two-Phase Transactional Locking Schema angewandt, bei dem in der ersten Phase - der Wachstumsphase - Sperren gesammelt werden, und erst in der zweiten Phase - der Schrumpfphase - alle auf einmal freigegeben werden.

Umsetzung in Schnittstellen
Die Schnittstelle für eine Sperre im Dienstmodul CosConcurrencyControl ist LockSet. Der Zusatz Set (engl. lock set = Sperrensatz) rührt daher, dass mehrere Clients Sperren des gleichen Typs auf eine Resource halten können, sofern dieser Typ es zulässt (siehe Tabelle 1). Es bietet folgende Methoden zum Zugriff auf den Sperrensatz:

Dieses Interface ist für Clients gedacht, die implizite Transaktionen verwenden, in diesem Fall wird eine Sperre mit der aktuellen Transaktion verknüpft, oder für solche, die ohne Transaktionen arbeiten. Bei letzteren wird die Sperre an den aufrufenden Thread geknüpft. Für transaktionale Clients steht desweiteren die Methode get_coordinator, die als Eingabe das Kontrollobjekt der Transaktion erhält (CosTransactions::Coordinator), und einen LockCoordinator zurückliefert, der verwendet werden kann, um bei Transaktionsende alle Sperren des Satzes freizugeben.

Für Clients, die explizite Transaktionen verwenden, gibt es das TransactionalLockSet Interface, das über genau die selben Methoden wie LockSet verfügt, die aber alle in einem zusätzlichen Parameter das Transaktionskontrollobjekt erwarten.

Um Sperrensätze beider Typen zu erstellen, wird das LockSetFactory Interface verwendet. Die erzeugten Sätze können dabei eigenständig angelegt werden, oder mit existierenden so verknüpft werden, dass sie ihre Sperren gleichzeitig freigeben.

nach oben

3.6  Transaction Service

Damit verteilte Objekte Aktionen ohne nach außen sichtbare Zwischenzustände durchführen können, und um diese bei Bedarf wieder rückgängig machen zu können, wurde der Transaction Service eingeführt. Er stellt umfassende Möglichkeiten bereit, transaktionale Objekte zu koordinieren, und sorgt bei entsprechender Verwendung für die Einhaltung der üblichen ACID-Bedingungen:

Um Resourcen gegenüber konkurrierenden Transaktionen zu sperren, ist der Dienst auf enge Zusammenarbeit mit dem Concurrency Control Service ausgelegt (siehe 3.5)

Transaktionen verwenden
Der einfachste Weg, Transaktion zu steuern, ist die Verwendung eines Objektes vom Typ CosTransactions::Current, welches von CORBA::Current abgeleitet ist und die transaktionale Sicht des ORB-Kontextes darstellt. Eine Referenz auf ein solches mit dem Ausführungsthread verknüpftes Objekt kann durch die ORB-Methode resolve_initial_references erhalten werden:

    CORBA::Object_var obj =
        orb->resolve_initial_references("TransactionCurrent");

    // Umwandlung in Current Objekt
    CosTransactions::Current_var current =
        CosTransactions::Current::_narrow(obj);
Mit seiner Methode begin kann eine mit dem Thread assoziierte Transaktion gestartet werden, während sie mit commit bzw. rollback erfolgreich beendet bzw. abgebrochen wird. Die Transaktion wird bei Aufrufen von Methoden anderer transaktionaler CORBA-Objekte automatisch auch mit deren Kontext verbunden.

Transaktionen können aber auch durch eine TransactionFactory des Dienstanbieters erstellt werden. Deren create-Methode liefert ein CosTransactions::Control Objekt zurück, über das wiederum die Transaktion gesteuert wird. Es ist in der Lage, Objektreferenzen zu liefern, mit denen commit und rollback Operationen durchgeführt werden können. Um den Transaktionskontext an andere transaktionale Objekte weiterzureichen, wird diesen eine Referenz auf das Control Objekt übergeben.

Zustände verwalten
Das Ziel von Transaktionen, Zustände von bestimmten Objekten bei Abbruch (rollback) der Transaktion wiederherzustellen, wird dadurch erreicht, dass derartige Objekte beim Coordinator Objekt der Transaktion, welches über das Control Objekt zu beziehen ist, ein Resource Objekt registrieren. Dieses Objekt wird dann vom Ende der Transaktion informiert, sodass im Falle von commit der Zustand übernommen werden kann, während bei rollback der vorherige wiederhergestellt werden muss.

nach oben

3.7  Property Service

Der Property Service bietet die Möglichkeit, Mengen von Eigenschaftswerten in einem vom Service bereitgestellten Objekt zu speichern. Eine Eigenschaft (engl. property) wird durch ein Name-Wert-Paar gebildet. Der Name ist ein normaler String und dient als Schlüssel, während der eigentliche Wert durch einen any dargestellt wird. Der Typ, den der any kapselt, muss jedoch eindeutig sein, und wird beim Einfügen überprüft.

Die OMG gibt nicht vor, zu welchem Zweck der Property Service eingesetzt werden soll, oder wie Eigenschaften an ein bestimmtes Objekt geknüpft werden; dies ist immer eine applikationsspezifische Entscheidung. Mögliche Beispiele sind globale Umgebungsvariablen für eine Anwendung, Konfigurationseinstellungen für bestimmte Module, oder eben Kennzeichnungen bestimmter Eigenschaften auf Objektebene.

Dazu definiert der Service im Modul CosPropertyService die Interfaces PropertySet und das davon abgeleitete PropertySetDef mit den zugehörigen Factories PropertySetFactory und PropertySetDefFactory. Der Unterschied zwischen PropertySet und PropertySetDef ist lediglich, dass letzterer zusätzlich die Angabe folgender Attribute für jeden Eigenschaftswert zulässt:

Beide Factories bieten die Möglichkeit, leere PropertySets, oder solche mit Vorgaben bezüglich erlaubten Typen und erlaubten Eigenschaftsnamen zu erzeugen. Darüberhinaus können auch so genannte initiale PropertySets erstellt werden, die mit Eigenschaftswerten und Rahmenbedingungen versehen sind, die allein vom Dienstanbieter abhängen.

Mit den hier aufgezählten Eigenschaften stellt der Property Service so eine Art Naming Service dar, der allerdings keine Objektreferenzen sondern typisierte Eigenschaftswerte speichert und zurückliefert.

nach oben

3.8  Licensing Service

Für viele Softwarepakete ist es nicht ausreichend, Lizenzen lediglich auf vertraglicher Basis zu erteilen, die strikte Einhaltung von Lizenzbedingungen soll zusätzlich auch mit technischen Mitteln erreichbar sein; zu diesem Zweck wurde der Licensing Service entworfen. Er bietet eine Infrastruktur, die Hersteller von CORBA-Anwendungen verwenden können, um ihre speziellen Bedürfnisse bezüglich der Nutzung ihrer Software umzusetzen.

Dynamischer Lizensierungsmechanismus
Das Konzept der OMG sieht vor, dass sich ein Softwaremodul, für das die Lizensierung überprüft werden soll, bei seinem Start bei einem zentralen Lizensierungsmanager anmeldet, und von diesem einen spezifischen Lizensierungsserver zugewiesen bekommt. Bei diesem frägt es nach, ob seine Ausführung erlaubt werden soll, oder nicht. Wird die Ausführung gewährt, kann ein Zeitintervall für regelmäßige Rückmeldungen vereinbart werden, um sicherzustellen, dass keine der beiden Seiten abgestürzt ist oder voneinander getrennt wurde. Nach getaner Ausführung meldet sich das Softwaremodul schließlich wieder beim Lizensierungsserver ab. Was hier mit Softwaremodul bezeichnet ist, könnte sowohl eine ganze Applikation, eine Teilkomponente davon, als auch eine einzelne Methode sein, je nach Anforderung des Anbieters.

Durch unterschiedliche Implementierungen der Lizensierungsserver, sind beispielsweise folgende Lizensierungsmodelle möglich:

Jeder Aufruf zwischen Softwaremodul und Lizensierungsmanager und -server wird dabei durch ein Challenge-Verfahren (basierend auf einem gemeinsamen Geheimnis) geschützt, um sicherzustellen, dass dem System kein falscher Server untergeschoben werden kann, der Lizenzen immer erteilt.

Umsetzung
Die Dienst-Interfaces sind im Modul CosLicensingManager untergebracht. Der Lizenzierungsmanager wird durch LicenseServiceManager beschrieben und hat lediglich eine Methode:

    ProducerSpecificLicenseService obtain_producer_specific_license_service (
        in string producer_name,
        inout Challenge challenge
    )
    raises ( InvalidProducer, InvalidParameter );
Das Softwaremodul identifiziert sich bei ihm mit dem String producer_name und erhält das ProducerSpecificLicenseService Interface seines zugehörigen Lizensierungsservers, welches aber nur akzeptiert wird, wenn der challenge Parameter korrekt ist. Es definiert drei Methoden:

Die Verwendung der hier vorgestellten Dienstinterfaces hat einen entscheidenden Vorteil: für verschiedene Konsumenten können unterschiedliche Lizensierungsmodelle verwendet werden, ohne den Code der zu lizensierenden Software selbst ändern zu müssen. Es müssen lediglich angepasste Implementierungen der Lizensierungsserver verteilt werden.

nach oben

3.9  Object Collection Service

Der Object Collection Service bietet eine umfassende Sammlung von Interfaces für Kollektionen (Collections) von Objekten. Kollektionen dienen dem Speichern von beliebigen Datentypen (also any Werte), z. B. Records oder Objekte. Dabei gibt es verschiedenste Ausprägungen: ungeordnete/geordnete, über Schlüssel arbeitende (z. B. Key Bags, eine Art Hashtable), mengenartige (jedes Element kommt nur einmal vor), auf bestimmte Typen beschränkte,… Der Zugriff auf die darin enthaltenen Elemente kann über Iteratoren erfolgen, von denen ebenfalls einige definiert sind, als auch über Elementnummern. Kollektionen werden über zugehörige Factories erzeugt (sind jedoch nicht von CosLifeCycle::LifeCycleObject abgeleitet). Der Dienst besteht also in der Bereitstellung dieser für viele Zwecke nützlichen Klassen auch für CORBA.

nach oben

3.10  Query Service

Um Operationen auf umfangreichen Objektverbänden ausführen zu können, wurde der Query Service eingeführt. Der Ausdruck "Query", also "Anfrage", ist dabei eigentlich nicht korrekt, den tatsächlich sind damit sowohl abfragende als auch verändernde Operationen gemeint.

Bestandteile
Die durch den Service verarbeiteten Objektverbände können in so genannten Collections verwaltet werden, die im Modul CosQueryCollection definiert sind, ähnlich den durch den Object Collection Service (siehe 3.9) definierten - letzterer wurde aber erst ein Jahr später spezifiziert. (Beide Formen seien im Großen und Ganzen kompatibel, behauptet die OMG, aber meiner Ansicht nach widerspricht diese Doppelspezifikation etwas dem Orthogonalitätsprinzip der CORBA Services). Im Gegensatz zum Object Collection Service, wird beim Query Service nur eine generische Collection (mit ebenso generischen Methoden) definiert, nicht deren unzählige spezifische Ausprägungen. Der Datentyp der gespeicherten Werte ist aber auch hier any, sodass also beliebige Daten und nicht nur Objekte gespeichert werden können.

Wie gesagt, Collections können verwendet werden, es ist aber genauso gut möglich, beliebige andere (in any verpackte) Datenstrukturen und Objekte zu verwenden, abhängig von der Implementierung des Dienstes.

Der eigentliche Abfragedienst wird durch Objekte mit den Interfaces aus CosQuery erbracht. Eine Abfrage wird dabei durch ein Prädikat in Stringform beschrieben. Die OMG schreibt vor, dass jede Dienstimplementierung als Abfragesprache mindestens SQL-92 oder OQL-93 bereitstellt, und somit also einen geeigneten Parser beinhalten muss. Der Dienst unterscheidet zwei Dienststufen:

  1. Die Basisstufe wird durch QueryEvaluator und QueryableCollection erbracht. QueryEvaluator stellt die Methode evaluate bereit, die ein Abfrageprädikat entgegennimmt, und daraus ein Ergebnis in Form eines any generiert. Die Verknüpfung mit einer Datenquelle, die der Evaluator implizit für die Generierung des Ergebnisses verwendet, ist im Standard nicht beschrieben, und hängt somit von der partikulären Dienstimplementierung und der Applikation ab. Auch der als any gekapselte Ergebniswert muss von der Applikation "richtig" verstanden werden.

    Das QueryCollection Interface ist abgeleitet von CosQueryCollection::Collection und QueryEvaluator, und stellt ein Abfrageergebnis dar, auf das weitere Abfragen angewandt werden können.

  2. Die erweiterte Stufe besteht aus Query und dem von QueryEvaluator abgeleiteten QueryManager. Letzterer verfügt über eine zusätzliche Methode create, die ein Abfrageprädikat als Eingabe erhält, und ein daraus ein Query Objekt erzeugt. Dieses kann dann so ähnlich wie die von Datenbanken bekannten vorkompilierten Anweisungen für mehrfache Abfragen verwendet werden, und stellt Methoden zum Vorbereiten und Ausführen derselbigen bereit, sowie zum Erhalt des Abfrageergebnisses, wieder als any Wert.

    Auch hier hängt die implizite Verknüpfung mit einer Datenquelle (z. B. eine Datenbank) und die Art des Ergebnisses von der Implementierung ab.

nach oben

3.11  Time Service

Um die Reihenfolge bzw. den zeitlichen Abstand von Ereignissen feststellen zu können und zeitgesteuert Aktionen auslösen zu können, wurde der Time Service eingeführt. Er stützt sich auf den Universal Time Coordinates (UTC) des X/Open DCE Time Service ab. Die Zeit wird dabei in Einheiten von 100ns Länge gemessen, die seit dem 15. Oktober 1582 00:00:00 verstrichen sind. Mit 64 Bit Werten lassen sich so Zeitpunkte bis etwa ins Jahr 30.000 darstellen.

Die zu Grunde liegende Zeitquelle des Dienstes hat folgende garantierte Eigenschaften:

Basisobjekte des Dienstes sind CosTime::UTO (Universal Time Object) und CosTime::TIO (Time Interval Object). UTO stellt einen Zeitpunkt dar und verfügt über Operationen zum Vergleich mit einem anderen Zeitpunkt, zum Berechnen des Intervalls zu einem anderen Zeitpunkt und zum Erhalten eines Fehlerintervalls, in dem der tatsächliche Zeitpunkt liegt. TIO stellt ein solches Zeitintervall dar und bietet Operationen, um festzustellen ob ein Zeitpunkt (UTO) im Intervall liegt, und ob sich zwei Intervalle überlappen.

UTOs und TIOs werden über das CosTime::TimeService erzeugt, welches Methoden zum Erhalt der aktuellen Zeit, zur Konstruktion eines UTO mit vorgegebener Zeit, und zur Konstruktion eines TIO mit vorgegebenen Intervallgrenzen definiert.

Timer Events
Das Modul CosTimerEvent definiert das Interface TimerEventService, über das zeitgesteuert Ereignisse generiert werden können. Dazu wird der seiner register Methode ein vom Event Service bekannter CosEventComm::PushConsumer übergeben, hinter dem beispielsweise ein Event Channel (siehe 2.2) stehen kann, sowie ein any-Wert. Der Dienst unterstützt dabei nur das Push Modell; obwohl es zwar sinnlos erscheinen mag, ist es jedoch wie beim Event Service beschrieben, Pull Consumer auf der Empfängerseite des Event Channel zu betreiben, die dann nur festzustellen können, ob ein bestimmter Zeitpunkt bereits überschritten wurde. Der any-Wert wird als Ereignisdatum verwendet, sodass der Consumer, der das Ereignis erhält, unterscheiden kann, welches Zeitereignis vorliegt. Ereignisse können sowohl zu festen Zeitpunkten getriggert werden, als auch periodisch.

nach oben

3.12  Security Service

Gerade in verteilten Anwendungen, bei denen teilweise sicherheitskritische Daten über Netzwerke oder sogar durch das Internet transportiert und gespeichert werden, ist Sicherheit - nicht im Sinne von Datensicherheit sondern eher von Vertraulichkeit - mit Sicherheit ein äußerst wichtiger Aspekt. Der Security Service der OMG nimmt deswegen auch einen großen Teil der CORBAservices Spezifikation ein, nämlich etwa 25%. Es würde den Rahmen dieser Ausarbeitung sprengen, ihn ganz vorzustellen, deswegen beschränke ich mich auf die Aufzählung der durch ihn abgedeckten Schlüsselaspekte von Sicherheit:

Anwendungsentwickler müssen an sicherheitskritischen Stellen Aufrufe an den Security Service einbauen, um Sicherheit zu gewährleisten.

nach oben

4  Implementierungen von Diensten

Abschließend gebe ich zu jedem der bisher aufgeführten Dienste einige Beispiele für Implementierungen an. Die Spalte ORB von Tabelle 2 enthält Object Request Broker, die den jeweiligen Dienst selbst mitbringen, die Spalte extern dagegen als externe Module erhältliche Dienste. Dienste, die soweit mir bekannt (zumindest für private/nicht-kommerzielle Nutzung) frei verfügbar sind, sind kursiv geschrieben.

Service ORBs extern
Naming Arachne, BEA WebLogic, ChorusORB, Component Broker/DSOM, CORBAplus, Distributed Smalltalk, Electra, GemORB, JacORB, JavaIDL, JavaORB, JBroker, LiveContent BROKER, MICO, ObjectDirector, omniORB2, ORBacus, ORBexpress, ORBit, Orbix, SmalltalkBroker, TAO, Tatanka, TIB/ObjectBus, VisiBroker, Voyager ORB DSTC Scalable Naming Service, jNames, OpenFusion Naming Service, ORBacus Names, TRC Naming Service
Event Component Broker/DSOM, CORBAplus, Distributed Smalltalk, Electra, GemORB, JacORB, JavaORB, LiveContent BROKER, MICO, ObjectDirector, ORBacus, Orbix, omniORB2, Secant Extreme Enterprise Server, SmalltalkBroker, TAO, Tatanka, TIB/ObjectBus, VisiBroker NetEvents, OpenFusion Event Service
Life Cycle Arachne, Component Broker/DSOM, GemORB, ObjectDirector, SmalltalkBroker, TAO, Tatanka OpenFusion Lifecycle Service
Relationship Arachne, CORBAPlus, ObjectDirector, Tatanka Enabled Systems Persistent Relationship Service, OpenFusion Relationship Service
Trading CORBAplus, JacORB, JavaORB, ORBacus, Orbix, TAO, Tatanka DSTC Object Trader, JTrader, OpenFusion Trading Service, ORBacus Trader, TOI, Trader
Externalization Arachne, Component Broker/DSOM, ObjectDirector, Tatanka  
Persistent Object GemORB, JavaORB, ObjectDirector, Secant Extreme Enterprise Server, Tatanka Secant Extreme Persistent Object
Concurrency Control Component Broker/DSOM, Distributed Smalltalk, GemORB, ObjectDirector, Secant Extreme Enterprise Server, TAO, Tatanka OpenFusion Concurrency Service
Transaction BEA Weblogic, Component Broker/DSOM, CORBAplus, Distributed Smalltalk, GemORB, JacORB, JavaIDL, JavaORB, LiveContent BROKER, Object Director, omniORB2, Orbix, Secant Extreme Enterprise Server, Smalltalk Broker, Tatanka, VisiBroker, Voyager ORB CORBAplus Transaction Service, Java Transaction Service, JTSArjuna, OTSArjuna, TPBroker, VisiBroker ITS
Licensing ObjectDirector SilkMeter
Property Arachne, JavaORB, MICO, omniORB2, ORBacus, TAO, Tatanka OpenFusion Property Service, Property Service (Carsten Zerbst)
Query Component Broker/DSOM, ObjectDirector  
Time MICO, ORBexpress, Tatanka OpenFusion Time Service, Time Service (Carsten Zerbst)
Security Component Broker/DSOM, JavaORB, JBroker, LiveContent BROKER, Orbix, Secant Extreme Enterprise Server, Tatanka, Voyager ORB ORBAsec, SecureBroker
Collection JavaORB OpenFusion Collection Service

Tabelle 2: Implementierungen von CORBA Services

Die Liste erhebt keinerlei Anspruch auf Vollständigkeit; sie soll lediglich einen Überblick geben.

nach oben

5  Ausblick

Zu den in diesem Kapitel beschriebenen Diensten sollen sich nach dem Willen der OMG noch eine Reihe weiterer gesellen. So erwähnt sie in der mir vorliegenden Spezifikation vom November 1997 die zehn weiteren möglichen Dienste Archive, Backup/Restore, Change Management (Versioning), Data Interchange, Implementation Repository, Internationalization, Logging, Recovery, Replication und Startup, von denen allerdings noch keiner verwirklicht wurde; der Event Service dagegen wurde bereits um einen Notification Service erweitert. Diese "Flut" von Diensten bringt zwar den Vorteil mit sich, dass viele häufig auftretenden Probleme durch eine Standardvorgehensweise gelöst werden (können), hat aber auch gewisse Nachteile:

nach oben

Anhang

A  Abbildungen

    1  Mögliches Aussehen eines Naming-Graphen
    2  Erweiterung von Kontext D
    3  Push und Pull Modell
    4  Gemischte Kommunikation über einen Event Channel
    5  Vorgänge beim Kopieren eines Objekts
    6  Beziehungen und Rollen
    7  Beispielszenario für den Trading Service
    8  Komponenten des Persistent Object Service

B  Tabellen

    1  Verträglichkeit von Sperren
    2  Implementierungen von CORBA Services

C  Quellen

[1 Keith Duddy Andreas Vogel, JAVA programming with CORBA, 2 ed., Object Management Group, 1998.
[2 Object Management Group, CORBAservices: Common object services specification, 1997.

nach oben

File translated from TEX by TTH, version 2.69.
On 12 Nov 2000, 19:43.