Corba in 14 Tagen: Inhaltsverzeichnis Danksagung 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
Tag: CORBA kennenlernen Tag: Die Architektur von CORBA Tag: Beherrschen der Sprache Interface Definition Language (IDL) Tag: Entwicklung einer CORBA-Anwendung Tag: Entwerfen des Systems: ein Schnellkurs in objektorientierter Analyse und Entwurfstechnik Tag: Implementierung grundlegender Anwendungsfunktionen Tag: Verwendung von Exceptions zur Fehlerprüfung Tag: Hinzufügen von Geldautomat-Fähigkeit Tag: Verwendung von Callbacks zum Hinzufügen von Push-Fähigkeit Tag: Lernen über CORBA-Entwurfsfragen Tag: Verwendung der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII) Tag: CORBAservices und CORBAfacilities Tag: Entwicklung von Internet-Anwendungen mit CORBA und Java Tag: Einrichten von Web-Fähigkeit für das Beispiel Bank mit Java Anhang A: Antworten auf die Quizfragen und Lösungen zu den Übungen Anhang B: CORBA-Werkzeuge und -Dienstprogramme Anhang C: Was steht bevor? Die Zukunft von CORBA
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Voraussetzungen
Danksagung An allererster Stelle möchte ich mich bei den hilfsbereiten Mitarbeitern von Sams Publishing ganz herzlich dafür bedanken, daß sie mir das Schreiben dieses Buches ermöglicht haben. Mein besonderer Dank gilt dabei meinem Redakteur Steve Straiger für seine großzügige Auslegung des Zeitplans bei der Erstellung dieses Buchs. Dann möchte ich meinem persönlichen Freund und Kollegen, Michael Jones, danken, der mich ermutigte, die Herausforderung zum Schreiben dieses Buches anzunehmen. Dank gebührt weiterhin noch meinen übrigen Kollegen bei Hughes Information Technology Systems, die so liebenswürdig waren, meine äußerst unregelmäßigen Arbeitszeiten (vor allem kurz vor der Abgabe des Entwurfs) ohne Murren zu tolerieren. Anerkennung verdienen auch die Mitarbeiter bei Visigenic Software für ihre hervorragende Software und dafür, daß sie mir diese für einen ausreichenden Zeitraum zum Ausprobieren zur Verfügung gestellt haben. Abschließend möchte ich noch Stonehenge Internet und GTE Internet wegen der Bereitstellung stets zuverlässiger Internet-Dienste - auf die man im heutigen Informationszeitalter beim Schreiben eines Buches nicht mehr verzichten kann - lobend erwähnen.
Der Autor Jeremy Rosenberger wurde in Toledo, Ohio, geboren und wuchs dort auch auf. Er legte sein Examen in Computerwissenschaften und -technik an der Cornell University in Ithaca, New York, ab. Seine weiteren Wirkungskreise waren Boston und St. Louis; heute lebt er zusammen mit seiner Frau Camilla in Denver. Jeremy Rosenberger arbeitet derzeit als Software-Ingenieur bei Hughes Information Systems in Aurora, Colorado, wo er Anwendungen entwickelt, und nutzt dabei eine Vielzahl von Werkzeugen, wie z.B. C++, CORBA, Java sowie Technologien für Software-Agenten. Er ist Mitentwickler von
Javology (http://www.javology.com/) und trug mit zwei Kapiteln zum Buch Special Edition Using JavaBeans (erschienen 1997 bei Que/Ziff-Davis Press) bei, das die Verwendung von Java zusammen mit CORBA behandelt. Er ist auch Autor eines Ausblicks zum zukünftigen Einsatz der Java-Plattform in den Unternehmen. Ferner plant er die Gründung einer Beratungsfirma, die Kunden in der Region Denver betreuen soll. Wenn er nicht gerade Software entwickelt oder sich dem Schreiben seines neuesten und größten Buchs widmet, verbringt Jeremy Rosenberger die Zeit zusammen mit seiner Frau, außerdem baut er gern Modellautos, er komponiert und musiziert, und er spielt Volleyball. Gelegentlich treibt es ihn auch zur sportlichen Betätigung in die Höhen der Rocky Mountains. Jeremy Rosenberger verfügt über eine Vielzahl von E-Mail-Adressen, ist aber immer unter
[email protected] erreichbar.
Einführung Willkommen bei SAMS CORBA in 14 Tagen! Dieses Buch eröffnet Ihnen die Geheimnisse der Entwicklung von verteilten Anwendungen mit Hilfe von CORBA (Common Object Request Broker Architecture). In einem Zeitraum von zwei Wochen werden Sie die in Grundkonzepte von CORBA eingeführt, schrittweise mit der Entwicklung einer einfachen CORBA-Anwendung vertraut gemacht und abschließend zur Entwicklung einer komplexeren Anwendung angeleitet. Sie werden Näheres zur Interface Definition Language (IDL) von CORBA erfahren und lernen, wie damit Schnittstellen zwischen einzelnen Anwendungskomponenten beschrieben werden. Darüber hinaus werden Sie die Object Management Group kennenlernen, die für die Entwicklung der Object Management Architecture (OMA), einer Spezifikation, in der CORBA eine Rolle spielt, verantwortlich zeichnet. Zusätzlich werden Sie noch einen Einblick in die CORBA-Architektur und die zugehörigen Komponenten der Object Management Architecture - wie z.B. die CORBAservices und die CORBAfacilities - gewinnen und erfahren, wie mit Hilfe dieser Bausteine verteilte Unternehmensanwendungen entwickelt werden können. Sie werden mit dem Schreiben von CORBAAnwendungen sowohl in C++ als auch in Java vertraut gemacht, da diese beiden die wichtigsten Sprachen für die Anwendungsentwicklung mit CORBA sind. In Ergänzung zu den 14 Lektionen enthält das Buch noch zwei Anhänge mit einer Einführung in die zur Verfügung stehenden CORBA-Entwicklungswerkzeuge und einer Vorschau auf die Zukunft von CORBA. Wenn auch die Weiterentwicklung von CORBA vom Tempo her manchmal an die Definition von Normen durch einen Ausschuß erinnert (was auch nicht überrascht, wenn man den Prozeß betrachtet, mit dem die OMG neue Standards in Kraft setzt), macht CORBA doch in einem solchen Maße Fortschritte, daß dieser Architektur ein Platz in vorderster Front bei der Entwicklung verteilter Anwendungen sicher ist.
Voraussetzungen
Sie werden von diesem Buch am meisten profitieren können, wenn Sie folgende Voraussetzungen mitbringen: ■
■
■
Kenntnisse in C++ oder Java; allerdings werden Sie auch mit Kenntnissen aus anderen Programmiersprachen in der Lage sein, die in diesem Buch vorgestellten Konzepte zu verstehen. Vertrautheit im Umgang mit Entwicklungswerkzeugen, wie z.B. Visual C++, Java Development Kit (JDK) oder anderen Entwicklungswerkzeugen auf der bzw. den von Ihnen bevorzugten Plattform(en). Verständnis der Konzepte zu objektorientierten Analyse- und Entwurfstechniken. Kenntnisse in diesem Bereich sind allerdings nicht unbedingt erforderlich, weil das vorliegende Buch eine Einführung in diese Konzepte bietet. Da es sich bei CORBA aber um eine objektorientierte Architektur handelt, werden Sie die Ausführungen leichter verstehen, wenn Sie mit den oben genannten Konzepten bereits vertraut sind.
Üben und Ausprobieren sind zwei ganz besonders wichtige Aspekte beim Erlernen jedes neuen Fachgebiets. Damit Sie die in den einzelnen Kapiteln vorgestellten Beispiele mitverfolgen, die jeweiligen Quizfragen beantworten und die angebotenen Übungen durchführen können, benötigen Sie folgende Ausrüstung: ■
■
■
Einen Computer eines beliebigen Typs. Da es sich bei CORBA um einen plattformübergreifenden Standard handelt, ist fast jeder Computer geeignet, gleichgültig, ob es sich dabei um einen Macintosh, einen PC unter Windows oder eine Unix-Workstation handelt. Kurz gesagt, jeder Rechner, der von einem CORBA-Entwicklungsprodukt (siehe den nächsten Punkt) unterstützt wird, kann verwendet werden. Ein für Ihren Computer geeignetes CORBA-Entwicklungsprodukt. Zu den beliebtesten Entwicklungsumgebungen gehören Orbix von IONA Technologies und VisiBroker von Visigenic Software. Zahlreiche Anbieter überlassen dem Anwender ihr Produkt für einen bestimmten Zeitraum zum Ausprobieren, der so lang ist, daß Sie reichlich Zeit zum Durcharbeiten dieses Buchs haben. Die C++-Beispiele wurden mit Hilfe von VisiBroker für C++ entwickelt; die Java-Beispiele mit dem Java IDL-Paket von Sun, sie können jedoch ohne oder mit nur geringen Änderungen auch unter Zuhilfenahme anderer Produkte durchgeführt werden. Die geeigneten Compiler- und/oder weiteren Software-Entwicklungswerkzeuge, die zur Unterstützung des von Ihnen gewählten CORBA-Entwicklungsprodukts erforderlich sind. Beispielsweise benötigen Sie für VisiBroker für C++ unter Windows 95 oder Windows NT das Produkt Visual C++ 4.2 oder höher (allerdings funktioniert das Ganze auch mit Compilern anderer Anbieter). Für Java-gestützte CORBA-Produkte benötigen Sie in der Regel lediglich eine aktuelle Version des Java Development Kit (JDK).
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Corba in 14 Tagen: Inhaltsverzeichnis Danksagung 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
Tag: CORBA kennenlernen Tag: Die Architektur von CORBA Tag: Beherrschen der Sprache Interface Definition Language (IDL) Tag: Entwicklung einer CORBA-Anwendung Tag: Entwerfen des Systems: ein Schnellkurs in objektorientierter Analyse und Entwurfstechnik Tag: Implementierung grundlegender Anwendungsfunktionen Tag: Verwendung von Exceptions zur Fehlerprüfung Tag: Hinzufügen von Geldautomat-Fähigkeit Tag: Verwendung von Callbacks zum Hinzufügen von Push-Fähigkeit Tag: Lernen über CORBA-Entwurfsfragen Tag: Verwendung der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII) Tag: CORBAservices und CORBAfacilities Tag: Entwicklung von Internet-Anwendungen mit CORBA und Java Tag: Einrichten von Web-Fähigkeit für das Beispiel Bank mit Java Anhang A: Antworten auf die Quizfragen und Lösungen zu den Übungen Anhang B: CORBA-Werkzeuge und -Dienstprogramme Anhang C: Was steht bevor? Die Zukunft von CORBA
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Corba in 14 Tagen: Stichwortverzeichnis -!- | { | A | B | - | C | D | E | F | - | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | Ü | U | V | W | X | Z
-! Quelltext-Listings : BankImpl.h 1 Quelltext-Listings : BankKarte.idl 1 -{{} Geschweifte Klammern, Definitionen 1
-AAbbildung (Einrichtung) 1 Abfragedienst 1 Abfragedienst : Bankanwendung 1 Abgeleitete Klasse 1 Abhebung() (Methode) 1 Abhebung() (Methode) : Exception 1 Accessor-Methoden 1 addBanks(), Methode (Java), BankApplet 1 Agent : Task Management Common Facilities 1
Aktiensymbol 1 AktienSymbol (Klasse) 1 Aktivierungsstrategien, BOA 1 Aktor, OOA 1 aktualisiereKontostand(), Methode 1, 2 aktualisiereKontoThreadFunktion(), Funktion 1 anfordernAktualisierungsDienst() Methode 1 anfordernAktualisierungsDienst() Methode : BankImpl class 1 Anfrage 1 Anwendung : als Client und als Server 1 Anwendung : Client 1 Anwendung : CORBA in Java 1 Anwendung : DII 1 Anwendung : entwickeln 1 Anwendung : von GIOP-abgeleitetes Protokoll 1 Anwendung ausführen : Bankanwendung : BankServer (Komponente) 1 Anwendung ausführen : Bankanwendung : CORBA-Bezeichnungsdienst 1 Anwendungsentwicklung (Einrichtung) 1 Any, Typ, DII 1 any-Typ 1, 2 Aonix-Software 1
API, Socket 1 Applet (Java) 1 Applets : BankApplet 1 Applets : BankApplet : Ausführen 1 Applets : BankApplet : Client-Funktionen 1 Applets : BankApplet : Kunde, Klasse 1, 2 Applets : BankApplet : Server-Funktionen 1 appletviewer 1, 2, 3 Arbeitsablauf, Task Management Common Facilities 1 Arbeitsverwaltungssystem 1 Architektur : CORBA 1 Architektur : Java im Vergleich zu CORBA 1 Architektur : Java im Vergleich zu CORBA : Java-RMI 1, 2 arguments(), Methode, Beispiel zu DII 1 Arrays 1 Assoziation, UML 1, 2 Attribut : Datenzugriffsmethode 1 Attribut : interface-Typ 1 Attribut : Kunde (Klasse) 1 Attribut : readonly 1
Aufforderung zur Abgabe von Informationen (RFI) 1 Aufforderung zur Einreichung von Vorschlägen (RFP) 1 Aufgaben- und Prozeßautomatisierung 1 aufhebenRegistrierungBank() (Methode) 1 aufhebenRegistrierungBank() (Methode) : Exception 1 Aufzählungstyp 1 Ausführen : Client 1, 2 Ausführen : Server 1, 2 ausgebenBankKarte(), Methode 1 Auslagerungsdienst 1 Auslagerungsdienst : Bankanwendung 1 Ausschließlicher oneway-Aufruf, Muster 1 AuthentifizierungException, Exception 1 AWT 1
-BBank (Klasse) 1, 2, 3 Bank (Komponente), Bankanwendung starten 1 Bank (Schnittstelle) 1, 2, 3, 4, 5, 6, 7 Bank (Schnittstelle) : Exception 1, 2 Bank (Schnittstelle) : Methoden, Exception 1
Bank (Server) 1 Bank, Anwendung : Geldautomat : Anforderungen 1 Bank, Anwendung : Geldautomat : exceptions 1 Bank, Anwendung : Geldautomat : Implementierung von Funktionen 1 Bank, Anwendung : Geldautomat : Klassen 1 Bank, Anwendung : Geldautomat : Schritte beim Hinzufügen 1 Bank, Anwendung : Geldautomat : zusätzliche Anforderungen 1 Bank, Anwendung : Kontoaktualisierung : Ausführen 1, 2, 3 Bank, Anwendung : Kontoaktualisierung : Implementierung 1 Bank, Anwendung : Kontoaktualisierung : Klassen 1 Bank, Anwendung : Kontoaktualisierung : zukünftige Erweiterungen 1 Bank, Anwendung : Kontoaktualisierung Anforderungen 1 Bank, Klasse : BankKarte, Klasse 1 Bank, Klasse : Geldautomat, Implementierung 1, 2, 3, 4 Bank.idl 1, 2 Bank.idl : für Exceptions aufbereitet 1 Bank.idl : für Geldautomat modifiziert 1 Bank.idl : modifiziert für Kontoaktualisierung 1 Bankanwendung : ausführen 1 Bankanwendung : ausführen : Bank (Komponente) 1 Bankanwendung : ausführen : BankServer (Komponente) 1, 2
Bankanwendung : ausführen : Client-Anwendung 1, 2 Bankanwendung : ausführen : CORBA-Bezeichnungsdienst 1, 2 Bankanwendung : Bank (Schnittstelle) 1, 2, 3, 4, 5, 6, 7 Bankanwendung : BankServer (Schnittstelle) 1, 2, 3, 4, 5 Bankanwendung : Bezeichnungsdienst 1 Bankanwendung : Bezeichnungsdienst : ausführen 1 Bankanwendung : Client 1 Bankanwendung : Client : Kunde (Schnittstelle) 1, 2 Bankanwendung : Client : zusätzliche Funktionen 1 Bankanwendung : CORBAservices 1, 2 Bankanwendung : CORBAservices : implementieren 1 Bankanwendung : Exception : Bank (Schnittstelle) 1, 2 Bankanwendung : Exception : BankServer (Schnittstelle) 1, 2 Bankanwendung : Exception : Beispielanwendung ausführen 1, 2 Bankanwendung : Exception : Client-Quelltext 1, 2, 3 Bankanwendung : Exception : GiroKonto (Schnittstelle) 1 Bankanwendung : Exception : Konto (Schnittstelle) 1, 2 Bankanwendung : Exception : Kunde (Schnittstelle) 1 Bankanwendung : Exception : Server-IDL 1, 2, 3, 4 Bankanwendung : Exception : Server-Quelltext 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Bankanwendung : Exception : SparKonto (Schnittstelle) 1 Bankanwendung : GiroKonto (Schnittstelle) 1, 2 Bankanwendung : Java-Version 1, 2, 3, 4, 5, 6, 7, 8, 9 Bankanwendung : Java-Version : Ausführung 1 Bankanwendung : Konto (Schnittstelle) 1, 2, 3 Bankanwendung : OOA 1 Bankanwendung : OOA : Klassendiagramm 1, 2 Bankanwendung : OOA : Systemanforderungen 1, 2 Bankanwendung : OOA : Systemobjekt 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 Bankanwendung : Server 1 Bankanwendung : SparKonto (Schnittstelle) 1, 2 BankApplet 1 BankApplet : Ausführen 1, 2 BankApplet : Ausführen : appletviewer 1, 2, 3 BankApplet : Ausführen : Web browser 1 BankApplet : Client-Funktionen 1 BankApplet : Client-Funktionen : Kunde, Klasse 1, 2 BankApplet : Dialogfelder 1 BankApplet : Java-Werkzeuge 1 BankApplet : Server-Funktionen 1 BankApplet.html 1
BankApplet.java 1 bankChoice_ItemStateChanged(), Methode (Java) : BankApplet 1 BankHaupt.cpp 1, 2, 3 BankHaupt.java 1 BankImpl, Exception 1, 2, 3 BankImpl, Klasse 1, 2, 3, 4 BankImpl, Klasse : Geldautomat, Klassen 1 BankImpl.cpp 1, 2, 3 BankImpl.cpp : Analyse 1, 2 BankImpl.cpp : für Exceptions aufbereitet 1 BankImpl.h 1, 2, 3 BankImpl.h : für Exceptions aufbereitet 1 BankImpl.java 1 BankKarte, Klasse 1, 2 BankKarte, Klasse : Geldautomat-Server 1 BankKarte.idl 1 BankKarteImpl, Schnittstelle 1 BankKarteImpl.cpp 1 BankKarteImpl.h 1 BankKarteImpl.java 1
BankServer (Klasse) 1 BankServer (Komponente) : Bankanwendung ausführen 1, 2 BankServer (Schnittstelle) 1, 2, 3, 4, 5 BankServer (Schnittstelle) : Exception 1, 2 BankServer app : Konto update 1 BankServer app : running 1 BankServer, Anwendung : Ausführen 1 BankServer, Anwendung : Ausführung 1 BankServer, Bezeichnungsdienst 1 BankServer, Schnittstelle : Geldautomat, Implementierung 1, 2 BankServer, Schnittstelle : Geldautomat, Klasse 1 BankServer, Schnittstelle : Geldautomat-Implementierung 1 BankServer.idl 1, 2 BankServer.idl : für Exceptions aufbereitet 1 BankServer-Anwendung : Java-Version 1, 2, 3, 4 BankServerHaupt.cpp 1, 2, 3 BankServerHaupt.java 1 BankServerImpl (Klasse) 1 BankServerImpl (Konstruktor), Vector (Klasse) 1 BankServerImpl, Exception 1, 2, 3 BankServerImpl.cpp 1, 2
BankServerImpl.cpp : für Exceptions aufbereitet 1 BankServerImpl.h 1, 2 BankServerImpl.h : für Exceptions aufbereitet 1 BankServerImpl.java 1 Basic Object Adapter (BOA), Objektmodell 1 Befehlszeile, Schalter, -c cpp 1 Befehlszeilenparameter, -fclient 1 Begriffe : abgeleitete Klassen 1 Begriffe : Aktivierungsstrategie 1 Begriffe : Anfrage (Request) 1 Begriffe : Assoziation 1 Begriffe : auslösen (Exceptions) 1 Begriffe : bindende Klasse 1 Begriffe : blockieren 1 Begriffe : Callback 1 Begriffe : Client 1 Begriffe : Client-Stub 1 Begriffe : Delegation 1 Begriffe : Entscheider (Discriminator) 1 Begriffe : Exception auslösen 1
Begriffe : Formatübertragung 1 Begriffe : GIOP (General Inter-ORB Protocol) 1 Begriffe : IDL (Interface Definition Language) 1 Begriffe : IIOP (Internet Inter-ORB Protocol) 1 Begriffe : Implementierungsvererbung 1 Begriffe : Klassendiagramm 1 Begriffe : Methodensignatur 1 Begriffe : Multiplizität 1 Begriffe : Partitionierung 1 Begriffe : Plattformunabhängigkeit 1 Begriffe : Polymorphie 1 Begriffe : Remote-Prozedurenaufruf (RPC) 1 Begriffe : Schnittstelle 1 Begriffe : Schnittstellenvererbung 1 Begriffe : Serialisierung 1 Begriffe : Server 1 Begriffe : Server-Skeleton 1 Begriffe : Sprachabbildung 1 Begriffe : Sprachenunabhängigkeit 1 Begriffe : Transaktionsmonitor 1 Begriffe : Übergabe durch den Wert 1
Begriffe : Übergabe durch Referenz 1 Begriffe : übergeordnete Klassen 1 Begriffe : Übertragungsformat 1 Begriffe : umgekehrte Formatübertragung 1 Begriffe : Vererbung 1 Begriffe : Verzeichnisdienst 1 Begriffe : zirkuläre Abhängigkeit 1 Benutzeroberfläche : Aktivatoren 1 Benutzeroberfläche : Aussehen 1 Bezeichner, Gültigkeitsbereichsbeschränkungen 1 Bezeichnungsdienst 1 Bezeichnungsdienst : Bankanwendung 1, 2, 3 Bezeichnungsdienst : Bankanwendung 1 Bezeichnungsdienst : Client, Server-Objekt auffinden 1 Bezeichnungsdienst : IDL-Schnittstelle 1 Bezeichnungskontext, Bankanwendung 1 Bezeichnungs-Server, Ausführung aus Java 1 Beziehung, Klassen 1 Beziehungsdienst 1 Beziehungsdienst : Bankanwendung 1
Bilddatenverarbeitung 1 bind(), Methode 1 bind(), Methode : Beispiel zu DII 1 Binden 1 Bindung, module-Konstrukt 1, 2 Black &White Software 1 blockieren 1 Blocking 1 Börsen-Server (Beispiel) : Server-Schnittstelle 1 Börsen-Server (Beispiel), Server-Schnittstelle 1, 2 BOA, Java 1 BoersenMarktClient.java, Quelltext-Listing 1 BoersenServer (Schnittstelle) 1 BoersenServer.idl, Stub 1 BoersenServer.java (Datei) 1 BoersenServerImpl.java (Datei) : Analyse 1 BoersenServerImpl.java (Datei) : Quelltext-Listing 1 BoersenServerImplBase.java (Datei) 1 BoersenServer-Schnittstelle, Client-Stub 1 boolean-Typ 1 Botschaft 1
Buchhaltung (Einrichtung) 1 Business Object Domain Task Force 1, 2 Bytecode (Java) 1
---c cpp (Schalter) 1
-CC++ : Exception deklarieren 1 C++ : ORBstreams.h++ 1 Callbacks 1 char-Typ 1 CHORUS/COOL ORB 1 Client 1, 2 Client : Anwendung als Client und als Server 1, 2 Client : Bank 1 Client : Bank : Kunde (Schnittstelle) 1, 2 Client : Bank : zusätzliche Funktionen 1 Client : Bankanwendung ausführen 1, 2 Client : Bezeichnungsdienst, IDL-Schnittstelle 1 Client : entwickeln 1
Client : implementieren 1 Client : implementieren : Server-Objekt 1 Client : implementieren : Server-Objektschnittstelle 1, 2 Client : implementieren : Stub 1, 2 Client : kompilieren/ausführen 1, 2 Client : Objektverteilung 1 Client : Plattformunabhängigkeit, ORB 1 Client-Anwendungen : Bankanwendung, Java 1 Client-Quelltext, Exception 1, 2, 3 Clients : BankApplet 1 Clients : BankApplet : Kunde, Klasse 1, 2 Clients : DII 1 Clients : Einfach-Thread-Anwendungen 1 Clients : Geldautomat 1, 2, 3 Clients : Konversionskonstruktoren 1 Client-Seite, Java-Anwendungen 1 Client-Server-Architektur : Geschichte : mehrschichtige Architektur 1, 2 Client-Server-Architektur, Geschichte 1, 2 Client-Server-Architektur, Geschichte : mehrschichtige Architektur 1 Client-Stub 1, 2, 3
Cobol : mehrschichtige Client-Server-Architektur 1 Communicator 4.0 1 Concurrency-Steuerungsdienst 1 Concurrency-Steuerungsdienst : Bankanwendung 1 connect (Methode) 1 const-Modifizierer 1 Container-Typen 1 Container-Typen : Arrays 1 Container-Typen : sequence 1 CORBA : Alternativen 1 CORBA : Alternativen : DCE 1 CORBA : Alternativen : DCOM 1, 2 CORBA : Alternativen : RMI 1 CORBA : Alternativen : RPC 1 CORBA : Alternativen : Socket-Programmierung 1 CORBA : Geschichte : OMG 1 CORBA : Geschichte : Version 1.0 1 CORBA : Geschichte : Version 2.0 1 CORBA : Überblick 1, 2 CORBA : Überblick über die Architektur 1 CORBA : Übersicht über die Architektur 1
CORBA : Übersicht über die Architektur : Clients/Server 1 CORBA : Übersicht über die Architektur : CORBAfacilities 1 CORBA : Übersicht über die Architektur : CORBAservices 1 CORBA : Übersicht über die Architektur : IDL 1 CORBA : Übersicht über die Architektur : Kommunikationsmodell 1, 2 CORBA : Übersicht über die Architektur : Objektmodell 1 CORBA : Übersicht über die Architektur : ORB 1 CORBA : Übersicht über die Architektur : Stubs/Skeletons 1 CORBA 2.1 1 CORBA 3.0 1 CORBAAlgorithms.java 1 CORBA-Bezeichnungsdienst : Bankanwendung 1, 2 CORBAfacilities 1, 2, 3, 4 CORBAfacilities : Domain Technology Committee 1 CORBAfacilities : horizontal 1 CORBAfacilities : horizontal : Information Management Common Facilities 1 CORBAfacilities : horizontal : Systems Management Common Facilities 1 CORBAfacilities : horizontal : Task Management Common Facilities 1 CORBAfacilities : horizontal : User Interface Common Facilities 1 CORBAfacilities : vertikaler Markt 1, 2
CORBA-kompatibel : IIOP 1 CORBAmed Domain Task Force 1, 2 CORBAplus 1 CORBAservices 1, 2, 3 CORBAservices : Abfragedienst 1 CORBAservices : Auslagerungsdienst 1 CORBAservices : Bankanwendung 1, 2 CORBAservices : Bankanwendung : implementieren 1 CORBAservices : Bezeichnungsdienst 1 CORBAservices : Beziehungsdienst 1 CORBAservices : Concurrency-Steuerungsdienst 1 CORBAservices : Eigenschaftendienst 1 CORBAservices : Ereignisdienst 1 CORBAservices : Lebenszyklusdienst 1 CORBAservices : Lizenzierungsdienst 1 CORBAservices : Objektpersistenzdienst 1 CORBAservices : Objektvermittlungsdienst 1 CORBAservices : Sicherheitsdienst 1 CORBAservices : Transaktionsdienst 1 CORBAservices : Zeitgeberdienst 1 Corbus 1
C-Präprozessor : IDL 1, 2 create_request(), Methode 1 createCustomer(), Methode (Java) : BankApplet 1 CustomerDialog.java 1
-DDaemon 1 Datenautobahnen (Einrichtung) 1 Datensammlung 1 Datentyp : IDL 1 Datentypen : Arrays 1 Datenzugriffsmethode : Objektattribute 1 DCE 1 DCOM 1, 2 Definitionen, IDL 1 Definitionssyntax, interface-Typ 1, 2 Delegation, IDL-Schnittstellen 1 Destruktoren, Schnittstellensyntax 1 Dialogfelder, BankApplet 1 Dienstprogramme : CORBA-relevant : Black &White Software 1 Dienstprogramme : CORBA-relevant : Distributed Smalltalk 1
Dienstprogramme : CORBA-relevant : Rose 1 Dienstprogramme : CORBA-relevant : SNiFF+ 1 Dienstprogramme : CORBA-relevant : Software through Pictures 1 Dienstprogramme : CORBA-relevant : Universal Network Architecture Services (UNAS) 1 Dienstprogramme : ORB : CHORUS/COOL ORB 1 Dienstprogramme : ORB : Communicator 4.0 1 Dienstprogramme : ORB : CORBAplus 1 Dienstprogramme : ORB : Corbus 1 Dienstprogramme : ORB : DOME 1 Dienstprogramme : ORB : Enterprise Server 1 Dienstprogramme : ORB : ILU 1 Dienstprogramme : ORB : Jaguar CTS 1 Dienstprogramme : ORB : NEO 1 Dienstprogramme : ORB : ObjectBroker 1 Dienstprogramme : ORB : OmniBroker 1 Dienstprogramme : ORB : omniORB2 1 Dienstprogramme : ORB : ORB Plus 1 Dienstprogramme : ORB : Orbix 1, 2 Dienstprogramme : ORB : SmalltalkBroker 1 Dienstprogramme : ORB : SOMobjects 1
Dienstprogramme : ORB : TIB/ObjectBus 1 Dienstprogramme : ORB : VisiBroker 1 Dienstprogramme : ORB : Voyager 1 Dienstqualitätsverwaltung 1 DII 1 DII : (Dynamic Invocation Interface) 1 DII : Any/TypeCode, Typen 1 DII : Beispiel 1, 2, 3 DII : Nachteile 1 DII : Request, Objekt 1, 2 DII : Vgl. mit statischen Schnittstellen/IDL 1, 2 DII : Vorteile 1 DII : Zweck 1 DII : Zweck : hypothetisches Entwurfswerkzeug 1 DII : Zweck : Objekt-Browser 1, 2 Discriminator 1, 2 Distributed Application, Platform Architecture, (Visigenic/DAP) 1 Distributed Smalltalk 1 Distributed Smalltalk (ParcPlace) 1 Domain Technology Committee 1 Domain Technology Committee : Electronic Commerce Domain Task Force 1
Domain Technology Committee : Business Object Domain Task Force 1, 2 Domain Technology Committee : CORBAmed Task Force (CORBAmed) 1, 2 Domain Technology Committee : Electronic Commerce Domain Task Force 1 Domain Technology Committee : Financial Domain Task Force 1, 2 Domain Technology Committee : Manufacturing Domain Task Force 1 Domain Technology Committee : Telecommunications Domain Task Force 1, 2 Domain Technology Committee : Transportation Domain Task Force (CORBAtransport) 1 DOME 1 double-Typ 1 Dreischichtige Client-Server-Architektur 1 duplicate() (Methode) 1 Dynamic Invocation Interface, siehe DII 1
-EEckige Klammern, Array 1 Eigenschaftendienst 1 Eigenschaftendienst : Bankanwendung 1 Einfache Typen 1 Einfache Typen : boolean 1 Einfache Typen : char 1 Einfache Typen : const-Modifizierer 1
Einfache Typen : double 1 Einfache Typen : float 1 Einfache Typen : IDL 1 Einfache Typen : Integer 1 Einfache Typen : long 1 Einfache Typen : long double 1 Einfache Typen : long long 1 Einfache Typen : octet 1 Einfache Typen : short 1 Einfache Typen : string 1 Einfache Typen : unsigned long 1 Einfache Typen : unsigned long long 1 Einfache Typen : unsigned short 1 Einfache Typen : void 1 Einfache Typen : wchar 1 Einfach-Thread-Anwendungen 1 Einfach-Thread-Anwendungen : X Window, System 1 Einfach-Thread-Anwendungen : Clients 1 Einfach-Thread-Anwendungen : Server 1, 2 Einfach-Thread-Anwendungen : Server/Client 1, 2
Einfach-Thread-Anwendungen : Server/Client : Object Factory, Muster 1, 2 Einfach-Thread-Anwendungen : Server/Client : oneway-Aufruf, Muster 1, 2, 3 Einfach-Thread-Anwendungen : X Window, System : Multithread-Anwendungen 1 Einfach-Thread-Anwendungen : X Window, System : Multithread-Anwendungen 1 Einrichtungen für den vertikalen Markt 1, 2 Eins-zu-Eins-Assoziation 1 Eins-zu-Viele-Assoziation 1 Einzahlung() (Methode), Exception 1 Electronic Commerce Domain Task Force 1, 2 enableKontoFeatures(), Methode (Java), BankApplet 1 Enge Bindung 1 Enterprise Server 1 Entscheider 1 Entwicklung, IDL, Java 1 Entwurf : Einfach-Thread-Anwendungen 1 Entwurf : Einfach-Thread-Anwendungen : Clients 1 Entwurf : Einfach-Thread-Anwendungen : Server 1, 2 Entwurf : Einfach-Thread-Anwendungen : Server/Client 1, 2, 3, 4, 5, 6, 7 Entwurf : enge Bindung 1 Entwurf : IDL creep 1 Entwurf : IDL Creep 1
Entwurf : Lebensdauer von Objekten 1, 2, 3 Entwurf : Server-Schnittstelle 1 Entwurfsmuster 1 env(), Methode, Request, Objekt 1 erase() (Methode) 1 Ereignisdienst 1 Ereignisdienst : Bankanwendung 1 Ereignisschleifen, Einfach-Thread-Anwendungen und : X Window, System 1 Ereignisverwaltung 1 Erschließung und Förderung in der %@%l- und Gasindustrie (Einrichtung) 1 erstelleKonto() (Methode) 1, 2 erstelleKonto(), Methode (Java), BankApplet 1 Evictor 1 Exception : Bank (Schnittstelle) 1, 2 Exception : BankServer (Schnittstelle) 1, 2 Exception : Client-Quelltext 1, 2, 3 Exception : definieren 1 Exception : GiroKonto (Schnittstelle) 1 Exception : Java 1 Exception : Konto (Schnittstelle) 1, 2
Exception : Kunde (Schnittstelle) 1 Exception : Objektverteilung 1 Exception : Server-IDL 1, 2, 3, 4 Exception : Server-Quelltext 1 Exception : Server-Quelltext : BankImpl 1, 2, 3 Exception : Server-Quelltext : BankServerImpl 1, 2, 3 Exception : Server-Quelltext : GiroKontoImpl 1, 2, 3 Exception : Server-Quelltext : KontoImpl 1, 2, 3 Exception : Server-Quelltext : SparKontoImpl 1, 2, 3 Exception : SparKonto (Schnittstelle) 1 Exception : verbesserte Beispieldatei 1, 2 Exceptions : AuthentifizierungException 1 Exceptions : BankApplet 1 Exceptions : UngueltigerGeldautomatException 1 Exceptions.idl 1 Exceptions.idl : für Geldautomat modifiziert 1 Exception-Typen 1, 2 Exception-Typen : Exception 1 Exception-Typen : Standard 1, 2
-F-
Factory-Objekte 1
---fclient (Schalter) 1
-FFehler, Exception, siehe auch Exception 1 Fehlerbehandlung : Exception-Typ 1 Fertigung (Einrichtung) 1 Financial Domain Task Force 1, 2 float-Typ 1 Format, Übertragungsformat 1 Formatübertragung 1, 2 Formatübertragung : Plattformunabhängigkeit 1 forward-Deklarationen 1, 2 Funktionen, aktualisiereKontoThreadFunktion() 1
-GGarbage Collection (Java) 1 GateKeeper (VisiBroker) 1 Geldautomat : Anforderungen 1
Geldautomat : Ausführen 1, 2 Geldautomat : Ausführung 1 Geldautomat : Exceptions : AuthentifizierungException 1 Geldautomat : Exceptions : UngueltigerGeldautomatException 1 Geldautomat : Grundfunktionen 1 Geldautomat : IDL-Spezifikation 1, 2, 3, 4 Geldautomat : Implementierung der Funktionen : BankServer, Schnittstelle 1 Geldautomat : Implementierung von Funktionen 1 Geldautomat : Implementierung von Funktionen : Bank, Klasse 1, 2, 3 Geldautomat : Implementierung von Funktionen : BankServer, Schnittstelle 1 Geldautomat : Implementierung von Funktionen : Geldautomat-Client 1, 2, 3 Geldautomat : Implementierung von Funktionen : Geldautomat-Server 1, 2, 3 Geldautomat : Implementierung, Funktionalität, BankServer, Schnittstelle 1 Geldautomat : Klassen : bestehende Klassen 1, 2 Geldautomat : Klassen : Geldautomat/BankKarte 1 Geldautomat : Klassen : GeldAutomat/BankKarte 1 Geldautomat, Anwendung, Kontoaktualisierung, Anwendung 1 Geldautomat, Klasse 1 GeldAutomat, Klasse 1 Geldautomat, Klasse : Schnittstelle 1 Geldautomat.idl 1
Geldautomat-Anwendung, Java-Version 1 Geldautomat-Client, Geldautomat, Implementierung 1, 2, 3 GeldautomatClient, Schnittstelle 1 GeldautomatClientHaupt.cpp 1, 2, 3, 4, 5, 6 GeldautomatClientHaupt.cpp : Analyse 1, 2 GeldautomatClientHaupt.java 1 GeldautomatHaupt.cpp 1, 2 GeldautomatHaupt.java 1 GeldautomatImpl, Objekt 1 GeldautomatImpl.cpp 1 GeldautomatImpl.h 1 GeldautomatImpl.java 1 Geldautomat-Server, Geldautomat, Implementierung 1, 2, 3 gemischte Server-/Client-Anwendungen 1 generischer Objekt-Browser (DII) 1 Geringe Kopplung 1 Geschäftslogik, Client-Server-Anwendungen 1 Geschweifte Klammern, Definitionen 1 getAktienSymbole (Methode) 1, 2 getAktienWert (Methode) 1
getBanken() (Methode), Exception 1 getKonten() (Methode) 1 getKonten() (Methode) : Exception 1 getKunden() (Methode), Exception 1 GIOP (General Inter-ORB Protocol) 1, 2 GiroKonto (Klasse) 1 GiroKonto (Schnittstelle) 1, 2 GiroKonto (Schnittstelle) : Exception 1 GiroKonto, Objekt, GeldautomatClient, Anwendung 1 GiroKonto.idl 1, 2 GiroKonto.idl : für Exceptions aufbereitet 1 GiroKontoImpl : Exception 1, 2, 3 GiroKontoImpl.cpp 1 GiroKontoImpl.cpp : für Exceptions aufbereitet 1 GiroKontoImpl.h 1 GiroKontoImpl.h : für Exceptions aufbereitet 1 GiroKontoImpl.java 1 GiroKontos 1 GNU General Public License (GPL) 1 Groß-/Kleinschreibung, IDL 1 Großrechner 1
Gruppenverwaltung 1 GUI, Java-Werkzeuge 1
-HHashtable, Klasse 1 helloWorld(), Methode, Beispiel zu DII 1 Horizontale Einrichtungen 1 Horizontale Einrichtungen : Information Management Common Facilities 1 Horizontale Einrichtungen : Systems Management Common Facilities 1 Horizontale Einrichtungen : Task Management Common Facilities 1 Horizontale Einrichtungen : User Interface Common Facilities 1 HTML : \\>-Tag (BankApplet) 1
-IIDL 1, 2 IDL : any-Typ 1, 2 IDL : Arrays 1 IDL : BoersenServer-Schnittstelle 1 IDL : Client-Stub 1, 2 IDL : Compiler : Funktion als Übersetzer 1 IDL : Compiler : Java 1, 2
IDL : Compiler : Java-Anwendungen 1 IDL : Compiler : Java-Klasse 1 IDL : Compiler : Server 1, 2, 3 IDL : Compiler : Server-Schnittstelle 1, 2, 3, 4, 5, 6 IDL : Container-Typen 1 IDL : CORBA 1.0 1 IDL : C-Präprozessor 1, 2 IDL : Einfache Typen 1 IDL : enum-Typ 1 IDL : Exception 1, 2, 3, 4 IDL : Exception-Typen 1, 2, 3 IDL : Exception-Typen : Standard 1 IDL : forward-Deklarationen 1 IDL : forward-Deklarationen 1 IDL : GiroKonto (Schnittstelle) 1 IDL : Groß-/Kleinschreibung 1 IDL : interface 1, 2, 3, 4 IDL : interface-Typ 1, 2, 3, 4 IDL : Java-Klassen 1 IDL : Kommentare 1
IDL : Konstrukt module 1 IDL : Kopplung und Bindung 1, 2 IDL : module-Konstrukt 1 IDL : Schnittstelle 1, 2, 3 IDL : Schnittstelle : Implementierungsmechanismen 1 IDL : Schnittstelle : Quelltextdateien 1 IDL : Schnittstellen 1 IDL : sequence-Typ 1 IDL : Server-Skeleton 1, 2 IDL : siehe auch Bankanwendung 1 IDL : SparKonto (Schnittstelle) 1 IDL : Spezifikation : Geldautomat 1, 2, 3, 4 IDL : Spezifikation : Kontoaktualisierung 1, 2, 3 IDL : Sprachenunabhängigkeit 1, 2 IDL : Standard-Exception-Typen 1 IDL : struct-Typ 1 IDL : TypeCode-Typ 1 IDL : typedef-Konstrukt 1 IDL : union-Typ 1, 2 IDL : Vgl. mit DII 1, 2 IDL : Zusammengesetzte Typen 1
IDL Creep 1, 2 IDL-Definitionssyntax 1 IDL-Definitionssyntax : Semikolon 1 idlGlobal (Package) 1 IDL-Typen : boolean 1 IDL-Typen : char 1 IDL-Typen : const-Modifizierer 1 IDL-Typen : double 1 IDL-Typen : float 1 IDL-Typen : Integer 1 IDL-Typen : long 1 IDL-Typen : long double 1 IDL-Typen : long long 1 IDL-Typen : octet 1 IDL-Typen : short 1 IDL-Typen : string 1 IDL-Typen : unsigned long 1 IDL-Typen : unsigned long long 1 IDL-Typen : unsigned short 1 IDL-Typen : void 1
IDL-Typen : wchar 1 IIOP : CORBA 2.0 1 IIOP : GIOP 1 IIOP : Java-RMI 1 ILU 1 impl_is_ready() (Methode) 1 Implementierung : Client 1 Implementierung : Client : Server-Objekt 1 Implementierung : Client : Server-Objektschnittstelle 1, 2 Implementierung : Client : Stub 1 Implementierung : Client : Stubs 1 Implementierung : Server : Ansatz wählen 1 Implementierung : Server, Ansatz wählen 1 Implementierung : Server-Schnittstelle 1, 2, 3 Implementierung : Server-Schnittstelle : Quelltext 1, 2, 3 Implementierung von Funktionen, Bank, Klasse 1 Implementierungsvererbung 1 import (Java-Anweisung) 1 Information Management Common Facilities 1 init(), Methode (Java), BankApplet 1 inout-Parameter 1
in-Parameter 1 Instanzenverwaltung 1 Instrumentierung 1 Integer-Typen 1 interface-Typ 1, 2 interface-Typ : Attribute 1, 2 interface-Typ : Definitionssyntax 1, 2 interface-Typ : Methoden und Parameter 1, 2 interface-Typ : Methoden/Parameter 1, 2 interface-Typ : readonly-Attribute 1 interface-Typ : Vererbung 1, 2 invoke(), Methode, Request, Objekt 1 IONA (Produkte) 1 IstBankGleich (Klasse) 1, 2
-JJ++ 1 Jaguar CTS 1 Java 1, 2 Java : Bankanwendung 1, 2, 3, 4, 5, 6, 7, 8 Java : BankApplet 1
Java : BankApplet : Client-Funktionen 1 Java : BankApplet : Kunde, Klasse 1, 2 Java : BankApplet : Server-Funktionen 1 Java : BankServer-Anwendung 1, 2, 3, 4 Java : Client-Funktionalität 1 Java : CORBA-Anwendungen 1 Java : Entfernen nicht benötigter Objekte 1 Java : Geldautomat-Anwendung 1 Java : IDL 1 Java : IDL-Compiler 1, 2 Java : Klasse, IDL-Compiler 1 Java : Konstruktor, IDL-Schnittstelle 1 Java : nicht-CORBA-Anwendung : statische Methode 1 Java : OrbixWeb for Java (Dienstprogramm) 1 Java : RMI 1 Java : Server-Schnittstellen implementieren 1 Java : Vergleich mit CORBA 1 Java : Vergleich mit CORBA : Java-RMI 1, 2 Java : VisiBroker 1 Java : VisiBroker für Java 1
Java : Voyager 1 Java Workshop 1 java.awt, Package 1 java.util, Package 1 JBuilder, BankApplet 1 Joe 1
-KkillObject() (Methode) 1 Klasse : Assoziation 1, 2 Klasse : Attribut 1 Klasse : Java : IDL-Implementierung 1 Klasse : Java : import (Anweisung) 1 Klasse : Java-RMI 1 Klasse : Sichtbarkeitsmodifizierer 1 Klasse : übergeordnete Klasse 1 Klasse : UML 1, 2 Klasse : Vererbung 1 Klassen : AktienSymbol 1 Klassen : BankImpl, Kontoaktualisierung 1, 2, 3, 4 Klassen : BankServerImpl 1
Klassen : Geldautomat/BankKarte 1 Klassen : GeldAutomat/BankKarte 1 Klassen : IDL creep 1 Klassen : IstBankGleich 1, 2 Klassen : Kontoaktualisierung 1, 2 Klassen : KundenImpl, Kontoaktualisierung 1, 2 Klassen : Vector 1 Klassenbeschreibung 1 Klassendiagramm 1 Klassendiagramm : Bankanwendung 1 Klassendiagramm : Bankanwendung 1 Kommentare, IDL 1 Kommunikationsmodell 1, 2 Kommunikationsmodell : Netzwerkmodell 1 Kommunikationsmodell : Protokolle für die Kommunikation zwischen ORBs 1, 2 Kompilieren : Client 1, 2 Kompilieren : Server 1, 2 Konstrukte : forward-Deklarationen 1, 2 Konstrukte : module 1 Konstrukte : typedef 1 Konstruktor : IDL-Schnittstelle 1
Konstruktor : Schnittstellensyntax 1 Konto (Klasse) 1, 2 Konto (Klasse) 1 Konto (Schnittstelle) 1, 2, 3, 4 Konto (Schnittstelle) : Exception 1, 2 Konto.idl 1, 2 Konto.idl : für Exceptions aufbereitet 1 Kontoaktualisierung : Ausführen 1, 2, 3 Kontoaktualisierung : BankImpl, Klasse 1, 2, 3 Kontoaktualisierung : BankImpl,Klasse 1 Kontoaktualisierung : GeldautomatClient, Schnittstelle 1 Kontoaktualisierung : IDL-Spezifikation 1, 2, 3 Kontoaktualisierung : Implementierung 1 Kontoaktualisierung : Klassen 1, 2 Kontoaktualisierung : KundenImpl, Klasse 1, 2 Kontoaktualisierung : zukünftige Erweiterungen 1 KontoDialog, Fenster 1 KontoDialog.java 1 KontoImpl, Exception 1, 2, 3 KontoImpl.cpp 1
KontoImpl.cpp : für Exceptions aufbereitet 1 KontoImpl.h 1 KontoImpl.h : für Exceptions aufbereitet 1 KontoImpl.java 1 KontoUpdateListener interface 1 Kontrollfluß, oneway-Methoden 1 Konversionskonstruktoren : Übergabe als Wert 1, 2 Kopplung, module-Konstrukt 1, 2 Kunde (Klasse) 1, 2 Kunde (Schnittstelle) 1, 2 Kunde (Schnittstelle) : Exception 1 Kunde interface, GeldautomatClientHaupt.cpp 1 Kunde, Klasse : BankApplet 1, 2 Kunde, Klasse : BankKarte, Klasse 1 Kunde, Klasse : Kontoaktualisierung 1 Kunde.idl 1, 2 Kunde.idl : für Exceptions aufbereitet 1 Kunde.idl : modifiziert für Kontoaktualisierung 1 KundenImpl, Klasse 1, 2 KundenImpl.cpp 1, 2 KundenImpl.h 1, 2
KundenImpl.java 1
-LLanguage Mapping 1 Lebenszyklusdienst 1 Lebenszyklusdienst : Bankanwendung 1 Listing, siehe Quelltext-Listings 1 Lizenzierungsdienst 1 Lizenzierungsdienst : Bankanwendung 1 loescheKonto() (Methode) 1 loescheKonto() (Methode) : Exception 1 loescheKonto() (Methode) : try...catch-Block 1 long double-Typ 1 long long-Typen 1 long-Typ 1 loose coupling 1
-Mmain() (Java-Methode) 1 Makro-Definitionen, C-Präprozessor 1 Manufacturing Domain Task Force 1
Marshaling 1, 2, 3 Mehrfachvererbung 1 Mehrprozessorverarbeitung, Multithreading 1 Mehrschichtige Client-Server-Architektur, Geschichte 1, 2, 3 meineAktualisiertenKonten, Datenelement 1 Methode : Abhebung() 1 Methode : aktualisiereKontostand() 1, 2 Methode : anfordernAktualisierungsDienst() 1 Methode : arguments(), Beispiel zu DII 1 Methode : aufhebenRegistrierungBank() 1 Methode : ausgebenBankKarte() 1 Methode : BankServer (Schnittstelle) 1 Methode : bind() 1, 2 Methode : bind() : Beispiel zu DII 1 Methode : Callback-Methoden 1 Methode : connect 1 Methode : CORBAAlgorithms.java 1 Methode : create_request() 1 Methode : DII, Rückgabeparameter 1 Methode : duplicate() 1
Methode : env(), Request, Objekt 1 Methode : erase() 1 Methode : erstelleKonto() 1, 2 Methode : Exceptions 1 Methode : Formatübertragung der aufrufenden Parameter 1 Methode : Geldautomat, Klasse : AuthentifizierungException 1 Methode : getAktienSymbole 1, 2 Methode : getAktienWert 1 Methode : impl_is_ready() 1 Methode : invoke(), Request, Objekt 1 Methode : killObject() 1 Methode : listKonto() 1 Methode : loescheKonto() 1 Methode : main() (Java) 1 Methode : name() 1 Methode : Object Factory, Muster 1 Methode : oneway 1, 2, 3 Methode : poll_response(), Request, Objekt 1 Methode : registriereBank() 1, 2 Methode : registriereGeldautomat() 1 Methode : release() 1
Methode : send_deferred(), Request, Objekt 1 Methode : send_oneway(), Request, Objekt 1 Methode : setZinsSatz() 1 Methode : Skeleton, Server-Schnittstelle 1 Methode : structs 1 Methode : Ueberweisung() 1 Methodenaufrufe, TypeCode-Typ 1 Methodensignaturen 1 MFS (Message Format Service) 1 Modell 1 Modell-Sicht (Muster) 1 module (CORBA-Konstrukt) 1 Module : BoersenMarkt 1 Module : geringe Kopplung/enge Bindung 1 module-Konstrukt 1, 2 module-Konstrukt : IDL 1 module-Konstrukt : Kopplung und Bindung 1, 2 Monolithisches System 1 Motif, Multithread-Anwendungen 1 Multiplizität (Assoziation) 1
Multithread-Anwendungen, X Window, System 1, 2 Multithreading 1 Multithreading : Blockieren 1 Multithreading : gemischte Server-/Client-Anwendung 1 Multithreading : Kosten 1 Multithreading : Object Factory, Muster 1 Multithreading : Server 1, 2, 3 Multithread-Server-Architektur 1 Mutator-Methoden 1
-NNachrichtenformatdienst (MFS) 1 name() (Methode) 1 NamingContext (Objekt) 1 Navigator 1 NEO 1 Netzwerkmodell 1 Netzwerkprotokoll, Kommunikationsmodell 1 NeuerKunde (Komponente) 1 NeuerKundeHauptmodul.cpp 1 NeuerKundeHauptmodul.cpp : für Exceptions aufbereitet 1
newCustomerButton_Action(), Methode (Java), BankApplet 1 Nichtprogrammierbares Terminal 1 Notation, Array 1, 2 NVList, Objekt 1
-OObject Analysis and Design Task Force (OA&D TF) 1, 2 Object Factory, Muster 1, 2 Object Management Architecture 1 Object Management Group 1 Object Management Group (OMG) 1 Object Management Group : Geschichte von CORBA 1 Object Modeling Technique (OMT) 1 Object Oriented Software Engineering (OOSE) 1 Object Services Platform Task Force, ORB 1 Object/LM 1 Object/Observer 1 ObjectBroker 1 Object-Oriented Concepts 1 objects, lifetime 1 Objekt : Bank-Anwendung 1
Objekt : BankServer 1 Objekt : Referenzübergabe 1 Objekt : Systemobjekt definieren 1 Objekt : Übergabe durch Werte 1 Objektanfrage, Referenzen 1 Objekt-Browser, DII 1 Objekte : BankApplet 1 Objekte : Lebensdauer 1, 2 Objekte : NamingContext 1 Objekte : Request (DII) 1 Objekte : Übergabe als Wert 1 Objekte : Übergabe als Wert : Konversionskonstruktoren 1, 2 Objekte : Übergabe als Wert : ORBstreams.h++ 1 Objekte : Übergabe als Wert : structs 1, 2 Objektidentität, Bankanwendung 1, 2 Objektmodell 1, 2 Objektmodell : Basic Object Adapter (BOA) 1 Objektmodell : Objektreferenz 1, 2, 3 Objektmodell : Objektverteilung 1 Objektpersistenzdienst 1
Objektpersistenzdienst : Bankanwendung 1 Objektreferenz : Objektmodell 1, 2, 3 Objektreferenz : ORB 1 Objektserialisierung (Java-RMI) 1 Objektvermittlungsdienst 1 Objektvermittlungsdienst : Bankanwendung 1 Objektverteilung, Objektmodell 1 octet-Typ 1 OMA 1 OMA : CORBAfacilities 1 OMA : Einrichtungen für den vertikalen Markt 1 OMA : ORB 1 OMG : BOA 1 OMG : Operationen 1 OMG : Sprachabbildung 1 OMG : Task Forces (Spezialeinheiten) 1 OMG : Web-Seite 1 OMG-Geschäftsobjekt 1 OmniBroker 1 omniORB2 1 OMT (Object Modeling Technique) 1
oneway-Aufruf, Muster 1, 2, 3 oneway-Methoden 1 oneway-Methoden : DII 1 OOA 1, 2 OOA : Assoziation, Benennungsschemata 1 OOA : Bankanwendung 1 OOA : Bankanwendung : Klassendiagramm 1, 2 OOA : Bankanwendung : Systemanforderungen 1 OOA : Bankanwendung : Systemanforderungen 1 OOA : Bankanwendung : Systemobjekt 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 OOA : Entwurfsmuster 1 OOA : Object-Oriented Analysis (objektorientierte Analyse) 1 OOA : Objektidentität 1 OOA : UML 1 OOA : Use-Case 1 OOD 1 OOD : Object-Oriented Design (objektorientierter Entwurf) 1 OOSE (Object-Oriented Software Engineering) 1 Open Software Foundation (OSF), DCE 1 Operation 1
ORB 1, 2 ORB : CHORUS/COOL ORB 1 ORB : Communicator 1 ORB : CORBAplus 1 ORB : Corbus 1 ORB : DOME 1 ORB : Enterprise Server 1 ORB : Formatübertragung 1, 2 ORB : Geschichte 1 ORB : ILU 1 ORB : Jaguar CTS 1 ORB : Java 1 ORB : NEO 1 ORB : ObjectBroker 1 ORB : OmniBroker 1 ORB : omniORB2 1 ORB : ORB Plus 1 ORB : Orbix 1, 2 ORB : Plattformunabhängigkeit 1 ORB : SmalltalkBroker 1 ORB : SOMobjects 1
ORB : TIB/ObjectBus 1 ORB : VisiBroker 1 ORB : Voyager 1 ORB Plus 1 ORB(colonx2)bind(), Methode, Beispiel zu DII 1 ORB.init(), Methode (Java), BankApplet 1 Orb/Enable 1 ORB/Object Services Task Force 1 ORBeline Smart Agent 1 Orbix 1, 2 OrbixBuilder 1 OrbixWeb, Java 1 ORBstreams.h++ 1 osagent (Dienstprogramm) 1 out-Parameter 1
-Ppackage (Java-Konstrukt) 1 Parameter : Entscheider (Discriminator) 1 Parameter : Formatübertragung 1 Parameter : in, out und inout 1
PARC, ILU 1 Partitionierung 1 Partitioning 1 PATH-Variable, IDL-Compiler 1 Permanenter Server 1 persistenter Speicher, Evictor 1 Physikalische Ebene, Kommunikationsmodell 1 PIN : BankKarte, Klasse 1 PIN : GeldautomatClientHaupt.cpp 1 Planungsverwaltung 1 Platform Technology Committee, Task Forces 1 Plattformunabhängigkeit 1 Plattformunabhängigkeit : ORB 1 poll_response(), Methode, Request, Objekt 1 Polymorphie 1, 2 Polymorphismus, structs 1 Portabilität, Java 1 Protokoll : IIOP, CORBA-kompatibel 1 Protokoll : Kommunikation zwischen ORBs 1 Protokoll zur Kommunikation zwischen ORB, Kommunikationsmodell 1
Protokolle : Kommunikation zwischen ORBs 1 Protokolle für die Kommunikation zwischen ORB, Kommunikationsmodell 1 Protokollschicht für die Kommunikation zwischen ORBs 1
-QQuelltext-Beispiele, union-Typ 1 Quelltext-Listings : Ausgabe für BoersenMarktClient 1 Quelltext-Listings : Ausgabe für BoersenServer 1 Quelltext-Listings : Bank.idl 1, 2 Quelltext-Listings : Bank.idl : für Exceptions aufbereitet 1 Quelltext-Listings : Bank.idl : für Geldautomat modifiziert 1 Quelltext-Listings : Bank.idl : modifiziert für Kontoaktualisierung 1 Quelltext-Listings : BankApplet.html 1 Quelltext-Listings : BankApplet.java 1 Quelltext-Listings : BankHaupt.cpp 1, 2, 3 Quelltext-Listings : BankHaupt.java 1 Quelltext-Listings : BankImpl.cpp 1, 2, 3 Quelltext-Listings : BankImpl.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : BankImpl.h 1, 2 Quelltext-Listings : BankImpl.h : für Exceptions aufbereitet 1 Quelltext-Listings : BankImpl.java 1
Quelltext-Listings : BankKarteImpl.cpp 1 Quelltext-Listings : BankKarteImpl.h 1 Quelltext-Listings : BankKarteImpl.java 1 Quelltext-Listings : BankServer.idl 1, 2 Quelltext-Listings : BankServer.idl : für Exceptions aufbereitet 1 Quelltext-Listings : BankServerHaupt.cpp 1, 2, 3 Quelltext-Listings : BankServerHaupt.java 1 Quelltext-Listings : BankServerImpl.cpp 1, 2 Quelltext-Listings : BankServerImpl.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : BankServerImpl.h 1, 2 Quelltext-Listings : BankServerImpl.h : für Exceptions aufbereitet 1 Quelltext-Listings : BankServerImpl.java 1 Quelltext-Listings : BoersenMarkt.idl 1 Quelltext-Listings : BoersenMarktClient.java 1 Quelltext-Listings : BoersenServer.java 1 Quelltext-Listings : BoersenServer-Dienste 1 Quelltext-Listings : BoersenServerImpl.java 1 Quelltext-Listings : CORBAAlgorithms.java 1 Quelltext-Listings : CustomerDialog.java 1 Quelltext-Listings : enum-Typ 1 Quelltext-Listings : Exception.idl, modifiziert mit Geldautomat-Exceptions 1
Quelltext-Listings : Exception-Definition 1 Quelltext-Listings : Exceptions.idl 1 Quelltext-Listings : Geldautomat.idl 1 Quelltext-Listings : GeldautomatClientHaupt.cpp 1, 2, 3, 4, 5, 6 Quelltext-Listings : GeldautomatClientHaupt.java 1 Quelltext-Listings : GeldautomatHaupt.cpp 1, 2 Quelltext-Listings : GeldautomatHaupt.java 1 Quelltext-Listings : GeldautomatImpl.cpp 1 Quelltext-Listings : GeldautomatImpl.h 1 Quelltext-Listings : GeldautomatImpl.java 1 Quelltext-Listings : GiroKonto.idl 1, 2 Quelltext-Listings : GiroKonto.idl : für Exceptions aufbereitet 1 Quelltext-Listings : GiroKontoImpl.cpp 1 Quelltext-Listings : GiroKontoImpl.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : GiroKontoImpl.h 1 Quelltext-Listings : GiroKontoImpl.h : für Exceptions aufbereitet 1 Quelltext-Listings : GiroKontoImpl.java 1 Quelltext-Listings : IDL-Schnittstellen 1 Quelltext-Listings : Kommentare in IDL 1 Quelltext-Listings : Konto.idl 1, 2
Quelltext-Listings : Konto.idl : für Exceptions aufbereitet 1 Quelltext-Listings : KontoDialog.java 1 Quelltext-Listings : KontoImpl.cpp 1 Quelltext-Listings : KontoImpl.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : KontoImpl.h 1 Quelltext-Listings : KontoImpl.h : für Exceptions aufbereitet 1 Quelltext-Listings : KontoImpl.java 1 Quelltext-Listings : Kunde.idl 1, 2 Quelltext-Listings : Kunde.idl : für Exceptions aufbereitet 1 Quelltext-Listings : Kunde.idl : modifiziert für Kontoaktualisierung 1 Quelltext-Listings : KundenImpl.cpp 1, 2 Quelltext-Listings : KundenImpl.h 1, 2 Quelltext-Listings : KundenImpl.java 1 Quelltext-Listings : Mehrfachvererbungssyntax 1 Quelltext-Listings : module-Konstrukt 1 Quelltext-Listings : NeuerKundeHauptmodul.cpp 1 Quelltext-Listings : NeuerKundeHauptmodul.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : Server BoersenServer 1 Quelltext-Listings : SparKonto.idl 1, 2 Quelltext-Listings : SparKonto.idl : für Exceptions aufbereitet 1 Quelltext-Listings : SparKontoImpl.cpp 1
Quelltext-Listings : SparKontoImpl.cpp : für Exceptions aufbereitet 1 Quelltext-Listings : SparKontoImpl.h 1 Quelltext-Listings : SparKontoImpl.h : für Exceptions aufbereitet 1 Quelltext-Listings : SparKontoImpl.java 1 Quelltext-Listings : struct-Typ 1 Quelltext-Listings : Typ any 1 Quelltext-Listings : UpdateAccountThread.java 1 Quelltext-Listings : Vererbungssyntax 1 Quelltext-Listings : zirkuläre Abhängigkeit 1, 2
-RRational Software Corporation 1 reference counting, object lifetime 1 Referenzübergabe 1 Referenzzähler 1 Regelverwaltung, Task Management Common Facilities 1 registriereBank() (Methode) 1, 2 registriereBank() (Methode) : Exception 1 registriereGeldautomat() Methode 1 release() (Methode) 1 remote Methoden : Object Factory pattern 1
remote Methoden : pass-by-value semantics 1 Remote-Methode, Formatübertragung 1 Remote-Objekte, structs 1 Request 1 request(), Methode, Beispiel zu DII 1 Request, Objekt : DII 1, 2 RFI 1 RFI : Asset and Content Management RFI (EC RFI1) 1 RFI : Clinical Decision Support RFI (CORBAmed RFI3) 1 RFI : Clinical Observations RFI (CORBAmed RFI2) 1 RFI : Common Business Objects RFI (CBO RFI) 1 RFI : CORBAtransport RFI 1 RFI : Enabling Technologies and Services for EC (EC RFI2) 1 RFI : HL7 RFI 1 RFI : Issues for Intelligent Networking with CORBA (Telecom RFI) 1 RFI : Lifescience RFI 1 RFI : Manufacturing DTF RFI-2 1 RFI : Manufacturing High-Level Requirements RFI (MFG RFI1) 1 RFI : Object Services Platform Task Force 1 RFI : ORB and Object Services RFI 1 1
RFI : ORB and Object Services RFI 2 1 RFP 1 RFP (Request for Proposals) : Übergabe durch Werte 1 RFP : Analysis and Design Task Force RFP1 1 RFP : Common Business Object : and Business Object Facility RFP 1 RFP : CORBA Component Model RFP (ORBOS RFP8) 1 RFP : CORBA Scripting Language RFP (ORBOS RFP9) 1 RFP : CORBA/TMN Interworking RFP 1 RFP : Currency RFP (Finance RFP31) 1 RFP : Data Interchange Facility and : Mobile Agent Facility RFP 1 RFP : DCE/CORBA Interworking RFP (ORBOS RFP6) 1 RFP : Electronic Healthcare Claims Facility RFP (EC RFP3) 1 RFP : Electronic Payment Facility (EC RFP1) 1 RFP : Finance DTF Insurance RFI 1 RFP : Finance/Insurance Party Management Facility RFP (Finance RFP2) 1 RFP : Firewall RFP 1 RFP : Healthcare Lexicon Service RFP (CORBAmed RFP2) 1 RFP : Input Methode Manager Facility RFP 1 RFP : Java to IDL RFP (ORBOS RFP5) 1 RFP : Messaging Service RFP (ORBOS RFP1) 1 RFP : Minimum CORBA RFP (ORBOS RFP10) 1
RFP : Multiple Interfaces and Composition RFP 1 RFP : Negotiation Facility RFP (EC RFP2) 1 RFP : Notification Service RFP (Telecom RFP3) 1 RFP : Object Analysis and Design Task Force 1 RFP : Objects-by-Value RFP (ORBOS RFP2) 1 RFP : Patient Identification Services RFP (CORBAmed RFP1) 1 RFP : Persistent State Service, : Version 2.0 RFP (ORBOS RFP7) 1 RFP : Printing Facility RFP 1 RFP : Product Data Management : Enablers RFP (MFG RFP1) 1 RFP : Realtime CORBA 1.0 RFP 1 RFP : Topology RFP (Telecom RFP2) 1 RFP : Workflow Management Facility RFP 1 RMI 1 RMI : CORBA im Vergleich zu Java 1, 2 RMI : Rolle von Client-Server 1 Rogue Wave Software, ORBstreams.h++ 1 Rose 1 Rose (Rational) 1 RPC 1 Rückruf 1, 2
-SSchalter : -c cpp 1 Schalter : -fclient 1 Schicht für die Benutzeroberfläche 1 Schicht für die Geschäftsregeln 1 Schicht, Protokoll für die Kommunikation zwischen ORBs 1 Schlüsselwort, virtual 1 Schnittstelle 1 Schnittstelle : Architektur der verteilten Systeme 1 Schnittstelle : Bank 1, 2, 3, 4, 5, 6, 7 Schnittstelle : Bank : Exception 1, 2 Schnittstelle : BankServer 1, 2, 3, 4, 5 Schnittstelle : BankServer : Exception 1, 2 Schnittstelle : BoersenServer 1 Schnittstelle : Client-Stub 1 Schnittstelle : CORBAfacilities 1 Schnittstelle : CORBAservices 1 Schnittstelle : GiroKonto 1, 2 Schnittstelle : GiroKonto : Exception 1 Schnittstelle : IDL 1
Schnittstelle : Konto 1, 2, 3, 4 Schnittstelle : Konto : Exception 1, 2 Schnittstelle : Kunde 1, 2 Schnittstelle : Kunde : Exception 1 Schnittstelle : module-Konstrukt 1 Schnittstelle : Server definieren 1, 2, 3 Schnittstelle : Server-Skeletons 1 Schnittstelle : Sicherheitsdienst 1 Schnittstelle : SparKonto 1, 2 Schnittstelle : SparKonto : Exception 1 Schnittstelle : Systems Management Common Facilities 1 Schnittstelle : zusammenzugruppieren 1 Schnittstellen : Geldautomat, Klasse 1 Schnittstellen : GeldautomatClient : Kontoaktualisierung 1 Schnittstellen : IDL creep 1 Schnittstellen : structs 1 Schnittstellenvererbung 1 Semikolon, IDL-Definitionssyntax 1 send_deferred(), Methode, Request, Objekt 1 send_oneway(), Methode, Request, Objekt 1 sequence-Typ 1
Serialisierung 1 Server 1, 2 Server : Aktivierungsstrategie 1 Server : Anwendung als Client und als Server 1, 2 Server : Bank 1 Server : Bank : Bank (Schnittstelle) 1, 2, 3, 4, 5, 6, 7 Server : Bank : BankServer (Schnittstelle) 1, 2, 3, 4, 5 Server : Bank : GiroKonto (Schnittstelle) 1, 2 Server : Bank : Konto (Schnittstelle) 1, 2, 3 Server : Bank : SparKonto (Schnittstelle) 1, 2 Server : BankApplet 1 Server : Einfach-Thread-Anwendungen 1, 2 Server : entwickeln 1 Server : Geldautomat 1, 2, 3 Server : IDL, Exception 1, 2, 3, 4 Server : IDL-Compiler 1, 2, 3 Server : Implementierungsansatz 1, 2 Server : kompilieren/ausführen 1, 2 Server : Objekt, Client implementieren 1 Server : Objektschnittstelle : Client implementieren 1, 2
Server : Schnittstelle : definieren 1, 2, 3 Server : Schnittstelle : implementieren 1, 2, 3, 4, 5, 6 Server-/Client-Anwendungen : Einfach-Thread-Anwendungen 1, 2 Server-/Client-Anwendungen : Einfach-Thread-Anwendungen : Object Factory, Muster 1, 2 Server-/Client-Anwendungen : Einfach-Thread-Anwendungen : oneway-Aufruf, Muster 1, 2, 3 Server-Quelltext : Exception 1 Server-Quelltext : Exception : BankImpl 1, 2, 3 Server-Quelltext : Exception : BankServerImpl 1, 2, 3 Server-Quelltext : Exception : GiroKontoImpl 1, 2, 3 Server-Quelltext : Exception : KontoImpl 1, 2, 3 Server-Quelltext : Exception : SparKontoImpl 1, 2, 3 Server-Skeleton 1, 2, 3 Server-Skeleton : implementieren 1 setStatus(), Methode (Java), BankApplet 1 setZinsSatz() (Methode) 1 setZinsSatz() (Methode) : Exception 1 short-Typ 1 Sicherheit, Java-Applet 1 Sicherheitsdienst 1 Sicherheitsdienst : Bankanwendung 1 Sicht 1
Sichtbarkeitsmodifizierer 1 Signaturen 1 Skeleton : IDL-Compiler 1 Skeleton : Server-Schnittstelle, implementieren 1 Skriptsprachen, DII-Objekt-Browser 1 SmalltalkBroker 1 SNiFF+ 1 Software through Pictures 1 Solaris NEO 1 Solstice NEO 1 SOMobjects 1 SparKonto (Klasse) 1 SparKonto (Schnittstelle) 1, 2 SparKonto (Schnittstelle) : Exception 1 SparKonto.idl 1, 2 SparKonto.idl : für Exceptions aufbereitet 1 SparKontoImpl, Exception 1, 2, 3 SparKontoImpl.cpp 1 SparKontoImpl.cpp : für Exceptions aufbereitet 1 SparKontoImpl.h 1
SparKontoImpl.h : für Exceptions aufbereitet 1 SparKontoImpl.java 1 SparKontos 1 Speicher, Lebensdauer von Objekten 1 Sprachabbildung 1, 2 Sprachabbildung : IDL-Compiler 1 Sprachenunabhängigkeit 1 Sprachenunabhängigkeit : IDL 1, 2 Standard-Exceptions in CORBA, Tabelle 1 Statische Methode (Java) 1 Statische Methode, nicht-CORBA-Anwendung 1 statische Typprüfung, DII 1 Statisches Klassenelement, Java 1 STL 1 Strategie der gemeinsam verwendeten Server 1 Strategie der nicht gemeinsam verwendeten Server 1 Strategie des permanenten Servers 1 Strategie des Server-pro-Methode 1 Strategie, Server-Aktivierung 1 Strategien zur Server-Aktivierung 1, 2 Strategieverwaltung 1
string-Typ 1 structs, Übergabe als Wert 1, 2 struct-Typ 1 Strukturtyp 1 Stub : Client implementieren 1, 2 Stub : IDL-Compiler 1 Subclass 1 Superclass 1 Sybase, Jaguar CTS 1 Syntax : Schnittstellenvererbung 1 Syntax : Vorwärtsdeklaration 1 System-Exceptions 1 Systemobjekt definieren 1 Systems Management Common Facilities 1
-TTakeFive (Software) 1 Task Force (Spezialeinheit), OMG 1 Task Management Common Facilities 1 TCP/IP, IIOP 1 Telecommunications Domain Task Force 1, 2
Terminal, nichtprogrammierbares 1 Threads 1 Thread-Warteschlangen, Multithreading 1 TIB/ObjectBus 1 TIBCO 1 tight cohesion 1 Tools.h++ 1 Transaktionen, Multithread-Server-Architektur 1 Transaktionsdienst 1 Transaktionsdienst : Bankanwendung 1 Transaktionsmonitor 1 Transportation Domain Task Force 1 Transportschicht, Kommunikationsmodell 1 try ... catch-Block : loescheKonto() (Methode) 1 TypeCode-Typ 1 TypeCode-Typ : DII 1 typedef-Konstrukt 1 Typen : any 1, 2 Typen : Arrays 1 Typen : Container 1
Typen : einfache 1 Typen : enum 1 Typen : Exception 1 Typen : Gleitkommatypen 1 Typen : Integer-Typen 1 Typen : interface 1, 2 Typen : sequence 1 Typen : Standard-Exceptions 1, 2 Typen : struct 1 Typen : TypeCode 1 Typen : union 1, 2 Typen : zusammengesetzte 1 Types, Exception 1, 2
- ÜÜbergabe als Wert 1 Übergabe als Wert : Konversionskonstruktoren 1, 2 Übergabe als Wert : ORBstreams.h++ 1 Übergabe als Wert : structs 1, 2 Übergabe durch Referenz 1 Übergabe durch Werte 1
Übergabe per Referenz 1 Übergeordnete Klasse 1, 2 Übertragungsformat 1
-UUeberweisung() (Methode) 1 UIM/Orbix 1 UML : (Unified Modeling Language) 1 UML : Assoziation 1, 2 UML : Begriffe/Symbole 1 UML : Geschichte 1 UML : Klasse 1, 2 UML : Klasse : Sichtbarkeitsmodifizierer 1 UML : Klassendiagramm 1 UML : Methodik 1, 2 UML : Modell-Sicht (Muster) 1 UML : Sicht 1 UML : Vererbung 1 UML : Zusammenfassung 1 UML Partners (Konsortium) 1 UNAS (TRW) 1
UngueltigeBankException (Exception) 1 UngueltigerGeldautomatException, Exception 1 Unified Modeling Language, siehe UML 1 union-Typ 1, 2 Universal Network Architecture Services (UNAS) 1 Unix, BankImpl.cpp 1 Unix-gestützter Server, Client-Server-Architektur 1 Unmarshaling 1 unsereZeichen (Array) 1 unsigned long long-Typ 1 unsigned long-Typ 1 unsigned short-Typ 1 UpdateAccountThread.java 1 UpdateAccountThread-Objekt (Java) 1 updateKontoInfo(), Methode (Java), BankApplet 1 Use Cases 1 User Interface Common Facilities 1
-VVector (Java-Klasse) 1 Vector (Klasse) 1
Vererbung : IDL-Schnittstellen 1 Vererbung : Schnittstellen 1, 2, 3 Vererbung : structs 1 Vererbung : UML 1 Verteilte Simulation (Einrichtung) 1 Verteiltes System : Geschichte 1 Verteiltes System : Geschichte : Client-Server-Architektur 1, 2 Verteiltes System : Geschichte : Großrechner 1 Verteiltes System : Geschichte : mehrschichtige Client-Server-Architektur 1, 2 Verteiltes System : Geschichte : verteilte Systeme 1, 2, 3 Verzeichnis, Server-Skeleton, Server-Schnittstelle 1 Verzeichnisdienst 1 Viele-zu-Viele-Assoziation 1 virtual (Schlüsselwort) 1 VisiBroker 1 VisiBroker : GateKeeper 1 VisiBroker : ORBeline Smart Agent 1 VisiBroker for Java 1 VisiBroker für C++, Exception-Behandlung 1 Visual Age für Java 1
Visual Cafe 1 Visual Cafe : BankApplet 1 Visual J++, BankApplet 1 void-Typ 1 Voyager 1
-Wwchar-Typ 1 Web browsers, BankApplet 1 Web/Enable 1 Web-Anwendungen : BankApplet 1 Web-Anwendungen : BankApplet : Client-Funktionen 1 Web-Anwendungen : BankApplet : Kunde, Klasse 1, 2 Web-Anwendungen : BankApplet : Server-Funktionen 1 Webseite, OMG 1 Werkzeuge : CORBA-relevant : Black &White Software 1 Werkzeuge : CORBA-relevant : Distributed Smalltalk 1 Werkzeuge : CORBA-relevant : Rose 1 Werkzeuge : CORBA-relevant : SNiFF+ 1 Werkzeuge : CORBA-relevant : Software through Pictures 1 Werkzeuge : CORBA-relevant : Universal Network Architecture (UNAS) 1
Werkzeuge : Java 1 Werkzeuge : ORB : CHORUS/COOL ORB 1 Werkzeuge : ORB : Communicator 4.0 1 Werkzeuge : ORB : CORBAplus 1 Werkzeuge : ORB : Corbus 1 Werkzeuge : ORB : DOME 1 Werkzeuge : ORB : Enterprise Server 1 Werkzeuge : ORB : ILU 1 Werkzeuge : ORB : Jaguar CTS 1 Werkzeuge : ORB : NEO 1 Werkzeuge : ORB : ObjectBroker 1 Werkzeuge : ORB : OmniBroker 1 Werkzeuge : ORB : omniORB2 1 Werkzeuge : ORB : ORB Plus 1 Werkzeuge : ORB : Orbix 1, 2 Werkzeuge : ORB : SmalltalkBroker 1 Werkzeuge : ORB : SOMobjects 1 Werkzeuge : ORB : TIB/ObjectBus 1 Werkzeuge : ORB : VisiBroker 1 Werkzeuge : ORB : Voyager 1 Win32-APIs, BankImpl.cpp 1
Windows, NEO Connectivity 1 WWW-Seite, PARC 1
-XX Window, System : Einfach-Thread-Anwendungen 1 X Window, System : Multithread-Anwendungen 1 X Window, System : Multithreaded-Anwendungen 1 Xerox PARC, ILU 1
-ZZeiger, Beispiel zu DII 1 Zeitgeberdienst 1 Zeitgeberdienst : Bankanwendung 1 Zirkuläre Abhängigkeit 1 Zusammengesetzte Typen 1 Zusammengesetzte Typen : Attribute 1, 2 Zusammengesetzte Typen : enum 1 Zusammengesetzte Typen : interface 1, 2 Zusammengesetzte Typen : interface-Definitionssyntax 1, 2 Zusammengesetzte Typen : Methoden/Parameter 1, 2 Zusammengesetzte Typen : Schnittstelle 1
Zusammengesetzte Typen : struct 1 Zusammengesetzte Typen : union 1, 2 Zusammengesetzte Typen : Vererbung 1, 2 Zweischichtige Client-Server-Architektur 1
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Wir sind an Ihrer Meinung interessiert! Als Teil unseres Bestrebens, Bücher höchster Qualität zu produzieren, ist SAMS an Ihrer Meinung interessiert. Sie können Ihre Kommentare, Ideen oder Vorschläge zur Verbesserung zukünftiger Ausgaben an die unten angegebene Adresse oder per Fax an 089-46003-330 schicken. Online-Verbindung bekommen Sie über das Internet http://www.mut.com. Schon jetzt vielen Dank – Ihre Kommentare werden uns dabei helfen, weiterhin die besten Computerbücher herauszugeben.
SAMS
Fragen zur HTML-Version?
Markt&Technik Jörg Wieter Hans-Pinsel-Str. 9B 85540 Haar bei München
Markt&Technik Harald Jörg Hans-Pinsel-Str. 9b 85540 Haar bei München
Tag 1 CORBA kennenlernen Was können Sie von diesem Buch erwarten? Etwas zum Hintergrund: die Geschichte der verteilten Systeme Der Anfang: monolithische Systeme und Großrechner Die Revolution: Client-Server-Architektur Die Weiterentwicklung: mehrschichtige Client-Server-Architektur Die nächste Generation: verteilte Systeme Welche Rolle spielt hier CORBA? CORBA 1.0 Die CORBA-Architektur im Überblick Zusammenfassung Fragen & Antworten Workshop
Was können Sie von diesem Buch erwarten? Natürlich ist dies nicht das erste Buch, das über die Common Object Request Broker Architecture (CORBA) geschrieben wird - bei weitem nicht. Es dürfte jedoch unter den Büchern, die derzeit zu CORBA erhältlich sind, vom Ansatz her einzigartig sein. Als dieses Buch geschrieben wurde, waren, wenn überhaupt, nur wenige Veröffentlichungen erhältlich, die eine Einführung in CORBA enthielten. Mit dem vorliegenden Buch soll diese Lücke geschlossen werden.
CORBA ist nichts für Zauderer, das ist sicher. Obwohl Entwicklungswerkzeuge verfügbar sind, die einen Teil der Komplexität von CORBA kaschieren, stehen die Chancen gut, daß Sie bei der Inangriffnahme eines Projekts, bei dem Sie eine einigermaßen komplizierte CORBA-Anwendung entwickeln möchten, die Komplexität von CORBA am eigenen Leibe zu spüren bekommen. Trotzdem gilt: Die Lernkurve für CORBA verläuft zwar sehr steil, aber jeder kompetente Programmierer ist in der Lage, gute CORBA-Kenntnisse zu erlangen. In diesem Buch wird davon ausgegangen, daß Sie bereits über ein gewisses Maß an Programmiererfahrungen verfügen. CORBA ist zwar eine sprachenunabhängige Architektur, da aber CORBA-Anwendungen hauptsächlich mit C++ und Java entwickelt werden, sind Kenntnisse in einer dieser Sprachen von Vorteil. (Die meisten Beispiele in diesem Buch sind in C++ geschrieben, wozu sich als Ausgleich eine gewisse Anzahl an Java-Anwendungen gesellt.) Es würde außerdem nichts schaden, wenn Sie sich bereits mit objektorientierten Analyse- und Entwurfstechniken auskennen, aber wenn Sie hier ein wenig Auffrischung brauchen, wird Ihnen das Buch bei der Wiederholung dieser Materie helfen. Dieses Buch geht von der Annahme aus, daß das Lernen von CORBA für die meisten Programmierer ein (wenn auch unter Schwierigkeiten) erreichbares Ziel ist, und beginnt mit den Grundlagen von CORBA, wobei zunächst ein Überblick über die Architektur gegeben wird. Dann folgt eine Einführung in die Schnittstellen-Definitionssprache Interface Definition Language (IDL), einem Eckpfeiler, auf dem die meisten CORBA-Anwendungen basieren. Anschließend werden Sie die ersten CORBA-Anwendungen programmieren, und bevor Sie es merken, lernen Sie weiterführende Konzepte und Entwurfsaspekte kennen, zusammen mit anderen nützlichen Dingen wie CORBAservices, CORBAfacilities und der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface) oder abgekürzt DII (keine Angst, Sie werden lernen, was das alles bedeutet, wenn es an der Reihe ist). All dies - und noch mehr - in nur 14 Tagen. Was dieses Buch nicht leistet und nicht leisten kann: Aus Ihnen über Nacht (oder in unserem Fall in 14 Tagen) einen CORBA-Experten machen. Das Buch zeigt Ihnen nur den Weg zur Beherrschung von CORBA. Bedenken Sie, daß es sich bei CORBA um eine komplexe Architektur handelt, bei deren Verwendung Sie viele Entwurfsaspekte und Kompromisse sowie Nuancen bei der Implementierung berücksichtigen müssen. Daher führt das Beherrschen von CORBA nur über die Erfahrung, die Sie mit dem Entwerfen und Entwickeln von CORBA-Anwendungen gewinnen. Dieses Buch wird Sie sicherlich nicht zu einem globalen CORBA-Spezialisten machen, aber es wird Ihnen helfen, eine gute Grundlage zu erlangen.
Etwas zum Hintergrund: die Geschichte der verteilten Systeme Wenn Sie so viel Interesse für CORBA aufbringen, daß Sie dieses Buch lesen, werden Sie wahrscheinlich bereits einiges über verteilte Systeme wissen. Verteilte Systeme gibt es in der einen oder anderen Form bereits eine ganze Weile, obwohl sie nicht immer so genannt wurden und mit Sicherheit nicht die Flexibilität aufwiesen, die sie heute auszeichnet. Wenn man nun herausfinden möchte, wohin in diesem Zusammenhang CORBA gehört, ist ein kurzer Blick auf die Geschichte der verteilten Systeme hilfreich. Alles begann mit dem ehrwürdigen Großrechner.
Der Anfang: monolithische Systeme und Großrechner Am Anfang (oder jedenfalls in zeitlicher Nähe dazu) war der Großrechner. Mit ihm zusammen kamen hierarchische Datenbanksysteme und nichtprogrammierbare Terminals auf , auch Einfachterminals genannt. Großrechner waren in der Regel sehr teuer in der Wartung, konnten aber eine große Anzahl von Benutzern bedienen und hatten den Vorteil (oder Nachteil, je nach Sichtweise), daß sie zentral verwaltet wurden. Die für Großrechner geschriebene Software war oft monolithisch, d.h., die Benutzeroberfläche, die Geschäftslogikfunktionen und die Funktionalität für den Datenzugriff waren in einer einzigen, großen Anwendung enthalten. Da die nichtprogrammierbaren Terminals, die für den Zugriff auf die Großrechner benutzt wurden, keine Daten verarbeiten konnten, lief die gesamte Anwendung auf dem Großrechner selbst, was die monolithische Architektur sinnvoll machte. Eine typische monolithische Architektur ist in Abbildung 1.1 zu sehen.
Abbildung 1.1: Eine typische monolithische Anwendungsarchitektur
Die Revolution: Client-Server-Architektur Das Aufkommen der PCs führte zu einer grundlegenden Abkehr vom Paradigma der monolithischen Architektur mit ihren auf Großrechnern laufenden Anwendungen. Während es bei diesen Anwendungen erforderlich war, daß der Großrechner selbst die gesamte Verarbeitung übernahm, ermöglichten es Anwendungen, die auf der Client-Server-Architektur beruhten, daß ein Teil der Verarbeitung auf die PCs an den Arbeitsplätzen der Anwender abgewälzt werden konnte. Zusammen mit der Client-Server-Revolution verbreiteten sich Unix-gestützte Server . Für viele Anwendungen war die gewaltige Rechenleistung der Großrechner einfach nicht erforderlich, und da es mit der Client-Server-Architektur möglich war, einen Großteil der Verarbeitungslast auf die PCs zu übertragen, war der Einsatz dieser kleineren Unix-gestützten Server oft kostengünstiger als der von Großrechnern. Auch waren diese Rechner für kleinere Unternehmen sehr viel preisgünstiger als Großrechner, die sich Firmen mit verhältnismäßig kleinen Umsätzen einfach nicht leisten konnten. Ein weiterer Vorteil war die Stärkung der einzelnen Abteilungen innerhalb eines Unternehmens, die eigene Server verteilten und verwalteten. Ergebnis: Diese Abteilungen konnten schneller auf ihre speziellen Bedürfnisse reagieren, indem sie ihre eigenen Anwendungen entwickelten. Dadurch entfielen die umständlichen und oft unfruchtbaren Verhandlungen, die Abteilungen, die die Kontrolle über die Großrechner hatten, dazu zu bringen, die benötigten Anwendungen zu entwickeln. Und schließlich konnten PCs, im Gegensatz zu den Einfachterminals, die auf das Ausführen von Anwendungen auf dem Großrechner beschränkt waren, viele andere Aufgaben unabhängig vom Großrechner durchführen, was ihre Nützlichkeit als Arbeitsplatzcomputer weiter erhöhte. Client-Server-Anwendungen verteilten die Komponenten der Anwendung in der Regel so, daß sich die Datenbank auf dem Server befand (unabhängig davon, ob es sich dabei um einen Unix-Rechner oder um einen Großrechner handelte), die Benutzeroberfläche befand sich auf dem Client, und die Geschäftslogik befand sich in einer oder in beiden Komponenten. Wenn an Teilen der ClientKomponente Änderungen vorgenommen wurden, mußten neue Kopien der Client-Komponente (in der Regel eine ausführbare Datei oder eine Gruppe von ausführbaren Dateien) an jeden Benutzer verteilt werden. Mit dem Aufkommen der mehrschichtigen Client-Server-Architektur (die ich im nächsten Abschnitt behandeln werde) kam es dazu, daß die »ursprüngliche« Client-Server-Architektur nun als »zweischichtige« Client-Server-Architektur bezeichnet wird. Diese zweischichtige Client-ServerArchitektur ist in Abbildung 1.2 gezeigt.
Abbildung 1.2: Zweischichtige Client-Server-Architektur
Die Weiterentwicklung: mehrschichtige Client-Server-Architektur Die Client-Server-Architektur war in vieler Hinsicht eine Revolution, eine Abkehr von herkömmlichen Verfahrensweisen. Diese Architektur löste zwar die Probleme mit Großrechneranwendungen, war aber selbst nicht ohne Nachteile. Ein Beispiel: Da sich die Funktionalität für Datenbankzugriffe (wie z.B. eingebettete Datenbankabfragen) und die Geschäftslogik oft in der Client-Komponente befanden, war bei Änderungen an der Geschäftslogik, dem Datenbankzugriff oder sogar an der Datenbank selbst nicht selten der Einsatz einer neuen ClientKomponente für alle Benutzer der Anwendung notwendig. In der Regel führten diese Änderungen dazu, daß frühere Versionen der Client-Komponente auseinandergerissen wurden, was zu einer instabilen Anwendung führte.
Diese Probleme in einer traditionellen Client-Server-Architektur (nun oft als »zweischichtige« ClientServer-Architektur bezeichnet) wurden von der mehrschichtigen Client-Server-Architektur behoben. Von der Konzeption her kann eine Anwendung eine beliebige Anzahl von Schichten aufweisen, die beliebteste Architektur ist jedoch die mit drei Schichten. Dabei wird das System in drei logische Schichten unterteilt: die Benutzeroberflächenschicht, die Geschäftslogikschicht und die Datenbankzugriffsschicht. Eine dreischichtige Client-Server-Architektur ist in Abbildung 1.3 gezeigt. FONT>
Abbildung 1.3: Dreischichtige Client-Server-Architektur Die mehrschichtige Client-Server-Architektur stellt in zweierlei Hinsicht eine Verbesserung der zweischichtigen Client-Server-Architektur dar: Einerseits (und dies ist vielleicht am wichtigsten) ist die Anwendung durch diese Architektur weniger instabil, weil der Client von Änderungen im Rest der Anwendung stärker isoliert ist. Außerdem ist beim Einsatz der Anwendungen mehr Flexibilität
möglich, weil die ausführbaren Komponenten kleiner sind. Der Einsatz mehrschichtiger Client-Server-Architekturen verringert die Instabilität der Anwendungen, weil die einzelnen Schichten stärker voneinander isoliert und getrennt sind. Die Schicht für die Benutzeroberfläche steht nur mit der Schicht für Geschäftsregeln in Verbindung, niemals direkt mit der Schicht für den Datenbankzugriff. Die Schicht für die Geschäftsregeln wiederum steht mit der Schicht für die Benutzeroberfläche auf der einen Seite und mit der Schicht für den Datenbankzugriff auf der anderen Seite in Verbindung. Somit betreffen Änderungen an der Schicht für den Datenbankzugriff die Schicht für die Benutzeroberfläche nicht, weil beide voneinander isoliert sind. Mit dieser Architektur ist es weniger wahrscheinlich, daß Änderungen an der Anwendung Auswirkungen auf die Client-Komponente haben (die, wie bereits erwähnt, nach Änderungen neu verteilt werden muß). Da bei einer mehrschichtigen Client-Server-Architektur die Anwendung in mehr Komponenten unterteilt wird als beim herkömmlichen zweischichtigen Modell, ist mehr Flexibilität beim Einsatz der Anwendung möglich. So zeigt Abbildung 1.3 ein System, in dem sich die Schichten für die Geschäftsregeln und für den Datenbankzugriff auf demselben Server-Computer befinden, obwohl es sich um getrennte logische Einheiten handelt. Es ist auch möglich, jede Server-Komponente auf einem separaten Computer unterzubringen. Tatsächlich können mehrere Geschäftslogikkomponenten (und mehrere Datenbankzugriffskomponenten, wenn mehrere Datenbanken benutzt werden) für eine einzelne Anwendung entwickelt werden, wodurch der Verarbeitungsaufwand verteilt wird und eine robustere, skalierbare Anwendung entsteht. Es ist interessant festzustellen, daß die mehrschichtige Client-ServerArchitektur ihre Wurzeln tatsächlich in Großrechneranwendungen haben könnte. Cobol-Anwendungen auf IBM-Großrechnern konnten die Benutzeroberfläche mit Hilfe eines Werkzeugs namens MFS (Message Format Service, Nachrichtenformatdienst) definieren. Durch MFS wurde der Terminaltyp (Terminals konnten z.B. eine unterschiedliche Anzahl von Zeilen und Spalten anzeigen) von der restlichen Anwendung getrennt. Auf gleiche Weise konnten Anwendungen auch die Datenbankschnittstellen definieren. Obwohl die Anwendung immer noch in einem monolithischen Block lief, ermöglichten die verfügbaren Werkzeuge den Entwurf von Anwendungen mit Hilfe einer logischen dreischichtigen Architektur.
Die nächste Generation: verteilte Systeme Der nächste logische Schritt in der Evolution von Anwendungsarchitekturen ist das Modell der verteilten Systeme. Bei dieser Architektur wird das Konzept der mehrschichtigen Client-ServerArchitektur natürlich weitergeführt. Statt zwischen Geschäftslogik und Datenbankzugriff zu unterscheiden, wird beim Modell der verteilten Systeme einfach die gesamte Funktionalität der
Anwendung in Form von Objekten dargestellt, wobei jedes von ihnen die von den übrigen Objekten im System oder sogar von Objekten in anderen Systemen bereitgestellten Dienste nutzen kann. Mit dieser Architektur wird auch die Unterscheidung zwischen »Client« und »Server« verwischt, weil die Client-Komponenten auch Objekte erzeugen können, die sich wie Server verhalten. Mit der Architektur der verteilten Systeme wird eine absolute Flexibilität zur Verfügung gestellt. Die Architektur der verteilten Systeme erhält ihre Flexibilität dadurch, daß sie die Definition spezieller Komponentenschnittstellen fördert (oder erzwingt). Die Schnittstelle einer Komponente gibt den übrigen Komponenten an, welche Dienste die Komponente anbietet und wie diese genutzt werden. Solange die Schnittstelle einer Komponente konstant bleibt, kann sich die Implementierung der Komponente grundlegend ändern, ohne daß dies Auswirkungen auf die übrigen Komponenten hat. Beispiel: Eine Komponente, die Kundendaten für eine Firma zur Verfügung stellt, speichert diese in einer relationalen Datenbank. Später beschließen die Anwendungsentwickler, daß eine objektorientierte Datenbank für diesen Zweck besser geeignet wäre. Sie können beliebige Änderungen an der Implementierung der Komponente vornehmen, selbst so grundlegende Änderungen wie die Verwendung eines anderen Datenbanktyps, vorausgesetzt, daß dabei die Schnittstelle der Komponente intakt bleibt. Auch hier gilt, daß die zugrundeliegende Implementierung der Komponente beliebig geändert werden kann, solange ihre Schnittstelle unverändert bleibt. Eine Schnittstelle dient zur Definition des Kommunikationsprotokolls zwischen zwei getrennten Komponenten eines Systems. (Bei diesen Komponenten kann es sich um getrennte Prozesse, um getrennte Objekte oder um einen Benutzer und eine Anwendung handeln, also um beliebige getrennte Einheiten, die miteinander kommunizieren müssen.) Die Schnittstelle beschreibt, welche Dienste von einer Komponente zur Verfügung gestellt werden und mit welchem Protokoll diese Dienste genutzt werden können. Bei einem Objekt kann man sich die Schnittstelle als die Gruppe von Methoden vorstellen, die von diesem Objekt definiert sind, einschließlich der Eingabe- und Ausgabeparameter. Man kann sich eine Schnittstelle wie einen Vertrag vorstellen, in dem die Komponente, die eine Schnittstelle zur Verfügung stellt, zusichert, Dienstanforderungen so zu berücksichtigen, wie dies in der Schnittstelle festgelegt ist.
Bei verteilten Systemen handelt es sich in Wirklichkeit um mehrschichtige Client-Server-Systeme, in denen die Anzahl der unterschiedlichen Clients und Server potentiell sehr hoch ist. Ein wichtiger Unterschied besteht darin, daß verteilte Systeme in der Regel zusätzliche Dienste zur Verfügung stellen, wie beispielsweise Verzeichnisdienste, die es ermöglichen, daß einzelne Komponenten der Anwendung von anderen gefunden werden können. Andere Dienste könnten z.B. einen Transaktionsmonitordienst umfassen, der es Komponenten ermöglicht, mit anderen Komponenten in Transaktion zu treten.
Unter Verzeichnisdiensten versteht man eine Gruppe von Diensten, die es Objekten, beispielsweise Servern, Geschäften oder sogar Personen, ermöglicht, von anderen Objekten gefunden zu werden. Es ist nicht nur möglich, daß die zu suchenden Objekte verschiedenen Typs sind, auch die Verzeichnisinformationen selbst können sich unterscheiden. So würde man ein Telefonbuch dazu verwenden, um Telefonnummern und Adressen herauszufinden, ein E-Mail-Verzeichnis zum Suchen von EMail-Adressen. Verzeichnisdienste umfassen alle diese Informationen; in der Regel werden zusammengehörige Informationen gruppiert (so gibt es verschiedene Bände der Gelben Seiten für verschiedene Städte; der Inhalt jedes Bandes ist in verschiedene Branchen unterteilt).
Ein Transaktionsmonitordienst überwacht Transaktionen für andere Objekte. Bei einer Transaktion handelt es sich um eine Operation oder eine Gruppe von Operationen, die bis ins Letzte durchgeführt werden müssen. Dies bedeutet, daß entweder alle an der Transaktion beteiligten Objekte die Transaktion erfolgreich durchführen müssen (ihre eigenen Datensätze aktualisieren), oder alle beteiligten Objekte müssen die Transaktion abbrechen (in den Zustand vor Einleitung der Transaktion zurückkehren). Das Ergebnis ist, daß sich alle beteiligten Objekte, unabhängig davon, ob die Operation erfolgreich durchgeführt oder abgebrochen wurde, in einem konsistenten Zustand befinden. Ein Transaktionsmonitor stellt transaktionsrelevante Dienste für andere Objekte zur Verfügung.
Zusammenfassend könnte man sagen, daß Geschäftsanwendungen sich mit der Zeit von einer relativ starren, monolithischen hin zu einer extrem flexiblen, verteilten Architektur entwickelt haben. Auf dem Weg dorthin bieten die Anwendungsarchitekturen in zunehmendem Maße Stabilität aufgrund der Definitionen der Schnittstellen zwischen Komponenten und der Skalierbarkeit der Anwendungen (die teilweise durch die Fähigkeit geliefert wurde, Server-Komponenten auf verschiedenen Computern zu vervielfältigen). Darüber hinaus wurden Dienste eingeführt, die es dem Endbenutzer einer Anwendung ermöglichen, die unüberschaubare Menge von verfügbaren Diensten sinnvoll zu nutzen. Diejenigen, die seit den Tagen der Großrechner mit dem Entwurf und der Entwicklung von Geschäftsanwendungen beschäftigt sind, haben sicher schon viel Interessantes gesehen.
Welche Rolle spielt hier CORBA? Bisher war bei der Darstellung der Entwicklung von Geschäftsanwendungen von der monolithischen Großrechnerarchitektur bis hin zur stark dezentralisierten, verteilten Architektur nirgends die Rede von CORBA. Deshalb werden Sie sich wahrscheinlich jetzt fragen, welche Rolle CORBA bei all diesem spielt. Die Antwort finden Sie im weiteren Verlauf dieses Buches. Wie bereits erwähnt, basieren verteilte Systeme auf der Definition von Schnittstellen zwischen Komponenten und auf dem
Vorhandensein verschiedener Dienste (wie Verzeichnisregistrierung und -suche), die für eine Anwendung zur Verfügung stehen. CORBA stellt einen Standardmechanismus zum Definieren der Schnittstellen zwischen Komponenten sowie einige Werkzeuge zur Verfügung, die das Implementieren dieser Schnittstellen in den vom Programmierer gewählten Sprachen erleichtern. Zusätzlich definiert die Object Management Group (dies ist die Organisation, die für die Standardisierung und Verbreitung von CORBA zuständig ist) eine Fülle von Standarddiensten wie Verzeichnis- und Bezeichnungsdienste (Directory- und Naming-Services), Objektpersistenzdienste (Persistent Object Services) und Transaktionsdienste (Transaction Services). Jeder dieser Dienste ist CORBA-kompatibel definiert, so daß sie für alle CORBA-Anwendungen zur Verfügung stehen. Schließlich stellt CORBA noch die »Leitungen« zur Verfügung, die es den verschiedenen Komponenten einer Anwendung oder verschiedener Anwendungen ermöglicht, miteinander zu kommunizieren. Damit sind aber die Fähigkeiten von CORBA noch nicht erschöpft. Zwei Merkmale von CORBA, die in der Computersoftware-Branche Seltenheitswert haben, sind Plattformunabhängigkeit und Sprachenunabhängigkeit. Plattformunabhängigkeit bedeutet, daß die CORBA-Objekte auf jeder Plattform verwendet werden können, für die eine CORBA ORB-Implementierung vorhanden ist (hierzu gehören praktisch alle modernen Betriebssysteme sowie einige nicht mehr ganz so neue). Sprachenunabhängigkeit bedeutet, daß CORBA-Objekte und Clients in quasi jeder Programmiersprache implementiert werden können. Ferner ist es für CORBA-Objekte nicht relevant, in welcher Sprache andere CORBA-Objekte implementiert wurden, mit denen sie kommunizieren. Sie werden bald die Komponenten der CORBAArchitektur zu sehen bekommen, die Plattformunabhängigkeit und Sprachenunabhängigkeit möglich machen.
Die Alternativen zu CORBA Beim Entwerfen und Implementieren verteilter Anwendungen ist CORBA sicherlich nicht die einzige Möglichkeit für den Entwickler. Es gibt auch noch andere Mechanismen, mit deren Hilfe solche Anwendungen entwikkelt werden können. In Abhängigkeit von der Art der Anwendung, angefangen bei ihrer eigenen Komplexität über die Plattform(en), auf denen sie läuft, bis hin zu der bzw. den zum Implementieren verwendeten Sprache(n), gilt es für jeden Entwickler, einige Alternativen zu bedenken. In diesem Abschnitt werde ich Ihnen einige dieser Alternativen vorstellen und sie mit CORBA vergleichen. Die Socket-Programmierung In den meisten neueren Systemen erfolgt die Kommunikation zwischen den Rechnern und manchmal auch zwischen den Prozessen auf demselben Rechner über Sockets. Einfach gesagt, ein Socket ist ein Kanal, über den Anwendungen miteinander verbunden werden und kommunizieren. Das einfachste Verfahren zur Kommunikation zwischen Anwendungskomponenten ist die direkte Verwendung von
Sockets (dies wird als Socket-Programmierung bezeichnet), was bedeutet, daß der Entwickler Daten auf das Socket schreibt und/oder daraus liest. Die Anwendungsprogrammierschnittstelle (API) für die Socket-Programmierung arbeitet recht hardwarenah. Daraus resultiert, daß der Systemaufwand, der mit einer auf diese Weise kommunizierenden Anwendung verbunden ist, kaum ins Gewicht fällt. Da die API jedoch auf einer solch niedrigen Ebene angesiedelt ist, ist die Socket-Programmierung nicht besonders gut zur Bearbeitung komplexer Datentypen geeignet, besonders wenn die Anwendungskomponenten sich auf unterschiedlichen Rechnertypen befinden oder in unterschiedlichen Programmiersprachen implementiert wurden. Obwohl die direkte Socket-Programmierung sehr effektive Anwendungen ergeben kann, ist dieser Ansatz allerdings für komplexe Anwendungen in der Regel ungeeignet. Remote-Prozedurenaufruf (RPC) Eine Stufe höher auf der Leiter, oberhalb der Socket-Programmierung, befindet sich der RemoteProzedurenaufruf (Remote Procedure Call, RPC). RPC bietet eine funktionsorientierte Schnittstelle zur Kommunikation auf Socket-Ebene. Anstatt die zum und vom Socket strömenden Daten direkt zu bearbeiten, definiert der Entwickler mit RPC eine Funktion, wie in einer funktionalen Sprache wie C, und generiert Code, der dafür sorgt, daß die Funktion für die aufrufende Anwendung wie eine normale Funktion aussieht. Bei Licht betrachtet, stellt man fest, daß die Funktion tatsächlich Sockets verwendet, um mit einem Remote-Server (einem Remote-Server) zu kommunizieren, der die Funktion ausführt und das Ergebnis zurückgibt, wobei wiederum Sockets verwendet werden. Da RPC eine funktionsorientierte Schnittstelle bietet, ist dieses Verfahren oft einfacher zu verwenden als die reine Socket-Programmierung. RPC ist außerdem leistungsfähig genug, um als Basis für viele Client-Server-Anwendungen zu dienen. Obwohl es unterschiedliche, nicht miteinander kompatible Implementierungen des RPC-Protokolls gibt, steht ein Standard-RPC-Protokoll zur Verfügung, das auf den meisten Plattformen ohne Änderungen eingesetzt werden kann. Distributed Computing Environment (DCE) von OSF Die DCE (Distributed Computing Environment, verteilte Rechnerumgebung) umfaßt eine Reihe von Standards, für die die Open Software Foundation (OSF) die Pionierarbeit geleistet hat, und enthält einen Standard für RPC. Obwohl es den DCE-Standard bereits eine ganze Weile gibt und er sinnvoll ist, hat er nie breite Anerkennung erfahren und ist heute nicht viel mehr als ein Kuriosum aus vergangenen Tagen. Distributed Component Object Model (DCOM) von Microsoft Das DCOM (Distributed Component Object Model, Objektmodell für verteilte Komponenten) stellt Microsofts Einstieg in den Bereich der verteilten Rechnersysteme dar und bietet Funktionen, die denen von CORBA entsprechen. DCOM ist ein relativ robustes Objektmodell, das von den Betriebssystemen von Microsoft besonders gut unterstützt wird, weil es in Windows 95 und Windows NT integriert ist. Da es sich um eine Microsoft-Technologie handelt, ist DCOM außerhalb der Windows-Welt kaum anzutreffen. Microsoft arbeitet jedoch daran, dies abzustellen, indem es eine
Partnerschaft mit der Software AG eingegangen ist, um DCOM auch auf anderen Plattformen als nur Windows verfügbar zu machen. Bei Entstehung dieses Buchs war DCOM für das Betriebssystem Solaris von Sun verfügbar, Unterstützung wurde für Unix von Digital, MVS von IBM und andere Betriebssysteme für Ende des Jahres angekündigt. Wenn Sie dieses Buch in den Händen halten, werden einige oder alle dieser Portierungen zur Verfügung stehen. (Weitere Informationen über die Portierung von DCOM auf andere Plattformen können Sie unter http://www.softwareag.com/corporat/dcom/default.htm abrufen.) Microsoft hat bei zahlreichen Gelegenheiten deutlich gemacht, daß DCOM auf den WindowsBetriebssystemen am besten unterstützt wird. Daher ist jeder Entwickler, der plattformübergreifende Anwendungen im Auge hat, gut beraten, wenn er die Möglichkeiten von DCOM auf der bzw. den betreffenden Plattform(en) prüft, bevor er sich für den Einsatz dieser Technologie entscheidet. Für die Entwicklung reiner Windows-Anwendungen jedoch kann man sich kaum eine Grundstruktur für verteilte Rechnersysteme vorstellen, die besser in das Betriebssystem Windows integriert werden kann. Eine interessante Entwicklung bezüglich CORBA und DCOM betrifft die Verfügbarkeit von CORBADCOM-Bridges, durch die es CORBA-Objekten ermöglicht wird, mit DCOM-Objekten zu kommunizieren und umgekehrt. Aufgrund der »verschiedenen Wellenlängen« von CORBA- und DCOM-Objekten (es gibt inhärente, schwer zu überwindende Inkompatibilitäten zwischen den beiden) ist die CORBA-DCOM-Bridge keine Ideallösung, kann sich aber in Situationen, in denen sowohl DCOM- als auch CORBA-Objekte verwendet werden sollen, als nützlich erweisen. Remote Methode Invocation (RMI) im Java-Standard Die Übersicht über die Alternativen zu CORBA endet bei RMI (Remote Methode Invocation, FernMethodenaufruf) im Java-Standard, einer CORBA sehr ähnlichen Architektur mit einigen Eigenheiten. Ein Vorteil von RMI besteht darin, daß dieses Verfahren die Weitergabe eines Objekts durch seinen Wert unterstützt, eine Funktion, die (derzeit) von CORBA nicht unterstützt wird. Nachteil ist jedoch, daß es sich bei RMI um eine Java-spezifische Lösung handelt, d.h., RMI-Clients und -Server müssen in Java geschrieben sein. Für alle Java-Anwendungen, insbesondere diejenigen, die die Funktion zur Weitergabe von Objekten durch Werte nutzen, ist RMI eine gute Wahl, aber wenn die Wahrscheinlichkeit besteht, daß die Anwendung später mit Anwendungen zusammenarbeiten muß, die in anderen Sprachen geschrieben wurden, ist CORBA die bessere Alternative. Zum Glück gibt es für Java bereits komplette CORBA-Implementierungen, die sicherstellen, daß Java-Anwendungen mit der übrigen CORBA-Welt zusammenarbeiten können. Die Geschichte von CORBA Jetzt wissen Sie schon einiges über den Hintergrund von CORBA und die Gründe, warum es entwickelt wurde. Nun scheint es mir an der Zeit, kurz die Entstehungsgeschichte von CORBA darzulegen. Die Object Management Group (OMG)
Die Object Management Group (OMG) wurde 1989 mit ursprünglich acht Mitgliedern gegründet und hat heute mehr als 760 Mitglieder, deren erklärtes Ziel es ist, »ein gemeinsames architekturbezogenes Gerüst für objektorientierte Anwendungen zur Verfügung zu stellen, das auf weit verbreiteten Schnittstellenspezifikationen basiert«. Dies ist ein hohes Ziel, aber die OMG erreicht es mit der Einführung der OMA (Object Management Architecture, Objektmanagement-Architektur) , zu deren Bestandteilen CORBA gehört. Diese Standards liefern das architekturbezogene Gerüst, anhand dessen Anwendungen entwickelt werden. Sehr kurz gesagt, besteht die OMA aus der Funktion ORB (Object Request Broker, Objektanfragenvermittler), Objektdiensten (diese werden als CORBAservices bezeichnet), gemeinsamen Einrichtungen (CORBAfacilities), Domänenschnittstellen und Anwendungsobjekten. Die Funktion von CORBA innerhalb der OMA besteht darin, die ORBFunktion zu implementieren. Der größte Teil dieses Buches wird sich mit CORBA selbst befassen und CORBAservices sowie CORBAfacilities nur gelegentlich streifen.
CORBA 1.0 Nach der Gründung der OMG im Jahre 1989 wurde CORBA 1.0 im Dezember 1990 eingeführt und in Kraft gesetzt. Anfang 1991 folgte CORBA 1.1. In dieser Version waren die Interface Definition Language (IDL) und die API für Anwendungen zur Kommunikation mit einem Object Request Broker (ORB) definiert. (Diese Begriffe werden Sie ausführlich am 2. Tag kennenlernen.) Eine überarbeitete Version 1.2 erschien kurz vor CORBA 2.0. Diese Version mit ihren zusätzlichen Funktionen ließ die 1.x-Versionen schnell verblassen. Durch die 1.x-Versionen von CORBA wurde ein erster wichtiger Schritt in Richtung auf Objektinteroperabilität gemacht, denn sie ermöglichten es Objekten auf unterschiedlichen Rechnern, die mit Hilfe von unterschiedlichen Architekturen und in unterschiedlichen Sprachen geschrieben waren, miteinander zu kommunizieren. CORBA 2.0 und IIOP CORBA 1.x stellte den ersten wichtigen Schritt in Richtung Objektkompatibilität dar, aber es handelte sich nicht um eine vollständige Spezifikation. Obwohl darin Standards für die IDL und den Zugriff auf einen ORB über eine Anwendung enthalten waren, bestand die wichtigste Einschränkung darin, daß kein Standardprotokoll definiert war, über das die ORBs miteinander kommunizieren konnten. Ergebnis: Ein CORBA-ORB eines Anbieters konnte nicht mit dem CORBA-ORB eines anderen Anbieters kommunizieren. Diese Einschränkung beeinträchtigte die Kompatibilität zwischen den verteilten Objekten in erheblichem Maße. Dann kam der Auftritt von CORBA 2.0. Nach der Inkraftsetzung im Dezember 1994 war die erste Errungenschaft von CORBA 2.0 die Definition eines Standardprotokolls, über das die ORBs der verschiedenen CORBA-Anbieter miteinander kommunizieren konnten. Dieses Protokoll heißt Internet Inter-ORB Protocol (IIOP) und muß von allen Anbietern implementiert werden, die ihre Produkte als CORBA 2.0-kompatibel bezeichnen wollen. Die wichtigste Funktion des IIOP besteht darin, eine echte Interoperabilität zwischen den Produkten der zahlreichen Anbieter herzustellen, wodurch die CORBA-Anwendungen anbieterunabhängiger werden konnten. Da es sich beim IIOP um das Internet Inter-ORB Protocol handelt, ist dies nur auf Netzwerke anwendbar, die auf TCP/IP basieren; hierzu gehören das Internet sowie die meisten Intranets.
Der CORBA-Standard wurde auch über Version 2.0 hinaus weiterentwikkelt. Im September 1997 wurde die Version 2.1 fertig, kurz darauf folgte 2.2. Version 2.3 wird für Anfang 1998 erwartet. (Die OMG weiß sich selbst zu beschäftigen!) Mit diesen überarbeiteten Versionen wurden bzw. werden evolutionäre (aber keineswegs revolutionäre) Weiterentwicklungen der CORBA-Architektur eingeführt.
Die CORBA-Architektur im Überblick Jetzt haben Sie Einzelheiten zur Geschichte und zu den Gründen für die Entwicklung von CORBA gelernt. Nun werden wir uns mit der Architektur von CORBA beschäftigen. Dieses Thema wird am 2. Tag noch ausführlicher behandelt, aber ich möchte Ihnen am 1. Tag eine ganz allgemeine Zusammenfassung über den Aufbau der Architektur von CORBA geben. Zunächst sei gesagt, daß es sich bei CORBA um eine objektorientierte Architektur handelt . CORBAObjekte weisen viele Merkmale auf, die auch in anderen objektorientierten Systemen zu finden sind, hierzu gehören Vererbung und Polymorphie. Was CORBA noch interessanter macht, ist die Tatsache, daß diese Merkmale auch zur Verfügung stehen, wenn eine nicht objektorientierte Sprache wie C oder Cobol verwendet wird. Allerdings wird CORBA besonders gut durchobjektorientierte Sprachen wie C++ und Java abgebildet. Unter Schnittstellenvererbung versteht man ein Konzept, das Entwicklern bekannt sein müßte, die objektorientiert in C oder Java arbeiten. Den Gegensatz dazu bildet die Implementierungsvererbung , bei der eine Implementierungseinheit (in der Regel eine Klasse) von einer anderen abgeleitet werden kann. Im Vergleich dazu kann bei der Schnittstellenvererbung eine Schnittstelle von einer anderen abgeleitet werden. Obwohl Schnittstellen durch Vererbung miteinander in Beziehung gesetzt werden können, braucht dies bei den Implementierungen dieser Schnittstellen nicht der Fall zu sein.
Der Object Request Broker (ORB) Einen grundlegenden Bestandteil der Common Object Request Broker Architecture bildet der Object Request Broker oder kurz ORB. (Daß das Akronym ORB in CORBA enthalten ist, konnte gar kein Zufall sein.) Unter einem ORB versteht man eine Software-Komponente, die die Kommunikation zwischen Objekten erleichtern soll. Hierzu werden eine Reihe von Leistungsmerkmalen bereitgestellt, eine davon besteht darin, ein Remote-Objekt aufzufinden, sofern eine Objektreferenz vorhanden ist. Ein anderer vom ORB zur Verfügung gestellter Dienst besteht im Übertragen des Formats von Parametern und Rückgabewerten (dies wird auch als »Marshaling« bezeichnet), die bei RemoteMethodenaufrufen gesendet bzw. empfangen werden. (Es ist klar, daß Sie diese Erläuterung jetzt noch nicht verstehen, ich komme aber am 2. Tag noch eingehend darauf zu sprechen. Jetzt brauchen Sie sich bloß zu merken, daß die Object Management Architecture (OMA ) die ORB-Funktionalität vorsieht und daß CORBA der Standard für die Implementierung dieser ORB-Funktionalität ist. Sie
werden bald sehen, daß die Verwendung von ORBs den verteilten CORBA-Objekten Plattformunabhängigkeit beschert. Die Interface Definition Language (IDL) Ein weiterer zentraler Bestandteil der Architektur von CORBA ist die darin verwendete Interface Definition Language (IDL). Die IDL dient zur Definition von Schnittstellen zwischen CORBAObjekten und ist das Instrument, mit dem die Sprachenunabhängigkeit von CORBA sichergestellt wird. Da die mit der IDL beschriebenen Schnittstellen auf jede beliebige Programmiersprache abgebildet werden können, sind die mit CORBA entwickelten Anwendungen und Komponenten unabhängig von der bzw. den Sprache(n), die zur Implementierung verwendet wurde(n). Anders gesagt, ein in C++ geschriebener Client kann mit einem in Java geschriebenen Server kommunizieren, dieser wiederum kann mit einem anderen Server kommunizieren, der in Cobol geschrieben wurde usw. Ein wichtiger Aspekt bei der IDL ist die Tatsache, daß es sich nicht um eine Implementierungssprache handelt. Dies bedeutet, daß Sie in dieser Sprache keine Anwendungen schreiben können. Die IDL hat nur den Zweck, Schnittstellen zu definieren, die Implementierungen für diese Schnittstellen erfolgen über eine andere Sprache. Am 3. Tag werden Sie die IDL näher kennenlernen und dabei über dieses und andere Themen Näheres erfahren. Das Kommunikationsmodell von CORBA In CORBA werden »Objektreferenzen« verwendet (diese werden im CORBA/IIOP-Sprachgebrauch als IORs (Interoperable Object References, Interoperable Objektreferenzen) bezeichnet), um die Kommunikation zwischen den Objekten zu erleichtern. Wenn eine Komponente einer Anwendung auf ein CORBA-Objekt zugreifen möchte, erhält es zunächst eine IOR für das betreffende Objekt. Mit Hilfe der IOR kann die Komponente (die als Client des betreffenden Objekts bezeichnet wird) Methoden des Objekts aufrufen (dieses wird hier als Server bezeichnet). In CORBA versteht man unter einem Client einfach eine Anwendung, die die Dienste eines CORBA-Objekts nutzt, d.h. eine Anwendung, die eine oder mehrere Methoden eines anderen Objekts aufruft. Ein Server ist somit eine Anwendung, die CORBA-Objekte erstellt und die Dienste, die von diesen Objekten zur Verfügung gestellt werden, für andere Anwendungen verfügbar macht. Am 2. Tag folgt eine eingehendere Behandlung der CORBA-Clients und -Server.
Wie bereits erwähnt, verwenden CORBA-ORBs für die Kommunikation in der Regel das Internet Inter-ORB Protocol (IIOP). Es gibt auch andere Protokolle für die Kommunikation zwischen ORBs, aber das IIOP ist dabei, in kurzer Zeit zum meistverwendeten Protokoll zu werden. Dies liegt vor allem daran, daß es Standard ist, und zum anderen an der Beliebtheit von TCP/IP (dem vom Internet verwendeten Netzwerkprotokoll), der Schicht, über der sich das IIOP befindet. CORBA ist jedoch unabhängig von Netzwerkprotokollen und könnte (zumindest theoretisch) über jedes beliebige
Netzwerkprotokoll laufen. Es gibt beispielsweise auch Implementierungen von CORBA, die über DCE und nicht über TCP/IP laufen, und es besteht ein Interesse daran, CORBA über ATM und SS7 laufen zu lassen. Das Objektmodell von CORBA In CORBA erfolgt die gesamte Kommunikation zwischen Objekten über Objektreferenzen (hier sei nochmals erwähnt, daß diese auch als IORs (Interoperable Object References, Interoperable Objektreferenzen) bezeichnet werden, wenn Sie das IIOP verwenden). Die Sicht auf diese Objekte ist nur dadurch möglich, daß Referenzen an diese Objekte weitergegeben werden (Variablenparameter, Pass By Reference), die Weitergabe der Objekte kann nicht durch Werte (Wertparameter, Pass By Value) erfolgen (zumindest ist dies in der aktuellen Spezifikation von CORBA der Fall). Anders gesagt, Remote-Objekte bleiben in CORBA entfernt (Remote), derzeit gibt es keine Möglichkeit für ein Objekt, sich selbst an einen anderen Ort zu verschieben oder zu kopieren. (Dieses Thema sowie andere Einschränkungen und Entwurfsthemen von CORBA werden am 10. Tag behandelt.) Ein weiterer Aspekt des Objektmodells von CORBA ist der BOA (Basic Object Adapter, grundlegender Objektadapter); auch diesen Begriff werden Sie am 2. Tag kennenlernen. Ein BOA stellt allgemeine Basisdienste für alle CORBA-Objekte zur Verfügung. CORBA-Clients und -Server Wie auch bei reinen Client-Server-Architekturen werden bei CORBA die Begriffe Client und Server beibehalten. In CORBA kann eine Komponente sowohl als Client als auch als Server fungieren. Im Grunde wird eine Komponente als Server betrachtet, wenn sie CORBA-Objekte enthält, deren Dienste anderen Objekten zur Verfügung stehen. Analog dazu wird eine Komponente als Client betrachtet, wenn sie auf Dienste eines anderen CORBA-Objekts zugreift. Natürlich kann eine Komponente gleichzeitig sowohl verschiedene Dienste zur Verfügung stellen als auch solche nutzen, und daher kann sie, je nach Szenario, als Client oder Server betrachtet werden. Stubs und Skeletons Beim Implementieren von CORBA-Anwendungskomponenten werden Ihnen auch Client-Stubs und Server-Skeletons begegnen. Unter einem Client-Stub versteht man einen kleinen Quelltextteil, der es einem Client ermöglicht, auf eine Server-Komponente zuzugreifen. Dieser Quelltext wird zusammen mit dem Client-Teil der Anwendung kompiliert. Analog versteht man unter Server-Skeletons Quelltextteile, die beim Implementieren eines Servers quasi »ausgefüllt werden«. Sie brauchen die Client-Stubs und Server-Skeletons nicht selbst zu schreiben, denn dieser Quelltext wird beim Kompilieren von IDL-Schnittstellendefinitionen generiert. Auch dies werden Sie bald kennenlernen. Erweiterte Funktionalität: CORBAservices und CORBAfacilities Neben den CORBA-Basisfunktionen, die die Kommunikation der Objekte miteinander ermöglichen, sind auch noch Funktionen wichtig, die von der OMA, zu der CORBA gehört, zusätzlich zur Verfügung gestellt werden und als CORBAservices und CORBAfacilities bekannt sind. Wie Sie noch
feststellen werden, bieten CORBAservices und CORBAfacilities sowohl horizontale (in der Regel für alle Branchen nützliche) als auch vertikale (für spezielle Branchen konzipierte) Dienste und Einrichtungen. Diese Leistungsmerkmale werden am 12. Tag noch eingehend behandelt, danach werden Sie Gelegenheit haben, einen Teil dieser Funktionalität in einer CORBA-Anwendung einzusetzen.
Zusammenfassung Heute haben Sie einen sehr komprimierten Überblick über die Architektur von CORBA erhalten, einen Abriß über die Geschichte der Entwicklung von Geschäftsanwendungen und der Rolle, die CORBA bei all diesem spielt. Sie wissen nun, was Sie von diesem Buch erwarten können: Sie werden nicht über Nacht zum CORBA-Experten, erhalten aber wertvolle Kenntnisse zum Entwurf und zur Entwicklung von CORBA-gestützten Anwendungen. In den nächsten Tagen werden Sie die Architektur von CORBA noch viel eingehender kennenlernen, Sie werden erfahren, was Sie schon immer über IDL wissen wollten, und sind damit schon auf dem Weg zum Entwikkeln von CORBA-Anwendungen.
Fragen & Antworten Frage: Mir ist immer noch nicht richtig klar, warum ich CORBA und nicht eine der anderen Methoden zur Kommunikation zwischen Prozessen verwenden soll. Antwort: Es gibt einige Bereiche, in denen sich CORBA richtig hervortut. Bei Anwendungen, die verschiedene, in unterschiedlichen Sprachen geschriebene Komponenten enthalten und/oder auf verschiedenen Plattformen laufen sollen, ist die Verwendung von CORBA sehr sinnvoll. CORBA übernimmt einige potentiell lästige Details für Sie, so beispielsweise das automatische Konvertieren von Zahlenformaten zwischen verschiedenen Rechnern (dies erfolgt durch Formatübertragung, also dem Marshaling-Vorgang). Zusätzlich bietet CORBA eine leicht verständliche Abstraktion für verteilte Anwendungen, in der objektorientierter Entwurf, ein Exception-Modell und andere nützliche Konzepte enthalten sind. Die Stärken von CORBA kommen jedoch erst bei Anwendungen gut zur Geltung, die in einem gesamten Unternehmen eingesetzt werden. Die vielen Robustheitsmerkmale von CORBA sowie die über die CORBAservices und CORBAfacilities der OMA zur Verfügung gestellten Funktionen und insbesondere die Skalierbarkeit von CORBA machen diesen Standard für Unternehmensanwendungen zum idealen Instrument. Frage: Was versteht man unter IDL, und wofür kann die Sprache eingesetzt werden?
Antwort: Die Sprache IDL (Interface Definition Language, Schnittstellendefinitionssprache) wird in den nächsten beiden Tagen noch eingehender behandelt. Jetzt sei nur so viel verraten: Der Wert von IDL liegt darin, daß sie zum Abstrahieren unterschiedlicher Sprachen, Hardware und Betriebssystemarchitekturen dient. So wird beispielsweise der IDL-Datentyp long automatisch in den Zahlentyp für die Architektur umgesetzt, unter der die Anwendung ausgeführt wird. Außerdem kann die IDL, weil sie sprachenunabhängig ist, zur Definition der Schnittstellen von Objekten dienen, die in einer beliebigen Sprache implementiert sind.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen finden Sie in Anhang A. An den meisten Tagen finden Sie hier außerdem noch einige Übungen, die jedoch heute entfallen, weil an diesem Tag noch keine »verwertbaren Kenntnisse« vorgestellt wurden. Quiz 1. 2. 3. 4.
Wofür steht IIOP und was bedeutet es? Welche Beziehungen bestehen zwischen CORBA, OMA und OMG? Was versteht man unter einem Client-Stub? Was ist eine Objektreferenz? Und was ist eine IOR?
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 2 Die Architektur von CORBA Überblick Der Object Request Broker (ORB) Die Interface Definition Language (IDL) Das Kommunikationsmodell von CORBA Das Objektmodell von CORBA CORBA-Clients und -Server Stubs und Skeletons Erweiterte Funktionalität: CORBAservices und CORBAfacilities Zusammenfassung Fragen & Antworten Workshop
Überblick Am ersten Tag haben Sie einiges über die Geschichte von CORBA gelernt und erfahren, welchen Platz die Architektur von CORBA innerhalb der Welt der Client-Server-Anwendungsentwicklung einnimmt. Sie haben außerdem eine kurze Übersicht über die Architektur von CORBA erhalten. Am Ende dieses Tages werden Sie ein tiefergehendes Verständnis der Architektur von CORBA und ihrer Komponenten gewonnen haben. In diesem Kapitel werden die folgenden Hauptthemen behandelt: ■ ■ ■
Der Object Request Broker (ORB), einer der Eckpfeiler der Architektur von CORBA Die Interface Definition Language (IDL), der zweite Eckpfeiler der Architektur von CORBA Das Kommunikationsmodell von CORBA und der Platz, den CORBA innerhalb der Netzwerkarchitektur
■ ■ ■ ■
einnimmt Das Objektmodell von CORBA einschließlich Objektreferenzen und Basic Object Adapters (BOAs) Die Definition der Clients und Server und ihre Rolle in der Architektur von CORBA Die Verwendung von Client-Stubs und Server-Skeletons beim Erstellen von CORBA-Anwendungen Übersicht über die CORBAservices und CORBAfacilities, über die zusätzliche Funktionalität für CORBAAnwendungen zur Verfügung gestellt wird
Der Object Request Broker (ORB) Wie man sich unschwer vorstellen kann, ist der Object Request Broker (ORB) ein grundlegender Bestandteil der Common Object Request Broker-Architektur. Dem ORB liegt folgendes Konzept zugrunde: Wenn eine Anwendungskomponente einen Dienst nutzen möchte, der von einer anderen Komponente zur Verfügung gestellt wird, muß sie zunächst eine Objektreferenz für das Objekt erlangen, welches den Dienst zur Verfügung stellt. (Wie diese Objektreferenz erlangt wird, ist ein eigenes Thema, das später an die Reihe kommt; momentan wollen wir aber den ORB-Mechanismus untersuchen und gehen einfach einmal davon aus, daß die Objektreferenz bereits zur Verfügung steht.) Nachdem die Objektreferenz erlangt wurde, kann die Komponente Methoden des Objekts aufrufen und damit auf die vom Objekt zur Verfügung gestellten Dienste zugreifen. (Der Entwickler der ClientKomponente weiß zum Zeitpunkt des Kompilierens, welche Methoden eines bestimmten Server-Objekts zur Verfügung stehen.) Die Hauptaufgabe des ORB ist die Auflösung der Anfragen von Objektreferenzen, so daß die Anwendungskomponenten die Konnektivität miteinander herstellen können. (Diese ORB-Konzepte sind in Abbildung 2.1 gezeigt.) Sie werden sehen, daß der ORB noch weitere Aufgaben erfüllt.
Abbildung 2.1: ORB-Auflösung von Objektanfragen Die Formatübertragung (Marshaling)
Nachdem eine Anwendungskomponente eine Referenz auf ein Objekt erhalten hat, dessen Dienste sie nutzen möchte, kann die Komponente Methoden dieses Objekts aufrufen. In der Regel übernehmen diese Methoden Parameter als Eingabe und geben andere Parameter als Ausgabe zurück. Eine weitere Aufgabe des ORB besteht im Empfang der Eingabeparameter von der Komponente, welche die Methode aufruft, und in der Formatübertragung dieser Parameter (dieser Vorgang wird auch als Marshaling bezeichnet). Darunter versteht man eine Umsetzung der Parameter in ein Format, das über das Netzwerk zum Remote-Objekt übertragen werden kann. (Manchmal wird dies auch als »Übertragungsformat« bezeichnet.) Der ORB nimmt auch die umgekehrte Formatübertragung (Unmarshaling) der zurückgegebenen Parameter vor; diese werden also aus dem Übertragungsformat in ein Format umgesetzt, das die aufrufende Komponente versteht. Dieser Prozeß ist in Abbildung 2.2 dargestellt. Formatübertragung ist die Umsetzung der Eingabeparameter in ein Format, das über ein Netzwerk übertragen werden kann.
Die umgekehrte Formatübertragung ist die Rückumsetzung, bei der die Daten aus einem Netzwerk in Ausgabeparameter umgesetzt werden.
Das Übertragungsformat ist das Format, in dem die Daten nach bzw. vor der (umgekehrten) Formatübertragung über das Netzwerk übertragen werden.
Abbildung 2.2: Die Formatübertragung von Parametern und Rückgabewerten Die gesamte Formatübertragung erfolgt ohne jeglichen Eingriff des Programmierers. Die Client-Anwendung ruft einfach die gewünschte Remote-Methode auf, die für den Client so aussieht, als wäre sie eine lokale Methode, und es wird ein Ergebnis zurückgegeben (oder eine Exception ausgelöst), ebenfalls gerade so, als handle es sich um eine lokale Methode. Der gesamte Prozeß, also die Formatübertragung der Eingabeparameter, das Auslösen des Methodenaufrufs auf dem Server und die umgekehrte Formatübertragung der Rückgabeparameter werden automatisch und transparent durch den ORB durchgeführt. Plattformunabhängigkeit Ein Ergebnis der Formatübertragung/umgekehrten Formatübertragung besteht darin, daß die Kommunikation zwischen den Komponenten plattformunabhängig stattfindet, weil die Parameter für die Übertragung in ein plattformunabhängiges Format und beim Empfang wieder in ein plattformspezifisches Format konvertiert werden (das Übertragungsformat ist Bestandteil der CORBA-Spezifikation). Dies bedeutet, daß ein Client, der beispielsweise auf einem Macintosh-System läuft, Methoden auf einem Server aufrufen kann, der auf einem UnixSystem läuft. Zusätzlich zur Unabhängigkeit vom verwendeten Betriebssystem werden auch HardwareUnterschiede (wie die Reihenfolge der Prozessor-Bytes oder die Art der Adressierung) irrelevant, weil der ORB automatisch die notwendigen Umwandlungen vornimmt. Im wesentlichen fängt der ORB die PlattformUnterschiede ab, unabhängig davon, ob es sich um das Betriebssystem, die Art der Adressierung, die Wortlänge usw. handelt. Beachten Sie, daß die Formatübertragung und die umgekehrte Formatübertragung der Parameter vollständig vom ORB übernommen werden, wobei dieser Vorgang sowohl für den Client als auch für den Server vollständig transparent ist. Da der Prozeß vollständig vom ORB durchgeführt wird, braucht sich der Entwickler nicht mit Einzelheiten bezüglich des Mechanismus für die Formatübertragung und die umgekehrte Formatübertragung zu beschäftigen. Der ORB: Zusammenfassung Da das Konzept des ORB eine wesentliche Grundlage für das Verständnis der Architektur von CORBA darstellt, ist es wichtig, daß Sie dieses verstanden haben. Daher fasse ich den Zweck des ORB noch einmal zusammen. Er hat folgende Aufgaben: ■
■
■
■
■
Wenn eine Objektreferenz von einem Client vorliegt, findet der ORB die zugehörige Objektimplementierung (den Server) für den Client. (Denken Sie daran, daß es Aufgabe des Clients ist, zunächst eine Objektreferenz zu erlangen, den hierzu benötigten Prozeß werden Sie später kennenlernen.) Wenn der Server gefunden wurde, stellt der ORB sicher, daß der Server bereit ist, die Anfrage zu empfangen. Der ORB auf der Client-Seite akzeptiert die Parameter der aufgerufenen Methode und führt eine Formatübertragung der Parameter an das Netzwerk durch. Der ORB auf der Server-Seite führt eine umgekehrte Formatübertragung der Parameter aus dem Netzwerk durch und liefert diese an den Server. Für die Rückgabeparameter, sofern vorhanden, wird auf gleiche Weise eine Formatübertragung/umgekehrte Formatübertragung durchgeführt.
Der Hauptvorteil des ORB ist die plattformunabhängige Verarbeitung der Daten. Die Parameter werden, während die Formatübertragung/umgekehrte Formatübertragung durchgeführt wird, für die Übertragung zwischen unterschiedlichen Computerformaten konvertiert.
Die Interface Definition Language (IDL) Wenn man den Object Request Broker als einen der Eckpfeiler der Architektur von CORBA bezeichnen kann, so ist die IDL (Interface Definition Language, Schnittstellendefinitionssprache) der zweite. Wie schon ihr Name sagt, dient die IDL zur Definition von Schnittstellen zwischen einzelnen Anwendungskomponenten. Beachten Sie, daß es sich bei der IDL nicht um eine prozedurenorientierte (prozedurale) Sprache handelt; sie dient einzig und allein zur Definition von Schnittstellen und nicht von Implementierungen. C++-Programmierer können sich IDLDefinitionen analog zu Header-Dateien für Klassen vorstellen, denn eine solche Header-Datei enthält in der Regel keine Implementierung einer Klasse, sondern beschreibt die Schnittstelle der betreffenden Klasse. JavaProgrammierer können sich IDL-Definitionen wie die Definitionen von Java-Schnittstellen vorstellen, auch hier wird nur die Schnittstelle beschrieben, und es wird keine Implementierung zur Verfügung gestellt. Die Interface Definition Language (IDL) ist eine Standardsprache, mit der die von CORBA-Objekten verwendeten Schnittstellen definiert werden. Sie wird am 3. Tag ausführlich beschrieben.
Die IDL-Spezifikation ist dafür verantwortlich, daß die Daten zwischen Anwendungen in unterschiedlichen Sprachen korrekt ausgetauscht werden. Wenn es sich beispielsweise beim IDL-Typ long um eine vorzeichenbehaftete 32 Bit große Ganzzahl handelt, kann diese in den C++-Typ long (je nach Plattform) oder in den Java-Typ int umgewandelt werden. Es obliegt der IDL-Spezifikation und den IDL-Compilern, die diese implementieren, solche Datentypen auf sprachunabhängige Weise zu definieren. Die IDL wird im nächsten Kapitel ausführlich behandelt. Anschließend werden Sie mit der IDL Schnittstellen (was sonst) für die Beispiele definieren, die im Verlauf dieses Buchs verwendet werden. Sprachenunabhängigkeit Die IDL ist Teil der Standard-CORBA-Spezifikation und unabhängig von jeglicher Programmiersprache. Sie stellt die Sprachenunabhängigkeit durch Sprachabbildung (Language Mapping) sicher. Die OMG hat eine Anzahl von Standard-Sprachabbildungen für viele verbreitete Sprachen wie C, C++, Cobol, Java und Smalltalk definiert. Abbildungen für andere Sprachen sind ebenfalls vorhanden, aber entweder sind diese Abbildungen nicht standardgemäß, oder sie werden derzeit gerade von der OMG standardisiert. Unter Sprachabbildung (Language Mapping) versteht man eine Spezifikation, mit deren Hilfe IDL-Konstrukte auf die entsprechenden Konstrukte einer Programmiersprache abgebildet werden. So wird beispielsweise bei der Sprachabbildung für C++ das IDL-Konstrukt interface auf das C++-Konstrukt class »gemappt« (abgebildet).
Sprachenunabhängigkeit ist ein äußerst wichtiges Merkmal der Architektur von CORBA. Da beim Arbeiten mit CORBA keine bestimmte Sprache verwendet werden muß, haben die Anwendungsentwickler die Freiheit, jeweils diejenige Sprache zu wählen, die für die betreffende Anwendung am besten geeignet ist. Diese Freiheit noch einen Schritt weiter zu führen bedeutet, daß die Entwickler auch mehrere unterschiedliche Sprachen für die einzelnen Komponenten ihrer Anwendung wählen können. So ist es beispielsweise möglich, die Client-Komponenten einer Anwendung in Java zu implementieren, wodurch sichergestellt wird, daß die Clients praktisch auf jedem Rechner ausgeführt werden können. Aus Leistungsgründen könnten dann die Server-Komponenten dieser Anwendung in C++ implementiert werden. CORBA ermöglicht die Kommunikation zwischen diesen doch recht unterschiedlichen
Komponenten.
Das Kommunikationsmodell von CORBA Damit Sie CORBA richtig verstehen können, müssen Sie zunächst die Rolle dieser Architektur in einem Netzwerk von Computersystemen verstehen. In der Regel besteht ein solches Netzwerk aus Systemen, die physikalisch miteinander verbunden sind (obwohl uns die Einführung der drahtlosen Netzwerktechnologie dazu zwingen könnte, darüber nachzudenken, ob »physikalisch miteinander verbunden« noch der passende Ausdruck ist). Diese physikalische Ebene stellt das Medium zur Verfügung, über das die Kommunikation abgewickelt wird, unabhängig davon, ob es sich dabei um eine Telefonleitung, ein Glasfaserkabel, eine Satellitenverbindung oder eine Kombination von Netzwerktechnologien handelt. Oberhalb der physikalischen Schicht liegt die Transportschicht, zu der die Protokolle gehören, die für den Transport von Datenpaketen von einem Punkt zum anderen verantwortlich sind. Im Zeitalter des Internet ist TCP/IP (Transmission Control Protocol/Internet Protocol) das wohl am häufigsten verwendete Protokoll. Die meisten Internet-gestützten Anwendungen verwenden TCP/IP zur Kommunikation miteinander, einschließlich der Anwendungen, die auf FTP (File Transfer Protocol), Telnet (einem Protokoll für die Host-Kommunikation) und HTTP (Hypertext Transport Protocol), der Basis für das World Wide Web, basieren. Protokolle zur Kommunikation zwischen ORBs Welche Rolle spielt nun CORBA innerhalb dieses Netzwerkmodells? Es läßt sich feststellen, daß die CORBASpezifikation hinsichtlich der Netzwerkprotokolle neutral ist. Der CORBA-Standard legt das GIOP (General InterORB Protocol, allgemeines Protokoll für die Kommunikation zwischen ORBs) fest, das auf einer hohen Ebene einen Standard für die Kommunikation zwischen verschiedenen CORBA-ORBs und -Komponenten definiert. Wie sein Name vermuten läßt, handelt es sich bei GIOP nur um ein allgemeines Protokoll, und im CORBA-Standard sind auch noch zusätzliche Protokolle definiert, die GIOP dazu bringen, ein bestimmtes Transportprotokoll zu verwenden. So gibt es GIOP-Protokolle für TCP/IP und für DCE (das Distributed Computing EnvironmentProtokoll der Open Software Foundation). Zusätzlich können Hersteller herstellerspezifische Protokolle für die Kommunikation zwischen CORBA-Komponenten definieren und verwenden (dies tun sie auch). Das GIOP (General Inter-ORB Protocol, allgemeines Protokoll für die Kommunikation zwischen ORBs) ist ein Standardprotokoll auf einer hohen Ebene, das zur Kommunikation zwischen ORBs dient. Da es sich bei GIOP um ein allgemeines Protokoll handelt, wird es nicht direkt verwendet, sondern durch ein direkt zu verwendendes spezielles Protokoll ergänzt.
Der Hauptschwerpunkt in diesem Buch wird auf der Behandlung und Verwendung von CORBA mit dem GIOPProtokoll für TCP/IP-Netzwerke liegen, das als das IIOP (Internet Inter-ORB Protocol, Internet-Protokoll für die Kommunikation zwischen ORBs) bezeichnet wird. Gemäß dem Stand von Version 2.0 der CORBA-Spezifikation müssen Hersteller das IIOP-Protokoll implementieren, damit ihre Anwendungen als CORBA-kompatibel bezeichnet werden können (obwohl sie zusätzlich zum IIOP ihre eigenen Protokolle anbieten können). Diese Anforderung hilft, die Kompatibilität zwischen CORBA-Produkten verschiedener Hersteller sicherzustellen, weil jedes mit CORBA 2.0 kompatible Produkt dieselbe Sprache »sprechen« muß. Einige Hersteller sind so weit gegangen, daß sie das IIOP als das native Protokoll ihres Produkts (dies ist das standardmäßig verwendete Protokoll) übernommen haben und kein herstellerspezifisches Protokoll verwenden. Ein ORB kann allerdings eine beliebige Anzahl von Protokollen unterstützen, solange das IIOP unterstützt wird (bei der Kommunikation können
die ORBs aushandeln, welches Protokoll verwendet werden soll). Ferner fügen eine Reihe von Herstellern IIOPkompatible ORBs zu ihren Produkten, von Datenbank-Servern über Werkzeuge zur Anwendungsentwicklung bis hin zu Web-Browsern, hinzu. Wie Sie sehen, ist das IIOP also ein wichtiger Faktor bei der Kompatibilität von CORBA-Anwendungen. Das IIOP (Internet Inter-ORB Protocol, Internet-Protokoll für die Kommunikation zwischen ORBs) ist eine Spezialversion des GIOP. Das IIOP ist das Standardprotokoll für die Kommunikation zwischen ORBs in TCP/IP-Netzwerken. Damit ein ORB als kompatibel zu CORBA 2.0 betrachtet werden kann, muß er das IIOP unterstützen (kann aber auch noch weitere Protokolle unterstützen).
CORBA und das Netzwerkmodell Bei der ganzen Diskussion um die Protokolle für die Kommunikation zwischen ORBs darf natürlich nicht unberücksichtigt bleiben, wo CORBA innerhalb des übrigen Netzwerkmodells ins Spiel kommt. In Abbildung 2.3 sehen Sie die Netzwerkarchitektur einer typischen CORBA-Anwendung. Im wesentlichen werden CORBAAnwendungen auf von GIOP abgeleiteten Protokollen wie dem IIOP aufgesetzt. Diese Protokolle wiederum liegen oberhalb von TCP/IP, DCE oder dem sonst im Netzwerk verwendeten, zugrunde liegenden Transportprotokoll. CORBA-Anwendungen sind nicht darauf beschränkt, nur eines dieser Protokolle zu verwenden. Die Anwendungsarchitektur kann so konzipiert sein, daß eine Bridge beispielsweise zur Verbindung von DCEgestützten Anwendungskomponenten mit IIOP-gestützten Komponenten verwendet wird. Sie sehen, daß die Architektur von CORBA keine Netzwerkprotokolle verdrängt, sondern eine zusätzliche Schicht hinzufügt, nämlich die Protokollschicht für die Kommunikation zwischen ORBs, bei der die darunterliegende Transportschicht als Basis verwendet wird. Auch dies ist ein wichtiger Faktor bei der Interoperabilität zwischen CORBAAnwendungen, da CORBA nicht die Verwendung eines bestimmten Netzwerktransportprotokolls vorschreibt.
Abbildung 2.3: Die Architektur einer verteilten CORBA-Anwendung
Das Objektmodell von CORBA Jede objektorientierte Architektur weist ein Objektmodell auf, in dem beschrieben ist, wie die Objekte im System dargestellt werden. Natürlich gibt es auch für CORBA ein solches Objektmodell, denn es handelt sich ja um eine objektorientierte Architektur.
Da CORBA jedoch eine verteilte Architektur ist, unterscheidet sich das darin definierte Objektmodell wahrscheinlich in einigen Punkten von dem, was die meisten Leser kennen werden (beispielsweise das Objektmodell von C++ oder von Java). Drei der Hauptunterschiede zwischen dem Objektmodell von CORBA und den herkömmlichen Modellen liegen in der »halbtransparenten« Unterstützung der Objektverteilung, der Behandlung von Objektreferenzen und der Verwendung von Objektadaptern, besonders BOAs (Basic Object Adapters, grundlegende Objektadapter). Sie werden diese Konzepte im Folgenden näher kennenlernen. Objektverteilung Für einen CORBA-Client sieht ein Remote-Methodenaufruf genauso aus wie ein lokaler Methodenaufruf. Dies liegt an der Verwendung der Client-Stubs (die ich weiter unten in diesem Kapitel noch eingehend erläutern werde). Daher ist die Tatsache, daß die CORBA-Objekte verteilt sind, für die Benutzer dieser Objekte transparent; die Clients nehmen nicht wahr, daß sie mit Objekten zu tun haben, die im Netzwerk verteilt sind. In der Tat stimmt die vorstehende Aussage fast immer. Da die Verteilung von Objekten eine höhere Fehlerwahrscheinlichkeit (verursacht durch Netzwerkausfälle, Server-Abstürze usw.) mit sich bringt, muß CORBA Ausweichmöglichkeiten bieten, die in solchen Fällen greifen. Dies erfolgt durch Bereitstellung von SystemExceptions, die durch jede Remote-Methode ausgelöst werden können. Exceptions werden in den späteren Kapiteln noch ausführlich erläutert: Am 3. Tag wird gezeigt, wie Exceptions in der IDL deklariert werden, am 7. Tag werden Sie eine Exception-Behandlungsfunktion zu einer Beispielanwendung hinzufügen. Zum jetzigen Zeitpunkt brauchen Sie nur zu wissen, daß alle Operationen in allen CORBA-Objekten implizit eine CORBASystem-Exception auslösen können, die einen Netzwerkfehler, die Nichtverfügbarkeit des Servers oder eine ähnliche Situation signalisiert. So ist mit Ausnahme dieser zusätzlichen, über CORBA-Objektmethoden ausgelösten Exception jede Remote-Methode ansonsten identisch mit ihrem lokalen Gegenstück. Objektreferenzen In einer verteilten Anwendung gibt es zwei Möglichkeiten für eine Anwendungskomponente, um auf ein Objekt in einem anderen Prozeß zuzugreifen. Eines davon wird als »Übergabe durch Referenz« (oder Referenzübergabe) bezeichnet und in Abbildung 2.4 dargestellt. Bei dieser Methode gibt der erste Prozeß (Prozeß A) eine Objektreferenz an den zweiten Prozeß (Prozeß B) weiter. Wenn Prozeß B eine Methode dieses Objekts aufruft, wird diese von Prozeß A ausgeführt, weil dieser Prozeß Eigentümer des Objekts ist. (Das Objekt ist im Speicherund Prozeßraum von Prozeß A enthalten.) Prozeß B hat (über die Objektreferenz) nur eine Sicht auf das Objekt und kann so nur anfordern, daß Prozeß A Methoden für Prozeß B ausführt. Übergabe eines Objekts durch Referenz bedeutet, daß ein Prozeß einem anderen Prozeß die Sicht auf eines seiner Objekte gewährt, während er Eigentümer des betreffenden Objekts bleibt. Wenn ein Objekt durch Referenz weitergegeben wird, bleibt das Objekt selbst »an Ort und Stelle«, während nur eine Objektreferenz weitergegeben wird. Operationen am Objekt, die über die Objektreferenz erfolgen, werden vom Objekt selbst verarbeitet.
Abbildung 2.4: Die Objektübergabe durch Referenz Die zweite Methode zur Übergabe eines Objekts von einer Anwendungskomponente zu einer anderen heißt Übergabe durch Werte. Diese Methode ist in Abbildung 2.5 zu sehen. Bei dieser Methode wird der tatsächliche Status des Objekts (z.B. die Werte für die Elementvariablen) an die anfordernde Komponente weitergegeben (dies erfolgt in der Regel durch einen Prozeß, der als »Serialisierung« bezeichnet wird). Wenn die Methoden des Objekts durch Prozeß B aufgerufen werden, werden diese von Prozeß B und nicht von Prozeß A ausgeführt, bei dem sich das ursprüngliche Objekt befindet. Da hier eine Übergabe durch den Wert erfolgt, ändert sich der Status des ursprünglichen Objekts nicht, nur die Kopie (deren Eigentümer nun Prozeß B ist) wird geändert. Im allgemeinen obliegt es dem Entwickler, den Quelltext so zu schreiben, daß die Objekte serialisiert und entserialisiert werden (obwohl diese Funktionalität in manche Sprachen, wie z.B. Java, integriert ist).
Wenn für ein Objekt eine Übergabe durch den Wert erfolgt, wird der Status des Objekts kopiert und an den Zielprozeß weitergegeben, wo eine neue Kopie des Objekts als Instanz angelegt wird. Operationen an der Kopie des Objekts werden vom kopierten Objekt durchgeführt und nicht vom ursprünglichen Objekt.
Serialisierung bezeichnet die Codierung des Status eines Objekts, beispielsweise einer Datei auf einer Festplatte oder einer Netzwerkverbindung, in einen Stream. Wenn ein Objekt serialisiert wurde, kann es in einen solchen Stream geschrieben und anschließend gelesen und entserialisiert werden, wobei die serialisierten Daten, die den Objektstatus enthalten, in eine Objektinstanz zurückkonvertiert werden.
Abbildung 2.5: Übergabe eines Objekts durch Werte Ein wichtiger Aspekt des Objektmodells von CORBA besteht darin, daß alle Objekte durch Referenz weitergegeben werden. (Während dieses Buch entsteht, hat die OMG eine Aufforderung zur Einreichung von Vorschlägen (RFP, Request for Proposals) eingereicht, um CORBA die Funktionalität zur Übergabe von Objekten durch Werte hinzuzufügen. Daher kann man davon ausgehen, daß diese Funktionalität in naher Zukunft zum CORBA-Standard hinzugefügt werden wird.) Damit die Übergabe von Objekten durch Werte in einer verteilten Anwendung erleichtert wird, muß zusätzlich zur Übergabe des Objektstatus über das Netzwerk auch sichergestellt werden, daß die Komponente, die das Objekt empfängt, Implementierungen für die von dem Objekt unterstützten Methoden aufweist. (Dies ist nicht notwendig, wenn Objekte durch Referenz weitergegeben werden, weil dabei, wie bereits erwähnt, die Methodenaufrufe von der Komponente ausgeführt werden, die Eigentümer des tatsächlichen Objekts ist.) Wenn die Funktionalität zur Übergabe durch Werte für CORBA definiert wird, werden diese Aspekte zur Sprache kommen. Sie als Leser
sollten sich über die Ankündigungen der OMG in Hinblick auf Aktualisierungen im Rahmen dieser Entwicklung auf dem laufenden halten. (Die OMG-Web-Seite, auf der viele Informationen und Spezifikationen zu CORBA zur Verfügung gestellt werden, finden Sie unter http://www.omg.org/. Es gibt einige Aspekte, die bei der Übergabe von Objekten nur durch Referenz zu berücksichtigen sind. Wie Sie jetzt wissen, werden für den Fall, daß Objekte nur durch Referenz weitergegeben werden können, für ein Objekt aufgerufene Methoden immer von der Komponente ausgeführt, die Eigentümer des Objekts ist (d.h., die das Objekt erstellt hat). Ein Objekt kann nicht von einer Anwendungskomponente zu einer anderen importiert werden. (Es ist allerdings möglich, Methoden zu erstellen, mit denen dieses Verhalten simuliert wird, es wird nur derzeit von der CORBA-Architektur selbst nicht zur Verfügung gestellt.) Dies bedeutet auch, daß alle Methodenaufrufe RemoteMethodenaufrufe sind (es sei denn, sowohl das aufrufende Objekt als auch das aufgerufene Objekt gehören derselben Anwendungskomponente). Wenn eine Komponente eine längere Reihe von Methodenaufrufen eines Remote-Objekts durchführt, kommt es offensichtlich zu einem ziemlich großen Systemaufwand für die Kommunikation zwischen den beiden Komponenten. Aus diesem Grund kann es unter Umständen effektiver sein, ein Objekt durch seinen Wert weiterzugeben, so daß die Komponente, die dieses Objekt verwendet, es lokal bearbeiten kann. Am 10. Tag werden Sie diese Problematik im Detail kennenlernen, aber in der Zwischenzeit sollten Sie wissen, daß das Fehlen einer Semantik für die Übergabe durch Werte in CORBA diese Problematik hervorruft. Basic Object Adapter (BOAs) Der CORBA-Standard beschreibt eine Anzahl sogenannter Objektadapter, deren Hauptzweck es ist, die Schnittstelle zwischen einer Objektimplementierung und dem zugehörigen ORB zur Verfügung zu stellen. Die OMG empfiehlt, neue Typen von Objektadaptern nur dann zu erstellen, wenn dies notwendig ist, und stellt drei Gebrauchsmuster für Objektadapter zur Verfügung: den BOA (Basic Object Adapter, grundlegender Objektadapter), mit dem wir uns jetzt beschäftigen werden, den Bibliotheksobjektadapter (Library Object Adapter) und den objektorientierten Datenbankadapter (Object-Oriented Database Adapter). Die beiden letzteren dienen zum Zugriff auf Objekte im dauerhaften Speicher. (In der CORBA-Spezifikation sind diese Objektadapter ausführlich beschrieben.) Wie bereits gesagt, werden wir uns allerdings nur mit dem Basic Object Adapter beschäftigen, der bei weitem am häufigsten verwendet wird. Der BOA stellt für CORBA eine allgemeine Gruppe von Methoden für den Zugriff auf ORB-Funktionen zur Verfügung. Diese Funktionen reichen von der Benutzeridentifikationsüberprüfung über die Objektaktivierung bis hin zur Objektpersistenz. Der BOA ist in der Tat die Schnittstelle des CORBA-Objekts zum ORB. Gemäß der CORBA-Spezifikation muß der BOA in jeder ORB-Implementierung vorhanden sein, und dies schien bei den meisten (wenn nicht allen) verfügbaren CORBAProdukten der Fall zu sein. Strategien zur Server-Aktivierung Ein besonders wichtiges (und nützliches) Merkmal des BOA sind seine Funktionen zum Aktivieren und Deaktivieren von Objekten. Der BOA unterstützt vier verschiedene Aktivierungsstrategien, die angeben, wie die Anwendungskomponenten initialisiert werden sollen. Es handelt sich um die folgenden: ■
■ ■
■
Die Strategie der gemeinsam verwendeten Server, bei der ein einziger Server (in diesem Zusammenhang ist damit ein auf einem Rechner laufender Prozeß gemeint) von mehreren Objekten gemeinsam verwendet wird. Die Strategie der nicht gemeinsam verwendeten Server, bei der ein Server immer nur ein Objekt enthält. Die Strategie der Server-pro-Methode, bei der ein Server automatisch gestartet wird, wenn eine Objektmethode aufgerufen wird. Beim Zurückgeben dieser Methode wird der Server automatisch beendet. Die Strategie der permanenten Server, bei der der Server manuell gestartet wird (durch einen Benutzer, einen Stapelverarbeitungsauftrag, einen System-Daemon oder einen anderen externen Agent).
Eine Strategie zur Server-Aktivierung gibt an, wie der Zugriff auf den betreffenden Server erfolgen soll, ob z.B. ein einziger Server von allen Clients verwendet werden soll oder für jeden Client eine eigene Instanz des Servers gestartet werden soll usw.
Diese Vielzahl von Aktivierungsstrategien ermöglicht es dem Anwendungsentwickler, die Art von Verhalten auszuwählen, die für den betreffenden Server am sinnvollsten ist. So ist es beispielsweise für einen Server, der eine gewisse Zeit zur Initialisierung benötigt, am besten, wenn er als permanenter Server gestartet wird, weil sonst die zum Starten notwendige Initialisierungszeit die Antwortzeit für den Server beeinträchtigen würde. Andererseits könnte ein Server, der auf Anforderung schnell initialisiert wird, mit der Strategie des Server-pro-Methode definiert werden. Ich möchte hier noch ausführen, daß der Ausdruck »permanenter Server« nichts mit dem normalerweise verwendeten Begriff »permanent« zu tun hat, der sich auf die Fähigkeit eines Objekts bezieht, seinen aktuellen Status in einer Art von nichtflüchtiger Speichereinrichtung wie z.B. einer auf Festplatte in einzelnen Dateien gespeicherten Datenbank zu speichern. Ein permanenter Server speichert nicht notwendigerweise seinen aktuellen Status in einem permanenten Speicher (obwohl dies möglich wäre). In diesem Fall bezieht sich der Begriff nur darauf, daß der Server permanent läuft.
CORBA-Clients und -Server In einer herkömmlichen Client-Server-Anwendung stellt der Server die Komponente(n) dar, die anderen Anwendungskomponenten Dienste zur Verfügung stellt. Der Client ist die Komponente, die von einem oder mehreren Servern zur Verfügung gestellte Dienste nutzt. Die Architektur einer CORBA-Anwendung unterscheidet sich hiervon nicht. In der Regel stellen bestimmte Komponenten einer Anwendung Dienste zur Verfügung, die von anderen Komponenten der Anwendung genutzt werden. Es ist daher nicht überraschend, daß die Begriffe »Client« und »Server« diese Komponenten einer CORBA-Anwendung bezeichnen. Wenn man allerdings einen einzelnen Aufruf einer Remote-Methode betrachtet, so können die Rollen von Client und Server vorübergehend vertauscht werden, weil jedes CORBA-Objekt gleichzeitig an mehreren Interaktionen beteiligt sein kann. In einer CORBA-Anwendung wird jede Komponente, die eine Implementierung für ein Objekt zur Verfügung stellt, als Server bezeichnet, zumindest, soweit dieses Objekt betroffen ist. Wenn eine Komponente ein Objekt erstellt und anderen Komponenten die Sicht auf dieses Objekt ermöglicht (also anderen Komponenten die Möglichkeit gibt, Referenzen auf dieses Objekt zu erlangen), fungiert diese Komponente als Server für das Objekt, und jede Anfrage, die von anderen Komponenten für dieses Objekt vorgenommen wird, wird von der Komponente verarbeitet, die das betreffende Objekt erstellt hat. Wenn eine Komponente als CORBA-Server bezeichnet wird, so bedeutet dies, daß sie (als Server) für andere Komponenten (Clients) Methoden eines bestimmten Objekts ausführt. Die Komponente ist gleichzeitig Client und Server Häufig kann eine Anwendungskomponente anderen Anwendungskomponenten Dienste zur Verfügung stellen, während sie selbst auf Dienste anderer Komponenten zugreift. In diesem Fall fungiert die Komponente als Client
einer Komponente und als Server für die übrigen Komponenten (siehe Abbildung 2.6). Es kann sogar vorkommen, daß zwei Komponenten gegenseitig gleichzeitig als Client und Server fungieren. Um diese Situation zu verstehen, sehen Sie sich die folgende Konstellation an (Abbildung 2.7): Die erste Komponente (Komponente A) erhält eine Referenz auf ein Objekt, das von einer zweiten Komponente (Komponente B) erstellt wurde, und ruft eine Methode für das Objekt auf. Hier fungiert Komponente A als Client und Komponente B als Server. Jetzt gehen wir einmal davon aus, daß Komponente A als Parameter für die aufgerufene Methode eine Referenz auf ein Objekt weitergibt, das sie erstellt hat (und damit eine Implementierung für das Objekt zur Verfügung stellt). Gehen wir weiter davon aus, daß Komponente B nun eine Methode für das Objekt aufruft. Für diesen betreffenden Methodenaufruf fungiert Komponente A als Server, während Komponente B der Client ist. Die beiden Komponenten haben ihre grundsätzliche Rolle in der Anwendung nicht geändert, aber sie haben vorübergehend ihre Rollen als Client und als Server vertauscht. Diesem Beispiel können Sie entnehmen, daß in einer CORBAAnwendung die Verwendung der Begriffe »Client« und »Server« vom Kontext der aufgerufenen Methode abhängt sowie von der Komponente, in der sich das Objekt für die Methode befindet.
Abbildung 2.6: Die Komponente als Client und als Server Bei der Benennung von Clients und Servern wäre noch ein letzter Punkt zu berücksichtigen: Obwohl eine Anwendungskomponente sowohl als Client als auch als Server fungieren kann, wird sie dennoch als Client oder Server bezeichnet, nicht als beides. Im obigen Beispiel kann man davon ausgehen, daß Komponente A die meiste Zeit Methoden für Objekte aufruft, die Komponente B gehören. Wie im Beispiel gezeigt, können einige (oder sogar alle) Methodenaufrufe Objektreferenzen an Komponente B weitergeben, die dann durch diese Objektreferenzen wiederum Aufrufe an Komponente A durchführen kann. Obwohl Komponente A für diese Methodenaufrufe als Server fungiert, könnte man diese Komponente mit Fug und Recht als den Client und Komponente B als Server bezeichnen, weil es die Hauptfunktion der Komponente A ist, Dienste zu nutzen, die von Komponente B zur Verfügung gestellt werden, und sie selbst nur Objekte zur Verfügung stellt, die in Methoden von Komponente B als Argumente verwendet werden. Methoden, die auf diese Weise aufgerufen werden, werden als Client-Callback-Methoden (Rückrufmethoden) oder einfach als Callbacks (Rückrufe) bezeichnet. Callbacks sind besonders wichtig, da in CORBA derzeit noch keine Objekte durch Werte weitergegeben werden können. Wenn diese Funktion einmal zur Verfügung steht, werden wahrscheinlich viele Callbacks unnötig. Client-Callback-Methoden oder einfach Callback (Rückruf) ist eine allgemeine Bezeichnung für eine Methode, die von einem Client implementiert und von einem Server aufgerufen wird. Durch einen Rückruf wird ein Client zu einer Art beschränktem Server.
Abbildung 2.7: Client-Callback-Methode
Stubs und Skeletons Nachdem ein Entwickler die Definitionen für die Komponentenschnittstelle mit Hilfe der IDL erzeugt hat, werden die entstandenen IDL-Dateien mit einem IDL-Compiler weiterverarbeitet, der dabei Client-Stubs und ServerSkeletons erzeugt. Diese fungieren als eine Art von »Kitt«, mit dem die sprachenunabhängigen IDLSchnittstellenspezifikationen mit sprachspezifischem Implementierungsquelltext verbunden werden. Client-Stubs für jede Schnittstelle werden zur Einbindung von Clients zur Verfügung gestellt, die diese Schnittstellen nutzen. Der Client-Stub für eine bestimmte Schnittstelle stellt eine Pseudoimplementierung für jede Methode in der Schnittstelle zur Verfügung. Anstatt die Server-Funktionen auszuführen, kommunizieren die Methoden des Client-Stub einfach mit dem ORB, damit für die benötigten Parameter eine Formatübertragung bzw. eine umgekehrte Formatübertragung durchgeführt wird. Ein Client-Stub, der vom IDL-Compiler generiert wird, ist ein kleiner Quelltextabschnitt, der einem Client eine CORBA-Server-Schnittstelle zur Verfügung stellt.
Ein Server-Skeleton wird ebenso vom IDL-Compiler generiert und ist ein Quelltextabschnitt, der das Gerüst darstellt, auf dem der ServerImplementierungsquelltext für eine bestimmte Schnittstelle erzeugt wird.
Auf der anderen Seite stehen die Server-Skeletons, die das Gerüst bilden, auf dem der Server erzeugt wird. Für jede Methode einer Schnittstelle generiert der IDL-Compiler eine leere Methode im Server-Skeleton. Der Entwickler stellt dann für jede dieser Methoden die Implementierung zur Verfügung. In Abbildung 2.8 wird gezeigt, welche Position Client-Stubs und Server-Skeletons in der CORBA-Architektur einnehmen.
Abbildung 2.8: Client-Stubs und Server-Skeletons Das Entwickeln eines CORBA-Clients und -Servers wird am 4. Tag eingehend behandelt. Sie werden dann erfahren, wie der IDL-Compiler verwendet wird, wie mit Hilfe der vom IDL-Compiler generierten Client-Stubs ein CORBA-Client entwickelt wird, und wie ein CORBA-Server entwickelt wird, wobei von den ebenfalls vom IDLCompiler generierten Server-Skeletons ausgegangen wird. Schließlich zeige ich Ihnen, daß CORBA-Clients auch ohne die Verwendung von Client-Stubs entwickelt werden können, wobei die DII (Dynamic Invocation Interface, dynamische Aufrufschnittstelle) verwendet wird. Solche Clients sind nicht statisch mit der Server-Schnittstelle verbunden, sondern können Server-Schnittstellen dynamisch finden und Dienste nutzen, an die beim Entwickeln der Clients noch niemand gedacht hat. (Die Verwendung der DII erhöht allerdings die Komplexität einer ClientAnwendung beträchtlich und sollte daher am besten nicht für zentrale Teile der Anwendung eingesetzt werden.) Die DII ist etwas für Fortgeschrittene, daher werden Sie ihr erst am 11. Tag wieder begegnen.
Erweiterte Funktionalität: CORBAservices und CORBAfacilities Sicherlich ist es möglich, allein mit den Grundlagen von CORBA vieles zu erreichen: Sie können mit Hilfe von IDL Komponentenschnittstellen erzeugen, diese Schnittstellen dann implementieren und Clients entwickeln, die die verfügbaren Dienste nutzen. Die OMA (zur Erinnerung: dies ist die von der Object Management Group konzipierte Globalarchitektur, zu der auch CORBA gehört) stellt mit den CORBAservices und CORBAfacilities viel mehr als nur die grundlegende ORB-Funktionalität zur Verfügung. Zu dieser Funktionalität gehören Dienste für Ereignisverwaltung, Lizenzverwaltung, Objektpersistenz, Bezeichnung, Sicherheit, Transaktionen, Benutzeroberflächenverwaltung, Datenaustausch und noch vieles andere. Die Schnittstellen für diese Funktionen wurden von der OMG standardisiert, was bedeutet, daß ihre Verwendung über Plattformen und Produkte hinweg konsistent erfolgt (oder erfolgen wird). Was noch wichtiger ist: Diese Schnittstellen für CORBAservices und CORBAfacilities wurden in der IDL definiert, was bedeutet, daß diese Dienste und Einrichtungen von Anwendungen genauso verwendet werden können wie andere CORBA-Objekte.
Mit den CORBAservices und CORBAfacilities und deren heutiger und zukünftiger Verwendung werden Sie sich am 12. Tag beschäftigen. Für jetzt genügt es zu wissen, daß ein Unterschied zwischen den von der OMG definierten Diensten und Funktionen und den Diensten und Funktionen besteht, die in den verschiedenen CORBAProdukten verfügbar sind. Bevor Sie einen bestimmten Dienst oder eine Funktion in den Entwurf einer Anwendung einbauen, sollten Sie sicherstellen, daß auch tatsächlich ein Produkt vorhanden ist, mit dem diese Funktion implementiert werden kann. Außerdem ist wichtig, daß in einem Produkt, das als CORBA 2.0-kompatibel bezeichnet werden darf, weder CORBAservices noch CORBAfacilities implementiert sein müssen, nur die Kernfunktionalität von CORBA ist zwingend erforderlich.
Zusammenfassung In diesem Kapitel haben Sie die beiden Eckpfeiler der Architektur von CORBA kennengelernt: den ORB, der die Kommunikation der CORBA-Objekte untereinander sicherstellt, und die IDL, mit der die Schnittstellen der Anwendungskomponenten definiert werden, von denen ausgehend CORBA-Anwendungen entwickelt werden. Sie haben mit dem Objektmodell von CORBA Bekanntschaft gemacht und einiges über die Protokolle zur Kommunikation zwischen ORBs (insbesondere IIOP), über die Verwendung von Objektreferenzen in CORBA und über den BOA gelernt. Sie wissen nun, wie die Begriffe Client und Server in CORBA zu verstehen sind und haben erfahren, daß eine einzige Anwendungskomponente gleichzeitig als Client und als Server fungieren kann. Sie haben außerdem gesehen, wie über IDL-Definitionen Client-Stubs und Server-Skeletons erzeugt werden, die wiederum zur Implementierung von CORBA-Clients und -Servern dienen. Schließlich haben Sie eine Einführung in die CORBAservices und CORBAfacilities erhalten, über die zusätzliche Funktionalität für CORBAAnwendungen zur Verfügung gestellt wird. Nachdem Sie sich nun die Gesamtarchitektur von CORBA erarbeitet haben, werden Sie mit den Grundlagen der IDL fortfahren, wobei Sie zunächst die einfachen Datentypen und dann die komplexeren IDL-Konstrukte kennenlernen werden. Diese IDL-Kenntnisse sind für das Entwerfen und Implementieren von CORBAAnwendungen unerläßlich.
Fragen & Antworten Frage: Warum würde der Bedarf an Callbacks geringer werden, wenn die Funktion zur Übergabe von Objekten durch Werte in CORBA vorhanden wäre? Antwort: In vielen Fällen muß ein Client nur ein einfaches Objekt an die Server-Methode übergeben. Da das Übergeben von Objekten durch Werte nicht möglich ist, muß der Server einen Callback an den Client durchführen, um das Objekt manipulieren zu können (auch wenn er nur die Statusinformationen des Objekts lesen möchte). Wenn ein Objekt durch seinen Wert weitergegeben werden kann, kann der Server eine lokale Kopie des Objekts bearbeiten, wodurch kein Client-Callback mehr notwendig ist. (Natürlich wird es auch Fälle geben, in denen der Client Eigentümer des Objekts bleiben will und der Server einen Callback durchführen muß. In solchen Fällen würde das Callback-Paradigma aufrechterhalten.) Frage: Client-Stubs weisen für meine Anwendung wahrscheinlich zu viele Einschränkungen auf. Muß ich die DII verwenden?
Antwort: Bei der überwältigenden Mehrzahl von Anwendungen, bei denen der Entwickler meint, die Verwendung der DII sei notwendig, sollte er sich dies gut überlegen. Aufgrund der Komplexität und des Systemaufwands, der für die DII erforderlich ist, ist es fast immer besser, ihre Verwendung zu vermeiden. (Nähere Informationen zu den sinnvollen Anwendungsbereichen für die DII finden Sie in Kapitel 11.) Frage: Warum sind die Sprachabbildungen (Language Mappings) ein notwendiger Bestandteil von CORBA? Antwort: Da CORBA-Objektschnittstellen in der IDL definiert werden und diese Sprache unabhängig von allen Implementierungssprachen ist, ist es notwendig, eine Methodik zu definieren, um IDL-Datentypen in die Datentypen der gewählten Implementierungssprache zu konvertieren. Die Sprachabbildung für eine bestimmte Implementierungssprache definiert diese Methodik. Ferner sind die Sprachabbildungen für viele häufig verwendete Sprachen standardisiert, so daß eine Anwendung, die zur Verwendung eines bestimmten CORBAProdukts geschrieben wurde, auch mit einem anderen CORBA-Produkt funktionieren kann, wobei keine oder nur wenige Änderungen erforderlich sind (solange die Anwendung nur die Merkmale der StandardSprachabbildung nutzt).
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen finden Sie in Anhang A. An den meisten Tagen finden Sie hier außerdem noch einige Übungen, die jedoch heute entfallen, weil an diesem Tag noch keine »verwertbaren Kenntnisse« vorgestellt wurden. Quiz 1. Weiter vorn in diesem Kapitel wurde angesprochen, daß die Fähigkeit, Objekte durch Werte weiterzugeben, sobald sie in CORBA verfügbar sein wird, viele Callbacks überflüssig machen wird. Warum ist dies der Fall? 2. Ein Entwickler einer CORBA-Anwendung möchte zwei Server-Komponenten in die Anwendung integrieren. Die erste Komponente weist eine einzige Methode auf, die einfach die Uhrzeit zurückgibt. Die zweite Komponente führt nach der Initialisierung eine langwierige Berechnung in einer umfangreichen Datenbanktabelle durch, sie enthält eine einzige Methode, die den zuvor berechneten Wert zurückgibt. Welche Strategien für die Server-Aktivierung müßten für die beiden Komponenten verwendet werden und warum? 3. Können Sie sich Nachteile bei der Verwendung von Client-Stubs in einer CORBA-Client-Anwendung vorstellen? (Tip: Welche potentiell nützlichen Funktionen stellt die DII zur Verfügung?) 4. Warum sind Sprachabbildungen ein notwendiger Bestandteil von CORBA?
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 3 Beherrschen der Sprache Interface Definition Language (IDL) Überblick Grundregeln von IDL Module Einfache Typen Weitere IDL-Konstrukte Container-Typen Der Typ exception Der Typ any Der Pseudotyp TypeCode Zusammenfassung
Fragen & Antworten Workshop
Überblick Sie haben im vorherigen Kapitel die Details der Architektur von CORBA kennengelernt sowie einen Überblick über die verschiedenen CORBA-Anwendungsbestandteile und deren Verwendungszweck erhalten. Ein Hauptbestandteil der CORBA-Architektur ist, wie Sie sehen konnten, die SchnittstellenDefinitionssprache »Interface Definition Language« (IDL). IDL wird zur Beschreibung der Schnittstellen (Interfaces) zwischen CORBA-Objekten verwendet. Sie haben außerdem erfahren, daß IDL bezüglich der Implementierungssprache neutral (implementationsunabhängig) ist; mit anderen Worten können IDL-Schnittstellen in jeder Sprache implementiert werden, für die eine Sprachabbildung (Language Mapping) verfügbar ist, wie zum Beispiel für Java, C, C++ und für zahlreiche andere. Heute werden Sie die verschiedenen Konstrukte in IDL erkunden und deren Verwendung lernen. Sie werden mit einfachen Datentypen, wie Booleschen Typen, Gleitkommatypen, Integer-Typen sowie Zeichen und Zeichenketten beginnen. Sie werden feststellen, daß diese Typen den Datentypen vieler anderer Programmiersprachen sehr ähnlich sind. Anschließend werden zusammengesetzte Typen besprochen (dazu gehört der Aufzählungstyp enum, der Aufzählungstyp struct, der Typ union und der Typ interface), bei denen es sich einfach um Typen handelt, die aus anderen Typen zusammengesetzt werden. Abschließend lernen Sie die erweiterten Typen kennen, wie zum Beispiel Container-Typen (Sequenzen und Arrays), Exceptions und andere. Gegen Ende dieses Kapitels werden Sie über einen praktisch vollständigen Überblick zur IDL verfügen.
Grundregeln von IDL Bevor wir mit den Datentypen und anderen Konstrukten von IDL beginnen, sehen wir uns einige Grundregeln der IDL-Syntax und andere Gesichtspunkte der Sprache IDL an. IDL verfügt insbesondere über Regeln bezüglich der Groß-/Kleinschreibung, Definitionssyntax, Kommentarsyntax und der Verwendung des C-Präprozessors. Groß-/Kleinschreibung In IDL wird bei der Unterscheidung zwischen den einzelnen Bezeichnern (wie zum Beispiel bei den Namen von Schnittstellen und Operationen) die Schreibweise (Groß-/Kleinschreibung) unterschieden. So können Sie sich nicht auf eine Schnittstelle mit der Bezeichnung meinObjekt beziehen, indem Sie meinOBJEKT angeben. Neben der Tatsache, daß bei Bezeichnern die Schreibweise unterschieden wird, erlegt IDL eine weitere Einschränkung auf: Die Namen von Bezeichnern im gleichen Gültigkeitsbereich (beispielsweise zwei Schnittstellen im gleichen Modul oder zwei Operationen in der gleichen Schnittstelle) dürfen sich nicht nur in der Schreibweise unterscheiden. So wäre es zum
Beispiel nicht erlaubt, in einer Schnittstelle meinObjekt gleichzeitig eine Operation eineOperation und eine andere eineOPERATION zu benennen. Module, Schnittstellen und Operationen wurden bislang noch nicht besprochen. Im weiteren Verlauf dieses Kapitels erfahren Sie weitere Details zu diesen Konstrukten. Was bei OMG als Operation bezeichnet wird, könnte Ihnen bereits unter Begriffen wie Methode, Elementfunktion oder gar als Botschaft bekannt sein. Unter welcher Bezeichnung Sie sie auch kennen, eine Operation definiert ein spezielles Verhalten einer Schnittstelle, einschließlich der dazugehörigen Eingabe- und Ausgabeparameter. Die Begriffe Operation und Methode werden in diesem Buch austauschbar verwendet, da sie sich auf genau das gleiche Konzept beziehen.
Definitionssyntax von IDL Alle Definitionen werden in IDL durch ein Semikolon (;) abgeschlossen, ähnlich wie in C, C++ und Java. Für Definitionen, die andere Definitionen beinhalten (wie zum Beispiel Module und Schnittstellen) werden, wiederum wie C, C++ und Java, geschweifte Klammern ({}) verwendet. Wenn eine abschließende geschweifte Klammer am Ende einer Definition erscheint, folgt ihr auch ein Semikolon. Ein Beispiel für diese Syntax finden Sie im Listing 3.2, im Abschnitt »Module« dieses Kapitels. Kommentare in IDL Kommentare werden in IDL gemäß den gleichen Konventionen wie in Java und C++ eingefügt. Es sind sowohl Kommentare im C- als auch im C++-Stil erlaubt, wie in Listing 3.1 gezeigt wird. (Beachten Sie, daß im zweiten Kommentar des Listings eingebettete Kommentarzeichen enthalten sind; dies dient jedoch nur der besseren Veranschaulichung und ist in IDL nicht erlaubt.) Listing 3.1: Kommentare in IDL 1: // Dies ist ein Kommentar im C++-Stil. Alles, was dem Zeichen "//" 2: // folgt, bis hin zum Zeilenende, wird als Teil des Kommentars 3: // betrachtet. 4: /* Dies ist ein Kommentar im C-Stil. Alles, was sich zwischen 5: dem ersten "/*" und dem darauf folgenden "*/" befindet, wird als 6: Teil des Kommentars betrachtet. */ Verwenden des C-Präprozessors IDL geht davon aus, daß für die Verarbeitung von Konstrukten, wie zum Beispiel Makro-Definitionen und bedingte Kompilierung, ein C-Präprozessor vorhanden ist. Falls Ihr IDL-Quelltext diese
Merkmale nicht verwendet, können Sie auch ohne einen C-Präprozessor auskommen. Sie sollten jedoch zur Kenntnis nehmen, daß IDL die Merkmale des C-Präprozessors bei Bedarf verwenden kann. Beim C-Präprozessor, welcher in C- und C++-Compilern sowie einigen Betriebssystemen enthalten ist, handelt es sich um ein Tool, das für die Verwendung dieser Sprachen grundlegend wichtig ist. (Die Sprache Java verwendet keinen Präprozessor.) Bevor ein C- oder C++-Compiler Ihren Quelltext kompiliert, führt er den Präprozessor mit diesem Quelltext aus. Der Präprozessor löst Makros auf, verarbeitet Direktiven wie #ifdef...#endif und #include und führt Ersetzungen von #define-Symbolen durch. Für weitere Informationen zum CPräprozessor lesen Sie ein entsprechendes C- oder C++-Dokument, oder versuchen Sie, falls Sie Zugriff auf ein Unix-System haben, den Befehl man cpp.
Module Das erste Sprachkonstrukt von IDL, das wir untersuchen werden, ist das Modul. Das Konstrukt module wird verwendet, um IDL-Definitionen, die einem gemeinsamen Zweck dienen, zusammen zu gruppieren. Die Verwendung des Konstrukts module ist einfach: In einer Deklaration wird der Name des Moduls festgelegt, und seine Elemente werden in geschweiften Klammern eingebunden. In Listing 3.2 finden Sie ein Beispiel hierzu. Die Gruppierung von ähnlichen Schnittstellen, Konstantenwerten usw. wird in der Regel als Partitionierung (Partitioning) bezeichnet. Dies ist ein üblicher Vorgang beim Entwurf von Systemen (vor allem beim Entwurf komplexer Systeme). Für Partitionen sind auch die Begriffe Modul (was keine Überraschung darstellen sollte) oder Package gebräuchlich (tatsächlich ähnelt das Modul-Konzept von IDL sehr dem Package-Konzept von Java - oder umgekehrt, da IDL zuerst da war).
Listing 3.2: Beispiel für ein Modul 1: 2: 3: 4: 5: 6: 7: 8:
module Bank { interface Kunde { ... }; interface Konto { ... } ...
9: }; Das Beispiel im Listing 3.2 definiert ein Modul mit der Bezeichnung Bank, welches die zwei Schnittstellen Kunde und Konto enthält (das Auslassungszeichen in Form von drei Punkten wird verwendet, um anzuzeigen, daß die tatsächlichen Definitionen weggelassen wurden). Das Beispiel greift der Besprechung etwas vor, indem es bereits hier das Konstrukt interface verwendet; Schnittstellen werden später in diesem Kapitel besprochen. Kopplung und Bindung Nun, da Sie in der Lage sind, Schnittstellen zusammen zu gruppieren, stellt sich die Frage, wie Sie entscheiden, welche Schnittstellen zusammen zu gruppieren sind. Dies ist im Grunde eine Frage des Systementwurfs und würde am besten in einem eigenen Dokument zu diesem Thema beantwortet. (Es sind zahlreiche ausgezeichnete Bücher zum Thema objektorientierte Analyse und Entwurf erhältlich.) Eine allgemeine Richtlinie ist jedoch, daß ein guter Entwurf grundsätzlich zwei Attribute aufweist: geringe Kopplung (loose coupling) und enge Bindung (tight cohesion). Ersteres bedeutet, daß Komponenten in separaten Modulen nicht fest miteinander integriert sind; eine Anwendung, die Komponenten aus einem Modul verwendet, muß in der Regel nichts über Komponenten in einem anderen Modul wissen. (Es gibt sicherlich des öfteren eine Überlappung zwischen Modulen. Dies kann verschiedene Gründe haben, wie zum Beispiel die Notwendigkeit, Daten zwischen Modulen gemeinsam zu nutzen, oder um eine einheitliche Funktionalität zwischen Modulen zu erleichtern.) Liegt keine oder nur eine geringe Abhängigkeit zwischen Komponenten vor, so werden sie als gering gekoppelt bezeichnet.
Andererseits ist es in einem einzelnen Modul für einen Entwurf vorteilhaft, eine enge Bindung zu erreichen. Dies bedeutet, daß Schnittstellen innerhalb des Moduls fest miteinander integriert sind. Beispielsweise kann ein Modul mit der Bezeichnung InternerMotor Schnittstellen wie Zylinderkopf, Keilriemen, Kurbelwelle, Kolben und viele andere enthalten. Es ist schwierig, den Zweck einer dieser Komponenten zu beschreiben, ohne sich auf die anderen zu beziehen; daher könnte man von einer festen Bindung sprechen. Apropos Vergleich: Sie werden wenige Gemeinsamkeiten zwischen der Komponente InternerMotor und beispielsweise der Komponente AudioSystem finden; Komponenten von InternerMotor, wie beispielsweise OelFilter und Zuendkerze haben eine geringe Kopplung mit Audiosystem-Komponenten wie zum Beispiel CDGeraet und Tiefpasslautsprecher. Die Abbildung 3.1 veranschaulicht das Konzept von Kopplung und Bindung; beachten Sie, daß es zahlreiche Beziehungen zwischen den Komponenten innerhalb des gleichen Moduls gibt, während es jedoch wenige Beziehungen zwischen den Komponenten aus separaten Modulen gibt. Die Abbildung veranschaulicht auch den Vorteil von geringer Kopplung zwischen Modulen: Stellen Sie sich vor, Sie müßten auch die Zündkerzen und die Steuerkette austauschen, wenn Sie einen neuen CD-Player in Ihr
Fahrzeug einbauen würden! Die geringe Kopplung von Komponenten reduziert die Wahrscheinlichkeit, daß Änderungen an einer Komponente auch Änderungen an einer anderen Komponente erforderlich machen.
Abbildung 3.1: Kopplung und Bindung
Einfache Typen Wie die meisten Programmiersprachen verfügt auch IDL über eine Vielfalt einfacher Typen (die zu zusammengesetzten Typen verknüpft werden können). Diese Typen speichern einfache Werte, wie ganzzahlige Werte, Gleitkommazahlen, Zeichenketten und so weiter. void Der Typ void in IDL entspricht dem Typ void in C, C++ und Java. Es macht zwar wenig Sinn, eine Variable vom Typ void zu verwenden, jedoch ist der Typ für Methoden nützlich, die keinen Wert
zurückgeben. boolean Der IDL-Typ boolean speichert, wie sein Name schon andeutet, einen booleschen Wert. IDL definiert die zwei booleschen Konstanten true (wahr) und false (falsch), deren Bedeutung nicht weiter erläutert werden muß. Je nachdem, welche Programmiersprache verwendet wird, kann der IDL-Typ boolean auf einem ganzzahligen Typ basieren (wie zum Beispiel auf dem Typ short von C und C++) oder auf den spracheigenen booleschen Typen der Programmiersprache (wie es bei Java der Fall ist). boolean EinBoolescherWert; char und wchar Der IDL-Typ char speichert, dem Typ char in C, C++ und Java entsprechend, ein einzelnes Zeichen. Wie erwartet, bildet er den Typ char dieser Sprachen direkt ab. Bei dem Datentyp char handelt es sich um einen 8-Bit-Wert. char EinZeichen; In der Version 2.1 wurde CORBA der Typ wchar hinzugefügt, der eine implementationsabhängige Größe hat (diese beträgt normalerweise 16 Bit, jedoch sollten Sie hierzu in Ihrer ORB-Dokumentation nachschlagen, um die genaue Größe zu ermitteln). Gleitkommatypen IDL definiert zur Darstellung von Gleitkommawerten mehrere Typen; diese dürften den meisten Entwicklern bekannt sein. float In IDL stellt der Typ float einen IEEE-Gleitkommawert mit einfacher Genauigkeit dar. Auch hier liegt eine Entsprechung zu dem gleichnamigen Typ in C, C++ und Java vor. float EinGleitkommaWert; double und long double Der Typ double stellt einen IEEE-Gleitkommawert mit doppelter Genauigkeit dar. Wie zu erwarten, entspricht er dem bekannten Typ double in verschiedenen Programmiersprachen. double EinDoubleWert; Der in CORBA 2.1 eingeführte Typ long double stellt einen IEEE-Gleitkommawert mit erweiterter
doppelter Genauigkeit (double extended) dar, der einen Exponenten von mindestens 15 Bit und eine vorzeichenbehaftete Mantisse von mindestens 64 Bit hat. Integer-Typen In IDL sind mehrere Integer-Typen definiert. Auch diese werden vielen Programmierern bekannt vorkommen. Im Gegensatz zu vielen bekannten Programmiersprachen definiert IDL jedoch keinen einfachen Typen int, sondern nur Typen der Kategorie short und long. long und long long Der Typ long stellt in IDL einen vorzeichenbehafteten 32-Bit-Wert dar (mit einem Bereich von 231 bis 231-1). Dies entspricht dem Typen int in C/C++ (auf den meisten Plattformen) und dem Typen int in Java (auf allen Plattformen, da Java im Gegensatz zu C und C++ die Größe von int explizit festlegt). long EinLongWert; In CORBA 2.1 wurde der Typ long long hinzugefügt, bei welchem es sich um einen vorzeichenbehafteten 64-Bit-Wert handelt (mit einem Bereich von -263 bis 263-1). unsigned long und unsigned long long Der Typ unsigned long stellt in IDL eine vorzeichenlose Version des Typen long mit einem Bereich von 0 bis 232-1 dar. unsigned long EinVorzeichenloserLongWert; CORBA 2.1 wurde durch den Typen unsigned long long erweitert, bei dem es sich um einen vorzeichenlosen 64-Bit-Wert mit einem Bereich von 0 bis 264-1 handelt. short Der Typ short stellt einen vorzeichenbehafteten 16-Bit-Wert dar. Dies entspricht dem Typen short oder short int in C/C++ (auf den meisten Plattformen) und dem Typen short in Java. Sein Bereich geht von -215 bis 215-1. short EinShortWert; unsigned short Der Typ unsigned short ist die vorzeichenlose Version des Typs short, mit einem Bereich von 0 bis 216-1.
unsigned short EinVorzeichenloserShortWert; octet Bei dem Typen octet handelt es sich um einen 8-Bit-Wert, der während der Übertragung in keiner Weise übersetzt wird. Dieser Typ hat kein direktes Gegenstück in C und C++. Zur Darstellung des entsprechenden Wertetyps werden in C und C++ oft die Typen char und unsigned char verwendet. Java verfügt bekanntlich über den Typen byte, der dem Typen octet sehr ähnlich ist. octet EinOctetWert; string Der Typ string stellt eine Zeichenkette dar und ist dem Typen Cstring in C++ und der Klasse String in Java sehr ähnlich. C verfügt über kein direktes Gegenstück (da es in C keinen »echten« String-Typen gibt); statt dessen werden Zeichenarrays verwendet. IDL unterstützt sowohl Strings mit fester als auch mit variabler Länge. string EinStringVonFesterLaenge[20]; string EinStringVonVariablerLaenge; Der Modifizierer const Zusätzlich zu den besprochenen Standardtypen erlaubt IDL, wie C und C++, durch die Verwendung des Modifizierers const auch die Angabe von Konstantenwerten. Die Verwendung von const ist für Werte hilfreich, die sich nicht ändern sollen, wie zum Beispiel physikalische Konstanten wie pi oder c. Wie bei jedem anderen Wert auch, entspricht der Gültigkeitsbereich des const-Werts dem Gültigkeitsbereich der Schnittstelle oder des Moduls, in dem er definiert wurde. const float EineGleitkommaKonstante = 3.1415926535897932384; const long EineLongKonstante = 12345; const string EineStringKonstante = "Ist IDL nicht eine tolle Sprache?" Zusammengesetzte Typen Zusammengesetzte Typen werden, wie der Name schon sagt, aus anderen Typen zusammengesetzt und ermöglichen die Erzeugung von benutzerdefinierten Typen. Das wohl nützlichste dieser Konstrukte ist die Schnittstelle (interface), welche die Dienste definiert, die von Ihren Anwendungsobjekten bereitgestellt werden. Da IDL die Schnittstellen-Definitionssprache ist, scheint es angemessen, daß Schnittstellen den Hauptbestandteil von IDL-Quelltext ausmachen. Der Aufzählungstyp enum
Der Aufzählungstyp enum erlaubt die Erzeugung von Typen, die einen Wert aus einer durch enum vordefinierten Wertemenge enthalten können. Obwohl die Bezeichner in der Aufzählung eine geordnete Liste bilden, legt IDL keine geordnete Numerierung für die Bezeichner fest. Aus diesem Grund ist der Vergleich von enum-Werten mit ganzzahligen Werten kein sicherer Vorgang und würde so gut wie sicher über Programmiersprachen hinweg nicht portierbar sein. C und C++ verfügen ebenfalls über einen Aufzählungstyp, der ähnliche Merkmale aufweist. Ein Beispiel für den Typ enum finden Sie im Listing 3.3. Listing 3.3: Beispiel zu dem Typ enum 1: 2: 3: 4: 5: 6: 7: 8: 9:
enum WochenTage { Sonntag, Montag, Dienstag Mittwoch, Donnerstag, Freitag, Samstag };
Der Strukturtyp struct IDL stellt mit struct einen Strukturtyp zur Verfügung, der wie in C und C++ eine beliebige Anzahl von unterschiedlichen Elementwerten (sogar weitere struct-Typen) enthalten kann. Der Typ struct ist in IDL besonders nützlich, da struct-Typen, im Gegensatz zu CORBA-Objekten (die durch den Typ interface dargestellt werden), mit ihrem Wert übergeben werden (Call By Value) und nicht durch eine Referenz (Call By Reference). Wenn also, mit anderen Worten, ein struct an ein Remote-Objekt übergeben wird, wird eine Kopie der Werte von diesem struct erzeugt und an das Remote-Objekt übergeben. Im Listing 3.4 finden Sie ein Beispiel zu dem Typ struct. Listing 3.4: Beispiel zu dem Typ struct 1: 2: 3: 4: 5: 6: 7: 8: 9:
struct DatumsStruktur { short Jahr, short Monat, short Tag short Stunde, short Minute, short Sekunde, long Millisekunde };
Der Typ union Der IDL-Typ union stellt, wie auch der Typ struct, Werte dar, die von verschiedenen Typen sind. Der
IDL-Typ union wird C- und C++-Programmierern etwas merkwürdig vorkommen, da er eine Mischung aus einem Typ union aus C/C++ und einer case-Anweisung darstellt. Pascal-Programmierer sollten das Format jedoch erkennen. Ein Beispiel einer union-Definition finden Sie in Listing 3.5. Listing 3.5: Beispiel zu dem Typen union 1: 2: 3: 4: 5: 6: 7: 8: 9:
union MultiplePersoenlichkeiten switch(long) { case 1: short meineShortPersoenlichkeit, case 2: double meineDoublePersoenlichkeit, case 3: default: string meineStringPersoenlichkeit; };
Im Beispiel aus dem Listing 3.5 kann eine Variable vom Typ MultiplePersoenlichkeiten entweder einen short-, double- oder string-Wert enthalten, je nachdem, welchen Wert der Parameter bei der Verwendung des union in einem Methodenaufruf enthält (der Parameter wird als Entscheider, englisch Discriminator, bezeichnet). Ein Entscheider (Discriminator) ist, so wie er in einer IDL-union verwendet wird, derjenige Parameter, der festlegt, welcher Wert in der union verwendet wird. Im Beispiel aus Listing 3.5 wurde ein Typ long für den Entscheider verwendet; es können auch andere Typen verwendet werden, darunter die Typen long, long long, short, unsigned long, unsigned long long, unsigned short, char, boolean oder enum. Der Konstantenwert in der caseAnweisung muß dem Typen des Entscheiders entsprechen.
In der Praxis sind IDL-Unions selten nützlich. Während der traditionelle C-Typ union in der Optimierung von C-spezifischem Code eine Verwendung findet, erscheinen Unions so gut wie nie in verteilten Anwendungen. Das Verhalten von Objekten wird in der Regel durch die Verwendung von Schnittstellen besser abstrahiert. Sollte jedoch die Verwendung des Typs union erforderlich werden, stellt ihn IDL für alle Fälle zur Verfügung.
Der Typ interface Der Typ interface ist der bei weitem vielseitigste aller IDL-Datentypen. Die Schnittstelle
beschreibt die von einem CORBA-Objekt bereitgestellten Dienste (Services). Diese Dienste erscheinen in Form von Operationen (oder Methoden) und ähneln Methoden in objektorientierten Programmiersprachen wie C++ und Java. Der Unterschied ist wiederum, daß IDL nur verwendet wird, um die Schnittstellen dieser Methoden anzugeben, während Sprachen wie Java und C++ verwendet werden, um Schnittstellen anzugeben und (in der Regel) eine Implementierung für die Methoden dieser Schnittstellen bereitzustellen. Der IDL-Typ interface ist dem Typen interface aus Java sehr ähnlich, da beide keine Implementierung für die definierten Methoden bereitstellen (der Hauptunterschied ist jedoch, daß in IDL der Typ interface Parameter enthalten kann, was bei Java nicht der Fall ist). C++ verfügt über kein direktes Gegenstück zu dem IDL-Typ interface, obwohl es für C++-Anwendungen üblich ist, für die Definition der Schnittstelle einer Klasse eine Header-Datei zu verwenden. Eine Schnittstellendefinition in IDL kann deshalb mit einer Header-Datei aus C++ verglichen werden, die eine Klassendefinition enthält. Darüber hinaus kann eine C++-Klasse, deren Methoden alle virtuell sind, als analog zur IDL-Schnittstelle betrachtet werden. Wie eine Java- oder C++-Klasse kann eine IDL-Schnittstelle Attribute enthalten (die allgemein als Elementvariablen bekannt sind) und Operationen (die wiederum als Methoden oder Elementfunktionen bekannt sind). Wie beim Typ interface von Java sind alle Methoden, die innerhalb einer IDL-Schnittstelle definiert werden, öffentlich (als public deklariert), das heißt, sie können von jedem anderen Objekt aufgerufen werden, das einen Verweis auf das Implementierungsobjekt der Schnittstelle enthält. Abschließend stellt IDL auch einige zusätzliche Modifizierer zur weiteren Beschreibung der Schnittstelle und seiner Elemente bereit, da IDL-Schnittstellen in der Regel RemoteObjekte beschreiben. Beispielsweise können Methoden als oneway deklariert werden; Argumente von Methoden können als in, out oder inout deklariert werden, und Attribute können als readonly deklariert werden. Sie werden gleich erfahren, was diese Modifizierer bedeuten und wie sie verwendet werden und warum. Methoden und Parameter Die Methoden einer Schnittstelle sind im wesentlichen das, was die Funktionalität eines Objekts definiert. Obwohl die Implementierung des Objekts festlegt, wie sich das Objekt verhält, legen die Methodendefinitionen der Schnittstelle fest, welches Verhalten das Objekt aufweist, das diese Schnittstelle bereitstellt. Diese Methodendefinitionen werden oft als Methodensignaturen oder einfach als Signaturen bezeichnet. IDL-Methoden können jeden beliebigen IDL-Datentypen als Eingabe- und Ausgabeparameter verwenden - einfache Typen, structs, sequences und sogar Schnittstellen. Die allgemeine Syntax einer Methodendeklaration sieht folgendermaßen aus: [oneway] return_type MethodenName (param1_dir param1_typ param1_name, param2_dir param2_typ param2_name, ...); Der Modifizierer oneway ist optional; return_type gibt die Daten an, die von der Methode zurückgegeben werden, die Modifizierer paramn_dir geben die Richtung eines jeden Parameters an (in, out oder inout), und paramn_typ gibt den Typ eines jeden Parameters an. Modifizierer dieser Art
sind in Programmiersprachen gewöhnlich nicht zu finden und verdienen deshalb besondere Beachtung. Eine Methodensignatur, oft einfach als Signatur bezeichnet, beschreibt, was eine Methode ausführt (im Idealfall sollte der Name der Methode zumindest in groben Zügen andeuten, was die Methode ausführt), welche Parameter (und deren Typen) die Methode als Eingabe benötigt, und welche Parameter (und deren Typen) sie als Ausgabe zurückgibt.
Die Parameter in, out und inout Wie bereits erwähnt, können Parameter einer Methode als in, out oder inout deklariert werden. Diese Namen sind ziemlich selbsterklärend: Ein in-Parameter dient als Eingabe der Methode; ein outParameter ist die Ausgabe der Methode; ein inout-Parameter dient als Eingabe und Ausgabe der Methode. In bezug auf Remote-Methoden bedeutet dies, daß vor Aufruf der Methode etwaige in- und outParameter über das Netzwerk zum Remote-Objekt übertragen werden. Nachdem die Methode ausgeführt wurde, werden alle in- und inout-Parameter, zusammen mit dem Rückgabewert der Methode (falls vorhanden), zurück zum aufrufenden Objekt übertragen. Beachten Sie, daß inoutParameter zweimal übertragen werden, einmal als ein Eingabewert und einmal als Ausgabewert. oneway-Methoden Das typische Musterbeispiel für den Aufruf einer Remote-Methode sieht folgendermaßen aus: Ruft ein Objekt eine Methode eines Remote-Objekts auf, wartet dieses aufrufende Objekt (dieser Vorgang wird als Blockieren bezeichnet) darauf, daß die Methode ausgeführt und zurückgegeben wird. Wenn das Remote-Objekt die Verarbeitung des Methodenaufrufs abgeschlossen hat (oft als Anfrage bezeichnet), kehrt es zum aufrufenden Objekt zurück, welches dann seine Verarbeitung fortsetzt. Dieser Vorgang wird in Abbildung 3.2 erläutert. Im allgemeinen bezieht sich der Begriff Blockieren (Blocking) auf jeden Punkt, an welchem ein Prozeß oder Thread auf besondere Ressourcen oder einen anderen Prozeß oder Thread wartet. Im Zusammenhang mit CORBA wird, wenn ein Client eine Remote-Methode aufruft, und auf das zurückzugebende Ergebnis warten muß, der Client als blockierend bezeichnet.
Eine Anfrage (Request) ist einfach eine andere Bezeichnung für einen Remote-Methodenaufruf. Der Begriff wird weithin verwendet, wenn auf die Operation eines verteilten Systems bezogen wird. Wenn Sie sich die dynamische Aufrufschnittstelle (Dynamic Invocation Interface, DII) von CORBA ansehen, werden Sie feststellen, daß Methoden durch ein Request-Objekt aufgerufen werden können.
Abbildung 3.2: Blockieren eines Remote-Methodenaufrufs Wurde eine Methode als oneway deklariert, bedeutet dies, daß das Objekt, welches diese Methode aufruft, nicht blockieren wird. Statt dessen wird dieses Objekt die Remote-Methode aufrufen und anschließend sofort mit der Verarbeitung fortfahren, während das Remote-Objekt die RemoteMethode ausführt. Der Vorteil dieser Vorgehensweise ist, daß das aufrufende Objekt mit seiner Arbeit fortfahren kann, anstatt darauf zu warten, daß das Remote-Objekt die Anfrage abschließt. Die Abbildung 3.3 veranschaulicht die Vorgehensweise einer oneway-Methode.
Abbildung 3.3: Der oneway-Remote-Methodenaufruf (blockiert nicht) Die Flexibilität des oneway-Aufrufmodells hat jedoch ihren Preis: Da der Methodenaufruf zurückgegeben wird, bevor die Ausführung der Methode abgeschlossen wurde, kann die Methode keinen Wert zurückgeben. Deswegen muß der Rückgabewert einer oneway-Methode als void und alle Parameter als in deklariert werden (out- und inout-Parameter sind nicht zulässig). Darüber hinaus
kann eine oneway-Methode keine Exceptions auslösen. Das aufrufende Objekt hat keine Möglichkeit festzustellen, ob die Methode erfolgreich ausgeführt wurde; die Infrastruktur von CORBA macht einen bestmöglichen Versuch, die Methode auszuführen, jedoch ist eine erfolgreiche Ausführung nicht gewährleistet. (Leser, die mit dem Protokoll User Datagram Protocol vertraut sind, werden hier eine Ähnlichkeit feststellen.) Aus diesem Grund sind oneway-Methoden vor allem für Situationen nützlich, in denen ein Objekt ein anderes Objekt über einen bestimmten Status informieren möchte, jedoch 1) diese Nachricht nicht als entscheidend betrachtet und 2) keine Antwort erwartet (oder wünscht). Eine Methode, die nur über in-Parameter verfügt, void zurückgibt und keine Exceptions auslöst, ist nicht automatisch eine oneway-Methode. Der Unterschied zwischen solchen Methoden und oneway-Methoden: Während bei der letzteren die Ausführung nicht gewährleistet ist (und der Client keine Möglichkeit hat festzustellen, ob sie ausgeführt wurde), wird die erstere dazu führen, daß der Client blockiert, bis ein Ergebnis von der Remote-Methode zurückgegeben wird (auch wenn das Ergebnis Null sein wird). Der zu beachtende Unterschied ist hier, daß die erfolgreiche Ausführung einer Nicht-oneway-Methode festgestellt werden kann, da eine CORBA-System-Exception ausgelöst werden würde, falls dies nicht der Fall wäre.
Es gibt Möglichkeiten, die mit dem Blockieren verbundenen Umstände zu bewältigen. Am häufigsten kann das Blockieren-»Problem« durch die Verwendung von Multithreading umgangen werden, indem ein separater Thread erzeugt wird, um die Remote-Methode aufzurufen. Während dieser Thread blockiert ist, solange er auf das zurückzugebende Ergebnis wartet, können andere Threads mit der Arbeit fortfahren. Am 10. Tag werden Sie solche Themen detaillierter analysieren und einige Lösungsmöglichkeiten kennenlernen. Attribute Ein Attribut einer IDL-Schnittstelle entspricht einem Attribut einer Java- oder C++-Klasse, mit dem Unterschied, daß IDL-Attribute immer über einen öffentlichen Gültigkeitsbereich verfügen. (Tatsächlich verfügt alles in einer IDL-Schnittstelle über einen öffentlichen Gültigkeitsbereich.) Die allgemeine Syntax für die Definition eines Attributs sieht wie folgt aus: [readonly] attribute attribut_typ attributName; In Java und C++ wird es allgemein als guter Programmierstil angesehen, Datenzugriffs- und Datenänderungsmethoden (Accessor- und Mutator-Methoden) für Attribute einer Klasse bereitzustellen und die Klasse selbst als geschützt (protected) oder privat (private) zu deklarieren. IDL führt dieses Konzept einen Schritt weiter: IDL-Attribute zeigen auf Datenzugriffs- und Datenänderungsmethoden, wenn der IDL-Quelltext kompiliert wird. So bildet die Definition attribute short meinChannel;
folgende zwei Methoden ab: short meinChannel; void meinChannel(short value); readonly Attribute Das vorangegangene Beispiel zeigt die optionale Verwendung des readonly-Modifizierers an. Wie der Name andeutet, wird dieser Modifizierer verwendet, um anzugeben, daß ein bestimmtes Attribut nur zum Lesen gedacht ist; sein Wert kann durch ein externes Objekt nicht direkt geändert werden. (Das Objekt, welches die Schnittstelle implementiert, kann selbstverständlich die Werte seiner eigenen Attribute nach Bedarf ändern.) Obwohl ein Nicht-readonly-Attribut auf ein Paar von Datenzugriffsund Datenänderungsmethoden zuordnet, wird der IDL-Compiler für ein readonly-Attribut nur eine Datenzugriffsmethode erzeugen. Vererbung von Schnittstellen IDL-Schnittstellen unterstützen, wie die ihnen ähnelnden Java- und C++-Konstrukte, das Konzept der Vererbung. Dies bedeutet, daß eine Schnittstelle die Methoden und Attribute einer anderen Schnittstelle erben kann (man kann auch davon sprechen, daß sich eine neuere Schnittstelle von einer älteren ableitet oder davon abgleitet wurde). Zusätzlich zur Vererbung der Methoden und Attribute seiner übergeordneten Klasse kann eine abgeleitete Klasse eigene Methoden und Attribute definieren. Die abgeleitete Klasse kann auch überall ersetzt werden, wo die übergeordnete Klasse erwartet wird: Erfordert beispielsweise eine Methode einen Parameter vom Typ Fisch, und eine Schnittstelle Heilbutt ist eine abgeleitete Klasse der Fisch-Schnittstelle, dann kann diese Methode mit einem Parameter vom Typ Heilbutt statt Fisch aufgerufen werden. Eine abgeleitete Klasse, im Englischen Subclass, ist eine Klasse, die Methoden und Attribute von einer anderen Klasse erbt. Die Klasse, von der diese Methoden und Attribute geerbt wurden, wird als übergeordnete Klasse, oder englisch Superclass, bezeichnet. Obwohl IDL Schnittstellen und nicht Klassen verwendet, können diese allgemeinen Begriffe auch hier angewendet werden.
In der Terminologie der objektorientierten Programmierung und der Objekte bezieht sich Polymorphie auf die Fähigkeit, eine abgeleitete Klasse in einem Parameter, der die übergeordnete Klasse dieser Klasse erwartet, zu ersetzen. Da die abgeleitete Klasse eine Instanz der übergeordneten Klasse ist (so wie ein Beagle ein Hund ist), kann jede Operation, die auf eine übergeordnete Klasse angewendet wird, auch auf eine abgeleitete Klasse angewendet werden. Polymorphie stellt ein grundlegendes Merkmal der objektorientierten Architektur dar.
Die Syntax für die Definition von Schnittstellenvererbung ähnelt der von C++ und Java verwendeten Syntax. Die Ausnahme in IDL ist, daß keine Sichtbarkeit für die übergeordnete Klasse angegeben wird (denken Sie daran, daß Methoden und Attribute im Grunde alle öffentlich sind, also als public deklariert gelten). Die Vererbungssyntax wird in Listing 3.6 veranschaulicht. Listing 3.6: Beispiel zur Vererbungssyntax 1: 2: 3: 4: 5: 6:
interface Fisch { ... }; interface Heilbutt : Fisch { ... };
Im Listing 3.6 wird die Schnittstelle Heilbutt von der Schnittstelle Fisch abgeleitet, wie durch den Doppelpunkt-Operator (:) angezeigt wird. Die Attribute und Methoden wurden in diesem Beispiel weggelassen. Eine letzte Anmerkung bezüglich der Vererbung von IDL-Schnittstellen: IDL-Schnittstellen können, wie C++-Klassen, von mehr als einer übergeordneten Klasse erben. Diese Fähigkeit wird als Mehrfachvererbung bezeichnet und ist in Java nicht verfügbar. Java erlaubt es einer einzelnen Klasse, mehrere Schnittstellen zu implementieren, was oft zum gleichen Ergebnis wie Mehrfachvererbung führt. Die Syntax für Mehrfachvererbung von IDL wird in Listing 3.7 gezeigt. Listing 3.7: Beispiel zur Mehrfachvererbungssyntax 1: 2: 3: 4: 5: 6: 7: 8:
interface LandFahrzeug { ... }; interface WasserFahrzeug { ... }; interface AmphibienFahrzeug : LandFahrzeug, Wasserfahrzeug { ...
9: }; Im Listing 3.7 erbt die Schnittstelle AmphibienFahrzeug, die sowohl ein LandFahrzeug als auch ein WasserFahrzeug darstellt, von diesen beiden Schnittstellen. Beachten Sie, daß wegen des polymorphen Verhaltens von abgeleiteten Klassen ein AmphibienFahrzeug sowohl ein LandFahrzeug als auch ein WasserFahrzeug ersetzen kann. Definitionssyntax von Schnittstellen Die Syntax einer Schnittstellendefinition ist der in C++ oder Java verwendeten Syntax nicht unähnlich. Der Hauptteil der Schnittstelle enthält Methodensignaturen und Attributdeklarationen, die keiner bestimmten Anordnung entsprechen müssen. Einige Beispiele zu Schnittstellendefinitionen finden Sie in Listing 3.8. Listing 3.8: Beispiel-IDL-Schnittstellen 1: // Dieses Modul definiert einige nützliche Haushaltsgeräte. 2: module Geraete { 3: // Definition der Schnittstelle für den Fernseher 4: interface Fernseher { 5: // Meine Seriennummer 6: readonly attribute string meineSerienNummer; 7: // Meine aktuell eingestellte Lautstärke 8: attribute short meineLautstaerke 9: // Mein aktueller Kanal 10: attribute short meinKanal; 11: // Fernseher einschalten 12: void einSchalten(); 13: // Fernseher ausschalten 14: void ausSchalten(); 15: // 16: void 17: }; 18: interface WWWFernseher : Fernseher { 19: // den angegebenen URL besuchen 20: void geheZuURL(in Internet::URL url); 21: }; 22: }; Eine genauere Betrachtung ergibt, daß IDL-Schnittstellen keinen Konstruktor oder Destruktor angeben. Für Java-Entwickler dürfte dies nicht überraschend sein, da auch Java-Schnittstellen keine Konstruktoren oder Destruktoren enthalten (andererseits sind in Java auch keine Destruktoren vorhanden). C++-Entwickler dürften sich fragen, was mit den Konstruktoren und Destruktoren passiert ist. Wie sich herausstellt, sind Konstruktoren und Destruktoren immer noch Bestandteil von CORBA-Objekten; sie sind nur nicht als Bestandteil der Objektschnittstelle enthalten. Sie werden den
Grund hierzu im nächsten Kapitel erfahren, wenn Sie mit der Erzeugung einer CORBA-Anwendung beginnen.
Weitere IDL-Konstrukte IDL unterstützt einige andere nützliche Konstrukte. Darunter fällt auch die Fähigkeit, sich auf jeden IDL-Typen durch einen benutzerdefinierten Typnamen zu beziehen, und die Fähigkeit, einen Typnamen zu deklarieren, ohne ihn zu definieren, was für die Behandlung von zirkulären Abhängigkeiten hilfreich ist. typedef IDL unterstützt, wie C und C++, für die Erzeugung eines benutzerdefinierten Typnamens die Anweisung typedef. Durch typedef kann jeder IDL-Typ durch Angabe eines vom Benutzer ausgewählten Typnamens zugänglich gemacht werden. Diese Fähigkeit fügt der Sprache IDL eine gewisse Benutzerfreundlichkeit hinzu. Beispielsweise enthält die Schnittstelle Fernseher in Listing 3.8 das Element meineSerienNummer, welches vom Typen string ist. Da die Verwendung des Typen string nicht besonders aufschlußreich ist, wäre die Definition eines Typs SerienNummer vorzuziehen. Dieser Typ könnte dann von jeder Schnittstelle und jeder Methode verwendet werden, die eine Seriennummer erfordert. Angenommen, der Typ string ist für das Speichern von Seriennummern angemessen, wäre es angenehm, einen String-Typen zu verwenden, sich auf diesen jedoch über die Bezeichnung SerienNummer zu beziehen. Die Anweisung typedef string SerienNummer; weist den Compiler an, den Typen SerienNummer immer als String-Typ zu behandeln. Vorwärtsdeklarationen Sie werden hin und wieder Schnittstellen erzeugen, die sich gegenseitig referenzieren. Dies kann zum Beispiel vorkommen, wenn in der ersten Schnittstelle ein Attribut vom Typ der zweiten Schnittstelle enthalten ist oder eine Methode, die den Typen der zweiten Schnittstelle als Parameter oder Rückgabewert verwendet. Die zweite Schnittstelle wird die erste Schnittstelle auf die gleiche Weise referenzieren. Listing 3.9 veranschaulicht dieses Konzept, das als zirkuläre Abhängigkeit bezeichnet wird. Eine zirkuläre Abhängigkeit tritt ein, wenn zwei Klassen (oder Schnittstellen im Zusammenhang mit IDL) jeweils über Attribute oder Methoden verfügen, die sich auf die andere Klasse beziehen.
Listing 3.9: Beispiel zur zirkulären Abhängigkeit 1: module Zirkulaer {
2: 3: 4: 5: 6: 7: 8:
interface A { void verwendeB(in B einB); } interface B { void verwendeA(in A einA} } };
Im Listing 3.9 referenziert die Schnittstelle A die Schnittstelle B, welche selbst die Schnittstelle A referenziert. Würden Sie versuchen, den Quelltext wie angezeigt zu kompilieren, so würde der IDLCompiler einen Fehler in der Methode verwendeB() ausgeben, da die Schnittstelle B unbekannt ist. Würden Sie die Definitionsfolge umkehren und zuerst die Schnittstelle B definieren, so würde der IDLCompiler einen Fehler in der Methode verwendeA() ausgeben, da die Schnittstelle A dann unbekannt wäre. Wie Sie sehen können, beißt sich bei dem Problem der zirkulären Referenz die Katze in den Schwanz. C- und C++-Programmierer kennen vielleicht schon die Lösung des Problems der zirkulären Referenz: die Vorwärtsdeklaration. Eine Vorwärtsdeklaration erlaubt es Ihnen, dem IDL-Compiler mitzuteilen, daß Sie beabsichtigen, den deklarierten Typen später zu definieren, ohne den Typen zu diesem Zeitpunkt definieren zu wollen (an der Definition an sich werden Sie nicht vorbeikommen). Die Syntax einer Vorwärtsdeklaration, die der C- und C++-Syntax sehr ähnelt, ist einfach: interface B; Dies teilt dem Compiler mit, daß die Definition für die Schnittstelle B zu einem späteren Zeitpunkt folgen wird, und für den Augenblick die Referenz zur noch undefinierten Schnittstelle akzeptiert werden soll. Listing 3.10 veranschaulicht, wie eine Vorwärtsdeklaration zu dem vorherigen Beispiel aussehen würde. Listing 3.10: Beispiel zur zirkulären Abhängigkeit 1: module Zirkulaer { 2: //Vorwärtsdeklaration der Schnittstelle B 3: interface B; 4: interface A { 5: void verwendeB(in B einB); 6: } 7: interface B { 8: void verwendeA(in A einA} 9: } 10: }; Wenn der IDL-Compiler in Listing 3.10 die Definition für verwendeB() erreicht, stellt er fest, daß die
Schnittstelle B bereits deklariert wurde (durch die Vorwärtsdeklaration). Er gibt deswegen keine Fehlermeldung aus. Sie hätten genausogut auch die Schnittstelle B als erstes definieren können und eine Vorwärtsdeklaration auf die Schnittstelle A machen können; der IDL-Compiler legt keine bestimmte Reihenfolge fest, in der die Schnittstellen definiert werden müssen.
Container-Typen Die meisten Programmiersprachen verfügen über Konstrukte für die Behandlung von mehreren Werten eines ähnlichen Typs. Arrays werden in vielen Programmiersprachen verwendet: Java verfügt über java.util.Vector, C++ über die Bibliothek Standard Template Library (STL) und natürlich auch zahlreiche Bibliotheken von Container-Klassen. IDL stellt hier keine Ausnahme dar und bietet zwei solcher Konstrukte: den Typ sequence, bei dem es sich um ein dynamisch größenveränderbares Array handelt und den Typ array, der das Array-Konstrukt widerspiegelt, das auch in vielen anderen Programmiersprachen zu finden ist. Der Typ sequence Der Typ sequence ist einfach ein dynamisch größenveränderbares Array von Werten. Diese Werte können dynamisch eingefügt werden oder aus der Sequenz entfernt werden; die Sequenz verwaltet ihre Größe entsprechend. Alle Werte einer Sequenz müssen vom gleichen Typ sein oder vom gleichen Typ abgeleitet sein (mit Ausnahme des Typs any). Zum Beispiel: sequence
temperaturSequenz Dieses Beispiel definiert eine Sequenz von Gleitkommawerten und weist diesen Typen der Variablen temperaturSequenz zu. Werte vom Typ float können anschließend temperaturSequenz hinzugefügt werden, oder Werte können entfernt werden. Denken Sie an das Haushaltsgeräte-Beispiel aus Listing 3.8; sie können auch eine Sequenz von Fernseher-Objekten erzeugen: sequence BestandAnFernsehern; In diesem Fall kann BestandAnFernsehern mit Objekten vom Typ Fernseher ausgefüllt werden oder einem anderen, von Fernseher abgeleiteten Objekt, in diesem Fall WWWFernseher. Falls Sie eine dritte, von Fernseher abgeleitete Schnittstelle erzeugt haben, wie zum Beispiel TragbarerFernseher, könnte die Sequenz BestandAnFernsehern auch Fernseher, WWWFernseher und TragbarerFernseher enthalten. Das Array
Ein Array in IDL entspricht den Array-Konstrukten in C, C++ und Java. Ein Array speichert eine Serie von ähnlichen Datentypen bekannter Größe. Zum Beispiel: string WochenTage[7] Dieses Beispiel definiert ein Array, das sieben String-Werte enthalten kann und die Bezeichnung WochenTage trägt. Arrays können Elemente eines jeden beliebigen IDL-Datentyps enthalten; bezüglich des Haushaltsgeräte-Beispiels (siehe Listing 3.8) könnten Sie folgendes Array definieren: Fernseher TVArray[10]; Hierdurch wird ein Array mit zehn Fernsehern definiert. Denken Sie daran, daß jedes Array-Element, dank der Polymorphie, entweder einen Fernseher oder einen davon abgeleiteten Typen WWWFernseher enthalten kann.
Der Typ exception Ein Konzept, das in letzter Zeit von vielen Entwicklern aufgegriffen wird, ist die Verwendung von Exceptions zur Durchführung einer Fehlerbehandlung. Exceptions, die in objektorientierten Sprachen wie Java und C++ integriert sind, stellen Konstrukte dar, die bereitgestellt wurden, um bestimmte Fehlerbedingungen zu bezeichnen. Wenn die aufrufende Methode die Exception abfängt, kann sie entweder die Exception behandeln oder sie bis zur aufrufenden Stelle dieser Methode weiterreichen. Dieser Vorgang könnte bis hin zur obersten Ebene (in der Regel main()) fortgesetzt werden; falls die Methode der obersten Ebene die Exception nicht behandelt, wird die Anwendung in der Regel beendet (eine Anwendung, die dies nicht verhindert, wird als schlecht programmiert angesehen). Wenn eine Methode eine Exception an die aufrufende Stelle weitergibt, spricht man davon, daß die Methode eine Exception auslöst (im Englischen werden für diesen Vorgang die Begriffe »throw an exception« und »raise an exception« verwendet).
CORBA und IDL unterstützen die Exception-Behandlung über vordefinierte Standard-Exceptions und benutzerdefinierte Exceptions vollständig. IDL erlaubt es Entwicklern, Exceptions zu definieren und anzugeben, welche Exceptions von welchen Methoden ausgelöst werden. Wenn eine Exception ausgelöst wird, reicht das ORB diese Exception zurück an das ORB des aufrufenden Objekts, welches dann die Exception zurückgibt an das aufrufende Objekt. Auf diese Art erweitert CORBA den bekannten Exception-Übergabemechanismus zu einer verteilten Architektur. Zusätzlich zu ihrer verteilten Natur unterscheiden sich Exceptions in IDL auch in anderer Hinsicht von ihren Gegenstücken in C++ und Java. C++-Exceptions können praktisch von jedem beliebigen Typ sein; C++ erwartet nicht einmal, daß Exception-Objekte von einem bestimmten Typ abgeleitet werden (beispielsweise könnte eine Methode eine const char* auslösen, falls dies gewünscht wäre). Java-Exceptions können von jedem Typ sein, die die Schnittstelle java.lang.Throwable implementiert.
IDL jedoch ist hier etwas stärker einschränkend als diese Sprachen; Exception-Objekte müssen explizit als solche deklariert werden. Darüber hinaus unterstützt IDL keine Vererbung von ExceptionTypen (C++ und Java erlauben es, Exception-Typen von anderen Typen abzuleiten) . exception Der IDL-Typ exception selbst hat eine Ähnlichkeit mit dem Typen struct; er enthält verschiedene Datenelemente (jedoch keine Methoden). Die Definition eines Exception-Typs erinnert auch an die Definition von struct, wie das Beispiel in Listing 3.11 zeigt. Das Beispiel demonstriert, daß eine Exception nicht unbedingt über Elemente verfügen muß; manchmal stellt die einfache Auslösung einer Exception genügend Information zur Fehlerbehandlung bereit. Listing 3.11: Beispiel zur Exception-Definition 1: // Diese Exception könnte verwendet werden, wenn eine zu öffnende 2: // Datei nicht gefunden werden konnte. Da es für den Aufrufer 3: // hilfreich sein könnte, den ungültigen Namen zu erfahren, stellt 4: // die Exception diese Information bereit 5: exception DateiNichtGefundenException { 6: string DateiName; 7: }; 8: // Diese Exception könnte für eine bestimmte Operation verwendet 9: // werden, die nicht wie erwartet innerhalb eines zuvor festgelegten 10: // Zeitraums ausgeführt werden konnte. Die Exception stellt den Namen 11: // der Operation und den für die Operation vorgesehenen Zeitraum 12: // bereit. 13: exception ZeitueberschreitungFuerOperationException { 14: string NameDerOperation; 15: long Zeitlimit; 16: }; 17: // Diese Exception könnte verwendet werden, wenn der Versuch, sich in 18: // einem System anzumelden, fehlgeschlagen ist. Es wird keine weitere 19: // Information benötigt. 20: exception UngueltigeAnmeldungException { 21: }; Standard-Exceptions CORBA erlaubt es dem Anwender, benutzerdefinierte Exceptions zu erzeugen und stellt darüber hinaus zahlreiche Standard-Exceptions, auch als System-Exceptions bezeichnet, bereit. Die
Exceptions dieser Gruppe können von jedem Remote-Methodenaufruf ausgelöst werden. IDLMethodendefinitionen deklarieren nicht explizit, daß sie eine System-Exception auslösen; statt dessen werden diese Exceptions implizit ausgelöst. (Wenn der IDL-Quelltext kompiliert wird, deklarieren die generierten Methodendefinitionen, daß CORBA-System-Exceptions ausgelöst werden, sowie etwaige benutzerdefinierte Exceptions, die von diesen Methoden ausgelöst werden.) Zusätzlich zu regulären Methoden können sogar die Datenzugriffs- und Datenänderungsmethoden (Accessor- und MutatorMethoden) - entsprechend den Attributen von Schnittstellen - Exceptions auslösen, auch wenn sie keine benutzerdefinierten Exceptions auslösen können. Die in CORBA verfügbaren Standard-Exceptions sind in Tabelle 3.1 aufgelistet. Tabelle 3.1: Standard-Exceptions in CORBA Exception
Beschreibung
UNKNOWN
Unbekannte Exception
BAD_PARAM
Ein ungültiger Parameter wurde übergeben
NO_MEMORY
Fehler bei Reservierung dynamischen Speichers
IMP_LIMIT
Verletzung der Implementationsbegrenzung
COMM_FAILURE
Kommunikationsfehler
INV_OBJREF
Ungültige Objektreferenz
NO_PERMISSION
Keine Zugriffsrechte für aufgerufene Operation
INTERNAL
Interner ORB-Fehler
MARSHAL
Fehler beim Abrufen des Parameters oder Ergebnisses
INITIALIZE
Fehler bei der ORB-Initialisierung
NO_IMPLEMENT
Implementation der Operation nicht verfügbar
BAD_TYPECODE
Fehlerhafter TypeCode
BAD_OPERATION
Ungültiger Vorgang
NO_RESOURCES
Unzureichende Ressourcen zur Ausführung der Anfrage
NO_RESPONSE
Antwort auf Anfrage noch nicht verfügbar
PERSIST_STORE
Fehler bei persistentem Speicher
BAD_INV_ORDER
Aufruf der Routinen in ungültiger Folge
TRANSIENT
Vorübergehender Fehler - Anfrage erneut durchführen
FREE_MEM
Kann Speicher nicht freigeben
INV_IDENT
Ungültige Bezeichner-Syntax
INV_FLAG
Angabe eines ungültigen Flags
INTF_REPOS
Fehler beim Zugriff auf Schnittstellenablage
BAD_CONTEXT
Fehler beim Verarbeiten des Kontextobjekts
OBJ_ADAPTER
Fehler vom Objektadapter festgestellt
DATA_CONVERSION Fehler bei der Datenumwandlung OBJECT_NOT_EXIST Objekt nicht vorhanden - Referenz sollte gelöscht werden
Der Typ any Für jene gelegentlich vorkommenden Methoden, die jede Art eines CORBA-Objekts als Parameter akzeptieren müssen oder einen Parameter erwarten, der potentiell einer von mehreren nicht zusammenhängenden Datentypen sein könnte (mit anderen Worten, keiner der Datentypen ist von einem der anderen abgeleitet), stellt IDL den Typen any zur Verfügung. Wenn any als der Typ eines Parameters oder Rückgabewerts verwendet wird, kann dieser Wert praktisch von jedem beliebigen IDL-Typen sein. Eine Methode, die any als Eingabeparameter akzeptiert, wird in der Regel zunächst genau ermitteln müssen, welcher Objekttyp übergeben wurde, bevor sie das Objekt bearbeiten kann; Sie werden sehen, wie dies bewerkstelligt wird, sobald Sie anfangen, CORBA-Clients und -Server zu
implementieren. Für eine Methode, die aus einer Vielzahl von Typen von CORBAObjekten einen als Parameter akzeptieren muß, ist any nicht immer die einzige Option. Falls der Typ des Parameters aus einer kleineren Anzahl von Typen stammen kann, und der Typ des Parameters bekannt ist, könnte der Typ union die bessere Alternative sein. Jedoch müßten bei der Verwendung des Typs union alle diese Typen im Vorfeld bekannt sein; dies ist für den Typ any nicht erforderlich. Ein any-Parameter kann praktisch jeden Objekttyp enthalten, sogar einen, der dem den anyParameter annehmenden Server unbekannt ist.
Ein Beispiel für die Verwendung des Parameters any finden Sie im Listing 3.12. In diesem Beispiel akzeptiert die Methode ObjektAnzeigen() einen einzigen Parameter (mit der Bezeichnung Object), der von einem beliebigen Typ sein kann. Ein Client, der diese Methode aufruft, kann ein Objekt von einem beliebigen IDL-Typen verwenden, um diesen Parameter zu belegen. Innerhalb von ObjektAnzeigen() wird die Methode versuchen festzustellen, von welchem Typen Object ist. Falls sie feststellt, daß Object ein bekannter Objekttyp ist, wird die Verarbeitung fortgesetzt. Ansonsten wird eine Exception vom Typ UnbekannterObjektTyp ausgelöst und diese an die aufrufende Stelle zurückgegeben. Listing 3.12: Beispiel zu dem Typ any 1: 2: 3: 4: 5: 6:
interface ObjektAnzeige { exception UnbekannterObjektTyp { string meldung; }; void ObjektAnzeigen(in any object) raises (UnbekannterObjektTyp); };
Der Pseudotyp TypeCode Zusammen mit dem Typ any tritt der Pseudotyp TypeCode auf. TypeCode ist kein IDL-Typ, sondern stellt einer CORBA-Anwendung Typinformation zur Verfügung. In den meisten Methodenaufrufen sind die Typen der übergebenen Parameter bekannt, da sie von der IDL-Methodensignatur angegeben werden. Wenn eine Methode als Parameter jedoch den Typ any akzeptiert, ist der aktuelle Typ dieses Objekts unbekannt. An dieser Stelle findet TypeCode Verwendung. Da mit jedem CORBA-Typ (sowohl Standardtypen wie long und string als auch benutzerdefinierte Typen wie Fernseher) ein eindeutiger Typcode verknüpft ist, kann eine Methodenimplementation feststellen, welcher Objekttyp über den Parameter any übergeben wurde. Nachdem der Objekttyp ermittelt wurde, kann die Methode mit dem Objekt weiterarbeiten. Stellen Sie sich TypeCode als eine Art Laufzeit-Typinformation für
CORBA-Anwendungen vor.
Zusammenfassung In diesem Kapitel wurden die grundlegenden Datentypen der CORBA-Programmiersprache Interface Definition Language (IDL), wie numerische Integer- und Gleitkommawerte, boolesche Werte sowie Zeichen und Zeichenketten, vorgestellt. Da viele dieser Datentypen sehr den Datentypen aus Programmiersprachen wie C, C++ und Java ähneln, werden Leser, die mit einer dieser Programmiersprachen vertraut sind, keine Probleme mit den IDL-Typen haben. Sie haben Ihr Wissen von IDL nun um die erweiterten Datentypen (insbesondere um die benutzerdefinierten Typen) und deren Verwendung ergänzt. Am wichtigsten ist, daß Sie mit dem Schnittstellen-Konstrukt vertraut sind und wissen, wie es das Verhalten von CORBA-Objekten definiert. Da interface einer der fundamentalen IDL-Datentypen ist, ist ein klares Verstehen dieses Konstrukts für den Entwurf von CORBA-Anwendungen wesentlich. Sie können ohne Schnittstellen keine CORBA-Anwendungen entwerfen oder implementieren. Sie haben heute einige nützliche IDL-Datentypen und -Konstrukte kennengelernt, insbesondere die Typen sequence und array, die zum Speichern von mehreren Werten eines ähnlichen Typs verwendet werden. Sie haben außerdem die Exceptions in IDL, einschließlich der vordefinierten StandardExceptions, kennengelernt. Abschließend haben Sie den Typ any untersucht, der einen Wert eines beliebigen IDL-Typen enthalten kann, und sein Gegenstück, den Pseudotyp TypeCode, mit dessen Hilfe Datentypen ermittelt werden können, die an eine Methode übergeben werden. Diese Konstrukte, insbesondere sequence und exception, sind wesentlich für die Erzeugung von stabilen CORBAAnwendungen. Sie haben nun praktisch alles behandelt, was es über IDL zu wissen gibt; nun ist es an der Zeit, dieses Wissen anzuwenden. Im nächsten Kapitel - »Tag 4, Erzeugen einer CORBA-Anwendung« - werden Sie genau dies bewerkstelligen, indem Sie IDL-Definitionen in funktionierende CORBA-Server und Client-Anwendungen übersetzen.
Fragen & Antworten Frage: Was ist der Zweck der Existenz sowohl des Typs sequence als auch des Typs array? Antwort: Array-Typen sind nützlich, wenn die Anzahl der Elemente in einem Array fest und bereits im Vorfeld bekannt ist. Oft ist dies nicht der Fall, da die Größe der Arrays sich ändert, wenn Elemente dynamisch hinzugefügt und entfernt werden. Konsequenterweise ist ein dynamisch größenveränderbares Array, wie der IDL-Typ sequence, oft wesentlich praktischer. Für eine größtmögliche Flexibilität bietet IDL beide Typen an.
Frage: Was sind all diese Exceptions und wann (bzw. wie) werden sie ausgelöst? Antwort: Zunächst einmal haben nicht-verteilte Methoden wenig Verwendung für viele der CORBAStandard-Exceptions. Sie machen jedoch sehr viel mehr Sinn in einer verteilten Umgebung. Wie zuvor erwähnt, werden die Standard-Exceptions grundsätzlich nicht in dem von Ihnen geschriebenen Quelltext ausgelöst, sondern sie werden automatisch durch den ORB ausgelöst, wenn eine Fehlerbedingung eintritt. Denken Sie daran, daß alle IDL-Methoden implizit diese Exceptions auslösen, sogar die vom IDL-Compiler für jedes Attribut erzeugten Datenzugriffs- und Datenänderungsmethoden (Accessor- und Mutator-Methoden). Frage: Warum wird der Datentyp »...« (fügen Sie hier Ihren bevorzugten, einfachen Datentypen ein) nicht unterstützt? Antwort: Da eines der Hauptziele von IDL in der Sprachneutralität (Implementationsunabhängigkeit) besteht, ist es nicht sinnvoll, alle einfachen Datentypen aller Sprachen zu unterstützen. Anstatt zu versuchen, diese (kaum zu erreichende) Ebene der Unterstützung zu erreichen, stellt IDL die gebräuchlichsten und nützlichsten einfachen Datentypen zur Verfügung. Frage: Wie ändert ein Objekt eines seiner Attribute, wenn dieses Attribut als readonly deklariert wurde? Antwort: Wenn Sie sich Fragen wie diese stellen, denken Sie ganz eindeutig voraus. Wenn Sie die Implementierung von IDL-Schnittstellen am 4. Tag lernen, wird klar werden, wie dies bewerkstelligt wird. Im Moment sollte die Information ausreichen, daß das Objekt, welches eine Schnittstelle implementiert, vollen Zugriff auf seinen eigenen Status hat. Ein readonly-Attribut wird einer Methode dieses Objekts zugeordnet, und diese Methode kann jeden beliebigen, vom Objekt gewünschten Wert zurückgeben. In der Regel wird das implementierende Objekt eigene aktuelle Attribute definieren, die den Attributen in der IDL-Schnittstelle entsprechen, obwohl dies nicht unbedingt erforderlich ist. Frage: Da Java keine Mehrfachvererbung unterstützt, wie wird Mehrfachvererbung von IDL-Schnittstellen in
Java erzielt? Antwort: Wenn Sie mit der Implementierung von IDL-Schnittstellen in Java beginnen, werden Sie feststellen, daß die IDL-Sprachabbildung für Java IDL-Schnittstellen in Java-Schnittstellen abbildet. Obwohl Mehrfachvererbung von Klassen in Java nicht unterstützt wird, wird Mehrfachvererbung von Schnittstellen unterstützt.
Workshop Der folgende Abschnitt wird Ihnen dabei helfen zu überprüfen, wie gut Sie das in diesem Kapitel vorgestellte Material verstanden haben und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A. Quiz 1. Definieren Sie einen Typ (unter Verwendung von typedef) mit der Bezeichnung temperaturSequenz, der eine Sequenz in Form von sequence of floats darstellt. (Ja, dies ist erlaubt.) 2. Warum könnte ein Typ, wie in der vorherigen Frage beschrieben, sinnvoll sein? 3. Wozu sind Exceptions hilfreich? 4. Wozu ist das Konstrukt module hilfreich? 5. Benennen Sie einige Einsatzmöglichkeiten für den Datentyp octet. 6. Definieren Sie einen Aufzählungstyp, der die Monate eines Jahres enthält. 7. Warum könnte eine nicht-blockierende Methode im Vergleich zu einer blockierenden Methode von Vorteil sein? 8. Stellen Sie sich einen zusammengesetzten Datentyp mit einer großen Anzahl von Datenelementen vor. Dieser Datentyp wird häufig von der Client-Anwendung verwendet werden, die im allgemeinen auf alle Datenelemente zugreifen muß. Wäre es effizienter, diesen Datentyp in einem struct oder in einer Schnittstelle zu verkapseln? Begründen Sie Ihre Antwort. 9. Eine IDL-Methode kann einen Wert zurückgeben. Was ist jedoch der Zweck der Parametertypen in und inout? 10. Warum ist eine oneway-Methode nicht in der Lage, einen Wert an die aufrufende Stelle zurückzuliefern? Können Sie sich einen Mechanismus vorstellen, der oneway-Aufrufe verwendet, um der aufrufenden Stelle ein Ergebnis zu übergeben? Übungen 1. Nehmen Sie folgende Klassen an: Leitungsrohr, Wasserhahn, Sicherungskasten, Steckdose, Rohr, Wassererhitzer, Wasserpumpe, Leitung. Wie würden Sie diese Klassen partitionieren? Welche Verbindung, falls überhaupt, liegt zwischen den von Ihnen erzeugten Partitionen vor? 2. Erzeugen Sie eine Schnittstelle, die einen Radiowecker beschreibt (mit dem man die Stunden, Minuten, Weckzeit und so weiter setzen kann).
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 4 Entwicklung einer CORBA-Anwendung Entwicklung eines CORBA-Servers Implementierung der Server-Schnittstellen Entwicklung eines CORBA-Clients Zusammenfassung Fragen & Antworten Workshop
Bis hierher habe ich Ihnen vor allem Kenntnisse zur Interface Definition Language (IDL) von CORBA vermittelt. Sie haben gelernt, daß die IDL zur Definition der Schnittstellen von CORBAObjekten verwendet wird, und Sie haben sogar schon eigene IDL-Schnittstellen definiert. Jetzt ist es an der Zeit, daß Sie Ihre IDL-Kenntnisse weiter in die Praxis umsetzen, und zwar nicht nur zur Definition von Schnittstellen, sondern auch zur Implementierung dieser Schnittstellen im Rahmen der Entwicklung eines CORBA-Servers. Hierzu werden Sie die folgenden Hauptschritte durchführen: 1. Definieren der Server-Schnittstellen mit Hilfe der IDL. 2. Wahl eines Implementierungsansatzes für die Server-Schnittstellen. (Sie werden sehen, daß CORBA hierzu zwei Ansätze anbietet: Vererbung und Delegation.) 3. Generieren von Client-Stubs und Server-Skeletons für die Server-Schnittstellen mit Hilfe des IDL-Compilers. (Zunächst werden Sie nur mit den Server-Skeletons zu tun haben.) 4. Implementieren der Server-Schnittstellen.
5. Kompilieren der Server-Anwendung. 6. Ausführen der Server-Anwendung. (Mit ein wenig Glück wird alles auf Anhieb klappen!) Anschließend werden Sie einen Client entwickeln, der die im ersten Teil dieses Kapitels implementierten Dienste nutzt. Hinsichtlich der Konzeption ist der Prozeß der Erstellung eines Clients sehr viel einfacher, denn Sie brauchen nur festzulegen, welche Aufgaben der Client ausführen soll, die geeigneten Client-Stubs für die zu verwendenden Server-Objekttypen mit aufzunehmen und schließlich die Client-Funktionen zu implementieren. Und schon ist alles für die Kompilierung und Ausführung des Clients bereit.
Entwicklung eines CORBA-Servers Der erste Schritt bei der Entwicklung einer CORBA-Anwendung besteht normalerweise in der Implementierung der Server-Funktionen. Dies hat den Grund, daß zwar ein Server (zumindest in beschränktem Maße) auch ohne einen Client getestet werden kann, es jedoch im allgemeinen ungleich schwieriger ist, einen Client ohne einen funktionierenden Server zu testen. Es gibt natürlich auch Ausnahmen von dieser Regel, aber normalerweise müssen Sie zumindest die Server-Schnittstellen definiert haben, bevor Sie den Client implementieren können. Damit das Arbeiten nicht zu kompliziert wird, werden im Rahmen dieses Buches zunächst die Server-Funktionen und anschließend die Client-Funktionen implementiert. Zur Erstellung eines Servers müssen Sie zunächst die Server-Schnittstellen definieren (über diese wird festgelegt, welche Funktionen der Server zur Verfügung stellt und wie der Zugriff auf diese Funktionen erfolgen soll), anschließend diese Schnittstellen implementieren und zum Schluß den Server kompilieren und ausführen. Im Verlauf dieser Arbeiten werden an der einen oder anderen Stelle Fragen auftauchen, die ich in diesem Abschnitt bereits behandeln möchte. Definition der Server-Schnittstellen Für diese Aufgabe können Sie erstmals Ihre am 3. Tag erworbenen Kenntnisse einsetzen. Sie haben in diesem Kapitel viel über IDL gelernt, hatten aber noch keine echte Gelegenheit, dieses Wissen anzuwenden. Mit Hilfe dieser Kenntnisse können Sie quasi aus dem Nichts einen Systementwurf erstellen. Einige Worte zum Systementwurf Es versteht sich von selbst, daß zur Entwicklung eines Systems zunächst eine Grundvorstellung über die Aufgaben des Systems vorhanden sein muß. Bevor Sie auch nur eine einzige IDL-Zeile schreiben können, müssen Sie sich darüber im klaren sein, was Sie mit dem System erreichen möchten. Obwohl die umfassende Behandlung des Themas Systementwurf den Rahmen dieses Buches bei weitem sprengen würde, werden Sie am 5. Tag einige der Grundlagen zum Entwerfen und Abbilden eines Systementwurfs in IDL kennenlernen. Im vorliegenden Kapitel werden Sie von dieser Aufgabe entlastet, im wahren »Programmiererleben« können Sie kaum mit so etwas rechnen. Das Beispiel eines Börsen-Servers
An dieser Stelle möchte ich Ihnen den Vorgang zur Implementierung eines CORBA-Servers anhand eines einfachen Beispiels näherbringen. Bei einem komplexeren Beispiel würde der Lernerfolg vermutlich durch das Labyrinth der einzelnen Implementierungsdetails gefährdet, daher werden wir es uns für später aufheben. Stellen Sie sich eine Börse vor: Dort benötigt man einen Dienst, der bei Angabe einer bestimmten Aktie deren Wert zum momentanen Zeitpunkt ausgibt. Als zusätzliche Leistung gibt der Dienst auf Anforderung auch eine Liste aller bekannten Aktien aus. Eine flüchtige Analyse dieses Szenarios ergibt, daß eine Schnittstelle mit dem Namen BoersenServer definiert werden könnte, die zwei Dienste (Methoden) zur Verfügung stellt: getAktienWert() und getAktienSymbole(). Die Methode getAktienWert() müßte den Parameter AktienSymbol für das Aktiensymbol annehmen und als Ergebnis einen Gleitkommawert liefern. (Ein Wert vom Typ float müßte hierfür ausreichen.) getAktienSymbole() benötigt keine Parameter und soll eine Liste der AktienSymbol-Objekte liefern. Während der Festlegung der Systemfunktionalität für BoersenServer wurde unversehens die Klasse AktienSymbol erzeugt. Diese spontane Klassengenerierung, die häufig als Nebeneffekt einer objektorientierten Analyse abläuft, kann das Verständnis für die Abläufe in einem bestimmten System fördern.
Listing 4.1: BoersenMarkt.idl. 1: // BoersenMarkt.idl 2: 3: // Das Modul BoersenMarkt besteht aus Definitionen, die zum Erstellen 4: // börsenorientierter Anwendungen geeignet sind. 5: module BoersenMarkt { 6: 7: // Der Typ AktienSymbol wird für Symbole (Namen) 8: // von Aktien verwendet. 9: typedef string AktienSymbol; 10: 11: // Eine AktienSymbolListe ist einfach eine Sequenz 12: // von AktienSymbolen. 13: typedef sequence AktienSymbolListe; 14: 15: // Die Schnittstelle BoersenServer ist die Schnittstelle für einen Server, 16: // der Börseninfos zur Verfügung stellt.
17: // (Weitere Informationen hierzu finden Sie in den Kommentaren 18: // zu den einzelnen Methoden.) 19: interface BoersenServer { 20: 21: // getAktienWert() liefert den aktuellen Wert eines 22: // AktienSymbols. Ist das angegebene 23: // AktienSymbol unbekannt, ist das Ergebnis undefiniert (dies wäre eine 24: // gute Stelle, um eine Exception auszulösen). 25: float getAktienWert(in AktienSymbol symbol); 26: 27: // getAktienSymbole() gibt eine Sequenz mit allen 28: // AktienSymbolen zurück, die diesem BoersenServer bekannt sind. 29: AktienSymbolListe getAktienSymbole(); 30: }; 31: }; Die Abbildung dieses speziellen Entwurfs in IDL ist ein eindeutiger Prozeß, dessen Endergebnis, BoersenMarkt.idl, in Listing 4.1 zu sehen ist. Da es sich empfiehlt, zusammengehörende Schnittstellen und Typen in IDL-Modulen zu gruppieren, sollten Sie zunächst alle Definitionen in ein Modul mit dem Namen BoersenMarkt: module BoersenMarkt { aufnehmen. Befassen Sie sich dann damit, wie die Klasse AktienSymbol eingesetzt werden soll. Im Rahmen dieses Beispiels benötigt diese Klasse keine über den Typ string hinausgehende Funktionalität. Daher können Sie entweder den Typ AktienSymbol an allen Stellen durch den Typ string ersetzen oder AktienSymbol mit Hilfe von typedef als String definieren. In einem komplexen System erleichtert die Verwendung von speziellen Datentypen wie AktienSymbol das Verständnis für den Systementwurf. Wenden Sie diese Vorgehensweise gleich an, indem Sie mit Hilfe von typedef den speziellen Typ AktienSymbol als Zeichenkette definieren: typedef string AktienSymbol; Jetzt können Sie als nächstes die Schnittstelle für das Objekt BoersenServer definieren: interface BoersenServer { Die erste Methode in BoersenServer erhält als Eingabe den Typ AktienSymbol und liefert als Ausgabe einen Wert des Typs float. Diese Festlegung kann in IDL ohne Probleme folgendermaßen ausgedrückt werden: float getAktienWert(in AktienSymbol symbol);
Es ist vorstellbar, daß ein Client die Methode getAktienWert() mit einem ungültigen Namen für AktienSymbol aufruft. In einem solchen Fall könnte (und müßte) getAktienWert() bei der Übergabe des ungültigen Namens eine Exception auslösen. Aus Gründen der Einfachheit werden wir im vorliegenden Beispiel jedoch nicht mit Exceptions arbeiten.
Die andere BoersenServer-Methode erhält keine Parameter und gibt eine Liste von AktienSymbolWerten zurück. Hier sei daran erinnert, daß die IDL zwei Konstrukte zur Darstellung von Listen anbietet: Sequenzen und Arrays. Da in diesem Fall die Größe der auszugebenden Liste unbekannt ist, wäre die Verwendung einer Sequenz vorteilhaft. Eine Methode kann jedoch eine Sequenz (sequence) nicht direkt zurückgeben. Zunächst muß mit typedef eine AktienSymbol-Sequenz für diese Methode definiert werden. Aus praktischen Gründen empfiehlt es sich, die typedef-Definition unmittelbar nach der typedef-Definition für den Typ AktienSymbol einzubauen: typedef sequence AktienSymbolListe; Jetzt können Sie die Methode getAktienSymbole() zur BoersenServer-Schnittstelle hinzufügen. Diese Methode wird in der IDL folgendermaßen beschrieben: AktienSymbolListe getAktienSymbole(); Dies ist der ganze IDL-Quelltext, den Sie für dieses Beispiel benötigen. Ausgestattet mit der Datei BoersenMarkt.idl sind Sie nun bereit für den nächsten Schritt, nämlich die Entscheidung, wie diese IDL-Definitionen implementiert werden sollen. Wahl eines Ansatzes für die Implementierung Bevor Sie die Server-Funktion tatsächlich implementieren, müssen Sie zunächst über den Ansatz für die Implementierung entscheiden. CORBA unterstützt zwei Mechanismen für die Implementierung von IDL-Schnittstellen. Entwickler, die sich mit objektorientierten Konzepten bereits auskennen, dürften diese Mechanismen oder zumindest deren Namen kennen. Es handelt sich hierbei um den Vererbungsmechanismus, bei dem eine Klasse eine Schnittstelle durch Vererbung der Methoden aus der betreffenden Schnittstellenklasse implementiert, und um den Delegationsmechanismus, bei dem die Methoden der Schnittstellenklasse die Methoden der implementierenden Klasse aufrufen (die Implementierung an diese Methoden delegieren). Diese Konzepte sind in den Abbildungen 4.1 bzw. 4.2 dargestellt. Abb. 4.2 zeigt ferner, daß eine bindende Klasse die Methoden einer beliebigen Klasse oder keiner Klasse erben kann - ganz im Gegensatz zum Vererbungsansatz, bei dem die Implementierungsklasse die Methoden von der Schnittstellenklasse erben muß, die sie implementieren soll.
Implementierung durch Vererbung ist ein Implementierungsmechanismus, bei dem eine Basisklasse die Schnittstellen eines bestimmten Objekts definiert und eine gesonderte Klasse, die die Definitionen von dieser Klasse erbt und die tatsächliche Implementierung dieser Schnittstellen darstellt.
Implementierung durch Delegation ist ein Implementierungsmechanismus, bei dem eine Klasse die Schnittstellen für ein Objekt definiert und anschließend ihre Implementierung an eine andere Klasse bzw. an andere Klassen delegiert. Der Hauptunterschied dieses Ansatzes zum Vererbungsmechanismus besteht darin, daß hier die Implementierungsklassen nicht von einer bestimmten Klasse abgeleitet sein müssen. Eine bindende Klasse (Tie Class) ist diejenige Klasse, an die die Implementierungen beim Delegationsansatz delegiert werden. Daher wird dieser Ansatz häufig einfach als Binden bezeichnet. Die meisten IDL-Compiler akzeptieren Befehlszeilenparameter zur Festlegung, für welchen Implementierungsansatz Quelltext generiert werden soll. Bevor Sie also mit dem IDL-Compiler Quelltext aus Ihren IDL-Definitionen generieren, müssen Sie den zu verwendenden Ansatz festlegen. Der Dokumentation zu Ihrem IDL-Compiler können Sie entnehmen, welche Befehlszeilenparameter dieser ggf. erwartet.
Abbildung 4.1:
Implementierung durch Vererbung
Abbildung 4.2: Implementierung durch Delegation Wie wählt man den geeigneten Implementierungsansatz? Sie werden sich nun sicherlich fragen, wie Sie den geeigneten Ansatz zur Implementierung auswählen sollen. In vielen Fällen können Sie sich dabei nach Ihrer persönlichen Vorliebe richten. Es gibt jedoch auch Fälle, in denen ein bestimmter Ansatz besser geeignet ist als der andere. Hierbei ist beispielsweise zu bedenken, daß die Implementierungsklasse von einer vom IDL-Compiler zur Verfügung gestellten Klasse abgeleitet wird. Wenn eine Anwendung zur Implementierung einer Schnittstelle bereits vorhandenen, älteren Quelltext (Legacy-Code) nutzt, wäre es unpraktisch, die Klassen in diesem vorhandenen Quelltext zu ändern, damit diese die Methoden einer vom IDLCompiler generierten Klasse erben können. Bei einer solchen Anwendung wäre es daher sinnvoller, den Delegationsansatz zu verwenden, denn vorhandene Klassen können problemlos in bindende Klassen umgewandelt werden.
Nachdem Sie nun den Ansatz zur Implementierung gewählt und einen großen Teil des Quelltextes geschrieben haben, sollten Sie sinnvollerweise beim für diesen Server gewählten Ansatz bleiben. Zwar ist es möglich, von einem Implementierungsansatz zum anderen zu wechseln, doch handelt es sich hierbei um einen recht mühevollen Prozeß, wenn bereits ein großer Teil des Quelltextes geschrieben wurde. Diese Problematik stellt sich nicht sehr oft, aber Sie sollten den Aspekt nicht ganz außer acht lassen.
Für das vorliegende Beispiel ist jeder der beiden Implementierungsansätze geeignet, tatsächlich werden wir hier den Delegationsmechanismus verwenden. Die Implementierung des Servers mit Hilfe der Vererbung bleibt Ihnen als Übung vorbehalten. Beachten Sie, daß die beiden Implementierungsansätze innerhalb einer einzigen Server-Anwendung nebeneinander eingesetzt werden können. Zwar wählen Sie pro Schnittstelle jeweils nur einen einzigen Ansatz, doch können Sie für die einzelnen Schnittstellen im System unterschiedliche Ansätze verwenden. Wenn Sie sich beispielsweise grundsätzlich für den Vererbungsansatz als den am besten geeigneten Mechanismus entschieden haben, aber gleichzeitig einige ältere, bereits im Einsatz befindliche Klassen einsetzen, die den Bindungsansatz erfordern, können Sie diesen Ansatz für die älteren Klassen und den Vererbungsansatz für alle übrigen Klassen wählen. Verwendung des IDL-Compilers Nachdem Sie die IDL-Definitionen für die Objektschnittstellen des Systems erstellt und den Implementierungsansatz gewählt haben, ist nun alles für die Kompilierung der IDL-Datei bzw. Dateien (in einem komplexeren System) vorbereitet. Die Methode und die Befehlszeilenparameter zum Aufrufen des IDLCompilers unterscheiden sich von Plattform zu Plattform und von Produkt zu Produkt. Nähere Anweisungen hierzu entnehmen Sie der Produktdokumentation Ihres IDL-Compilers.
Denken Sie daran, daß für dieses Beispiel der Delegationsansatz (auch Binden genannt) verwendet werden soll, daher benötigen Sie aus der Dokumentation zum IDL-Compiler die geeigneten Befehlszeilenparameter (sofern erforderlich) zum korrekten Generieren von Dateien und Quelltext. Zum Aufrufen des IDL-Compilers, der mit der Java-IDL von Sun geliefert wird, müssen Sie beispielsweise folgenden Befehl eingeben: idltojava -fno-cpp -fclient -fserver BoersenMarkt.idl In diesem Fall heißt der IDL-Compiler idltojava. Der Schalter fno-cpp bewirkt, daß der IDL-Compiler den C-Präprozessor nicht vor der Kompilierung der Datei aufruft. Die Schalter -fclient und -fserver
weisen den IDL-Compiler an, Client-Stubs bzw. Server-Skeletons zu generieren. Zunächst bräuchte der Schalter -fclient noch nicht mit eingegeben zu werden, weil jetzt nur der Server implementiert werden soll, aber da Sie die Client-Stubs später auch noch brauchen werden, sparen Sie Zeit, wenn Sie sie bei dieser Gelegenheit gleich mit generieren. Der Befehl zum Aufrufen des in VisiBroker/C++ für Windows 95 von Visigenic enthaltenen IDLCompilers lautet folgendermaßen: orbeline -h h BoersenMarkt.idl Mit diesem Befehl generiert der IDL-Compiler namens orbeline Client-Stubs und Server-Skeletons für die Datei BoersenMarkt.idl. Der Schalter -c cpp diente dazu, daß der Compiler die Dateinamenerweiterung .cpp für C++-Quelltextdateien verwendet; analog dazu bewirkt hier der Schalter -h h, daß der Compiler die Dateinamenerweiterung.h für Header-Dateien verwendet. Selbstverständlich können Sie die hier genannten Dateinamenerweiterungen durch die von Ihnen bevorzugten ersetzen. Wie für jedes andere Dienstprogramm, das von der Befehlszeile aus gestartet wird, müssen Sie auch vor der Ausführung des IDL-Compilers die PATH-Variable in Ihrer Systemumgebung so ändern, daß das Verzeichnis, in dem sich der IDL-Compiler befindet, darin enthalten ist. Die Dokumentation zu Ihrem CORBA-Produkt enthält normalerweise Informationen darüber, wie die gewünschten Verzeichnisse in die PATHVariable eingetragen werden.
Client-Stubs und Server-Skeletons Beim Aufrufen des IDL-Compilers wird entsprechend der Sprachabbildung, die vom betreffenden Produkt unterstützt wird, Quelltext generiert. Der IDL-Compiler generiert eine Anzahl von Dateien, bei denen es sich um drei verschiedene Typen handelt: Hilfsklassen, Client-Stub-Klassen und ServerSkeleton-Klassen.
Am 2. Tag haben Sie gelernt, daß die Client-Stubs für eine Schnittstelle kleine Quelltextabschnitte sind, die mit den Client-Anwendungen kompiliert werden, welche diese Schnittstelle verwenden. Diese Stubs dienen nur dazu, den ORB des Clients anzuweisen, die Formatübertragung für abgehende und ankommende Parameter durchzuführen. Analog dazu handelt es sich bei den Server-Skeletons um Quelltextsegmente, die das Gerüst für den Server bilden. Diese Skeletons übergeben ankommende Parameter an den von Ihnen, dem Entwickler, geschriebenen Implementierungsquelltext und leiten die abgehenden Parameter wieder an den Client zurück.
Die Namen der vom IDL-Compiler generierten Dateien hängen von der verwendeten Sprachabbildung und manchmal auch von den an den IDL-Compiler übergebenen Befehlszeilenparametern ab. (Beispielsweise akzeptieren einige IDL-Compiler Schalter, mit denen zum Klassennamen hinzuzufügende Präfixe und Suffixe festgelegt werden.) Der Inhalt dieser Dateien ist im wesentlichen vom verwendeten IDL-Compiler unabhängig (vorausgesetzt, die betreffenden Produkte verwenden die Standard-Sprachabbildungen). Beispiel: Die Ausgabe des in Orbix/C++ von IONA enthaltenen IDL-Compilers unterscheidet sich nicht wesentlich von der Ausgabe des in VisiBroker/C++ von Visigenic enthaltenen IDL-Compilers. Auch die entsprechenden Java-Produkte liefern annähernd den gleichen Quelltext. Genaugenommen ist die Bezeichnung »IDL-Compiler« nicht ganz korrekt, denn während ein Compiler normalerweise Quelltext in Objektcode umwandelt, hat der IDL-Compiler mehr die Funktion eines Übersetzers. Er übersetzt IDL-Quelltext in C++-Quelltext oder in JavaQuelltext usw. Der generierte Quelltext wird dann zusammen mit den vom Entwickler zur Verfügung gestellten Implementierungen vom C++Compiler oder vom Java-Compiler usw. tatsächlich kompiliert.
Implementierung der Server-Schnittstellen Nachdem Sie mit dem IDL-Compiler erfolgreich Server-Skeletons und Client-Stubs für Ihre Anwendung generiert haben, ist nun alles für die Implementierung der Server-Schnittstellen bereit. Der IDL-Compiler generiert zu jeder IDL-Schnittstelle eine Quelltextdatei und eine Header-Datei für den Client-Stub sowie eine Quelltextdatei und eine Header-Datei für das Server-Skeleton, so daß pro Schnittstelle vier Dateien erzeugt werden. (Dies gilt für einen IDL-Compiler, der den IDL-Quelltext in C++-Quelltext übersetzt; bei einer Übersetzung in Java-Quelltext werden natürlich keine HeaderDateien generiert.) Ferner kann der IDL-Compiler gesonderte Verzeichnisse für die IDL-Module und zusätzliche Dateien für Hilfsklassen erstellen. Auch bieten die meisten IDL-Compiler die Möglichkeit, die Datennamenerweiterung für Client-Stubs und Server-Skeletons anzugeben. Beispielsweise könnten die Client-Stub-Dateien BoersenMarkt_c.h und BoersenMarkt_c.cpp oder
BoersenMarkt_st.h und BoersenMarkt_st.cpp genannt werden. In der Dokumentation zu Ihrem IDLCompiler können Sie nachlesen, welche Typen von Dateien erzeugt werden, welche Dateinamen verwendet werden und wie Sie die Standard-Dateinamenerweiterungen für die Dateinamen ändern können. Damit unser Beispiel möglichst einfach bleibt, werden wir Java als Implementierungssprache verwenden. Java ist für dieses Beispiel besonders gut geeignet, weil sich diese Sprache dadurch auszeichnet, daß sie vor allem bei der Entwicklung von CORBA-Anwendungen relativ einfach handzuhaben ist. Von allen häufig zur Entwicklung von CORBA-Anwendungen verwendeten Sprachen stellt Java dem Entwickler wohl die wenigsten Hindernisse in den Weg und ist daher die Sprache der Wahl für ein einführendes Beispiel. Im weiteren Verlauf dieses Buches wird vor allem mit C++-Beispielquelltext gearbeitet werden, mit Ausnahme der Java-spezifischen Kapitel 13 und 14. Verwendung von Server-Skeletons Das Server-Skeleton stellt, wie Sie bereits gelernt haben, ein Gerüst für die Server-Implementierung dar. Bei C++ besteht ein Server-Skeleton aus einer Gruppe von Klassen, die rein virtuelle Methoden (Methoden ohne Implementierung) oder Methoden für die Delegation an Methoden in anderen Klassen (die zuvor behandelten bindenden Klassen) enthalten. Als Entwickler liefern Sie dazu die Implementierung für diese Methoden. Bei Java vereint ein Server-Skeleton eine Gruppe von Hilfsklassen mit einer Schnittstelle, für die wiederum Sie als Entwickler die Implementierung liefern. Wenn Sie die Server-Skeletons für Ihre Anwendung mit Hilfe des Java-IDL-Compilers von Sun erzeugen, werden Sie feststellen, daß der Compiler entsprechend dem Namen des in BoersenMarkt.idl definierten IDL-Moduls ein Verzeichnis namens BoersenMarkt anlegt. In diesem Verzeichnis befinden sich eine Reihe von Dateien, die die Definitionen für die Client-Stubs und die ServerSkeletons enthalten: AktienSymbolHelper.java AktienSymbolListeHolder.java AktienSymbolListeHelper.java BoersenServer.java BoersenServerHolder.java BoersenServerHelper.java _BoersenServerStub.java _BoersenServerImplBase.java Jetzt werden Sie sich lediglich mit dem Server-Skeleton-Teil dieser Dateien beschäftigen. Beachten Sie, daß die Java-Schnittstelle, die die BoersenServer-Dienste beschreibt, in der Datei BoersenServer.java enthalten ist. Ihr Inhalt ist in Listing 4.2 dargestellt. Die Datei BoersenServerImplBase.java enthält eine Hilfsklasse, aus der Sie Ihre Server-Implementierungsklasse ableiten werden. Sie brauchen sich mit dem Inhalt der Datei nicht zu befassen, da die darin definierten Funktionen für Sie im Verborgenen arbeiten. Listing 4.2: BoersenServer.java.
1: /* 2: * File: ./BoersenMarkt/BoersenServer.java 3: * From: BoersenMarkt.idl 4: * Date: Mon Jul 21 16:12:26 1997 5: * By: D:\BIN\DEVEL\JAVA\JAVAIDL\BIN\IDLTOJ~1.EXE JavaIDL 6: * Thu Feb 27 11:22:49 1997 7: */ 8: 9: package BoersenMarkt; 10: public interface BoersenServer 11: extends org.omg.CORBA.Object { 12: float getAktienWert(String symbol) 13: ; 14: String[] getAktienSymbole() 15: ; 16: } Bei näherer Betrachtung von Listing 4.2 sehen Sie, daß sich die BoersenServer-Schnittstelle im Package BoersenMarkt befindet. Ferner ist zu erkennen, daß diese Schnittstelle eine Erweiterung der Schnittstelle org.omg.CORBA.Object darstellt. Alle Schnittstellen für CORBA-Objekte sind Erweiterungen dieser Schnittstelle, aber Sie brauchen sich auch mit dem Inhalt dieser Schnittstelle jetzt nicht zu befassen. Schließlich werden Sie noch bemerken, daß die BoersenServer-Schnittstelle zwei Methoden enthält, die den IDL-Methoden in BoersenServer.idl entsprechen. Die IDL-Typen wurden mit ihren Java-Gegenstücken abgebildet: Der Typ AktienSymbol, der mit typedef als IDLZeichenkette (String) definiert worden war, wird auf eine Java-Zeichenkette abgebildet, der Typ AktienSymbolListe, der als Sequenz (sequence) des Typs AktienSymbol definiert worden war, wird als ein Array von Java-Zeichenketten abgebildet. Der IDL-Typ float, wird, was keine Überraschung ist, als Java-Typ float abgebildet. Schreiben der Implementierung Die Implementierung der BoersenServer-Schnittstelle ist einfach. In diesem Abschnitt werden Sie die Implementierung Zeile für Zeile durcharbeiten (im Beispiel lautet der Klassenname BoersenServerImpl, Sie können die Klasse jedoch beliebig nennen), wobei für jeden Schritt erläutert wird, was dabei vor sich geht. Listing 4.3: BoersenServerImpl.java. 1: 2: 3: 4: 5: 6: 7:
// BoersenServerImpl.java package BoersenMarkt; import java.util.Vector; import org.omg.CORBA.ORB;
8: import org.omg.CosNaming.NameComponent; 9: import org.omg.CosNaming.NamingContext; 10: import org.omg.CosNaming.NamingContextHelper; 11: 12: // BoersenServerImpl implementiert die BoersenServer-IDLSchnittstelle. 13: public class BoersenServerImpl extends _BoersenServerImplBase implements 14: BoersenServer { 15: 16: // Aktiensymbole und deren entsprechende Werte. 17: private Vector meineAktienSymbole; 18: private Vector meineAktienWerte; 19: 20: // Die Zeichen, aus denen die Bezeichnungen der AktienSymbole generiert werden. 21: private static char unsereZeichen[] = { 'A', `B', `C', `D', `E', `F', 22: `G', `H', `I', `J', `K', `L', `M', `N', `O', `P', `Q', `R', 23: `S', `T', `U', `V', `W', `X', `Y', `Z' }; 24: 25: // Pfad für BoersenServer-Objekte. 26: private static String unserPfad = "BoersenServer"; 27: 28: // Erzeuge neue BoersenServerImpl. 29: public BoersenServerImpl() { 30: 31: meineAktienSymbole = new Vector(); 32: meineAktienWerte = new Vector(); 33: 34: // Die Symbole und die Werte mit Zufallswerten initialisieren. 35: for (int i = 0; i < 10; i++) { 36: 37: // Eine Zeichenkette aus vier zufälligen Zeichen erzeugen. 38: StringBuffer AktienSymbol = new StringBuffer(" "); 39: for (int j = 0; j < 4; j++) { 40: 41: AktienSymbol.setCharAt(j, unsereZeichen[(int)(Math.random() 42: * 26f)]); 43: } 44: 45: meineAktienSymbole.addElement(AktienSymbol.toString()); 46: 47: // Den Aktienwert als Zahl zwischen 0 und 100 angeben. In diesem 48: // Beispiel behält die Aktie diesen Wert für die Dauer der
49: // Anwendungsausführung bei. 50: meineAktienWerte.addElement(new Float(Math.random() * 100f)); 51: } 52: 53: // Die oben generierten Aktiensymbole ausgeben. 54: System.out.println("Generierte Aktiensymbole:"); 55: for (int i = 0; i < 10; i++) { 56: System.out.println(" " + meineAktienSymbole.elementAt(i) + " " + 57: meineAktienWerte.elementAt(i)); 58: } 59: System.out.println(); 60: } 61: 62: // Den aktuellen Wert für das jeweilige AktienSymbol zurückgeben. 63: public float getAktienWert(String symbol) { 64: 65: // Versuch, das angegebene Symbol zu finden. 66: int AktienIndex = meineAktienSymbole.indexOf(symbol); 67: if (AktienIndex != -1) { 68: 69: // Symbol gefunden; Wert wird zurückgegeben. 70: return ((Float)meineAktienWerte.elementAt(AktienIndex)). 71: floatValue(); 72: } else { 73: 74: // Symbol wurde nicht gefunden. 75: return 0f; 76: } 77: } 78: 79: // Eine Sequenz aller diesem BoersenServer bekannten Aktiensymbole zurückzugeben. 80: public String[] getAktienSymbole() { 81: 82: String[] symbols = new String[meineAktienSymbole.size()]; 83: meineAktienSymbole.copyInto(symbols); 84: 85: return symbols; 86: } 87: 88: // Ein BoersenServer-Objekt erzeugen und initialisieren. 89: public static void main(String args[]) { 90: 91: try {
92: 93: // Den ORB initialisieren. 94: ORB orb = ORB.init(args, null); 95: 96: // Ein BoersenServerImpl-Objekt erzeugen und 97: // beim ORB registrieren. 98: BoersenServerImpl BoersenServer = new BoersenServerImpl(); 99: orb.connect(BoersenServer); 100: 101: // Root-Bezeichnungskontext abrufen. 102: org.omg.CORBA.Object obj = orb. 103: resolve_initial_references("NameService"); 104: NamingContext namingContext = NamingContextHelper.narrow(obj); 105: 106: // Die BoersenServer-Objektreferenz im 107: // Bezeichnungskontext binden. 108: NameComponent nameComponent = new NameComponent(unserPfad, 109: ""); 110: NameComponent path[] = { nameComponent }; 111: namingContext.rebind(path, BoersenServer); 112: 113: // Auf Aufrufe von Clients warten. 114: java.lang.Object warteAufMich = new java.lang.Object(); 115: synchronized (warteAufMich) { 116: warteAufMich.wait(); 117: } 118: } catch (Exception ex) { 119: System.err.println("BoersenServer konnte nicht gebunden werden: " + ex. 120: getMessage()); 121: } 122: } 123: } Der gesamte Quelltext für BoersenServerImpl.java ist in Listing 4.3 dargestellt. Im weiteren Verlauf dieses Abschnitts werden Sie die Datei Schritt für Schritt durcharbeiten, so daß Sie die Vorgänge in allen Einzelheiten mitverfolgen können. package BoersenMarkt; Da die BoersenServer-Schnittstelle Teil des Moduls BoersenMarkt ist, werden die Java-Klassen- und Schnittstellendefinitionen in das package (Paket) BoersenMarkt aufgenommen. Aus praktischen Gründen wird auch BoersenServerImpl in dieses Package aufgenommen. (Wenn Sie sich mit Java oder mit dieser Art von Packages nicht auskennen, können Sie diesen Quelltextabschnitt für den Augenblick auch ignorieren, ohne daß Ihnen etwas entgeht.)
import java.util.Vector; BoersenServerImpl verwendet die Klasse Vector. Die Anweisung import müßte einem JavaEntwickler bereits bekannt vorkommen. Wenn Sie mit Java nicht vertraut sind, sei gesagt, daß sich die Anweisung import weitgehend wie die Präprozessor-Direktive #include in C++ verhält; die Klasse java.util.Vector ist eine Container-Klasse, die als erweiterbares Array von Elementen definiert ist. import import import import
org.omg.CORBA.ORB; org.omg.CosNaming.NameComponent; org.omg.CosNaming.NamingContext; org.omg.CosNaming.NamingContextHelper;
Die hier importierten Klassen werden in CORBA-Anwendungen häufig verwendet. Die erste stellt erkennbar die ORB-Funktionalität zur Verfügung, die anderen Klassen gehören zum CORBABezeichnungsdienst, den Sie am 12. Tag näher kennenlernen werden. // BoersenServerImpl implementiert die IDL-BoersenServerSchnittstelle. public class BoersenServerImpl extends _BoersenServerImplBase implements BoersenServer { Im Falle der IDL-Schnittstelle für BoersenServer generiert der IDL-Compiler eine Klasse mit dem Namen BoersenServerImplBase und eine Schnittstelle mit dem Namen BoersenServer. Zur Implementierung der IDL-Schnittstelle für BoersenServer muß die Klasse BoersenServerImpl die Klasse _BoersenServerImplBase erweitern und BoersenServer implementieren. Genau dies wird durch die folgenden Zeilen deklariert: // Aktiensymbole und die zugehörigen Werte. private Vector meineAktienSymbole; private Vector meineAktienWerte; BoersenServerImpl speichert die Aktiensymbole und ihre Werte in der Klasse Vector. // Die Zeichen, aus generiert werden. private static char `F', `G', `H', `I', `J', `S', `T', `U', `V',
denen die Bezeichnungen der AktienSymbole unsereZeichen[] = { `A', `B', `C', `D', `E', `K', `L', `M', `N', `O', `P', `Q', `R', `W', `X', `Y', `Z' };
Das Array unsereZeichen enthält die Zeichen, aus denen Aktiensymbole zusammengesetzt sind. // Pfad für BoersenServer-Objekte.
private static String unserPfad = "BoersenServer"; Die Variable unserPfad dient zum Speichern des Pfads, über den dieses Objekt der Klasse BoersenServer im Bezeichnungsdienst gesucht werden kann. Hier könnten Sie auch einen beliebigen Namen angeben, aber für dieses Beispiel wird BoersenServer verwendet. // Eine neue BoersenServerImpl-Klasse erzeugen. public BoersenServerImpl() { meineAktienSymbole = new Vector(); meineAktienWerte = new Vector(); Obwohl Konstruktoren kein Bestandteil einer IDL-Schnittstelle sind, verfügt die Implementierungsklasse für diese Schnittstelle dennoch über Konstruktoren, so daß der Server die Implementierungsobjekte erzeugen kann. BoersenServerImpl verfügt nur über einen Standardkonstruktor, aber eine Klasse, die eine IDL-Schnittstelle implementiert, kann - wie jede beliebige andere Klasse - eine beliebige Anzahl von Konstruktoren aufweisen. Der folgende Teil des Konstruktors erzeugt Vektoren, in denen dann die Aktiensymbole und die dazugehörigen Werte gespeichert werden. // Die Symbole und die Werte mit Zufallswerten initialisieren. for (int i = 0; i < 10; i++) { Die Klasse BoersenServerImpl erzeugt zehn zufällige Aktiensymbole. // Eine Zeichenkette aus vier zufälligen Zeichen erzeugen. StringBuffer AktienSymbol = new StringBuffer(" "); for (int j = 0; j < 4; j++) { AktienSymbol.setCharAt(j, unsereZeichen[(int)(Math.random() * 26f)]); } meineAktienSymbole.addElement(AktienSymbol.toString()); Für jedes Aktiensymbol erzeugt die Klasse BoersenServerImpl eine Zeichenkette aus vier zufälligen Zeichen (die aus dem vorangehenden Array unsereZeichen ausgewählt werden). Die Länge vier Zeichen wurde wie die Anzahl der Symbole willkürlich ausgewählt. Der Einfachheit halber wird keine Prüfung auf doppelt vorhandene Zeichenketten durchgeführt. // Den Aktienwert als Zahl zwischen 0 und 100 angeben. In diesem // Beispiel behält die Aktie diesen Wert für die Dauer der // Anwendungsausführung bei. meineAktienWerte.addElement(new Float(Math.random() * 100f)); } Hiermit wird jedem Aktiensymbol ein Zufallswert zwischen 0 und 100 zugewiesen. In diesem
Beispiel wird der zugewiesene Wert für die gesamte Ausführungsdauer von BoersenServerImpl beibehalten. // Die oben generierten Aktiensymbole ausgeben. System.out.println("Generierte Aktiensymbole:"); for (int i = 0; i < 10; i++) { System.out.println(" " + meineAktienSymbole.elementAt(i) + " " + meineAktienWerte.elementAt(i)); } System.out.println(); } Abschließend gibt der Konstruktor die Aktiensymbole und zugehörigen Werte aus. // Den aktuellen Wert für das jeweilige Aktiensymbol zurückgeben. public float getAktienWert(String symbol) { // Versuch, das angegebene Symbol zu finden. int AktienIndex = meineAktienSymbole.indexOf(symbol); if (AktienIndex != -1) { // Symbol gefunden; Wert wird zurückgegeben. return ((Float)meineAktienWerte.elementAt(AktienIndex)). floatValue(); } else { // Symbol wurde nicht gefunden. return 0f; } } Die Methode getAktienWert() übernimmt eine Zeichenkette (String), versucht, im Datenelement meineAktienSymbole eine Entsprechung zu finden, und gibt den Wert für das Aktiensymbol (sofern es gefunden wurde) zurück. Wenn das Aktiensymbol nicht gefunden wird, wird der Wert Null zurückgegeben. Natürlich ist die Methode getAktienWert() hervorragend dazu geeignet, eine Exception auszulösen. Wenn ein ungültiges Aktiensymbol an die Methode zurückgegeben wird, könnte diese z.B. die Exception UngueltigesAktienSymbolException auslösen, anstatt den hier gelieferten Wert Null zurückzugeben. Dieser Fall ist Gegenstand einer Übung am Ende des Kapitels.
// Eine Sequenz aller diesem BoersenServer bekannten
// Aktiensymbole zurückgeben. public String[] getAktienSymbole() { String[] symbols = new String[meineAktienSymbole.size()]; meineAktienSymbole.copyInto(symbols); return symbols; } Die Methode getAktienSymbole() erzeugt einfach ein Array von Zeichenketten, kopiert die (in meineAktienSymbole enthaltenen) Aktiensymbole in das Array und gibt das Array zurück. // Ein BoersenServer-Objekt erzeugen und initialisieren. public static void main(String args[]) { Die Methode main() in der Klasse BoersenServerImpl erzeugt ein BoersenServerImpl-Objekt, bindet dieses Objekt an einen Bezeichnungskontext und wartet dann darauf, daß Clients die Methoden dieses Objekts aufrufen. try { Da die Methoden, die von main() später aufgerufen werden können, Exceptions auslösen könnten, werden die betreffenden Aufrufe in einen Block vom Typ try ... catch eingebunden. // Den ORB initialisieren. ORB orb = ORB.init(args, null); Bevor der ORB für irgendeine Aktivität eingesetzt werden kann, muß er zunächst einmal initialisiert werden. // Ein BoersenServerImpl-Objekt erzeugen und beim ORB // registrieren. BoersenServerImpl BoersenServer = new BoersenServerImpl(); orb.connect(BoersenServer); Hiermit wird ein neues BoersenServerImpl-Objekt erzeugt und beim ORB registriert. // Den Root-Bezeichnungskontext abrufen. org.omg.CORBA.Object obj = orb. resolve_initial_references("NameService"); NamingContext namingContext = NamingContextHelper.narrow(obj); Nun folgt ein wenig »schwarze Magie«. Bei dem CORBA-Bezeichnungsdienst handelt es sich um einen Dienst, der es CORBA-Objekten ermöglicht, mit einem Namen registriert und anschließend über diesen Namen von anderen CORBA-Objekten gefunden zu werden. Wie weiter oben bereits erwähnt, wird die Verwendung des Bezeichnungsdienstes erst am 12. Tag ausführlich beschrieben,
aber die Client-Beispielanwendung in diesem Kapitel muß trotzdem in der Lage sein, einen Server zu finden. Aus diesem Grund wird der Bezeichnungsdienst hier bereits eingeführt, was im Grunde sicherlich etwas verfrüht ist. Folglich sollten Sie sich keine Sorgen machen, wenn Sie im weiteren Verlauf dieses Kapitels nicht alle Details zum Bezeichnungsdienst oder zu seiner Verwendung verstehen. Damit Clients mit der Klasse BoersenServerImpl verbunden werden können, müssen sie auf irgendeine Weise diesen Dienst im Netzwerk ausfindig machen können. Eine Möglichkeit hierfür stellt der CORBA-Bezeichnungsdienst dar. Hierbei wird ein Objekt vom Typ NamingContext durch Auflösung einer Referenz zu einem Objekt namens NameService gefunden. // Die BoersenServer-Objektreferenz im Bezeichnungskontext // binden. NameComponent nameComponent = new NameComponent(unserPfad, ""); NameComponent path[] = { nameComponent }; namingContext.rebind(path, BoersenServer); Hier wird das Objekt NamingContext aufgefordert, das Objekt BoersenServerImpl an den zuvor definierten Pfad (BoersenServer) zu binden. Die Clients können nun den Bezeichnungsdienst nach einem Objekt dieses Namens abfragen, und der Bezeichnungsdienst gibt eine Referenz an dieses BoersenServerImpl-Objekt zurück. // Auf Aufrufe von Clients warten. java.lang.Object warteAufMich = new java.lang.Object(); synchronized (warteAufMich) { warteAufMich.wait(); } Da das BoersenServerImpl-Objekt nun beim Bezeichnungsdienst registriert ist, braucht es jetzt nur noch auf Clients zu warten, die Methoden dieses Objekts aufrufen. Da die tatsächliche Behandlung dieser Methodenaufrufe in einem gesonderten Thread erfolgt, braucht der Thread main() einfach nur endlos zu warten. } catch (Exception ex) { System.err.println("BoersenServer konnte nicht gebunden werden: " + ex. getMessage()); } } } Wenn von einer der aufgerufenen Methoden eine Exception ausgelöst wird, wird diese hier abgefangen und behandelt.
Kompilierung und Ausführung des Servers Jetzt ist es so weit, daß der Server kompiliert und ausgeführt werden kann. Wenn Sie mit einer integrierten Entwicklungsumgebung arbeiten, können Sie den »Build«-Befehl dieses Werkzeugs (oder einen entsprechenden Befehl) zur Erzeugung der Anwendung verwenden. Bei Verwendung des JDK von der Befehlszeile aus müssen Sie zuvor in das Verzeichnis wechseln, in dem sich BoersenMarkt.idl befindet (in diesem Verzeichnis müßte auch ein Verzeichnis namens BoersenMarkt enthalten sein). Führen Sie dann den folgenden Befehl aus: javac BoersenMarkt\BoersenServerImpl.java (Möglicherweise müssen Sie im obigen Befehl den umgekehrten Schrägstrich durch das für Ihre Plattform gültige Verzeichnistrennzeichen ersetzen.) Mit diesem Befehl werden die ServerImplementierung und alle Dateien, von denen diese abhängt, kompiliert. Überprüfen Sie vor der Kompilierung des Servers, ob in der Anweisung CLASSPATH das korrekte Verzeichnis bzw. die korrekte Datei für die CORBA-Klassen eingetragen ist. Beim Paket JavaIDL von Sun ist die Datei /lib/classes.zip (Verzeichnis, in dem JavaIDL installiert ist) in der Anweisung CLASSPATH eingetragen. Die korrekte Einstellung für CLASSPATH finden Sie in der Dokumentation zu Ihrem CORBA-Produkt.
Unter der Voraussetzung, daß die Server-Anwendung korrekt kompiliert wurde, können Sie den Server nun ausführen. Zuvor muß jedoch noch der Bezeichnungs-Server ausgeführt werden. (Wie bereits erwähnt, macht die Client-Anwendung die Server-Anwendung mit Hilfe des CORBABezeichnungsdienstes ausfindig; der Bezeichnungs-Server stellt den Mechanismus zur Verfügung, der diesen Vorgang ermöglicht.) Die genaue Methode für die Ausführung des Bezeichnungs-Servers unterscheidet sich je nach Produkt, doch das Endergebnis ist jeweils das gleiche. Bei JavaIDL von Sun wird der BezeichnungsServer einfach durch Eingabe von nameserv gestartet. Sobald der Bezeichnungs-Server ausgeführt wird, ist alles für die Ausführung Ihres Servers vorbereitet. Sie können den Server mit dem folgenden Befehl aufrufen: java BoersenMarkt.BoersenServerImpl Der Einfachheit halber sollten der Bezeichnungs-Server und der von Ihnen entwickelte Server erst einmal auf dem gleichen Computer laufen. Wenn alles korrekt funktioniert, erfolgt eine Ausgabe wie in Listing 4.4 dargestellt. Natürlich wird die Ausgabe andere Aktiensymbole und zugehörige Werte enthalten, aber sie wird dem Listing entsprechen, und am Ende der Ausgabe werden keine ExceptionMeldungen stehen.
Listing 4.4: Beispielausgabe für BoersenServer 1: Generierte Aktiensymbole: 2: PTLF 72.00064 3: SWPK 37.671585 4: CHHL 78.37782 5: JTUX 75.715645 6: HUPB 41.85024 7: OHQR 14.932466 8: YOEX 64.3376 9: UIBP 75.80115 10: SIPR 91.13683 11: XSTD 16.010124 Wenn Sie so weit gekommen sind, herzlichen Glückwunsch! Sie haben eine CORBA-ServerAnwendung erfolgreich entworfen, implementiert und zum Einsatz gebracht. Wenn Sie Ihren Erfolg ausreichend genossen haben, können Sie die Anwendung beenden, da erst am Ende dieses Kapitels ein Client zur Verfügung stehen wird, der mit der Server-Anwendung verbunden werden könnte. Sie können den Server aber auch weiterlaufen lassen, um sich später die Mühe zu ersparen, ihn neu zu starten (oder um Ihre Freunde zu beeindrucken und in Erstaunen zu versetzen).
Entwicklung eines CORBA-Clients Am Ende des ersten Teils dieses Kapitels stehen Sie mit einem Server da, mit dem Sie kaum etwas anfangen können, weil es keine Clients gibt, die mit ihm verbunden werden könnten. Im weiteren Verlauf dieses Kapitels werden Sie diese unbefriedigende Situation bereinigen, indem Sie einen Client implementieren, der die Dienste des von Ihnen entwickelten Servers nutzen kann. Da Sie die IDL-Schnittstellen bereits programmiert und kompiliert (und eigentlich auch schon implementiert) haben, wird die Implementierung des Clients sehr viel einfacher vor sich gehen. Hinzu kommt, daß Clients oft (wenn auch nicht immer) von Natur aus einfacher aufgebaut sind als Server. Daher ist ihre Implementierung in dieser Hinsicht auch leichter. Implementierung des Clients Wie bereits erwähnt, ist die Implementierung des Clients ein einfacher Vorgang. Hierbei müssen nur einige wenige Konzepte berücksichtigt werden: Wie werden Client-Stubs in der ClientImplementierung verwendet, wie wird das Auffinden eines Server-Objekts realisiert, und wie werden die Schnittstellen eines Server-Objekts nach dessen Auffinden verwendet? Verwendung von Client-Stubs Bei der Kompilierung von BoersenServer.idl hat der IDL-Compiler sowohl Client-Stubs als auch Server-Skeletons generiert. Da die Client-Stubs bei der Server-Implementierung nicht gebraucht wurden, haben Sie die Stubs zu diesem Zeitpunkt beiseite gelassen. Jetzt ist die Zeit für ihren Einsatz
gekommen. Wenn Sie neugierig sind, können Sie die Datei _BoersenServerStub.java öffnen und sich den Inhalt ansehen. Sie werden eine große Menge unverständlichen Quelltext zusammen mit zwei wohlvertrauten Methoden entdecken: public float getAktienWert(String symbol) { ... } public String[] getAktienSymbole() { ... } Die Implementierungen für diese Methoden dienen, wie weiter oben erwähnt, zur Formatübertragung für die Parameter durch den ORB zum Remote-Objekt und anschließend zur Formatübertragung für die Rückgabewerte an den Client. (Genau diese Aufgabe erfüllt der so unverständlich aussehende Quelltext.) Sie brauchen sich mit dem Inhalt der Datei _BoersenServerStub.java nicht näher zu befassen. Jetzt brauchen Sie nur zu wissen, daß diese Datei den Client-Stub für die BoersenServer-Schnittstelle enthält. Der Java-Compiler ist so »intelligent«, daß er diese Datei automatisch kompiliert, aber wenn Sie einen Client in C++ implementieren würden, müßten Sie das Client-Stub-Objekt mit dem Rest der Client-Anwendung verbinden. Ferner sollten Sie über den Client-Stub noch wissen, daß er die tatsächlichen Schnittstellen für das Server-Objekt angibt. Anders gesagt, Sie können aus dem vorhergehenden Beispiel erkennen, daß die Methode getAktienWert() den Java-Datentyp string als Parameter verwendet und den Java-Datentyp float zurückgibt. Analog dazu verwendet getAktienSymbole() keine Parameter und gibt ein JavaArray vom Typ string zurück. Auffinden eines Server-Objekts Genauso, wie eine Server-Anwendung praktisch nutzlos ist, wenn sie ihren Standort nicht bekanntgeben kann, kann eine Client-Anwendung keine nützliche Arbeit verrichten, wenn sie die zu verwendenden Dienste nicht findet. An dieser Stelle kommt wieder der CORBA-Bezeichnungsdienst ins Spiel. Nachdem sich ein Server beim Bezeichnungs-Server hat registrieren lassen, können Clients dieses Server-Objekt über den Bezeichnungs-Server finden, einen Bindevorgang zu diesem ServerObjekt durchführen und anschließend Methoden des Server-Objekts aufrufen. Auch hier brauchen Sie sich keine Sorgen machen, wenn Sie noch nicht alle Details im Zusammenhang mit dem Bezeichnungsdienst versehen, denn dieser wird am 12. Tag noch ausführlich behandelt. Für den Client BoersenMarktClient erfolgt das Binden an das Server-Objekt über die Methode connect(), wie in Listing 4.5 gezeigt. Diese Methode stellt zunächst eine Bindung zum BezeichnungsServer her, indem es nach einem Objekt mit dem Namen NameService sucht. Nach erfolgreichem Auffinden eines Bezeichnungs-Servers wird der Client an ein Objekt mit dem Namen BoersenServer gebunden, wobei es sich zufällig um denselben Namen handelt, unter dem Sie das Objekt BoersenServerImpl registriert haben (tatsächlich müssen die Namen gleich sein, wenn das Beispiel
funktionieren soll). Nach dem Binden dieses Objekts kann der Client die gewünschten Aufgaben erfüllen. Listing 4.5: Binden an den Server BoersenServer . 1: // Verbindung mit dem BoersenServer herstellen. 2: protected void connect() { 3: 4: try { 5: 6: // Root-Bezeichnungskontext abrufen. 7: org.omg.CORBA.Object obj = unserORB. 8: resolve_initial_references("NameService"); 9: NamingContext namingContext = NamingContextHelper.narrow(obj); 10: 11: // BoersenServer-Objekt im Bezeichnungskontext suchen. 12: NameComponent nameComponent = new NameComponent("BoersenServer", 13: ""); 14: NameComponent path[] = { nameComponent }; 15: meinBoersenServer = BoersenServerHelper.narrow(namingContext. 16: resolve(path)); 17: } catch (Exception ex) { 18: System.err.println("Konnte BoersenServer nicht auflösen: " + ex); 19: meinBoersenServer = null; 20: return; 21: } 22: 23: System.out.println("Bindung mit einem BoersenServer erfolgreich."); 24: } Verwendung von Server-Objektschnittstellen Listing 4.6 zeigt ein Beispiel für die Verwendung der Server-Objektschnittstellen, nachdem der Client die Bindung mit dem Server-Objekt vorgenommen hat. Wie Sie vermutlich schon erwarten, ruft der Client die Server-Methoden einfach nach Bedarf auf. Erneut können Sie in der Datei für den ClientStub (_BoersenServerStub.java) oder noch besser in der Datei für die BoersenServer-Schnittstelle (BoersenServer.idl) nachsehen, welche Methodensignaturen für BoersenServer vorhanden sind. Die Verwendung dieser Methoden ist eindeutig, wie in Listing 4.6 dargestellt. Listing 4.6: Verwendung der BoersenServer-Dienste. 1: // Einige interessante Dinge mit dem BoersenServer ausführen. 2: protected void fuehreEtwasAus() {
3: 4: try { 5: 6: // Die gültigen Aktiensymbole vom BoersenServer abrufen. 7: String[] AktienSymbole = meinBoersenServer.getAktienSymbole(); 8: 9: // Aktiensymbole und deren Werte anzeigen. 10: for (int i = 0; i < AktienSymbole.length; i++) { 11: System.out.println(AktienSymbole[i] + " " + 12: meinBoersenServer.getAktienWert(AktienSymbole[i])); 13: } 14: } catch (org.omg.CORBA.SystemException ex) { 15: System.err.println("Schwerwiegender Fehler: " + ex); 16: } 17: } In Listing 4.6 wird zunächst BoersenServer über einen getAktienSymbole()-Aufruf aufgefordert, eine Liste aller vom Server erkannten Aktiensymbole zu liefern. Der Client durchläuft dann schrittweise die Liste der Aktiensymbole und fragt den Server mit Hilfe von getAktienWert() nach dem Wert für die einzelnen Aktien ab. Jedes Aktiensymbol und der dazugehörige Wert werden an die Standardausgabe übergeben. Kompilierung und Ausführung des Clients Der gesamte Quelltext für BoersenMarktClient.java ist in Listing 4.7 enthalten. Beachten Sie, daß der Großteil der Arbeit in den Methoden connect() und fuehreEtwasAus() erfolgt, die Sie sich zuvor bereits angesehen haben. Listing 4.7: BoersenMarktClient.java. 1: // BoersenMarktClient.java 2: 3: package BoersenMarkt; 4: 5: import org.omg.CORBA.ORB; 6: import org.omg.CosNaming.NameComponent; 7: import org.omg.CosNaming.NamingContext; 8: import org.omg.CosNaming.NamingContextHelper; 9: 10: // BoersenMarktClient ist ein einfacher BoersenServer-Client. 11: public class BoersenMarktClient { 12: 13: // Einen neuen BoersenMarktClient erzeugen. 14: BoersenMarktClient() { 15:
16: } 17: 18: // BoersenMarktClient ausführen. 19: public void run() { 20: 21: connect(); 22: 23: if (meinBoersenServer != null) { 24: fuehreEtwasAus(); 25: } 26: } 27: 28: // Verbindung zu BoersenServer herstellen. 29: protected void connect() { 30: 31: try { 32: 33: // Root-Bezeichnungskontext abrufen. 34: org.omg.CORBA.Object obj = unserORB. 35: resolve_initial_references("NameService"); 36: NamingContext namingContext = NamingContextHelper.narrow(obj); 37: 38: // Ein BoersenServer-Objekt im Bezeichnungskontext 39: // suchen. 40: NameComponent nameComponent = new NameComponent("BoersenServer", 41: ""); 42: NameComponent path[] = { nameComponent }; 43: meinBoersenServer = BoersenServerHelper.narrow(namingContext. 44: resolve(path)); 45: } catch (Exception ex) { 46: System.err.println("Konnte BoersenServer nicht auflösen: " + ex); 47: meinBoersenServer = null; 48: return; 49: } 50: 51: System.out.println("Bindung mit einem BoersenServer erfolgreich."); 52: } 53: 54: // Einige interessante Dinge mit dem BoersenServer ausführen. 55: protected void fuehreEtwasAus() { 56: 57: try { 58:
59: // Die gültigen Aktiensymbole vom BoersenServer abrufen. 60: String[] AktienSymbole = meinBoersenServer.getAktienSymbole(); 61: 62: // Aktiensymbole und deren Werte anzeigen. 63: for (int i = 0; i < AktienSymbole.length; i++) { 64: System.out.println(AktienSymbole[i] + " " + 65: meinBoersenServer.getAktienWert(AktienSymbole[i])); 66: } 67: } catch (org.omg.CORBA.SystemException ex) { 68: System.err.println("Schwerwiegender Fehler: " + ex); 69: } 70: } 71: 72: // Einen BoersenMarktClient starten. 73: public static void main(String args[]) { 74: 75: // ORB initialisieren. 76: unserORB = ORB.init(args, null); 77: 78: BoersenMarktClient AktienClient = new BoersenMarktClient(); 79: 80: AktienClient.run(); 81: 82: // Dieser Abschnitt wartet "ewig". Dadurch bleibt das DOS-Fenster erhalten 83: // (für Entwickler die Windows-Entwicklungsumgebungen verwenden). 84: while (true) 85: ; 86: } 87: 88: // Mein ORB. 89: public static ORB unserORB; 90: 91: // Mein BoersenServer. 92: private BoersenServer meinBoersenServer; 93: } Das Kompilieren der Client-Anwendung ist ein unkomplizierter Vorgang. Wie der Server, so kann auch der Client ganz einfach mit dem folgenden Befehl kompiliert werden: javac BoersenMarkt\BoersenMarktClient.java Auch beim Kompilieren des Clients sollten Sie sich vergewissern, daß das richtige Verzeichnis gewählt ist, nämlich dasselbe Verzeichnis, in dem auch der Server kompiliert wurde.
Unter der Voraussetzung, daß die Client-Anwendung korrekt kompiliert wurde, kann die Anwendung nun ausgeführt werden. Starten Sie zunächst den Bezeichnungsdienst und die Anwendung BoersenServer (wie zuvor), wenn Sie diese am Ende des ersten Teils beendet haben. Geben Sie dann zur Ausführung der Client-Anwendung den folgenden Befehl ein: java BoersenMarkt.BoersenMarktClient Wenn der Client erfolgreich läuft, sehen Sie eine Ausgabe wie in Listing 4.8. Listing 4.8: Ausgabe für BoersenMarktClient. 1: Bindung mit einem BoersenServer erfolgreich. 2: PTLF 72.00064 3: SWPK 37.671585 4: CHHL 78.37782 5: JTUX 75.715645 6: HUPB 41.85024 7: OHQR 14.932466 8: YOEX 64.3376 9: UIBP 75.80115 10: SIPR 91.13683 11: XSTD 16.010124 Die Aktiensymbole und ihre Werte werden genauso angezeigt wie in der Ausgabe für die ServerAnwendung.
Zusammenfassung In diesem Kapitel haben Sie anfangs mit Hilfe von IDL die Schnittstellen für einen CORBA-Server definiert. Anschließend haben Sie unter Verwendung des Vererbungsmechanismus (im Gegensatz zum Delegationsmechanismus) den Server implementiert und nebenher ein wenig über den CORBABezeichnungsdienst gelernt. Danach folgte die Erzeugung einer einfachen Client-Anwendung, welche die von der Anwendung BoersenServer zur Verfügung gestellten Dienste nutzt. Hierbei haben Sie gelernt, wie das BoersenServer-Objekt mit Hilfe des Bezeichungsdienstes aufgefunden werden kann und wie die Client-Stubs sich in die Client-Anwendung einfügen. Schließlich haben Sie CORBAServer und -Client zusammen ausgeführt und dabei etwas erzeugt, das als Ihre allererste verteilte CORBA-Anwendung bezeichnet werden kann. Herzlichen Glückwunsch! Dies war kein Kinderspiel. Ausblick Mit diesem Kapitel ist der erste Teil dieses Buchs, in der die CORBA-Architektur und -Methodik beleuchtet wurden, abgeschlossen. Im nächsten Hauptabschnitt, der die Tage 5 bis 9 umfaßt, werden Sie eine umfangreichere und komplexere CORBA-Anwendung entwickeln, wobei Sie zunächst die grundlegende Funktionalität erstellen und in den nachfolgenden Kapiteln weitere Leistungsmerkmale hinzufügen werden. Hierbei werden Sie die im ersten Teil gelernten Techniken weiter anwenden. Der
einzige Unterschied besteht darin, daß Sie es dann mit komplexerem IDL-Quelltext und daher auch mit komplexeren Servern und Clients zu tun haben werden. Sie verfügen inzwischen über die gesamten Grundlagenkenntnisse zur Entwicklung einer vollständigen CORBA-Anwendung. In den nächsten Tagen werden Sie Gelegenheit haben zu üben, wie diese Kenntnisse auf ein komplexeres System angewendet werden können.
Fragen & Antworten Frage: Ich bin C++-Programmierer und bin mir nicht ganz sicher, ob ich die ganze Java-Syntax verstehen werde. Antwort: Die Syntax von Java ähnelt der C++-Syntax in weiten Bereichen, aber Java enthält zusätzlich noch einige eigene Konstrukte. Solange Sie erfassen, was mit dem jeweiligen Quelltext bewirkt wird, brauchen Sie sich keine zu großen Sorgen machen, wenn Sie einige Merkmale dieser Sprache nicht verstehen. Frage: Wenn ich meine Server-Schnittstellen mit Hilfe des Vererbungsmechanismus implementiere und den Server dann nachträglich umschreibe, um den Delegationsmechanismus zu verwenden, muß ich dann meinen Client bzw. meine Clients ebenfalls umprogrammieren? Antwort: Unabhängig vom Mechanismus, mit dem der Server implementiert wurde, bleibt der ClientQuelltext immer gleich. Sie können daher beruhigt sein: Sie brauchen keine Änderungen an den Clients vorzunehmen, wenn Sie einmal gezwungen sein sollten, einen Server für einen anderen Implementierungsmechanismus umzuprogrammieren. Frage: Wie können die Klassen, die IDL-Schnittstellen implementieren, über Konstruktoren verfügen, wenn in der IDL keine spezifiziert sind? Antwort: Die IDL spezifiziert lediglich öffentliche (public) Schnittstellen; das heißt, daß die Methoden von anderen Objekten an einer beliebigen Stelle im Netzwerk verwendet werden können. Die Klasse, die eine IDL-Schnittstelle implementiert, kann jedoch auch eigene Methoden zur Verfügung stellen, allerdings sind diese außerhalb des Prozeßraums des betreffenden Objekts nicht sichtbar. Solche
Methoden können dennoch auch innerhalb der Server-Anwendung nützlich sein; Konstruktoren sind ein Beispiel hierfür. (Server-Objekte müssen ja auch irgendwie erzeugt werden.) Haben Sie also keine Scheu, zusätzliche Methoden (öffentliche, geschützte und private) in Ihre ServerImplementierungen einzubinden, wenn dies sinnvoll ist.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösungen für die Übungen finden Sie in Anhang A. Quiz 1. Welchen Zweck erfüllen die Server-Skeletons und die Client-Stubs? 2. Warum muß der Server das Implementierungsobjekt beim CORBA-Bezeichnungsdienst registrieren? 3. Warum müssen Client und Server Exceptions abfangen, vor allem dann, wenn die von Ihnen definierten IDL-Operationen keine auslösen? Übungen 1. Im Beispiel BoersenMarkt wurde herausgestellt, daß es sinnvoll wäre, in der Methode getAktienWert() eine Exception auszulösen, wenn ein ungültiger AktienSymbol-Wert übergeben wird. Ändern Sie die Datei BoersenMarkt.idl so ab, daß die Methode eine Exception vom Typ UngueltigesAktienSymbolException auslösen kann. (Sie müssen auch eine Definition für diese Exception hinzufügen.) 2. Im BoersenMarkt-Beispiel wurde eine Implementierung nach dem Delegationsmechanismus vorgenommen. Implementieren Sie den Server BoersenServer unter Verwendung des Vererbungsmechanismus. (Für besonders Ehrgeizige: Integrieren Sie auch den Exceptionauslösenden Mechanismus aus der ersten Übung.)
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 5 Entwerfen des Systems: ein Schnellkurs in objektorientierter Analyse und Entwurfstechnik Was versteht man unter objektorientierter Analyse und Entwurfstechnik? Einführung in die Unified Modeling Language (UML) Die Beispielbankanwendung Zusammenfassung Fragen & Antworten Workshop
Mit dem bisher erlangten Verständnis der Grundlagen von CORBA, wozu beispielsweise das ORBKonzept (Object Request Broker, Objektanfragenvermittler) und die IDL (Interface Definition Language, Schnittstellendefinitionssprache) gehören, sowie mit den Kenntnissen der IDL sind Sie nun
in der Lage, auf CORBA basierende Anwendungen zu entwickeln und einzusetzen. Sie haben bereits erste Erfahrungen beim Implementieren eines einfachen CORBA-Servers und -Clients gemacht. Nun werden Sie eine komplexere CORBA-Anwendung entwerfen, implementieren und dann noch verbessern. Was Sie in den nächsten Tagen erwartet, können Sie der folgenden Übersicht entnehmen: ■
■
■
■
■
Heute werden Sie das Beispielsystem entwerfen, eine kleine Bankanwendung. Da es sich bei CORBA um eine objektorientierte Architektur handelt, werden Sie in diesem Kapitel mit objektorientierten Analyse- und Entwurfskonzepten vertraut gemacht, während Sie den Entwurf des Systems vornehmen. Am 6. Tag werden Sie den Systementwurf in eine IDL-Spezifikation umsetzen. Dabei werden Sie sehen, wie Systemobjekte auf IDL-Klassen und Partitionen auf IDL-Module abgebildet werden, und Sie lernen weitere Aspekte beim Definieren der Schnittstelle kennen. Sie werden auch die verschiedenen CORBAservices und CORBAfacilities begutachten, um zu sehen, wie diese in den Systementwurf eingebracht werden können. Dann werden Sie die grundlegende Funktionalität der Bankanwendung implementieren, wobei Sie die zuvor erstellten IDLDefinitionen als Ausgangspunkt verwenden, von dem aus Sie getrennte Implementierungen für den Server und den Client schreiben. Am 7. Tag werden Sie die grundlegende Funktionalität der Anwendung erweitern, indem Sie eine Exception-Behandlung hinzufügen. Die Exception-Behandlung bietet Fehlerprüffunktionen, so daß die Anwendungsimplementierung robuster wird. Änderungen an der Anwendung beginnen beim IDL-Quelltext und werden dann in den Implementierungsquelltext übernommen. Am 8. Tag wird die Anwendung noch komplexer, wenn Sie einen weiteren CORBA-Client hinzufügen: den Geldautomat-Client. CORBA-Anwendungen umfassen in der Regel mehrere Client-Komponenten, und Sie erhalten in diesem Kapitel eine Einführung in die Verwaltung mehrerer Clients in einer CORBA-Anwendung. Am 9. Tag werden Sie das Beispiel für die Bankanwendung fertigstellen und eine letzte Verbesserung hinzufügen: die Möglichkeit, Aktualisierungsinformationen vom Server auf die Clients zu »schieben«, wobei Client-Callbacks verwendet werden.
Was versteht man unter objektorientierter Analyse und Entwurfstechnik? Obwohl es objektorientierte Technologien bereits eine ganze Weile gibt, hat der Begriff »objektorientiert« in den letzten Jahren sehr an Popularität gewonnen (er ist regelrecht zum Modewort geworden). In der Tat wurde mit diesem Begriff recht verantwortungslos umgegangen, so daß seine wahre Bedeutung eher im Dunkeln liegt. Was die Angelegenheit noch komplizierter macht, ist die Tatsache, daß er zur Beschreibung der verschiedensten Sachverhalte, von Entwicklungsumgebungen über Programmiersprachen bis hin zu Datenbanken verwendet wird. Was bedeutet der Begriff »objektorientiert« denn nun eigentlich? Mit diesem Begriff wird recht unterschiedslos alles Mögliche benannt, so könnte sowohl eine Programmiersprache als auch ein Zeichenprogramm als »objektorientiert« bezeichnet werden. Innerhalb dieses Buches werden wir uns im wesentlichen auf drei Anwendungsbereiche der objektorientierten Methodik konzentrieren: objektorientierte Analyse (OOA), die sich mit den Entwurfsanforderungen und der globalen
Architektur eines Systems befaßt, objektorientierter Entwurf (Object-Oriented Design, OOD), bei dem eine Systemarchitektur in Programmierkonstrukte (wie beispielsweise Schnittstellen, Klassen und Methodenbeschreibungen) umgesetzt wird, und schließlich objektorientierte Programmierung (OOP), bei der diese Programmierkonstrukte implementiert werden. Für unsere Zwecke steht »objektorientiert« für die verschiedenen methodischen Verfahrensweisen, die ich im Folgenden noch kurz beschreiben werde und die zum Entwerfen und Implementieren von Software dienen. Dieses Kapitel befaßt sich vorrangig mit der objektorientierten Analyse. Am 6. Tag werden Sie den objektorientierten Entwurf praktisch kennenlernen. Wenn Sie die Systemfunktionalität implementieren, werden Sie objektorientierte Programmiertechniken verwenden. Obwohl Ihnen dieses Buch eine Einführung in die Konzepte der objektorientierten Analyse, Entwurfstechnik und Programmierung gibt, erhebt es nicht den Anspruch, diese Themen im Detail abzudecken. Es wurden bereits eine Reihe von Büchern zu diesem Thema verfaßt, die eine definierende Einführung und Erläuterung dieser Konzepte bieten. Die Zeit, die Sie aufbringen, um sich mit diesen Konzepten vertraut zu machen, ist auf jeden Fall sinnvoll investiert.
Einführung in die Unified Modeling Language (UML) Die UML (Unified Modeling Language, vereinheitlichte Modelliersprache) ist ein leistungsfähiges Werkzeug zum Darstellen von objektorientierten Entwürfen. Sie wurde von der Rational Software Corporation entwickelt und ist eine Weiterentwicklung bisheriger Modelliersprachen und -techniken. Eine Beschreibung der UML sowie Links zu anderen Dokumenten über die UML finden Sie auf der WWW-Seite von Rational unter http://www.rational.com/uml/index.html. Die Geschichte der UML Bei der UML handelt es sich um eine Weiterentwicklung bisheriger Modelliersprachen und techniken. Vor der UML gab es bereits viele objektorientierte Methodikansätze. Zu diesen gehören die drei wichtigsten überhaupt: die Booch-1993-Methode von Grady Booch, die Object Modeling Technique von Jim Rumbaugh (OMT) und das Object-Oriented Software Engineering (OOSE) von Ivar Jacobson. Im Oktober 1994 taten sich Booch und Rumbaugh zusammen, um ihre Methoden zusammenzuführen. Das Ergebnis war die Unified Methode 0.8 im Oktober 1995. Etwa zu dieser Zeit stieß Jacobson zu ihnen und führte die OOSE-Methode mit dem Werk von Booch und Rumbaugh zusammen, woraus im Juni 1996 UML 0.9 entstand. Das UML Partners-Konsortium, das aus Unternehmen wie Digital, Hewlett-Packard, IBM, Microsoft, Oracle, Rational und Unisys besteht, wurde dann gegründet, um die UML noch zu verfeinern, wodurch im Januar 1997 UML 1.0 entstand. Die UML-1.0-Dokumente wurden dann zur Standardisierung bei der Object Management Group (OMG) eingereicht, welche, wie Sie ja bereits wissen, die für die Definition der CORBA-Standards zuständige Organisation ist. Wichtige Begriffe und Symbole Die UML ist eine in hohem Maße visuell aufgebaute Sprache; neben Wörtern und Text besteht sie auch (und eigentlich im wesentlichen) aus Graphen und Symbolen. Wohl eines der wichtigsten Diagramme, die Ihnen bei der objektorientierten Analyse und Entwurfstechnik begegnen werden, ist
das Klassendiagramm, das wiederum die Notationen für Klassen, Assoziationen und Vererbung enthält (es gibt auch noch andere, aber diese drei Aspekte sollen hier behandelt werden). Das Klassendiagramm Ein wichtiges Element der UML (oder auch jeder anderen Modelliersprache) ist das Klassendiagramm, das manchmal fälschlicherweise als Objektdiagramm oder Objektmodell bezeichnet wird. Es beschreibt die Klassen und ihre Beziehungen zu anderen Klassen im System. Das Klassendiagramm gibt nur statische Beziehungen (Beziehungen der Klassen zueinander) an, und keine dynamischen Beziehungen (der Erstellungszeitpunkt der Objekte oder das Aufrufen der Dienste eines anderen Objekts). Ein Klassendiagramm dient zur grafischen Darstellung der Beziehungen zwischen den Klassen in einem System. Je nachdem auf welcher Ebene der Gültigkeitsbereich des Diagramms liegt, sind auch die Attribute sowie die Operationen beschrieben, die von jeder Klasse zur Verfügung gestellt werden.
Das Klassendiagramm ist eines der wichtigsten Elemente in einer objektorientierten Methodik. Es ist für das Verständnis einer komplexen Systemarchitektur von entscheidender Bedeutung und bietet tiefe Einblicke in den Entwurf eines Systems. Klassen Natürlich impliziert das Vorhandensein des Klassendiagramms, daß es auch Klassen gibt. Wie zu erwarten, entsprechen die Klassen in der UML denen in einer objektorientierten Programmiersprache wie Java oder C++. Jede Klasse weist einen Namen, keine oder mehrere Attribute und keine oder mehrere Operationen auf. Die Attribute kann man sich wie Elementdaten und Operationen als Elementfunktionen oder Methoden vorstellen. In einem Klassendiagramm kann eine Klassenbeschreibung eine der in Abbildung 5.1 gezeigten Formen annehmen.
Abbildung 5.1: Beschreibungen der UML-Klassen Abbildung 5.1 zeigt drei Beispiele für die Darstellung einer Klasse. Im ersten Beispiel ist nur der Klassenname sichtbar. Diese Form eignet sich für ein Klassendiagramm, bei dem der Schwerpunkt auf der Beziehung zwischen den einzelnen Klassen liegt. Wenn Sie z.B. mit einem äußerst komplexen Klassendiagramm arbeiten, wird diese Vereinfachung hilfreich sein, insbesondere wenn es Ihnen nur
einen Überblick über das gesamte System verschaffen soll. Im zweiten Beispiel sind der Name, die Attribute und die Operationen der Klasse zu sehen, für die Attribute und Operationen sind allerdings nur die Namen angegeben, die Typen und Parameter wurden bei dieser Darstellung weggelassen. Das dritte Beispiel zeigt eine komplette Beispiel-Klassenbeschreibung mit Namen, Attributen und Typen sowie Operationen einschließlich Parametern und Rückgabewerten. Diese Art der Klassenbeschreibung ist hilfreich, wenn Sie Detailinformationen zu einem System und seinen Klassen benötigen. Eine Klassenbeschreibung kann auch Sichtbarkeitsmodifizierer für Attribute und Operationen zur Verfügung stellen. Der Sichtbarkeitsmodifizierer, der optional ist, steht direkt vor dem Attribut bzw. der Operation, das bzw. die beschrieben wird. (Wenn kein Sichtbarkeitsmodifizierer angegeben ist, wird davon ausgegangen, daß das Attribut bzw. die Methode öffentlich ist.) Eine Beschreibung dieser Modifizierer finden Sie in Tabelle 5.1. Tabelle 5.1: Sichtbarkeitsmodifizierer in der UML. Symbol
Beschreibung
+
Öffentliche Attribute und Operationen einer Attribut bzw. Klasse stehen der Klasse selbst sowie allen Operation ist öffentlich übrigen Klassen zur Verfügung.
#
Geschützte Attribute und Operationen einer Attribut bzw. Klasse stehen nur der Klasse selbst sowie den Operation ist geschützt von ihr abgeleiteten Klassen zur Verfügung.
-
Attribut bzw. Operation ist privat
Private Attribute und Operationen einer Klasse stehen nur der Klasse selbst zur Verfügung, nicht einmal den von ihr abgeleiteten Klassen.
Abgeleitetes Attribut
Ein abgeleitetes Attribut hängt von einem anderen Attribut ab. Beispiel: Das Alter einer Person kann als Attribut angesehen werden, es hängt jedoch vom aktuellen Datum sowie vom Geburtsdatum der betreffenden Person ab. Abgeleitete Attribute können öffentlich, geschützt oder privat sein.
/
Bedeutung
Klassenattribut bzw. operation
$
Auf ein Klassenattribut oder eine Klassenoperation kann zugegriffen werden, ohne daß eine Instanz der Klasse vorhanden ist. Klassenattribute und -operationen entsprechen den statischen Klassenelementen in C++ oder Java. Auch Klassenattribute und -operationen können öffentlich, geschützt oder privat sein.
Assoziationen Eine Klasse existiert in der Regel nicht für sich allein, sondern wird normalerweise mit anderen Klassen in Interaktion stehen. Daher kann man sagen, daß eine Klasse Beziehungen zu anderen Klassen aufweist. In der UML werden solche Beziehungen als Assoziationen bezeichnet. Eine Klasse weist eine Assoziation mit einer anderen Klasse auf, wenn sie deren Dienste auf irgendeine Weise nutzt. Bei Bedarf kann die Assoziation benannt werden, und den Rollen, die jede Klasse in einer Assoziation spielt, können ebenfalls Namen gegeben werden. In Abbildung 5.2 ist die Notation für eine Assoziation gezeigt; es handelt sich dabei um eine Linie, die zwischen zwei Klassen gezeichnet wird. Eine Assoziation zwischen zwei Klassen bedeutet, daß die beiden Klassen in irgendeiner Weise miteinander in Beziehung stehen. In der Regel sagt dies aus, daß eine der Klassen Dienste der anderen nutzt oder ein Element aufweist, das eine Instanz der anderen Klasse ist.
Für eine Assoziation kann auch eine Multiplizität definiert sein, was bedeutet, daß eine bestimmte Zahl einer Klasse mit einer bestimmten Zahl einer anderen Klasse assoziiert werden kann. So zeigt Abbildung 5.2, daß ein Kunde mehr als ein Konto haben kann. Außerdem kann es zu einem Konto mehr als einen Kunden geben (so wie dies bei Gemeinschaftskonten der Fall ist). Schließlich kann ein Konto immer nur bei einer Bank eröffnet werden. Es gibt somit drei Arten von Multiplizität bei Beziehungen. ■
■
■
Eine Eins-zu-Eins-Assoziation ist eine Assoziation, bei der genau ein Objekt eines Typs mit genau einem Objekt eines anderen Typs assoziiert ist. Eine Eins-zu-Viele-Assoziation (oder andersherum eine Viele-zu-Eins-Assoziation) ist eine Assoziation zwischen genau einem Objekt eines Typs mit keinem oder mehreren Objekten eines anderen Typs. Eine Viele-zu-Viele-Assoziation ist eine Assoziation zwischen keinem oder mehreren Objekten eines Typs mit keinem oder mehreren Objekten eines anderen Typs.
Multiplizität bezeichnet die Anzahl der Klassen, die an einer Assoziation beteiligt sind. Beispiele sind Eins-zu-Eins, Eins-zu-Viele und Viele-zuViele.
Abbildung 5.2: UML-Klassenassoziationen Die Angabe der Multiplizität ist optional, in Klassendiagrammen der höheren Ebene werden diese Informationen manchmal der Übersichtlichkeit halber weggelassen. Es ist allerdings erforderlich, die Multiplizitäten der Assoziationen vor dem Implementieren des Entwurfs festzulegen, da Einzelheiten bei der Implementierung von dieser Information abhängen. Vererbung Die Vererbung ist eigentlich ein Sonderfall einer Assoziation. Dieser Begriff hat dieselbe Bedeutung wie in einer objektorientierten Sprache. Eine Klasse, die ihre Definition von einer anderen Klasse erbt, d.h. die von dieser Klasse abgeleitet ist (am 3. Tag wurde dies als übergeordnete Klasse bezeichnet), erbt die nicht-privaten Attribute und Methoden dieser übergeordneten Klasse. Am 3. Tag wurde weiterhin gesagt, daß die abgeleitete Klasse an allen Stellen, an denen die ihr übergeordnete Klasse erforderlich ist, eingesetzt werden kann (beispielsweise als Parameter bei einem Methodenaufruf). Dieses Verhalten heißt Polymorphie. In der UML wird die Vererbung durch einen Pfeil dargestellt, der von der abgeleiteten Klasse zur übergeordneten Klasse zeigt. Die UML unterstützt auch die Mehrfachvererbung, in diesem Pfeil werden Pfeile von der abgeleiteten Klasse zu allen ihr übergeordneten Klassen dargestellt. Beispiele für die UML-Darstellung von Vererbungsassoziationen finden Sie in Abbildung 5.3. Die grundlegende Methodik
Wiederum würde es den Rahmen dieses Buches sprengen, die methodischen Vorgehensweisen für die objektorientierte Analyse und die Entwurfstechnik eingehend zu behandeln. Es ist jedoch ein sinnvoller erster Schritt in der Analyse-Phase, die Objekte bzw. Klassen zu identifizieren, aus denen das System besteht. Viele Objekte im System lassen sich leicht identifizieren, eine Methode hierbei ist es, zunächst eine Beschreibung des Systems und seiner Funktion zu formulieren. Wenn diese Beschreibung vollständig ist, müssen Sie sie durchgehen und darin nach Substantiven suchen. Wenn Sie eines finden, ist es ziemlich wahrscheinlich, daß es ein Objekt im System darstellt. Bei dem Satz »Kunden haben Konten bei einer Bank« sind die Objektkandidaten Kunden, Konto und Bank. Dieser Prozeß, bei dem die Objekte entdeckt werden, ist für das Verständnis des Umfangs eines bestimmten Systems sehr hilfreich.
Abbildung 5.3: UML-Vererbungsassoziationen Nachdem Sie die Objekte gefunden haben, müssen Sie die Beziehungen zwischen den einzelnen
Klassen definieren. Diese Beziehungen werden oft durch Verben in der Systembeschreibung ausgedrückt. Im Beispiel von oben hieß es: Kunden haben Konten, was eine Beziehung zwischen beiden Klassen nahelegt. Ferner ist ersichtlich, daß Konten Teil einer Bank sind, obwohl nicht ganz klar ist, was für eine Beziehung zwischen einem Konto und einer Bank besteht. Die Assoziationen zwischen Klassen brauchen nicht benannt zu werden, obwohl benannte Assoziationen oft zusätzliche Erkenntnisse über den Entwurf eines Systems bieten. Wenn Sie aus dem oben Erwähnten schließen, daß Klassen mit Substantiven und Assoziationen mit Verben benannt werden sollen, haben Sie völlig recht. Dieses Benennungsschema ist eine allgemein anerkannte Konvention für die objektorientierte Analyse. Eine andere Konvention besteht darin, den Klassen Namen im Singular zu geben, also Bank anstelle von Banken.
Nachdem Sie nun die Klassen und ihre Assoziationen festgelegt haben, werden Sie etwas mehr Zeit damit verbringen, die Attribute und Operationen innerhalb der Klassen zu definieren. Für diesen Schritt müssen Sie sich mehr Gedanken machen als für die beiden ersten, und es ist zu vermuten, daß Sie es beim ersten Anlauf nicht gleich richtig hinbekommen. Der gesamte Vorgang erfolgt iterativ: Während Sie die Beziehungen zwischen zwei Klassen definieren, werden Sie unter Umständen neue Klassen entdecken, oder es ist möglich, daß Sie beim Festlegen der Operationen für Objekte neue Assoziationen oder gar neue Klassen entdecken. So kann es vorkommen, daß sich Ihr Entwurf zwischen Anfang und Ende ziemlich stark verändert, aber dies ist beim Entwerfen von Software ganz normal. Durch die Wiederholung dieses Vorgangs erstellen Sie einen robusten Entwurf, und dies hilft Ihnen später in der Entwicklungsphase, Probleme zu vermeiden. (Es läßt sich oft folgendes feststellen: Je später im Entwicklungsprozeß eine Änderung vorgenommen wird, um so mehr Aufwand wird diese Änderung hervorrufen. Daher lohnt es sich auf jeden Fall, viel Zeit in die Verfeinerung des Entwurfs zu investieren.) Zusammenfassung zur UML Die UML ist ein sehr weitreichendes Werkzeug, das nicht nur den statischen Entwurf eines Systems umfaßt (beispielsweise als Klassendiagramm), sondern auch den dynamischen Entwurf (hierzu gehören Use-Cases, Statusübergangsdiagramme und weitere Werkzeuge). Da ich in diesem Buch gerade eben an der Oberfläche zum Thema UML und ihrer Funktionalität als Entwurfswerkzeug gekratzt habe, sollten Sie sich entweder auf der WWW-Seite der Rational Software Corporation (die am Anfang dieses Abschnitts genannt wurde) informieren oder eine andere Informationsquelle zur UML nutzen, wenn Sie das Thema noch vertiefen möchten.
Die Beispielbankanwendung In den nächsten Kapiteln dreht sich alles um ein einziges Beispiel: ein elektronisches Banksystem. In diesem Kapitel werden Sie mit Hilfe der objektorientierten Analyse die Objekte im System definieren und ein Objektmodell für die Anwendung erstellen. In den folgenden Kapiteln werden Sie die
Grundfunktionalität des Systems implementieren und dann zusätzliche Funktionen implementieren sowie das System stabilisieren. Am Schluß werden Sie eine komplexe (aber für eine Geschäftsanwendung immer noch recht triviale) CORBA-Anwendung erstellt haben. Auf dem Weg dorthin werden einige Aspekte beleuchtet, die beim Erstellen eines solchen Systems relevant sind. Unsere Bankanwendung unterstützt das »Telebanking« (auch »elektronischer Bankdienst« genannt). Sie sieht mehrere Banken, mehrere Konten (Giro- und Sparkonten), mehrere Kunden, das Eröffnen und Auflösen von Konten, das Abheben, Einzahlen und Überweisen von einem Konto zum anderen vor. (In den nachfolgenden Kapiteln werden noch weitere Funktionen hinzugefügt, aber die Grundanwendung Bank beginnt mit dieser Funktionalität.) Definition der Systemanforderungen und -funktionen Der erste Schritt beim Entwerfen jedes Systems besteht darin, festzulegen, was das System eigentlich tun soll. Je nach Art der Anwendung kann es in diesem Prozeß erforderlich sein, Kunden und/oder potentielle Systemanwender mit einzubeziehen, Marktforschung zu betreiben oder nur eine Lösung für ein bestimmtes Problem zu finden. Die Anforderungen müssen später oft überarbeitet oder klarer formuliert werden, daher brauchen Sie sich nicht zu wundern, wenn Sie später wieder bei diesem Schritt landen. Für unsere Bankanwendung werden die Funktionen des Grundsystems wie folgt definiert: ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
■ ■
Unterstützung mehrerer Banken Unterstützung mehrerer Konten innerhalb der Banken Unterstützung mehrerer Kunden, die ein Konto haben Unterstützung von Kunden, die mehrere Konten haben Unterstützung der Fähigkeit, neue Konten zu eröffnen (zu erstellen) Unterstützung der Fähigkeit, vorhandene Konten aufzulösen (zu löschen) Unterstützung der Fähigkeit, die Konten einer Bank aufzulisten Unterstützung der Fähigkeit, den bzw. die Inhaber eines Kontos zu ermitteln Unterstützung der Fähigkeit, Geld von einem Konto abzuheben Unterstützung der Fähigkeit, Geld auf ein Konto einzuzahlen Unterstützung der Fähigkeit, Geld bankintern von Konto zu Konto zu überweisen Unterstützung der Fähigkeit, Geld bankenübergreifend von einem Konto auf ein anderes zu überweisen Unterstützung von Girokonten (nicht zinsbringend) Unterstützung von Sparkonten (zinsbringend)
Beachten Sie, daß in jeder Zeile eine Funktion beschrieben ist, so daß beispielsweise die Funktionen zum Eröffnen und Auflösen von Konten in zwei verschiedenen Zeilen stehen. Diese Konvention erleichtert die Entwicklung der Testanforderungen, weil die in den einzelnen Zeilen beschriebenen Funktionen auch einzeln getestet werden können. Beim Analysieren der Systemanforderungen empfiehlt es sich ferner sicherzustellen, daß jede Funktion tatsächlich getestet werden kann. So ist eine Anforderung wie »muß einfach zu verwenden sein« subjektiv und kann daher höchstwahrscheinlich nicht getestet werden. Vermeiden Sie daher solche vagen Anforderungen, und formulieren Sie statt dessen lieber eine Gruppe von Anforderungen, in denen spezielle Funktionen einer Benutzeroberfläche
angegeben sind, die »einfach zu verwenden« ist. Definition der Systemobjekte Nachdem Sie nun die Systemanforderungen definiert haben, können Sie ermitteln, welche Objekte im System vorhanden sein müssen. Wie bereits vorgeschlagen, können Sie hierzu die Anwendungsbeschreibung und -anforderungen auf Substantive hin durchsuchen. Dabei werden Sie auf folgendes treffen: Bank, Konto (insbesondere Girokonto und Sparkonto), Kunden und Geld. All dies sind Kandidaten, die in das Objektmodell (oder Klassendiagramm) aufgenommen werden sollten. Wenn Sie feststellen möchten, ob für einen solchen Kandidaten eine Klasse erstellt werden sollte, stellen Sie sich jeweils die folgende Frage: Ist mit diesem Objekt eine Identität oder ein Verhalten verbunden? Wenn dies der Fall ist, sollten Sie ein Objekt erstellen. Versuchen Sie dies mit unserer Liste der Objektkandidaten: ■
■
■
■
Bank: Eine Bank hat in der Tat eine Identität, denn die ABC-Bank läßt sich von der XYZ-Bank unterscheiden. Ferner weist eine Bank ein zugehöriges Verhalten auf: Unter anderem kann sie für die Kunden Konten eröffnen und auflösen. Daraus läßt sich schließen, daß Bank tatsächlich ein Objekt in unserem System ist. Konto: Wie Banken weisen auch Konten eine Identität auf, da sie voneinander unterschieden werden können. Auch haben sie ein zugehöriges Verhalten: Geld kann auf sie eingezahlt oder von ihnen abgehoben oder von einem Konto zum anderen überwiesen werden. Daher wird Konto ein weiteres Objekt in unserem System sein. Kunden: Kunden haben selbstverständlich eine Identität: Kurt Konrad kann von Peter Hegner unterschieden werden, und die Person mit der Personalausweisnummer X123456 7890 unterscheidet sich von der Person mit der Personalausweisnummer Y234567 8901 usw. Den Kunden ist, zumindest für diese Anwendung, kein spezifisches Verhalten zugeordnet, aber aufgrund ihrer Identität ist es sinnvoll, Kunde als Objekt in unserem System zu definieren. Geld: Geld hat wirklich keine Identität, denn der Betrag 150,00 DM ist praktisch gesehen nicht von einem anderen Betrag 150,00 DM zu unterscheiden. Auch ist mit Geld direkt kein Verhalten assoziiert, es kann eingezahlt und abgehoben und von einem Konto zum anderen überwiesen werden, aber dies ist eher ein Verhalten des Kontos als des Geldes selbst. Schließlich kann Geld eher problemlos durch einen einfachen Gleitkommawert dargestellt werden als durch eine Klasse. Aus diesen Gründen empfiehlt es sich, Geld nicht in das Objektmodell der Anwendung aufzunehmen.
Diese Analyse hat gezeigt, daß das System mindestens drei Hauptklassen enthalten muß: Bank, Konto und Kunde. Jetzt werden wir uns mit den Attributen und dem Verhalten der einzelnen Objekte beschäftigen. Die Klasse Bank Einige der Systemanforderungen legen Verhaltensweisen nahe, die die Klasse Bank aufweisen muß: ■
Unterstützung mehrerer Banken
Obwohl dies eigentlich kein Verhalten einer Bank ist, erfordert dies, daß mehr als ein Objekt vom Typ Bank vorhanden sein kann. Ferner legt diese Anforderung nahe, daß in einer CORBAAnwendung ein Mechanismus vorhanden ist, der den Clients Sichtbarkeit auf die Objekte vom Typ Bank gewährt. Ein Ansatz hierfür wäre, die Objekte vom Typ Bank beim Bezeichnungsdienst registrieren zu lassen, so daß diese von den Clients gefunden werden können, ein anderer besteht darin, ein separates Objekt, beispielsweise namens BankServer zu erstellen, das die Sichtbarkeit auf die Objekte vom Typ Bank ermöglicht. In dieser Anwendung soll der zweite Ansatz verwendet werden. In einer mit C++ oder Java geschriebenen Anwendung, die nicht CORBA verwendet, ist es sehr gut möglich, eine statische Methode für Bank zur Verfügung zu stellen, die eine Liste der Objekte vom Typ Bank liefert, was ein sinnvoller Ansatz wäre. Da CORBA-Objekte jedoch keine statischen Methoden unterstützen, wird ein anderer Ansatz, wie der zuvor erwähnte, benötigt.
■
Unterstützung mehrerer Konten innerhalb der Banken Diese Anforderung legt nahe, daß eine Bank eine Liste der Konten verwaltet, obwohl Objekte außerhalb der Bank nicht unbedingt auf diese Liste zugreifen können müssen.
■
Unterstützung der Fähigkeit, neue Konten zu eröffnen (zu erstellen) Diese Anforderung bedeutet, daß eine Bank eine Operation wie erstelleKonto(), für die als Eingabe ein Kunde (oder eine Gruppe von Kunde(n)) verwendet wird, und eventuell einen Eröffnungskontostand unterstützt und das neue Konto-Objekt liefert. Die Bank stellt also die folgende Operation zur Verfügung: erstelleKonto(Kunde : Kunde, AnfangsSaldo : float) : Konto
■
Unterstützung der Fähigkeit, vorhandene Konten aufzulösen (zu löschen) Da ein Konto existieren muß, damit es gelöscht werden kann, gehört dieses Verhalten entweder zur Klasse Bank oder zur Klasse Konto. Aus Gründen der Konsistenz mit erstelleKonto() wird hier die Klasse Bank verwendet. Für loescheKonto(), wie diese Operation genannt werden könnte, sind außer der Angabe des zu löschenden Kontos keine weiteren Informationen erforderlich. Die Signatur könnte also wie folgt aussehen: loescheKonto(Konto : Konto) : void
Sie werden oft Situationen wie die gerade beschriebene erleben, in denen es keine eindeutige Antwort auf die Frage gibt, wo ein bestimmtes Verhalten einzuordnen ist. Gehen Sie nach gesundem Menschenverstand vor, in einigen Fällen werden Sie wahrscheinlich auch eine willkürliche Entscheidung treffen müssen.
■
Unterstützung der Fähigkeit, die Konten einer Bank aufzulisten Zuvor wurde bereits erwähnt, daß eine Bank eine Liste der Konten verwalten soll. Die hier genannte Anforderung legt nahe, daß die Konten einer Bank anderen Objekten zugänglich gemacht werden sollen. In einem System für die Praxis wäre der Zugriff auf die Konteninformationen wohl beschränkt, aber da es in diesem System (noch) keine solche Anforderung gibt, ist diese Operation ganz einfach: getKonten() : Konto[] Die Notation [] gibt an, daß getKonten() ein Array von Objekten des Typs Konto liefert.
Weitere Attribute für Bank wären sicher sinnvoll. So sollte die Bank beispielsweise einen Namen und eine Adresse haben. In dieser Anwendung werden hierfür ganz einfache Attribute verwendet: Name : string Adresse : string Die Klasse BankServer BankServer ist eine Klasse, die in der ersten Analyse noch nicht vorgesehen war, aber bei der Analyse der Klasse Bank aufgetaucht ist. Die Klasse BankServer ist eine einfache Klasse, deren einzige Aufgabe es ist, die Sichtbarkeit auf die Objekte des Typs Bank zur Verfügung zu stellen. Hierzu werden die folgenden Operationen benötigt: Die Bank muß beim BankServer registriert werden, die Registrierung der Bank beim BankServer muß aufgehoben werden können, und es muß eine Liste aller Banken ausgegeben werden können, die derzeit beim BankServer registriert sind. Diese Operationen sind formal wie folgt definiert: registriereBank(bank : Bank) : void aufhebenRegistrierungBank(bank : Bank) : void getBanken() : Bank[] Im Rahmen dieser Anwendung brauchen keine weiteren Funktionen für die Klasse BankServer definiert zu werden. Die Klasse Konto Die nächste Klasse, die wir hier berücksichtigen müssen, ist die Klasse Konto. Über diese wird ein
Großteil der anfänglichen Funktionalität der Bankanwendung implementiert. Die Klasse Konto erfüllt die Systemanforderungen folgendermaßen: ■
Unterstützung mehrerer Kunden, die ein Konto haben Diese Funktion wird durch eine Viele-zu-Eins-Beziehung zwischen den Objekttypen Kunde und Konto erreicht, impliziert aber, daß ein Objekt vom Typ Konto eine Operation unterstützt, die zeigt, welche Objekte vom Typ Kunde mit diesem Konto assoziiert sind: getKunden() : Kunde[]
■
Unterstützung von Kunden, die mehrere Konten haben Diese Funktion, die an die Anforderung gekoppelt ist, daß mehrere Kunden ein Konto haben können, impliziert eine Viele-zu-Viele-Beziehung zwischen den Objekttypen Kunde und Konto (und nicht eine Viele-zu-Eins-Beziehung wie oben erwähnt). Die eigentliche Funktionalität für diese Anforderung gehört zur Definition der Klasse Kunde.
■
Unterstützung der Fähigkeit, den bzw. die Inhaber eines Kontos zu ermitteln Diese Fähigkeit impliziert, daß eine Operation vorhanden ist, die das Objekt Kunde zurückgibt, das mit einem bestimmten Konto assoziiert ist. Diese Operation wurde bereits zuvor zur Verfügung gestellt.
■
Unterstützung der Fähigkeit, Geld von einem Konto abzuheben Die Fähigkeit, Geld von einem Konto abzuheben, würde wahrscheinlich in Form der Operation Abhebung() erfolgen, wobei der abzuhebende Betrag als Argument verwendet wird. Aus praktischen Gründen liefert diese Operation den neuen Kontostand des Objekts Konto: Abhebung(Betrag : float) : float
■
Unterstützung der Fähigkeit, Geld auf ein Konto einzuzahlen Für das Einzahlen von Geld gilt dieselbe Semantik wie beim Abheben. Der einzuzahlende Betrag ist ein Argument, und der Rückgabewert ist der neue Kontostand des Objekts Konto: Einzahlung(Betrag : float) : float
■
Unterstützung der Fähigkeit, Geld bankintern von Konto zu Konto zu überweisen Das Überweisen von Geld von einem Konto zu einem anderen ist etwas komplizierter als das Einzahlen bzw. Abheben. In diesem Fall muß nämlich auch das Zielkonto angegeben werden. Der Betrag der Transaktion darf natürlich auch nicht fehlen. Genauso wie bei den Operationen Einzahlung() und Abhebung() liefert die Operation Ueberweisung() den neuen Kontostand des Kontos, von dem der Betrag abgebucht wurde:
Ueberweisung(anderes : Konto, Betrag : float) : float
■
Unterstützung der Fähigkeit, Geld bankenübergreifend von einem Konto auf ein anderes zu überweisen Diese Fähigkeit wird bereits von der Operation Ueberweisung() unterstützt, weil das Konto, das an diese Operation übergeben wurde, zu jeder beliebigen Bank gehören kann. Daher braucht für das bankenübergreifende Überweisen von Geld von einem Konto zu einem anderen keine separate Operation definiert zu werden.
■ ■
Unterstützung von Girokonten (nicht zinsbringend) Unterstützung von Sparkonten (zinsbringend)
Diese Anforderung geht davon aus, daß Spezialfälle der Klasse Konto definiert werden. Insbesondere werden die Klassen GiroKonto und SparKonto definiert. Man könnte natürlich einwenden, daß es sich bei den Kontoarten eigentlich um Attribute der Klasse Konto handelt, aber für diese Anwendung sollen GiroKonto und SparKonto als abgeleitete Klassen von Konto definiert werden. Dieser Ansatz ist sinnvoll, weil für SparKonto Attribute und Verhaltensweisen definiert werden müssen, die für GiroKonto nicht gelten und umgekehrt. Da diese Typen unterschiedliche Verhaltensweisen aufweisen, ist es sinnvoll, sie als eigene Klassen zu erstellen. Schließlich muß das Konto noch mit einigen Attributen versehen werden. Zunächst wollen wir ihm eine Kontonummer zuweisen, so daß der Mensch als Benutzer es identifizieren kann und außerdem die Kontonummer auf Schecks angegeben werden kann. Ferner wäre es sinnvoll, das Eröffnungsdatum des Kontos festzuhalten. Diese Fähigkeiten haben wir bei der Auflistung der Anforderungen nicht formuliert, es wäre also prinzipiell möglich, auf sie zu verzichten. Sie werden sich aber früher oder später als sinnvoll erweisen, und daher sollen sie jetzt schon integriert werden: KontoNummer : string EroefffnungsDatum : date getKontoNummer() : string getEröffnungsdatum () : date Die hier aufgelisteten Operationen entsprechen Attributen. Dies geschieht in Übereinstimmung mit einer gängigen Praxis, gemäß der Attribute als privat definiert werden und der Zugriff auf sie über Datenzugriffsmethoden erfolgt. Bei nicht mit CORBA programmierten Anwendungen können Sie auch anders vorgehen, aber der Zugriff auf die Attribute von CORBA-Objekten erfolgt immer über Datenzugriffsmethoden. Denken Sie daran, daß ein Vorteil dieser Konvention darin besteht, daß Sie bei Bedarf den externen Zugriff auf Objektattribute so beschränken können, daß diese nur gelesen werden können. Dies ist in diesem Beispiel der Fall, weil nur Datenzugriffsmethoden und keine Datenänderungsmethoden zur Verfügung gestellt werden. Dadurch wird sichergestellt, daß Attribute wie Eröffnungsdatum und Kontonummer nicht geändert werden können. Die Klasse GiroKonto Die Klasse GiroKonto, die von der Klasse Konto abgeleitet ist, stellt zusätzliche Attribute und
Verhaltensweisen zur Verfügung. An diesem Punkt im Anwendungsentwurf wird durch GiroKonto allerdings nichts Neues zu Konto hinzugefügt. Die Klasse SparKonto Die Klasse SparKonto, die ebenfalls von der Klasse Konto abgeleitet wird, stellt ebenfalls zusätzliche Attribute und Verhaltensweisen zur Verfügung. Insbesondere ist wichtig, daß mit dieser Klasse ein Zinssatz mit Datenzugriffs- und Datenänderungsmethode für dieses Attribut assoziiert wird: ZinsSatz : float getZinsSatz() : float setZinsSatz(neuerZinssatz : float) : float Die Operation setZinsSatz() liefert als Service für den Benutzer den bisherigen Zinssatz. Die Klasse Kunde Die Klasse Kunde in dieser Anwendung ist eine relativ einfache Klasse, weil sie mehrheitlich die Dienste nutzt, die von anderen Klassen zur Verfügung gestellt werden. Nur eine der Systemanforderungen fällt in den Bereich der Klasse Kunde. ■
Unterstützung von Kunden, die mehrere Konten haben Da ein Kunde mehrere Konten haben kann, ist es sinnvoll, für die Klasse Kunde eine Operation zu definieren, mit der die Konten aufgelistet werden, die dem Kunden gehören:
getKonten() : Konto[] Außerdem sollen einige Attribute des Kunden hinzugefügt werden, wie beispielsweise sein Name, die Nummer seines Personalausweises (zur Identifikation), seine Adresse und bei verheirateten Frauen der Mädchenname der Mutter (aus Sicherheitsgründen und auch um der guten alten Tradition willen): Name : string PersonalAusweisNummer : string Adresse : string MuttersMaedchenName : string Um die Datenstruktur nicht unnötig kompliziert zu machen, wird hier die Adresse als einfache Zeichenkette definiert und nicht einzeln als Straße, Postleitzahl, Ort usw. Andererseits wäre es im Hinblick auf die Systemrobustheit sicherlich auch nicht verkehrt, eine eigene Klasse Adresse zu definieren, von der noch weitere Klassen abgeleitet werden könnten. Einiges zur Objektidentität Ihnen wird sicher aufgefallen sein, daß bei den zuvor beschriebenen Klassen nirgends die Rede von
Attributen war, die einzig und allein zum Identifizieren des Objekts dienen. (Die einzigen Ausnahmen hiervon sind die Ausweisnummer beim Objekt Kunde und die Kontonummer beim Objekt Konto, auf die ich später noch zurückkommen werde.) Dies liegt daran, daß bei der objektorientierten Analyse von der Annahme ausgegangen wird, daß Objekte grundsätzlich eindeutig sind, so daß ein Attribut zur Identifizierung überflüssig ist. Daher sollten die Klassen auf Analyse-Ebene keine Attribute enthalten, deren einziger Zweck die Identifikation des Objekts ist. Von dieser Regel gibt es Ausnahmen. Eine ist, daß für das Objekt Kunde ein Attribut PersonalAusweisNummer definiert ist, das nur dazu da ist, den Kunden zu identifizieren. Diese Art von Identitätsattribut kommt allerdings deshalb oft vor, weil sie den tatsächlichen Gegebenheiten entspricht. Ein Mensch hat schon durch die Tatsache, daß er existiert, eine eindeutige Identität. Die Personalausweisnummer ist eine eindeutige Methode zur Identifizierung und wird daher gern in Software-Anwendungen benutzt. Es gibt noch andere Identifikationsattribute, die genausogut für einen Anwendungsentwurf verwendet werden könnten. Ein weiteres Beispiel ist die Kontonummer in der Definition der Klasse Konto. Kontonummern werden oft zum Identifizieren eines Kontos oder eines Schecks bzw. von Kontoauszügen verwendet, die der Kunde erhält. Merken Sie sich folgendes, wenn Sie entscheiden müssen, ob die Verwendung von Identitätsattributen angebracht ist oder nicht: Ein künstlich definiertes Identitätsattribut hat in einer Klassenbeschreibung nichts verloren, während ein Identitätsattribut, das es im wirklichen Leben auch gibt, wie beispielsweise PersonalAusweisNummer akzeptabel und sogar hilfreich ist. Erstellung eines Klassendiagramms für die Anwendung Nachdem wir nun die Komponenten (Klassen) des Systems definiert und ihre Attribute und Operationen beschrieben haben, können Sie diese in ein zusammenhängendes Klassendiagramm übertragen. Das Klassendiagramm zeigt nicht nur die Klassen selbst (einschließlich der Attribute oder Operationen, wenn diese dargestellt werden sollen), sondern auch die Beziehungen zwischen den einzelnen Klassen. Abbildung 5.4 zeigt das erste Klassendiagramm für unsere Bankanwendung. (Sie werden von Zeit zu Zeit Änderungen daran vornehmen, wenn der Systementwurf im Verlauf der nächsten Kapitel weiterentwickelt wird.) Beachten Sie, daß in diesem Diagramm die UML-Notierung für die Klassenvererbung eingeführt wird, denn die Klassen SparKonto und GiroKonto sind beide von der Klasse Konto vererbt.
Abbildung 5.4: Das Klassendiagramm für die Bankanwendung An dieser Stelle möchte ich noch einmal darauf hinweisen, daß es sich bei Analyse und Entwurf um einen iterativen Prozeß handelt. Sie werden oft noch einmal zu einem vorherigen Schritt zurückgehen und verschiedene Aspekte des Entwurfs noch einmal überarbeiten, wenn Sie Merkmale finden, die Sie zuvor nicht berücksichtigt hatten oder wenn Sie (zufällig oder nicht) lernen, daß Sie bestimmte Punkte eleganter lösen können. Es ist in diesem Stadium ganz normal, daß Sie bisher Erarbeitetes nochmals prüfen und ggf. ändern. Streben Sie in dieser Phase eine möglichst hohe Systementwurfsqualität an, denn wenn Sie erst einmal mit der Implementierung des Systems begonnen haben, wird dies sehr viel aufwendiger sein. Es ist daher besser, diese Zeit jetzt in der Entwurfsphase zu investieren, wenn das Ändern in jeder Hinsicht noch viel weniger aufwendig ist. Wenn Sie sich noch weiter informieren möchten ... Natürlich wäre zur objektorientierten Analyse und Entwurfstechnik noch viel mehr zu sagen, als Sie hier finden. Ein wichtiger Aspekt bei der objektorientierten Methodik ist die Entwicklung von Use-
Cases (Fallbeispiele), mit denen Szenarien beschrieben werden, in welchen verschiedene Teile des Systems in Interaktion treten. Diese Szenarien enthalten Aktoren, wie einen Benutzer oder ein anderes Objekt, die wieder auf andere Objekte einwirken. Use-Cases beschreiben, wie ein Aktor mit dem System in Interaktion tritt, was für Ergebnisse dies hat und in welcher Reihenfolge diese Ereignisse auftreten. Es gibt viele mögliche Use-Cases für ein einzelnes Szenario. Für ein gegebenes Dialogfeld beispielsweise könnte es ein Use-Case für den Fall geben, daß der Benutzer gültige Daten eingibt, und ein anderes Use-Case für den Fall, daß der Benutzer ungültige Daten eingibt. Ein weiteres wichtiges Werkzeug bei der objektorientierten Analyse und Entwurfstechnik ist das Entwurfsmuster. Diese kann man sich wie die Bausteine für komplexere objektorientierte Konstrukte vorstellen. Ein weit verbreitetes Entwurfsmuster, das Sie vielleicht bereits kennen, ist das Muster »Modell-Sicht«. Bei diesem Muster stellt ein Objekt, das Modell, Daten oder ein Konzept dar. Ein anderes Objekt, die Sicht, teilt dem Modell mit, daß es Aktualisierungsinformationen erhalten möchte, wenn sich der Status des Modells ändert. Ein Beispiel hierfür ist die Modellklasse TemperaturFuehler, die zum Überwachen der Außentemperatur dient. Eine Sicht-Klasse, wie beispielsweise TemperaturAnzeige, könnte eine Sicht der Klasse TemperaturFuehler sein. Dies bedeutet, daß der TemperaturFuehler jede Änderung der Außentemperatur, die er entdeckt, an die TemperaturAnzeige weitergibt. Die TemperaturAnzeige erhält die neue Temperaturinformation und zeigt diese an. Für ein Modell können auch mehrere Sichten definiert sein, in unserem Fall würde der TemperaturFuehler seine Daten bei Änderungen der Außentemperatur an mehrere TemperaturAnzeigen weitergeben. Seit langem sind viele hervorragende Bücher zu Themen wie Use-Cases, Entwurfsmuster und weiteren wichtigen Begriffen des objektorientierten Arbeitens auf dem Markt. Ich möchte Sie ermutigen, sich eingehend mit diesen Themen zu beschäftigen. Kenntnisse in diesem Bereich zahlen sich beim Entwerfen jeder Art von System aus, sie sind nicht nur für CORBA-Anwendungen wichtig.
Zusammenfassung In diesem Kapitel haben Sie im Grunde einen Schnellkurs über objektorientierte Analyse und Entwurfstechnik absolviert. Sie haben einiges über die UML (Unified Modeling Language), ihre Notation und die hierbei relevanten grundlegenden methodischen Vorgehensweisen gelernt. Sie haben dieses Wissen dann beim Entwerfen einer grundlegenden Bankanwendung angewendet. In der Analyseund Entwurfsphase braucht man den Implementierungsdetails nicht allzuviel Aufmerksamkeit zu widmen, es wird sogar empfohlen, in diesem Stadium der Anwendungsentwicklung solche Details außer acht zu lassen. Ein Systementwurf, der nicht von solchen Details abhängig ist, ist flexibler als ein Entwurf, der von Details für eine bestimmte Implementierung abhängt. In der Analyse- und Entwurfsphase haben Sie die drei folgenden Hauptschritte durchgeführt: ■ ■ ■ ■
Ermittlung und Definition der Systemanforderungen Festlegung der möglichen Klassen, aus denen das System besteht Abbildung der Systemanforderungen auf Klassenattribute und -operationen Erstellung eines Klassendiagramms, mit dessen Hilfe die Klassen des Systems in ein zusammenhängendes Modell integriert werden
Ausblick Am 6. Tag werden Sie im letzten Teil der Entwurfsphase den Systementwurf in eine IDLSpezifikation umsetzen. Wie Sie unschwer erraten haben, wird die IDL als Leitlinie für die Systemimplementierung verwendet. Sie werden also im nächsten Kapitel die grundlegende Funktionalität des Systems implementieren, an den folgenden Tagen werden Sie dann die Basisfunktionalität noch optimieren.
Fragen & Antworten Frage: Was ist in der Bankanwendung vorgesehen, um zu verhindern, daß jemand Geld von einem Konto zu einem anderen überweist, ohne hierzu berechtigt zu sein? Antwort: Die Antwort ist kurz: gar nichts. In der Anwendung, die eine übermäßige Vereinfachung eines realen Banksystems ist, wird nicht versucht, irgendeine Art von Sicherheit zu implementieren. In einem kommerziellen System müßten natürlich Sicherheitsmaßnahmen vorgesehen sein, die so etwas verhindern würden. (Wenn Sie ganz ehrgeizig sind, entwerfen Sie übungshalber einen solchen Mechanismus.) Frage: Was versteht man unter Use-Cases? Antwort: Use-Cases sind leistungsfähige Werkzeuge beim Systementwurf. Sie helfen den Systemarchitekten, Entwurfs- und Entwicklungsingenieuren dabei, besser zu verstehen, wie das System funktioniert. Beim Erstellen von Use-Cases werden oft noch Szenarien entdeckt, an die man vielleicht vorher überhaupt nicht gedacht hat. Eingedenk Murphy's Law, das immer zutrifft, wird dann ein Benutzer all die Szenarien entdecken, an die nicht gedacht wurde, in der Regel mit unerwünschtem Ergebnis. So ist es immer besser, wenn sie schon beim Anwendungsentwurf gefunden werden.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übung finden Sie in Anhang A. Quiz
1. Finden Sie die möglichen Objekte im folgenden System: Ein Bestellungssystem ermöglicht es Kunden, Produkte bei einer bestimmten Firma zu bestellen. Jede Bestellung besteht aus einem oder mehreren Einträgen (Zeilen), in denen jeweils eine Anzahl und ein bestimmtes Produkt angegeben ist. Jedem Produkt wiederum ist ein Preis zugeordnet. 2. Was versteht man unter der UML, und wozu dient sie? 3. Beim Systementwurf für ein Bestellungsverarbeitungssystem lautet eine der Anforderungen »es muß schnell sein«. Ist diese Anforderung gut formuliert, oder könnte man das besser ausdrücken? Wenn ja, wie würden Sie es formulieren? Übung Ändern Sie den Systementwurf so ab, daß die Bank aus Zweigstellen besteht, von denen jede einige Kundenkonten hat. Erstellen Sie das Klassendiagramm für den geänderten Entwurf.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 6 Implementierung grundlegender Anwendungsfunktionen Implementierung der Grundfunktionen der Bankanwendung Implementierung der grundlegenden Client-Funktionen Ausführung des Beispiel-Quelltexts Zusammenfassung Fragen & Antworten Workshop
Am 5. Tag haben Sie einen Anwendungsentwurf und die zugehörigen Klassen definiert, die zur Festlegung der Struktur dienen, anhand derer der Entwurf verwirklicht werden soll. Heute werden Sie diese IDL-Schnittstellen implementieren und dadurch eine funktionierende Gruppe von Servern und Clients erstellen, die die grundlegenden Funktionen der Bankanwendung implementieren. An den folgenden Tagen werden Sie die Anwendung durch Hinzufügen weiterer Funktionen noch verbessern; heute werden wir uns jedoch auf das Implementieren der Grundfunktionen für die Anwendung beschränken.
Die Beispiele in diesem Kapitel wurden mit VisiBroker/C++ von Visigenic Software entwickelt (http://www.visigenic.com/). Obwohl es eine Standard-IDL-Sprachabbildung für C++ gibt, sind nach wie vor etliche Inkonsistenzen zwischen den verschiedenen CORBAProdukten vorhanden. Wenn Sie mit einem anderen Programm arbeiten, wie beispielsweise Orbix von IONA Technologies, ist es unter Umständen erforderlich, den Beispiel-Quelltext an einigen Stellen geringfügig zu ändern. Falls beim Kompilieren der Beispiele Probleme auftreten, informieren Sie sich in der Programmdokumentation über die Sprachabbildung.
Implementierung der Grundfunktionen der Bankanwendung Die Server-Funktionalität der Bankanwendung ist in die drei Hauptschnittstellen eingebunden: BankServer, Bank und Konto. Die Schnittstelle Konto ist in zwei abgeleitete Schnittstellen unterteilt: GiroKonto und SparKonto. Über diese Schnittstellen ist die Kernfunktionalität der Bankanwendung definiert. Nachdem Sie Implementierungen für diese Schnittstellen zur Verfügung gestellt haben, werden Sie auch die Client-Funktionen implementieren. Die einzige vom Client implementierte Schnittstelle heißt Kunde; sie wird vom Client für den Zugriff auf die verschiedenen Bankdienste verwendet. Implementierung der Schnittstelle BankServer Die erste zu implementierende Server-Schnittstelle heißt BankServer. Wie bereits erwähnt, hat sie den Zweck, Clients das Auffinden der Objekte vom Typ Bank zu ermöglichen. Die Objekte vom Typ BankServer wiederum werden von Clients und Objekten vom Typ Bank über den CORBABezeichnungsdienst oder einen anderen, ähnlichen Mechanismus aufgefunden. Wenn ein Objekt vom Typ Bank erstellt wird, sucht es einen BankServer und läßt sich bei diesem registrieren. Analog dazu macht das Bank-Objekt die Registrierung beim BankServer wieder rückgängig, wenn es beendet wird. Der IDL-Quelltext für die BankServer-Schnittstelle (die Sie am 5. Tag erstellt haben) ist in Listing 6.1 definiert. Listing 6.1: BankServer.idl. 1: 2: 3: 4: 5: 6: 7: 8:
// BankServer.idl #ifndef BankServer_idl #define BankServer_idl #include "Bank.idl" // BankServer ermöglicht den Clients die Sicht auf Bank-Objekte.
9: interface BankServer { 10: 11: // Angegebene Bank mit diesem BankServer registrieren. Die Bank wird 12: // durch getBanken() aufgelistet, bis aufhebenRegistrierungBank() mit dieser 13: // Bank aufgerufen wird. 14: void registriereBank(in Bank bank); 15: 16: // Registrierung der angegebenen Bank vom BankServer aufheben. Falls die Bank 17: // zuvor nicht registriert wurde, wird nichts ausgeführt. 18: void aufhebenRegistrierungBank(in Bank bank); 19: 20: // Liste aller Banken liefern, die aktuell auf diesem 21: // BankServer registriert sind. 22: BankenListe getBanken(); 23: }; 24: 25: #endif Sie müssen nun Implementierungen für die Methoden registriereBank(), aufhebenRegistrierungBank() und getBanken() sowie den bzw. die Konstruktor(en) und den Destruktor für diese Klasse zur Verfügung stellen. Wenn Sie BankServerImpl.h in Listing 6.2 betrachten, wird Ihnen zunächst (in Zeile 10) auffallen, daß die Klasse _sk_BankServer durch die Klasse BankServerImpl erweitert wird. _sk_BankServer ist das Server-Skeleton für die BankServer-Schnittstelle. Wenn Sie die Quelldatei für diese Klasse untersuchen würden, könnten Sie feststellen, daß diese rein virtuelle Methoden zur Verfügung stellt, die den zuvor definierten IDL-Methoden entsprechen. Da es sich jedoch hier um ein Skeleton handelt, stellt es keine Implementierungen für diese Methoden zur Verfügung; dies ist die Aufgabe der Klasse BankServerImpl. Beachten Sie ferner, daß der Name BankServerImpl rein willkürlich gewählt wurde, Sie können die Klassen so benennen, wie Sie möchten, aber es empfiehlt sich, eine Bezeichnungskonvention für die Implementierungsklassen festzulegen und einzuhalten. Listing 6.2: BankServerImpl.h. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// BankServerImpl.h #ifndef BankServerImpl_h #define BankServerImpl_h #include #include "../BankServer_s.h"
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
class BankServerImpl : public _sk_BankServer { public: // Konstruktor. BankServerImpl(); // Destruktor. ~BankServerImpl(); // Diese Methoden werden in BankServer.idl beschrieben. virtual void registriereBank(Bank_ptr bank); virtual void aufhebenRegistrierungBank(Bank_ptr bank); virtual BankenListe* getBanken(); private: // Die Bankenliste dieses BankServers. std::vector meineBanken; }; #endif
Beachten Sie auch die folgenden Listing-Einträge: #include und // Die Bankenliste dieses BankServers. std::vector meineBanken; Wenn Sie vermuten, daß für diese Implementierung die STL (Standard Template Library, StandardSchablonenbibliothek) von C++ verwendet wird, haben Sie völlig recht. Die meisten modernen C++Compiler enthalten die STL, wenn dies bei Ihrem nicht der Fall ist, können Sie sich entweder eine Implementierung der STL besorgen oder den Beispiel-Quelltext so ändern, daß STL nicht verwendet wird. Eine Beschaffungsquelle für die STL-Implementierungen ist ObjectSpace (unter http://www.objectspace.com/). Dort erhalten Sie kostenlose STL-Implementierungen für viele Plattformen und Compiler. Wenn Sie sich BankServerImpl.h genauer ansehen, stellen Sie fest, daß die zuvor definierten IDLMethoden auf die folgenden C++-Methoden abgebildet werden: virtual void registriereBank(Bank_ptr bank); virtual void aufhebenRegistrierungBank(Bank_ptr bank);
virtual BankenListe* getBanken(); Beachten Sie besonders, daß die Referenzen auf Bank auf den Typ Bank_ptr abgebildet wurden und BankenListe auf BankenListe*. Abgesehen von diesen Änderungen und dem Auftreten des Schlüsselworts virtual (das für CORBA-Implementierungsklassen nicht unbedingt erforderlich ist, dessen Verwendung aber empfehlenswert ist), ähneln die C++-Methodendefinitionen stark ihren IDLGegenstücken. Listing 6.3 enthält die Implementierungsklasse BankServerImpl.cpp, über die die Implementierung für die Schnittstelle _sk_BankServer zur Verfügung gestellt wird. Listing 6.3: BankServerImpl.cpp. 1: // BankServerImpl.cpp 2: 3: #include "BankServerImpl.h" 4: 5: #include 6: #include 7: 8: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Banken gleich sind. 9: class IstBankGleich : public std::unary_function { 10: public: 11: IstBankGleich(argument_type bank) { meineBank = bank; } 12: result_type operator()(argument_type bank) { return bank-> 13: _is_equivalent(meineBank) != 0; } 14: private: 15: argument_type meineBank; 16: }; 17: 18: // Konstruktor. 19: BankServerImpl::BankServerImpl() : meineBanken() { 20: 21: } 22: 23: // Destruktor. 24: BankServerImpl::~BankServerImpl() { 25: 26: } 27: 28: void BankServerImpl::registriereBank(Bank_ptr bank) { 29: 30: // Die angegebene Bank am Listenende einfügen. 31: cout << "BankServerImpl: Registriere Bank \"" << bank->name() <<
32: "\"." << endl; 33: meineBanken.push_back(Bank::_duplicate(bank)); 34: } 35: 36: void BankServerImpl::aufhebenRegistrierungBank(Bank_ptr bank) { 37: 38: std::vector::iterator first = meineBanken.begin(); 39: std::vector::iterator last = meineBanken.end(); 40: IstBankGleich predicate(bank); 41: 42: std::vector::iterator uebereinstimmendeBank = std:: 43: find_if(first, last, predicate); 44: if (uebereinstimmendeBank == last) { 45: 46: // Ungültige Bank; keine Aktionen. 47: cout << "BankServerImpl: Versuch Registrierung ungültiger Bank " 48: "wurde ignoriert ." << endl; 49: return; 50: } 51: cout << "BankServerImpl: Registrierung aufgehoben \"" << bank>name() 52: << "\"." << endl; 53: 54: // Angegebene Bank löschen. 55: meineBanken.erase(uebereinstimmendeBank); 56: bank->_release(); 57: } 58: 59: BankenListe* BankServerImpl::getBanken() { 60: 61: BankenListe* list = new BankenListe(meineBanken.size()); 62: CORBA::Long i; 63: 64: for (i = 0; i < meineBanken.size(); i++) { 65: (*list)[i] = Bank::_duplicate(meineBanken[i]); 66: } 67: 68: return list; 69: } In dieser Klasse sind insbesondere die folgenden Aspekte interessant: BankServerImpl.cpp verwendet von STL zur Verfügung gestellte Algorithmen und Funktionen; dies zeigt sich in Zeile 5 und 6 bei #include directivesinclude directivesBankServerImpl.cppinclude directivesBankServerImpl.cppinclude directivesBankServerImpl.cpp.
Die Klasse IstBankGleich in den Zeilen 8 bis 16 ist eine Einkapselung einer Funktion, bei der zwei Verweise auf das Objekt Bank auf Gleichheit verglichen werden (d.h. ob sie sich auf dasselbe Objekt vom Typ Bank beziehen). Dieser Vergleich wird über die Methode _is_equivalent() durchgeführt. Er liefert das Ergebnis TRUE (ungleich Null), wenn sein Objekt und das als Argument verwendete Objekt auf dasselbe Objekt verweisen. Gemäß der CORBA-Spezifikation kann _is_equivalent() tatsächlich den Wert FALSE liefern, auch wenn die beiden Objektreferenzen gleich sind. Die einzige Garantie, die die CORBASpezifikation liefert, besteht darin, daß die Objektreferenzen gleich sind, wenn _is_ equivalent() den Wert TRUE liefert, andernfalls können sie gleich sein oder auch nicht. (Weitere Informationen zu diesem Thema finden Sie in der CORBA-Spezifikation.)
Die Methode registriereBank() in den Zeilen 28-34 fügt die betreffende Bank zur intern verwalteten Bankenliste der Klasse BankServerImpl hinzu (im Datenelement meineBanken). Beachten Sie die Verwendung der Methode _duplicate() für die Klasse Bank, mit der der Referenzzähler des betreffenden Bank-Objekts um Eins erhöht wird. Dadurch wird dem Bank-Objekt angegeben, daß die Klasse BankServerImpl eine Referenz auf das Objekt beibehalten wird. Dieser Referenzzähler wiederum verwaltet einfach die Anzahl der ausstehenden Referenzen für das Objekt. (Ich zeige Ihnen später, wie BankServerImpl die Referenz auf ein Bankobjekt wieder freigibt.) Als nächstes sollen nun verschiedene Teile der Methode aufhebenRegistrierungBank() untersucht werden. Im ersten Teil der Methode aufhebenRegistrierungBank() in den Zeilen 36-52 wird eine Iteration durch die interne Bankenliste von BankServerImpl durchgeführt (auch diese ist im Datenelement meineBanken gespeichert). Mit Hilfe der bereits erwähnten Klasse IstBankGleich wird die Gleichheit der Bank-Referenzen untersucht. In diesem Beispiel verwendet die Methode std::find_if() ein Objekt vom Typ IstBankGleich, um die Objektreferenzen zu vergleichen. Dieser Schritt ist notwendig, damit die Methode BankServerImpl die Bank aus der internen Bankenliste entfernen kann, wenn die Registrierung einer Bank rückgängig gemacht wird. Im letzten Teil der Methode aufhebenRegistrierungBank() (Zeilen 54-56) wird die betreffende Bank zunächst mit Hilfe der Methode erase()aus dem Datenelement meineBanken entfernt. Dann teilt BankServerImpl der Bank mit, daß keine Referenz mehr auf sie verwaltet wird. Dies erfolgt durch Aufruf der Methode _release(). _release() ist das Gegenstück zur zuvor bereits erwähnten Methode _duplicate() und dient zum Verringern des Referenzzählers für das betreffende Objekt um Eins. Wenn der Referenzzähler für das Objekt den Wert Null erreicht, kann das Objekt aus dem Speicher gelöscht werden (dies muß aber nicht zwangsläufig geschehen). Da BankServerImpl den Referenzzähler beim Registrieren einer Bank um Eins erhöht und beim Rückgängigmachen der Registrierung um Eins verringert, ist der Netto-Änderungswert des Referenzzählers für eine Bank gleich Null, wenn diese registriert und anschließend die Registrierung rückgängig gemacht wurde.
Selbständig führt die Klasse BankServerImpl keine sinnvollen Operationen aus. Um die genannten Funktionen zu verwirklichen, müssen Sie Quelltext zur Verfügung stellen, mit dem ein Objekt der Klasse BankServerImpl erstellt und anderen Objekten im Netzwerk zur Verfügung gestellt wird. Dies erfolgt über BankServerHaupt.cpp (siehe Listing 6.4). Listing 6.4: BankServerHaupt.cpp. 1: // BankServerHaupt.cpp 2: 3: #include "BankServerImpl.h" 4: #include 5: 6: int main(int argc, char *const *argv) { 7: 8: // ORB und BOA initialisieren. 9: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 10: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 11: 12: // Ein BankServerImpl-Objekt erzeugen. 13: BankServerImpl bankServer; 14: 15: // BOA informieren, daß das BankServerImpl-Objekt bereit ist. 16: boa->obj_is_ready(&bankServer); 17: 18: // Auf CORBA-Ereignisse warten. 19: cout << "BankServer bereit." << endl; 20: boa->impl_is_ready(); 21: 22: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 23: return 0; 24: } Die erste Aufgabe einer CORBA-Anwendung besteht darin, ihre Umgebung, also den ORB (Object Request Broker) und den BOA (Basic Object Adapter), zu initialisieren. Dieser Schritt erfolgt über die in den Zeilen 8-10 genannten Methoden ORB_init() und BOA_init(). Nach der Initialisierung des ORB und des BOA können weitere CORBA-Objekte erstellt und dem Netzwerk zur Verfügung gestellt werden. Dies erfolgt für jedes Objekt einzeln über die in den Zeilen 15-16 enthaltene Methode obj_is_ready(). Der vorausgehende Quelltext teilt dem BOA mit, daß das Objekt BankServerImpl jetzt für die Benutzung durch andere Objekte im Netzwerk vorbereitet ist. Der BOA stellt auch die Methode impl_is_ready() zur Verfügung, die in den Zeilen 18-20 enthalten ist. Durch die Methode impl_is_ready() wird dem BOA mitgeteilt, daß die Anwendung nun für den Empfang von Ereignissen vorbereitet ist. In der Regel wartet die Methode impl_is_ready() während
eines von der Implementierung abhängigen Zeitraums auf Ereignisse; dieser Zeitraum kann normalerweise vom Anwendungsentwickler festgelegt werden. So könnte beispielsweise festgelegt werden, daß die Methode impl_is_ready() Ereignisse verarbeiten kann, bis die Anwendung unterbrochen wird, oder die Anwendung wird nach Ablauf eines bestimmten, festgelegten Zeitraums (z.B. nach einer Stunde) beendet, wenn bis dahin keine Ereignisse empfangen wurden. Jetzt haben Sie die BankServer-Schnittstelle so weit implementiert, daß Sie sich mit der Schnittstelle Bank beschäftigen können, die zur Interaktion mit dem BankServer verwendet wird. Implementierung der Schnittstelle Bank Wie bereits am 5. Tag erwähnt, dient die Schnittstelle Bank dazu, die von einer Bank zur Verfügung gestellten Dienste zu beschreiben. Hierzu gehört in der Regel die Verwendung der Konten der betreffenden Bank. Der IDL-Quelltext für die Schnittstelle Bank ist in Listing 6.5 aufgeführt. Listing 6.5: Bank.idl. 1: // Bank.idl 2: 3: // Forward-Deklaration der Schnittstelle Bank. 4: interface Bank; 5: 6: #ifndef Bank_idl 7: #define Bank_idl 8: 9: // Banksequenzen 10: typedef sequence BankenListe; 11: 12: #include "Kunde.idl" 13: #include "Konto.idl" 14: 15: // Bank ermöglicht Zugriff auf Konten und kann für 16: // einen Kunden ein Konto erzeugen, ein Konto löschen 17: // oder die aktuellen Konten der Banken auflisten. 18: interface Bank { 19: 20: // Name dieser Bank. 21: attribute string name; 22: 23: // Adresse dieser Bank. 24: attribute string adresse; 25: 26: // Ein Konto für den angegebenen Kunden erzeugen, unter Berücksichtigung 27: // der angegebenen Kontoart ("Sparkonto" oder "Girokonto", wobei auf die
28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:
// Schreibweise zu achten ist) und des angegebenen Saldos. Konto erstelleKonto(in Kunde Kunde, in string KontoArt, in float eroeffnungsSaldo); // Angegebenes Konto löschen. Gehört das Konto nicht zu dieser // Bank, führt diese Funktion nichts aus. void loescheKonto(in Konto Konto); // Alle Konten dieser Bank auflisten. KontoListe getKonten(); }; #endif
Hier müssen Sie Implementierungen für die Methoden name() und adresse() zur Verfügung stellen, wobei bei beiden für die Attribute name und adresse Datenzugriffs- und Datenänderungsmethoden vorzusehen sind. Dazu kommen noch die Methoden erstelleKonto(), loescheKonto() und getKonten() sowie der bzw. die Konstruktor(en) und der Destruktor für diese Klasse. Im Vergleich zur Methode BankServerImpl.h (die ich in Listing 6.2 gezeigt habe) wird Ihnen BankImpl.h nicht sonderlich neu vorkommen (siehe Listing 6.6). Auch hier ist die Abbildung der IDLMethoden auf die C++-Methoden einfach (obwohl Sie auch feststellen werden, daß der Datentyp CORBA::Float auf den der IDL-Typ float abgebildet wird), und Sie können feststellen, daß STL für BankImpl in ähnlicher Weise verwendet wird wie bei BankServerImpl. Listing 6.6: BankImpl.h. 1: // BankImpl.h 2: 3: #ifndef BankImpl_h 4: #define BankImpl_h 5: 6: #include 7: 8: #include "../Bank_s.h" 9: 10: class BankImpl : public _sk_Bank { 11: 12: public: 13: 14: // Konstruktor. 15: // 16: // name - Dies ist der Name der Bank. 17: BankImpl(const char* name); 18: 19: // Destruktor.
20: ~BankImpl(); 21: 22: // Diese Methoden werden in Bank.idl beschrieben. 23: virtual char* name(); 24: virtual void name(const char* val); 25: virtual char* adresse(); 26: virtual void adresse(const char* val); 27: virtual Konto_ptr erstelleKonto(Kunde_ptr Kunde, 28: const char* KontoArt, CORBA::Float eroeffnungsSaldo); 29: virtual void loescheKonto(Konto_ptr Konto); 30: virtual KontoListe* getKonten(); 31: 32: protected: 33: 34: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird 35: // in einem statischen Puffer zurückgegeben. 36: char* getNaechsteKontoNummer(); 37: 38: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis 39: // wird in einem statischen Puffer zurückgeliefert. 40: char* getCurrentDate(); 41: 42: private: 43: 44: // Standard-Konstruktor. 45: BankImpl(); 46: 47: // Dies ist der Name der Bank. 48: char* meinName; 49: 50: // Die Adresse der Bank. 51: char* meineAdresse; 52: 53: // Die Konten dieser Bank. 54: std::vector meineKonten; 55: 56: // Die nächste verfügbare Kontonummer. 57: unsigned int meineNaechsteKontoNummer; 58: }; 59: 60: #endif In Zeile 14 von Listing 6.7 ist zu sehen, daß BankHaupt.cpp eine globale Variable namens boa definiert, bei der es sich um eine Referenz auf den von der Anwendung verwendeten BOA (Basic
Object Adapter) handelt. In einer Beispielanwendung ist die Verwendung einer globalen boa-Variablen wegen ihrer Einfachheit sinnvoll, aber beim Erstellen einer kommerziellen Anwendung wäre es sinnvoll, einen saubereren Mechanismus für die gemeinsame Verwendung der Referenz zum BOA zu schaffen. Hierzu können Sie beispielsweise eine Klasse definieren, die den BOA über ein statisches Datenelement verfügbar macht, oder Sie könnten Klassenkonstruktoren schreiben, in denen ein BOA als Argument verwendet wird. Unabhängig davon, wie Sie dieses Problem lösen, wird es manchmal notwendig sein, daß verschiedene Objekte in einer Anwendung auf den BOA zugreifen. (In diesem Beispiel muß die Klasse BankImpl die Methode obj_is_ready() für die von ihr erstellten KontoObjekte aufrufen.) Listing 6.7: BankImpl.cpp. 1: // BankImpl.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: #include <string.h> 7: #include 8: #include 9: #include 10: 11: #include "SparKontoImpl.h" 12: #include "GiroKontoImpl.h" 13: 14: extern CORBA::BOA_var boa; 15: 16: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Banken 17: // gleich sind. 18: class IstKontoGleich : public std::unary_function { 20: public: 21: IstKontoGleich(argument_type Konto) { meinKonto = Konto; } 22: result_type operator()(argument_type Konto) { return Konto-> 23: _is_equivalent(meinKonto) != 0; } 24: private: 25: argument_type meinKonto; 26: }; 27: 28: // Konstruktor. 29: // 30: // name - Name dieser Bank. 31: BankImpl::BankImpl(const char* name) : meineKonten(), 32: meinName(strdup(name)), meineAdresse(strdup("123 Elm Street, " 33: "Anyware USA 12345")), meineNaechsteKontoNummer(0) {
34: 35: } 36: 37: // Standard-Konstruktor. 38: BankImpl::BankImpl() : meineKonten(), meinName(NULL), meineAdresse(NULL), 39: meineNaechsteKontoNummer(0) { 40: 41: } 42: 43: // Destruktor. 44: BankImpl::~BankImpl() { 45: 46: cout << "Bank \"" << name() << "\" wird gelöscht." << endl; 47: free(meinName); 48: free(meineAdresse); 49: } 50: 51: char* BankImpl::name() { 52: 53: return CORBA::strdup(meinName); 54: } 55: 56: void BankImpl::name(const char* val) { 57: 58: free(meinName); 59: meinName = strdup(val); 60: } 61: 62: char* BankImpl::adresse() { 63: 64: return CORBA::strdup(meineAdresse); 65: } 66: 67: void BankImpl::adresse(const char* val) { 68: 69: free(meineAdresse); 70: meineAdresse = strdup(val); 71: } 72: 73: Konto_ptr BankImpl::erstelleKonto(Kunde_ptr Kunde, 74: const char* KontoArt, CORBA::Float eroeffnungsSaldo) { 75: 76: Konto_ptr neuesKonto; 77: 78: if (strcmp(KontoArt, "savings") == 0) {
79: 80: // Erzeuge ein neues SparKontoImpl-Objekt für das Konto. 81: cout << "BankImpl: Erzeuge neues Sparkonto für " 82: "Kunde " << Kunde->name() << "." << endl; 83: neuesKonto = new SparKontoImpl(getNaechsteKontoNummer(), 84: getCurrentDate(), eroeffnungsSaldo, Kunde, 10.0); 85: } else if (strcmp(KontoArt, "checking") == 0) { 86: 87: // Erzeuge ein neues GiroKontoImpl-Objekt für das Konto. 88: cout << "BankImpl: Erzeuge neues Girokonto für " 89: "Kunden " << Kunde->name() << "." << endl; 90: neuesKonto = new GiroKontoImpl(getNaechsteKontoNummer(), 91: getCurrentDate(), eroeffnungsSaldo, Kunde); 92: } else { 93: 94: // Ungültige Kontoart; keine Aktion ausführen. 95: cout << "BankImpl: Kunde " << Kunde->name() << 96: " hat ungültige Kontoart angefordert \"" << KontoArt 97: << "\"." << endl; 98: return Konto::_nil(); 99: } 100: 101: // Das erzeugte Konto an das Listenende einfügen und zurückliefern. 102: ::boa->obj_is_ready(neuesKonto); 103: meineKonten.push_back(Konto::_duplicate(neuesKonto)); 104: return neuesKonto; 105: } 106: 107: void BankImpl::loescheKonto(Konto_ptr Konto) { 108: 109: std::vector::iterator first = meineKonten.begin(); 110: std::vector::iterator last = meineKonten.end(); 111: IstKontoGleich predicate(Konto); 112: 113: std::vector::iterator uebereinstimmendesKonto = std:: 114: find_if(first, last, predicate); 115: if (uebereinstimmendesKonto == last) { 116: 117: // Ungültiges Konto; keine Aktion ausführen. 118: cout << "BankImpl: Versuch ungültiges Konto zu löschen wurde " << 119: "ignoriert." << endl; 120: return; 121: }
122: cout << "BankImpl: Lösche Konto \"" << Konto-> 123: KontoNummer() << "\"." << endl; 124: 125: // Angegebenes Konto löschen. 126: meineKonten.erase(uebereinstimmendesKonto); 127: Konto->_release(); 128: } 129: 130: KontoListe* BankImpl::getKonten() { 131: 132: KontoListe* list = new KontoListe(meineKonten.size()); 133: CORBA::Long i; 134: 135: for (i = 0; i < meineKonten.size(); i++) { 136: (*list)[i] = Konto::_duplicate(meineKonten[i]); 137: } 138: 139: return list; 140: } 141: 142: // Die nächste verfügbare Kontonummer zurückliefern. Das Ergebnis wird in einem 143: // statischen Puffer zurückgegeben. 144: char* BankImpl::getNaechsteKontoNummer() { 145: 146: static char KontoNummer[16] = "Konto "; 147: 148: sprintf(KontoNummer + 7, "%08u", meineNaechsteKontoNummer++); 149: 150: return KontoNummer; 151: } 152: 153: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis 154: // wird in einem statischen Puffer zurückgeliefert. 155: char* BankImpl::getCurrentDate() { 156: 157: static char aktuellesDatum[12] = " "; 158: 159: time_t ltime; 160: time(<ime); 161: char* ctimeResult = ctime(<ime); 162: 163: memcpy(aktuellesDatum, ctimeResult + 4, 3); 164: memcpy(aktuellesDatum + 4, ctimeResult + 8, 2); 165: memcpy(aktuellesDatum + 7, ctimeResult + 20, 4);
166: 167: return aktuellesDatum; 168: } Jetzt möchte ich Ihnen die wichtigsten Merkmale von BankImpl.cpp (siehe Listing 6.7) erläutern. Beachten Sie, daß das Zurückgeben einer Zeichenkette durch eine CORBA-Methode, wie beim ersten Auftreten der Methode name() in den Zeilen 51-54, korrekt erfolgen muß. Wenn eine CORBAMethode einen String zurückgibt, muß für die betreffende Zeichenkette die Methode CORBA::strdup() verwendet werden (diese weicht von der STL-Methode strdup() ab). Die Methode CORBA::strdup() ermöglicht es der Anwendung, den von der Zeichenkette belegten Speicher wieder freizugeben, nachdem diese per Formatübertragung an die aufrufende Methode zurückgeschickt wurde. Im vorstehenden Beispiel wird dies für die Datenzugriffsmethode name() gezeigt, für die Datenzugriffsmethode adresse() wird analog vorgegangen. Beachten Sie auch die letzten Zeilen (101-104) in der Methode erstelleKonto(). Jedesmal beim Erstellen eines neuen Objekts vom Typ Konto muß der BOA darüber informiert werden, daß das Objekt »einsatzbereit« ist. Dies erfolgt wiederum mit Hilfe der Methode obj_is_ready(). (Aus diesem Grund benötigt das Objekt BankImpl eine Sicht auf den BOA.) Beachten Sie außerdem folgendes: Bevor das neu erstellte Objekt vom Typ Konto an die aufrufende Methode zurückgegeben wird, wurde sein Referenzzähler über die Methode _duplicate() um Eins erhöht. Dies ist wichtig, weil beim Zurückgeben einer Objektreferenz an eine aufrufende Methode (entweder als Rückgabewert oder als Parameter out oder inout) der Referenzzähler um Eins herabgesetzt wird. Wenn daher eine CORBA-Objektreferenz auf diese Weise zurückgegeben wird, müssen Sie die betreffende Objektreferenz zuvor mit _duplicate() duplizieren. Der Rest von BankImpl.cpp dürfte den C++-Programmierern bekannt vorkommen bzw. weist eine Analogie zu BankServerImpl.cpp auf. Wie BankServerImpl muß auch BankImpl noch mit zusätzlichem Quelltext versehen werden, damit BankImpl gestartet und für das Netz verfügbar gemacht werden kann. Dieser Quelltext ist in Listing 6.8 dargestellt. Listing 6.8: BankHaupt.cpp. 1: // BankHaupt.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: 7: #include "../BankServer_c.h" 8: 9: CORBA::BOA_var boa; 10: 11: int main(int argc, char *const *argv) { 12:
13: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 14: // (zwei, mit dem Namen des Programms zusammen). 15: if (argc != 2) { 16: cout << "Verwendung: Bank " << endl; 17: return 1; 18: } 19: 20: // Namen der Bank dem ersten Parameter zuweisen. 21: const char* bankName = argv[1]; 22: 23: // ORB und BOA initialisieren. 24: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 25: ::boa = orb->BOA_init(argc, argv); 26: 27: // Ein Bank-Objekt erzeugen. 28: BankImpl bank(bankName); 29: 30: // BOA benachrichtigen, daß das BankImpl-Objekt bereit ist. 31: ::boa->obj_is_ready(&bank); 32: 33: // Ein BankServer-Objekt finden und damit registrieren. 34: BankServer_var bankServer; 35: try { 36: bankServer = BankServer::_bind(); 37: } catch (const CORBA::Exception& ex) { 38: 39: // Die Bindung ist mißlungen... 40: cout << "BankImpl: Bindung mit einem BankServer nicht möglich." << endl; 41: cout << ex << endl; 42: return 1; 43: } 44: try { 45: bankServer->registriereBank(&bank); 46: } catch (const CORBA::Exception& ex) { 47: 48: // Befehl registriereBank() mißlungen... 49: cout << "BankImpl: Bank konnte nicht registriert werden." << endl; 50: cout << ex << endl; 51: return 1; 52: } 53: 54: // Auf CORBA-Ereignisse warten. 55: cout << "Bank \"" << bankName << "\" bereit." << endl;
56: 57: 58: 59: 60:
::boa->impl_is_ready(); // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. return 0; }
Ein Hauptunterschied zwischen BankServerHaupt.cpp und BankHaupt.cpp besteht darin, daß ein Objekt vom Typ BankServer andere Objekte nicht zu suchen und keine Verbindung mit diesen herzustellen braucht, während ein Objekt vom Typ Bank einen BankServer suchen und sich bei diesem registrieren lassen muß. Dies erfolgt über den Quelltext in den Zeilen 33-43. Mit dem Aufruf BankServer::_bind() wird versucht, eine Verbindung zu einem BankServer-Objekt herzustellen. Zusätzlich kann für _bind() der Name des Objekts angegeben werden, mit dem die Verbindung erfolgen soll. Wenn aber kein Name angegeben wird, versucht _bind(), eine Verbindung zu einem beliebigen verfügbaren Objekt des angeforderten Typs herzustellen. Wenn der Versuch, _bind() auszuführen, fehlschlägt, wird eine CORBA::Exception ausgelöst, abgefangen und ihr Inhalt an der Konsole ausgegeben. Obwohl die Methode _bind() in verschiedenen ORB-Produkten verfügbar ist (unter anderem in Orbix von IONA Technologies und in den VisiBroker-Produkten von Visigenic), ist sie kein Bestandteil des CORBA-Standards. Auf jeden Fall ist die Verwendung von _bind() für umfangreiche kommerzielle Anwendungen sowieso nicht geeignet, und Sie werden daher bevorzugt mit dem CORBA-Bezeichnungsdienst oder Vermittlungsdienst (Trading Service) arbeiten, um Objekte im Netzwerk zu finden. (Am 12. Tag werden die CORBAservices ausführlich behandelt.)
Wenn die Verbindung der Anwendung mit einem Objekt vom Typ BankServer erfolgreich durchgeführt werden konnte, wird die Bank dort registriert (siehe Zeilen 44-52). Hier ist registriereBank() die Remote-Methode der BankServer-Schnittstelle. Wie dies bei allen Remote-Methoden der Fall sein kann, kann registriereBank() eine CORBA::Exception auslösen, die dann von einer Anwendung abgefangen werden sollte. In diesem Fall wird die Exception abgefangen, und es wird eine Fehlermeldung ausgegeben. Bei einem Objekt vom Typ Bank handelt es sich im wesentlichen um ein »Sammelbecken« für Objekte vom Typ Konto (Konten). Im nächsten Abschnitt werden Sie nun die Implementierungen der Schnittstelle Konto und der von ihr abgeleiteten Schnittstellen untersuchen. Implementierung der Schnittstelle Konto Über die Schnittstelle Konto wird die Funktionalität eines allgemeinen Bankkontos definiert, wie
beispielsweise das Abheben oder Einzahlen von Geld. Der IDL-Quelltext für die Schnittstelle Konto ist in Listing 6.9 dargestellt. Listing 6.9: Konto.idl. 1: // Konto.idl 2: 3: // Forward-Deklaration der Schnittstelle Konto. 4: interface Konto; 5: 6: #ifndef Konto_idl 7: #define Konto_idl 8: 9: // Konten-Sequenz 10: typedef sequence KontoListe; 11: 12: #include "Kunde.idl" 13: 14: // Ein Konto ist eine Einheit, die zu einer Bank gehört und von einem Kunden geführt wird 15: // (oder mehreren Kunden). Ein Konto hat einen Saldo, welcher durch 16: // Einzahlungen und Abhebungen geändert wird. 17: interface Konto { 18: 19: // Dies ist die Nummer des Kontos. 20: readonly attribute string KontoNummer; 21: 22: // Das Erstellungsdatum des Kontos. 23: readonly attribute string EroefffnungsDatum; 24: 25: // Der aktuelle Kontostand. 26: readonly attribute float Kontostand; 27: 28: // Liste der Kunden zurückliefern, die dieses Konto führen. 29: KundenListe getKunden(); 30: 31: // Angegebenen Betrag von diesem Konto abheben. Rückgabewert ist der 32: // neue Kontostand. 33: float Abhebung(in float Betrag); 34: 35: // Angegebenen Betrag auf dieses Konto einzahlen. Rückgabewert ist der 36: // neue Kontostand. 37: float Einzahlung(in float Betrag);
38: }; 39: 40: #endif Hier benötigen Sie Implementierungen für die folgenden Methoden: KontoNummer(), EroefffnungsDatum() und Kontostand(), bei denen es sich um Datenzugriffsmethoden für die Attribute KontoNummer, EroefffnungsDatum und Kontostand handelt. Ferner werden die Methoden getKunden(), Abhebung() und Einzahlung() zusammen mit dem bzw. den Konstruktor(en) und dem Destruktor für diese Klasse benötigt. Die Header-Datei für die Implementierung (KontoImpl.h) ist in Listing 6.10 dargestellt, die Implementierung selbst (KontoImpl.cpp) in Listing 6.11. Listing 6.10: KontoImpl.h. 1: // KontoImpl.h 2: 3: #ifndef KontoImpl_h 4: #define KontoImpl_h 5: 6: #include "../Konto_s.h" 7: 8: class KontoImpl : public _sk_Konto { 9: 10: // erlaubt GiroKontoImpl und SparKontoImpl den Zugriff auf den 11: // geschützten Konstruktor. 12: friend class GiroKontoImpl; 13: friend class SparKontoImpl; 14: 15: public: 16: 17: // Destruktor. 18: ~KontoImpl(); 19: 20: // Diese Methoden werden in Konto.idl beschrieben. 21: virtual char* KontoNummer(); 22: virtual char* EroefffnungsDatum(); 23: virtual CORBA::Float Kontostand(); 24: virtual KundenListe* getKunden(); 25: virtual CORBA::Float Abhebung(CORBA::Float Betrag); 26: virtual CORBA::Float Einzahlung(CORBA::Float Betrag); 27: 28: protected: 29: 30: // Konstruktor. 31: // 32: // KontoNummer - Kontonnummer. 33: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde.
34: // anfangsSaldo - Der anfängliche Kontostand. 35: // Kunde - Der erste Inhaber des Kontos. 36: KontoImpl(const char* KontoNummer, const char* EroefffnungsDatum, 37: CORBA::Float anfangsSaldo, Kunde_ptr Kunde); 38: 39: private: 40: 41: // Standard-Konstruktor. 42: KontoImpl(); 43: 44: // Dies ist die Nummer des Kontos. 45: char* meineKontoNummer; 46: 47: // Das Erstellungsdatum des Kontos. 48: char* meinEröffnungsdatum; 49: 50: // Der aktuelle Kontostand. 51: CORBA::Float meinKontostand; 52: 53: // Die Inhaber dieses Kontos. 54: KundenListe meineInhaber; 55: }; 56: 57: #endif Listing 6.11: KontoImpl.cpp. 1: // KontoImpl.cpp 2: 3: #include "KontoImpl.h" 4: 5: #include <string.h> 6: 7: // Konstruktor. 8: // 9: // KontoNummer - Kontonnummer. 10: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 11: // anfangsSaldo - Der anfängliche Kontostand. 12: // Kunde - Der erste Inhaber des Kontos. 13: KontoImpl::KontoImpl(const char* KontoNummer, const char* 14: EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr 15: Kunde) : meineKontoNummer(strdup(KontoNummer)), 16: meinEröffnungsdatum(strdup(EroefffnungsDatum)), 17: meinKontostand(anfangsSaldo), meineInhaber() { 18:
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
// Kunden zur Inhaberliste hinzufügen. meineInhaber.length(1); meineInhaber[0] = Kunde::_duplicate(Kunde); } // Standard-Konstruktor. KontoImpl::KontoImpl() : meineKontoNummer(NULL), meinEröffnungsdatum(NULL), meinKontostand(0.0), meineInhaber() { } // Destruktor. KontoImpl::~KontoImpl() { free(meineKontoNummer); free(meinEröffnungsdatum); } char* KontoImpl::KontoNummer() { return CORBA::strdup(meineKontoNummer); } char* KontoImpl::EroefffnungsDatum() { return CORBA::strdup(meinEröffnungsdatum); } CORBA::Float KontoImpl::Kontostand() { return meinKontostand; } KundenListe* KontoImpl::getKunden() { return &meineInhaber; } CORBA::Float KontoImpl::Abhebung(CORBA::Float Betrag) { meinKontostand -= Betrag; return meinKontostand; } CORBA::Float KontoImpl::Einzahlung(CORBA::Float Betrag) {
65: 66: meinKontostand += Betrag; 67: 68: return meinKontostand; 69: } Implementierung der Schnittstelle GiroKonto Die Schnittstelle GiroKonto ist diejenige Schnittstelle, die am einfachsten implementiert werden kann, weil hierfür keine zusätzlichen Methoden definiert werden müssen. Die IDL-Definition für die Schnittstelle GiroKonto ist in Listing 6.12 dargestellt. Listing 6.12: GiroKonto.idl. 1: // GiroKonto.idl 2: 3: #ifndef GiroKonto_idl 4: #define GiroKonto_idl 5: 6: #include "Konto.idl" 7: 8: // GiroKonto stellt ein Girokonto dar. Es bringt im Gegensatz 9: // zu seinem Gegenstück, dem Sparkonnto, keine Zinsen 10: // ein. 11: interface GiroKonto : Konto { 12: 13: }; 14: 15: #endif Da hier keine Attribute bzw. Methoden als Bestandteile der Schnittstelle GiroKonto definiert werden müssen, ist der Aufwand für die Implementierungsklasse gering. Es genügt, wenn Sie einen leeren Konstruktor bzw. Destruktor zur Verfügung stellen. Die Implementierung der Schnittstelle GiroKonto ist in den Listings 6.13 und 6.14 zu sehen. Listing 6.13: GiroKontoImpl.h. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// GiroKontoImpl.h #ifndef GiroKontoImpl_h #define GiroKontoImpl_h #include "../GiroKonto_s.h" #include "KontoImpl.h" class GiroKontoImpl : public _sk_GiroKonto {
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
public: // Konstruktor. // // KontoNummer - Kontonnummer. // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. // anfangsSaldo - Der anfängliche Kontostand. // Kunde - Der erste Inhaber des Kontos. GiroKontoImpl(const char* KontoNummer, const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr Kunde); // Destruktor. ~GiroKontoImpl(); // Diese Methoden werden in Konto.idl beschrieben. virtual char* KontoNummer(); virtual char* EroefffnungsDatum(); virtual CORBA::Float Kontostand(); virtual KundenListe* getKunden(); virtual CORBA::Float Abhebung(CORBA::Float Betrag); virtual CORBA::Float Einzahlung(CORBA::Float Betrag); private: // Standard-Konstruktor. GiroKontoImpl(); // Unser verknüpftes KontoImpl-Objekt. KontoImpl meinKonto; }; #endif
Listing 6.14: GiroKontoImpl.cpp. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// GiroKontoImpl.cpp #include "GiroKontoImpl.h" // // // // //
Konstruktor. KontoNummer - Kontonnummer. EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. anfangsSaldo - Der anfängliche Kontostand.
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
// Kunde - Der erste Inhaber des Kontos. GiroKontoImpl::GiroKontoImpl(const char* KontoNummer, const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr Kunde) : meinKonto(KontoNummer, EroefffnungsDatum, anfangsSaldo, Kunde) { } // Standard-Konstruktor. GiroKontoImpl::GiroKontoImpl() : meinKonto(NULL, NULL, 0.0, Kunde::_nil()) { } // Destruktor. GiroKontoImpl::~GiroKontoImpl() { } char* GiroKontoImpl::KontoNummer() { return meinKonto.KontoNummer(); } char* GiroKontoImpl::EroefffnungsDatum() { return meinKonto.EroefffnungsDatum(); } CORBA::Float GiroKontoImpl::Kontostand() { return meinKonto.Kontostand(); } KundenListe* GiroKontoImpl::getKunden() { return meinKonto.getKunden(); } CORBA::Float GiroKontoImpl::Abhebung(CORBA::Float Betrag) { return meinKonto.Abhebung(Betrag); } CORBA::Float GiroKontoImpl::Einzahlung(CORBA::Float Betrag) {
56: return meinKonto.Einzahlung(Betrag); 57: } Implementierung der Schnittstelle SparKonto Die Schnittstelle SparKonto ist etwas komplizierter als die Schnittstelle GiroKonto, so daß das Implementieren aufwendiger ist. Der IDL-Quelltext zur Definition von SparKonto ist in Listing 6.15 dargestellt. Listing 6.15: SparKonto.idl. 1: // SparKonto.idl 2: 3: #ifndef SparKonto_idl 4: #define SparKonto_idl 5: 6: #include "Konto.idl" 7: 8: // Ein SparKonto ist ein Konto, das Sparbuch-Merkmale, 9: // wie z.B. Zinsen, unterstützt. 10: interface SparKonto : Konto { 11: 12: // Der Zinsatz für dieses Konto. 13: readonly attribute float ZinsSatz; 14: 15: // Zinsatz für dieses Konto auf angegebenen Wert setzen. 16: // Rückgabewert ist der vorherige Zinssatz. 17: float setZinsSatz(in float Satz); 18: }; 19: 20: #endif Für die Implementierungsklasse SparKonto müssen Sie die Methode getZinsSatz() (eine Datenzugriffsmethode für das Attribut ZinsSatz), die Methode setZinsSatz() und den bzw. die benötigten Klassenkonstruktor(en) sowie den Destruktor definieren. Die Implementierungsklasse ist in den Listings 6.16 und 6.17 dargestellt. Listing 6.16: SparKontoImpl.h. 1: 2: 3: 4: 5: 6: 7:
// SparKontoImpl.h #ifndef SparKontoImpl_h #define SparKontoImpl_h #include "../SparKonto_s.h" #include "KontoImpl.h"
8: 9: class SparKontoImpl : public KontoImpl { 10: 11: public: 12: 13: // Konstruktor. 14: // 15: // KontoNummer - Kontonnummer. 16: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 17: // anfangsSaldo - Der anfängliche Kontostand. 18: // Kunde - Der erste Inhaber des Kontos. 19: // ZinsSatz - Anfänglicher Zinssatz des Kontos. 20: SparKontoImpl(const char* KontoNummer, const char* 21: EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr 22: Kunde, CORBA::Float ZinsSatz); 23: 24: // Destruktor. 25: ~SparKontoImpl(); 26: 27: // Diese Methoden werden in Konto.idl beschrieben. 28: virtual char* KontoNummer(); 29: virtual char* EroefffnungsDatum(); 30: virtual CORBA::Float Kontostand(); 31: virtual KundenListe* getKunden(); 32: virtual CORBA::Float Abhebung(CORBA::Float Betrag); 33: virtual CORBA::Float Einzahlung(CORBA::Float Betrag); 34: 35: // Diese Methoden werden in SparKonto.idl beschrieben. 36: virtual CORBA::Float ZinsSatz(); 37: virtual CORBA::Float setZinsSatz(CORBA::Float Satz); 38: 39: private: 40: 41: // Standard-Konstruktor. 42: SparKontoImpl(); 43: 44: // Der Zinsatz für dieses Konto. 45: CORBA::Float meinZinsSatz; 46: 47: // Unser verknüpftes KontoImpl-Objekt. 48: KontoImpl meinKonto; 49: }; 50: 51: #endif Listing 6.17: SparKontoImpl.cpp.
1: // SparKontoImpl.cpp 2: 3: #include "SparKontoImpl.h" 4: 5: // Konstruktor. 6: // 7: // KontoNummer - Kontonnummer. 8: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 9: // anfangsSaldo - Der anfängliche Kontostand. 10: // Kunde - Der erste Inhaber des Kontos. 11: // ZinsSatz - Anfänglicher Zinssatz des Kontos. 12: SparKontoImpl::SparKontoImpl(const char* KontoNummer, 13: const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, 14: Kunde_ptr Kunde, CORBA::Float ZinsSatz) : 15: meinKonto(KontoNummer, EroefffnungsDatum, anfangsSaldo, 16: Kunde), meinZinsSatz(ZinsSatz) { 17: 18: } 19: 20: // Standard-Konstruktor. 21: SparKontoImpl::SparKontoImpl() : meinKonto(NULL, NULL, 22: 0.0, Kunde::_nil()), meinZinsSatz(0.0) { 23: 24: } 25: 26: // Destruktor. 27: SparKontoImpl::~SparKontoImpl() { 28: 29: } 30: 31: char* SparKontoImpl::KontoNummer() { 32: 33: return meinKonto.KontoNummer(); 34: } 35: 36: char* SparKontoImpl::EroefffnungsDatum() { 37: 38: return meinKonto.EroefffnungsDatum(); 39: } 40: 41: CORBA::Float SparKontoImpl::Kontostand() { 42: 43: return meinKonto.Kontostand(); 44: } 45: 46: KundenListe* SparKontoImpl::getKunden() {
47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
return meinKonto.getKunden(); } CORBA::Float SparKontoImpl::Abhebung(CORBA::Float Betrag) { return meinKonto.Abhebung(Betrag); } CORBA::Float SparKontoImpl::Einzahlung(CORBA::Float Betrag) { return meinKonto.Einzahlung(Betrag); } CORBA::Float SparKontoImpl::ZinsSatz() { return meinZinsSatz; } CORBA::Float SparKontoImpl::setZinsSatz(CORBA::Float Satz) { CORBA::Float alterZinsSatz = meinZinsSatz; meinZinsSatz = Satz; return alterZinsSatz; }
Implementierung der grundlegenden Client-Funktionen Nachdem Sie nun die grundlegenden Funktionen für den CORBA-Server der Bankanwendung implementiert haben, werden Sie als nächstes die grundlegenden Client-Funktionen bearbeiten. Da in unserer Beispielanwendung die meiste Funktionalität vom Server abgedeckt wird, werden Sie das Implementieren des Clients vergleichsweise einfach finden. Implementierung der Schnittstelle Kunde Die Schnittstelle Kunde umfaßt die Attribute und Operationen eines Kunden einer Bank. Hauptsächlich dient diese Schnittstelle als Behälter für die Objekte vom Typ Konto. Die IDLDefinition für die Schnittstelle Kunde ist in Listing 6.18 dargestellt. Listing 6.18: Kunde.idl. 1: // Kunde.idl 2:
3: // Forward-Deklaration der Schnittstelle Kunde. 4: interface Kunde; 5: 6: #ifndef Kunde_idl 7: #define Kunde_idl 8: 9: // Kundensequenz 10: typedef sequence KundenListe; 11: 12: #include "Konto.idl" 13: 14: // Ein Kunde kann über ein (oder mehrere) Konten verfügen. Der 15: // Kunde ist wohl das Kernelement der Anwendung. 16: interface Kunde { 17: 18: // Der Name dieses Kunden. 19: attribute string name; 20: 21: // Die Personalausweisnummer dieses Kunden. 22: readonly attribute string PersonalAusweisNummer; 23: 24: // Die Anschrift dieses Kunden. 25: attribute string adresse; 26: 27: // Der Mädchenname der Mutter dieses Kunden. 28: readonly attribute string MuttersMaedchenName; 29: 30: // Eine Kontoliste zurückgeben, die von diesem 31: // Kunden (mit-) geführt wird. 32: KontoListe getKonten(); 33: }; 34: 35: #endif Für diese Schnittstelle müssen Sie die Methoden getName(), setName(), getPersonalAusweisNummer(), getAdresse(), setAdresse() und getMuttersMaedchenName() implementieren. Dabei handelt es sich um die Datenzugriffsmethoden für die verschiedenen für Konto definierten Attribute. Ferner müssen die Methode getKonten() sowie der bzw. die Klassenkonstruktor(en) und der Destruktor definiert werden. Die Implementierung für KundenImpl ist in den Listings 6.19 und 6.20 dargestellt. Listing 6.19: KundenImpl.h. 1: // KundenImpl.h 2: 3: #ifndef KundenImpl_h
4: #define KundenImpl_h 5: 6: #include "../Kunde_s.h" 7: 8: class KundenImpl : public _sk_Kunde { 9: 10: public: 11: 12: // Konstruktor. 13: // 14: // name - Name des Kunden. 15: // PersonalAusweisNummer - Die Personalausweisnummer des Kunden. 16: // adresse - Die Anschrift des Kunden. 17: // MuttersMaedchenName - Der Mädchenname der Mutter dieses Kunden. 18: KundenImpl(const char* name, const char* PersonalAusweisNummer, 19: const char* adresse, const char* MuttersMaedchenName); 20: 21: // Destruktor. 22: ~KundenImpl(); 23: 24: // Diese Methoden werden in Kunde.idl beschrieben. 25: virtual char* name(); 26: virtual void name(const char* val); 27: virtual char* PersonalAusweisNummer(); 28: virtual char* adresse(); 29: virtual void adresse(const char* val); 30: virtual char* MuttersMaedchenName(); 31: virtual KontoListe* getKonten(); 32: 33: private: 34: 35: // Standard-Konstruktor. 36: KundenImpl(); 37: 38: // Der Name dieses Kunden. 39: char* meinName; 40: 41: // Die Personalausweisnummer dieses Kunden. 42: char* meinePersonalAusweisNummer; 43: 44: // Die Anschrift dieses Kunden. 45: char* meineAdresse; 46: 47: // Der Mädchenname der Mutter dieses Kunden. 48: char* meineMuttersMaedchenName;
49: 50: 51: 52: 53: 54:
// Die Konten dieses Kunden. KontoListe meineKonten; }; #endif
Listing 6.20: KundenImpl.cpp. 1: // KundenImpl.cpp 2: 3: #include "KundenImpl.h" 4: 5: #include <string.h> 6: 7: // Konstruktor. 8: // 9: // name - Name des Kunden. 10: // PersonalAusweisNummer - Die Personalausweisnummer des Kunden. 11: // adresse - Die Anschrift des Kunden. 12: // MuttersMaedchenName - Der Mädchenname der Mutter dieses Kunden. 13: KundenImpl::KundenImpl(const char* name, const char* 14: PersonalAusweisNummer, const char* adresse, const char* 15: MuttersMaedchenName) : meinName(strdup(name)), 16: meinePersonalAusweisNummer(strdup(PersonalAusweisNummer)), 17: meineAdresse(strdup(adresse)), 18: meineMuttersMaedchenName(strdup(MuttersMaedchenName)), 19: meineKonten() { 20: 21: } 22: 23: // Standard-Konstruktor. 24: KundenImpl::KundenImpl() : meinName(NULL), 25: meinePersonalAusweisNummer(NULL), meineAdresse(NULL), 26: meineMuttersMaedchenName(NULL), meineKonten() { 27: 28: } 29: 30: // Destruktor. 31: KundenImpl::~KundenImpl() { 32: 33: free(meinName); 34: free(meinePersonalAusweisNummer); 35: free(meineAdresse); 36: free(meineMuttersMaedchenName);
37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74:
} char* KundenImpl::name() { return CORBA::strdup(meinName); } void KundenImpl::name(const char* val) { free(meinName); meinName = strdup(val); } char* KundenImpl::PersonalAusweisNummer() { return CORBA::strdup(meinePersonalAusweisNummer); } char* KundenImpl::adresse() { return CORBA::strdup(meineAdresse); } void KundenImpl::adresse(const char* val) { free(meineAdresse); meineAdresse = strdup(val); } char* KundenImpl::MuttersMaedchenName() { return CORBA::strdup(meineMuttersMaedchenName); } KontoListe* KundenImpl::getKonten() { return &meineKonten; }
Implementierung zusätzlicher Client-Funktionen Zusätzlich zur Schnittstelle Kunde müssen für jeden sinnvollen Client weitere Funktionen implementiert werden. Wenn Sie sich die Implementierung der Schnittstelle Kunde einmal genauer ansehen, wird Ihnen auch klar, warum dies so ist: Obwohl es mit der Implementierung möglich ist, daß ein Objekt des Typs Kunde mit verschiedenen anderen Komponenten der Bankanwendung (beispielsweise Objekte vom Typ Bank und Konto) in Interaktion tritt, ist in der Implementierung der
Schnittstelle Kunde keine Funktionalität vorhanden, die die Client-Anwendung anweist, direkt Aufgaben durchzuführen. Aus diesem Grund besteht jede Client-Anwendung nicht nur aus der Implementierung der Schnittstelle Kunde, sondern erhält weitere Funktionen, mit denen sinnvolle Aufgaben durchgeführt werden können. Listing 6.21 enthält eine Beispiel-Client-Anwendung, die ein neues Objekt vom Typ Kunde erstellt, ein neues Objekt vom Typ Konto bei einer Bank erstellt (also ein Konto eröffnet) und anschließend einige Operationen (eine Einlage und eine Abhebung) mit diesem Konto durchführt. Diese Anwendung ist sehr einfach und nicht besonders interaktiv, da alle benötigten Parameter über die Befehlszeile eingegeben werden müssen, aber sie zeigt, wie Operationen mit CORBA-Objekten aufgerufen werden. Listing 6.21: NeuerKundeHauptmodul.cpp. 1: // NeuerKundeHauptmodul.cpp 2: 3: #include 4: 5: #include "../Kunde/KundenImpl.h" 6: 7: #include "../Bank_c.h" 8: 9: int main(int argc, char *const *argv) { 10: 11: // Anzahl der Parameter überprüfen; es sollten genau vier vorliegen 12: // (zusammen mit dem Programmnamen fünf). 13: if (argc != 5) { 14: cout << "Verwendung: NeuerKunder " 15: " <Mädchenname der Mutter>" << endl; 16: return 1; 17: } 18: 19: // Befehlszeilenparamater den Attributen des Kunden zuweisen. 20: const char* name = argv[1]; 21: const char* PersonalAusweisNummer = argv[2]; 22: const char* adresse = argv[3]; 23: const char* MuttersMaedchenName = argv[4]; 24: 25: // ORB und BOA initialisieren. 26: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 27: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 28: 29: // Ein Objekt Kunde erzeugen. 30: cout << "NeuerKunde: Erzeuge neuen Kunden:" << endl; 31: cout << " Name: " << name << endl;
32: cout << " Personalausweisnummer: " << PersonalAusweisNummer << 33: endl; 34: cout << " Adresse: " << adresse << endl; 35: cout << " Mädchenname der Mutter: " << MuttersMaedchenName << endl; 36: KundenImpl Kunde(name, PersonalAusweisNummer, adresse, 37: MuttersMaedchenName); 38: 39: // BOA mitteilen, daß das Objekt KundenImpl bereit ist. 40: boa->obj_is_ready(&Kunde); 41: 42: // Bank-Objekt finden und damit registrieren. 43: Bank_var bank; 44: try { 45: bank = Bank::_bind(); 46: } catch (const CORBA::Exception& ex) { 47: 48: // Die Bindung ist mißlungen... 49: cout << "NeuerKunde: Kann Bindung mit Bank nicht erzeugen." << endl; 50: cout << ex << endl; 51: return 1; 52: } 53: 54: // Einige interessante Dinge ausführen. 55: 56: cout << "NeuerKunde: Verbunden mit Bank \"" << bank->name() << 57: "\"." << endl; 58: Konto_var Konto; 59: 60: try { 61: Konto = bank->erstelleKonto(&Kunde, "checking", 0.0); 62: } catch (const CORBA::Exception& ex) { 63: 64: // erstelleKonto() ist mißlungen... 65: cout << "NeuerKunde: Kann Konto nicht erzeugen." << endl; 66: cout << ex << endl; 67: return 1; 68: } 69: 70: try { 71: 72: // Einige Kontostatistiken ausgeben. 73: cout << "NeuerKunde: Neues Konto wurde eröffnet:" << endl; 74: cout << " Kontonummer: " << Konto->KontoNummer() << 75: endl;
76: cout << " EroefffnungsDatum: " << Konto->EroefffnungsDatum() << 77: endl; 78: cout << " Kontostand: " << Konto->Kontostand() << endl; 79: 80: // Einige Kontotransaktionen durchführen. 81: cout << "NeuerKunde: Transaktionen werden durchgeführt." << endl; 82: cout << " Einzahlung in Höhe von 250,00 DM; neuer Kontostand: DM" << 83: Konto->Einzahlung(250.00) << endl; 84: cout << " Abhebung in Höhe von 500,00 DM; neuer Kontostand: DM" << 85: Konto->Abhebung(500.00) << " (Ja wie kommt denn so was?)" << endl; 86: 87: // Das Konto löschen. 88: bank->loescheKonto(Konto); 89: } catch (const CORBA::Exception& ex) { 90: 91: // Eine Kontofunktion konnte nicht ausgeführt werden ... 92: cout << "NeuerKunde: Fehler beim Versuch auf Konto zuzugreifen." << endl; 93: cout << ex << endl; 94: return 1; 95: } 96: 97: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 98: return 0; 99: }
Ausführung des Beispiel-Quelltexts Jetzt haben Sie alle Server- und Client-Komponenten der Bankanwendung implementiert und können diese ausprobieren. Das Ausführen der Anwendung besteht aus den folgenden Schritten: 1. Starten des CORBA-Bezeichnungsdienstes oder eines ähnlichen Dienstes, der beim Starten des BankServer benötigt wird. 0. Starten des Servers BankServer. Dieser läßt sich beim Bezeichnungsdienst registrieren. 0. Starten eines oder mehrerer Bank-Server. Beim Starten sucht jeder Bank-Server über den Bezeichnungsdienst einen BankServer-Server, bei dem er sich registrieren läßt. 0. Ausführen der Client-Anwendung NeuerKunde. Diese Anwendung erstellt einen neuen Kunden und ein neues Konto. Damit wird geprüft, ob die Objekte Bank und BankServer ordnungsgemäß funktionieren. Starten des CORBA-Bezeichnungsdienstes
Im allgemeinen müssen Sie vor dem Starten einer CORBA-Anwendung den Mechanismus aufrufen, mit dessen Hilfe die einzelnen Komponenten der Anwendung sich gegenseitig finden können. In einigen Fällen handelt es sich hierbei um den CORBA-Bezeichnungsdienst (den Sie später noch kennenlernen werden), in anderen Fällen um eine einfache auszuführende Datei, die zum verwendeten CORBA-Produkt gehört. Da sich die Methoden, mit denen diese ausführbaren Dateien aufgerufen werden, von einem Produkt zum anderen unterscheiden, sollten Sie sich zunächst in der Produktdokumentation über die betreffende Verfahrensweise informieren. Wenn Sie mit VisiBroker von Visigenic arbeiten, müssen Sie das mitgelieferte Dienstprogramm osagent verwenden. Das Ausführen dieses Dienstprogramms ist denkbar einfach. Sie brauchen in der Befehlszeile nur den folgenden Befehl einzugeben: osagent Das Dienstprogramm osagent liefert keine Ausgabe, startet aber einen neuen Prozeß (der bei Windows 95 bzw. Windows NT 4.0 in der Taskleiste als Symbol angezeigt wird). Dieser Prozeß, der oft im Unix-Jargon als Daemon bezeichnet wird, heißt ORBeline Smart Agent und ermöglicht es CORBA-Anwendungen, die mit VisiBroker erstellt wurden, einander zu finden. Wie in der Dokumentation zu VisiBroker angegeben, sollten Sie sicherstellen, daß das bin-Verzeichnis für VisiBroker in der Umgebungsvariablen PATH Ihres Systems enthalten ist. (Wie Verzeichnisse zur PATH-Variablen hinzugefügt werden, hängt vom verwendeten System ab. Schlagen Sie in der Dokumentation zu VisiBroker oder in Ihrer Systemdokumentation nach, wenn Sie Informationen zu diesem Thema benötigen.) Auch andere Produkte funktionieren ähnlich, so daß es oft praktisch ist, das Verzeichnis bin in die PATH-Variable des Systems aufzunehmen.
Starten der Komponente BankServer Nach dem Starten des Bezeichnungsdienstes (bzw. von ORBeline Smart Agent, wenn Sie mit VisiBroker arbeiten), können Sie die Anwendungskomponente BankServer starten. Diese muß vor allen übrigen Komponenten der Anwendung laufen, weil für die Objekte vom Typ Kunde Verbindungen mit den Objekten vom Typ Bank hergestellt werden müssen, die wiederum Verbindungen zu Objekten vom Typ BankServer herstellen müssen. Zum Starten des BankServer wechseln Sie zunächst in das Verzeichnis, in dem sich die ausführbare Datei für die Komponente BankServer befindet. (Wenn Sie mit Visual C++ von Microsoft arbeiten, legt der Compiler die ausführbare Datei standardmäßig in einem Verzeichnis unterhalb des Projektverzeichnisses ab, das je nach Version der kompilierten ausführbaren Datei entweder Debug oder Release heißt.) Geben Sie dann in der Befehlszeile den folgenden Befehl ein:
BankServer Es erfolgt die folgende Ausgabe: BankServer bereit. Der BankServer wird nun ausgeführt und wartet auf ankommende Anforderungen. Sie können nun eine Server-Komponente vom Typ Bank starten. Starten der Komponente Bank Zum Starten der Komponente Bank müssen Sie ein eigenes Befehlsfenster öffnen. (Unix-Benutzer können die Anwendung BankServer im Hintergrund ausführen lassen und dasselbe Fenster verwenden, aber aus Gründen der Übersichtlichkeit sollten Sie zumindest in diesem Stadium jede Komponente der Anwendung in einem eigenen Fenster ausführen.) Wechseln Sie zum Starten der Komponente Bank in das Verzeichnis, in dem sich die ausführbare Datei für die Komponente Bank befindet, analog zum Vorgehen bei der Komponente BankServer. Legen Sie dann einen beliebigen Namen für die Bank fest, beispielsweise »Ducksches Bankhaus«. Der Name der Bank darf Leerzeichen enthalten, muß aber in diesem Fall in Anführungszeichen gestellt werden, damit er für die Anwendung als ein einziger Parameter erkennbar ist. Geben Sie unter Verwendung des festgelegten Banknamens folgendes in die Befehlszeile ein: Bank "Ducksches Bankhaus" Ebenso wie die Komponente BankServer liefert auch die Komponente Bank an dieser Stelle keine großartige Ausgabe. Folgendes wird angezeigt: Bank "Ducksches Bankhaus" bereit. Die Komponente Bank ist jetzt zur Verbindung mit den Client-Komponenten bereit, und Sie können mit dem nächsten Schritt fortfahren. Ausführen der Client-Anwendung Öffnen Sie wiederum ein neues Befehlsfenster, und wechseln Sie zum Verzeichnis, in dem sich die ausführbare Datei zum Starten der Client-Komponente der Anwendung befindet. Für die Komponente NeuerKunde müssen Sie die folgenden Kundendaten als Parameter angeben: Name, Nummer des Personalausweises, Adresse sowie den Geburtsnamen der Mutter. Auch hier gilt wieder, daß die Parameter durchaus Leerzeichen enthalten dürfen, nur müssen Sie jeden einzelnen Parameter in Anführungszeichen setzen. Beispiel: NeuerKunde "Gustav Gans" 123456789 "Erpelstraße 12, 12345 Entenhausen" Gössel
Die Client-Komponente liefert daraufhin die folgende Ausgabe: NeuerKunde: Erzeuge neuen Kunden: Name: Gustav Gans Personalausweisnummer: 123456789 Adresse: Erpelstraße 12, 12345 Entenhausen Mädchenname der Mutter: Gössel NeuerKunde: Verbunden mit Bank "Ducksches Bankhaus". NeuerKunde: Neues Konto wurde eröffnet: Kontonummer: Konto00000000 EroefffnungsDatum: Sep 28 1997 Kontostand: 0 NeuerKunde: Transaktionen werden durchgeführt. Einzahlung 250,00 DM; neuer Kontostand: DM 250,00 Abhebung 500,00 DM; neuer Kontostand: DM -250,00 (Ja wie kommt denn so was?) Wenn Sie es bis hierher geschafft haben, darf ich Ihnen gratulieren! Sie haben erfolgreich eine voll funktionsfähige CORBA-Anwendung entwickelt und ausgeführt.
Zusammenfassung In diesem Kapitel haben Sie zunächst den für unsere Beispielanwendung benötigten IDL-Quelltext definiert, dann die darin beschriebenen Schnittstellen implementiert und anschließend die ausführbaren Dateien für die Server- und Client-Komponenten erstellt, die über diese Schnittstellen miteinander kommunizieren. Auf dem Weg dorthin haben Sie einige weitere Aspekte von CORBA-Anwendungen kennengelernt: ■
■
■
CORBA stellt die Methoden _duplicate() und _release() zur Verfügung, mit denen die Verwendung von CORBA-Objekten verfolgt werden kann. Obwohl Referenzzähler in CORBA nicht spezifiziert sind, stellen sie einen typischen Mechanismus dar, der mit den genannten Methoden realisiert werden kann. CORBA-Server teilen mit, daß die zugehörigen Objekte (oder der Server selbst) bereit sind. Hierzu werden die Methoden obj_is_ready() und impl_is_ready() im BOA (Basic Object Adapter) aufgerufen. CORBA-Clients stellen eine Verbindung zu Server-Objekten her, wozu entweder die Methode _bind() verwendet wird (hierbei handelt es sich um eine nicht dem Standard entsprechende Methode, die in den Beispielen in diesem Kapitel erklärt wurde) oder ein geeigneter CORBAservice zum Einsatz kommt. (Näheres hierzu lernen Sie am 12. Tag.) Nach dem Herstellen der Verbindung kann der Client Methoden des Objekts aufrufen, so als ob es sich dabei um ein lokales Objekt handeln würde.
Im nächsten Kapitel soll unsere Beispielanwendung noch weiter verbessert werden. Hierzu werden Sie neue Funktionen hinzufügen, die auch die Robustheit der Anwendungskomponenten erhöht. Am 7. Tag werden Sie Quelltext zur Exception-Behandlung zur Anwendung hinzufügen. Die Verwendung von Exceptions erhöht die Flexibilität der Anwendung bei der Behandlung von Fehlerbedingungen; C++- und Java-Programmierer kennen dieses Konzept. In unserer Anwendung wäre es z.B. hilfreich,
wenn der Versuch, Geld von einem nicht hinreichend gedeckten Konto abzuheben, zu einem Fehler führen würde. (Wie Sie sich erinnern, zeigte die Ausgabe für das Beispiel, daß das Abheben von Geld von einem Konto mit ungenügender Deckung einfach einen negativen Kontostand ergab.) Eine Aufgabe im nächsten Kapitel wird sein, eine Exception hinzuzufügen, mit der eine solche Bedingung abgefangen wird.
Fragen & Antworten Frage: Warum ist ein Referenzzähler für CORBA-Objekte erforderlich? Antwort: In einer nicht verteilten Umgebung läßt sich es leicht feststellen, wann ein Objekt nicht mehr benötigt wird (d.h., wenn zu diesem Objekt keine Referenz mehr von einem anderen Objekt aus vorhanden ist) und es damit aus dem Speicher entfernt wird. Einige Sprachen wie Java stellen diese Funktionalität sogar als in die Sprache integriertes Merkmal zur Verfügung. In einer verteilten Umgebung jedoch ist das Entfernen nicht mehr verwendeter Objekte weitaus komplizierter, weil es schwieriger ist festzustellen, welche Objekte von anderen (möglicherweise Remote-)Objekten gerade benutzt werden. Aus diesem Grund stellt CORBA den Mechanismus für Referenzzähler zur Verfügung, mit dem die Verwendung von Objekten verfolgt werden kann. Ein anderes Verfahren zur Objektverfolgung besteht darin, für jedes Objekt, zu dem eine Referenz von einem anderen Objekt aus vorhanden ist, einen Ping-Vorgang zu diesem Objekt durchzuführen, durch den der Referenzzähler gegebenenfalls aktualisiert wird. Dieser Ansatz kann sich jedoch, je nach Anzahl der Objekte im System, als problematisch erweisen. Frage: Die Verwendung der Referenzzähler habe ich jetzt verstanden. Aber was passiert, wenn eine ClientAnwendung abstürzt und damit die Methode _release() niemals aufgerufen wird? Wird dann das Objekt nie aus dem Speicher gelöscht? Antwort: Es stimmt, daß das von CORBA verwendete System der Referenzzähler nicht ohne Nachteile ist, und dies ist einer davon. Wenn eine Client-Anwendung, die auf ein Remote-Objekt verweist, abstürzt, wird _release() für das betreffende Objekt insgesamt nicht oft genug aufgerufen, und der Referenzzähler des Objekts wird nie auf Null gesetzt. Es gibt Entwurfsmuster (die Sie am 10. Tag kennenlernen werden), die beim Umgang mit solchen Problemen helfen. Der grundlegende Ansatz hierfür besteht darin, daß die Server ein Objekt, das in einem festgelegten Zeitraum nicht verwendet wurde, zwangsweise in eine Datenbank oder einen permanenten Speicher stellen. Wenn das betreffende Objekt dann wieder benötigt wird, kann es aus dem permanenten Speicher abgerufen oder auf andere Art und Weise neu erstellt werden.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übung finden Sie in Anhang A. Quiz 1. In diesem Kapitel wurde erwähnt, daß bei Verwendung von _is_equivalent() nicht garantiert der Wert TRUE zurückgegeben wird, wenn zwei Objektreferenzen auf dasselbe Objekt verweisen. Können Sie sich einen Mechanismus vorstellen, mit dem man zuverlässiger feststellen könnte, ob zwei Referenzen auf dasselbe Objekt verweisen? (Der Einfachheit halber können Sie davon ausgehen, daß es sich um Objekte desselben Typs handelt.) 2. Was würde passieren, wenn für ein Objekt, das zuvor mit _duplicate() dupliziert wurde, _release()nicht aufgerufen wird? 3. Warum ist in NeuerKundeHauptmodul.cpp der Block try ... catch (const CORBA::Exception& ex) enthalten? Übung Ändern Sie die Client-Anwendung so ab, daß sie die Namen der Kunden ausgibt, die dem eröffneten Konto zugeordnet sind (es müssen diejenigen Kundendaten ausgegeben werden, die über die Befehlszeile eingegeben wurden).
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 7 Verwendung von Exceptions zur Fehlerprüfung Definition von Exceptions für die Anwendung Änderung der Server-IDL zur Verwendung von Exceptions Änderungen am Server-Quelltext zum Auslösen von Exceptions Änderungen am Client-Quelltext zum Behandeln von Exceptions Zusammenfassung Fragen & Antworten Workshop
Am 6. Tag haben Sie ausgehend von einer Reihe von IDL-Spezifikationen eine grundlegende CORBA-Anwendung erstellt. Mit dieser Anwendung wurden einige Grundfunktionen implementiert, sie war aber noch nicht sehr robust. Bei Verwendung der Client-Komponente konnten wir beispielsweise sehen, daß es möglich war, mehr Geld von einem Konto abzuheben, als aufgrund der Kontostände vorhanden war (ohne daß eine Überziehungsfunktion vorhanden gewesen wäre). In diesem Kapitel werden Sie die Anwendung verbessern, so daß Fehlerbedingungen (wie der Versuch, von einem nicht gedeckten Konto Geld abzuheben) besser behandelt werden können.
Definition von Exceptions für die Anwendung Wenn Sie auf intelligente Weise Fehlerprüffunktionen zur Anwendung hinzufügen möchten, müssen Sie den Entwurf nochmals überarbeiten, dabei die einzelnen Methoden daraufhin prüfen, welche Fehlerbedingungen auftreten können, und anschließend festlegen, ob die Fehlerbedingung von der Methode selbst behandelt oder zum Client zurückgegeben werden soll, um dort behandelt zu werden (wie bereits erwähnt, bezeichnet man diesen Vorgang in CORBA als »Auslösen einer Exception«). In diesem Abschnitt werden Sie die einzelnen IDL-Schnittstellen nochmals analysieren und dabei ermitteln, welche Exceptions von wo ausgelöst werden können. Wenn eine Exception von einer Methode an das aufrufende Objekt zurückgegeben wird, spricht man in CORBA davon, daß die Exception ausgelöst wird.
Exceptions in der Schnittstelle BankServer Als erstes werden Sie Funktionen für die Exception-Behandlung zur Schnittstelle BankServer hinzufügen. Zur besseren Übersicht habe ich in Listing 7.1 die Definition der Schnittstelle BankServer.idl vom 6. Tag nochmals aufgeführt. Sie werden diese jetzt um Definitionen zur Exception-Behandlung erweitern. Listing 7.1: Ursprüngliche Schnittstelle BankServer.idl. 1: // BankServer.idl 2: 3: #ifndef BankServer_idl 4: #define BankServer_idl 5: 6: #include "Bank.idl" 7: 8: // BankServer erlaubt Clients den Zugriff auf Bank-Objekte. 9: interface BankServer { 10: 11: // Angegebene Bank bei diesem BankServer registrieren. Die Bank wird von 12: // getBanken() aufgelistet, bis aufhebenRegistrierungBank() mit dieser 13: // Bank aufgerufen wird. 14: void registriereBank(in Bank bank); 15: 16: // Registrierung der angegebenen Bank vom BankServer aufheben. Falls die Bank 17: // zuvor nicht registriert wurde, wird nichts ausgeführt.
18: 19: 20: 21: 22: 23: 24: 25:
void aufhebenRegistrierungBank(in Bank bank); // Liste aller Banken liefern, die aktuell auf diesem // BankServer registriert sind. BankenListe getBanken(); }; #endif
Wir beginnen die Analyse mit der Schnittstelle BankServer, bei der Sie sehen können, daß drei Methoden möglicherweise Exceptions auslösen können. Die erste Methode, registriereBank() in den Zeilen 11-14, könnte eine Exception auslösen, wenn versucht wird, eine Bank registrieren zu lassen, die bereits registriert ist. Halten Sie diese Exception als UngueltigeBankException fest (es ist sinnvoll, die Namen der Exceptions so selbsterklärend wie möglich zu formulieren). Bei der nächsten Methode, aufhebenRegistrierungBank() in den Zeilen 16-18, könnte es sein, daß eine Bank, die nie beim BankServer registriert war, versucht, die Registrierung rückgängig zu machen. Auch hierbei könnte die Exception UngueltigeBankException ausgelöst werden. Schließlich wurde für die Schnittstelle noch die Methode getBanken() (Zeilen 20-22) definiert, für die keine Exception ausgelöst zu werden braucht. Wenn keine Bank beim BankServer registriert ist, kann diese Methode einfach ein leeres Objekt vom Typ BankenListe zurückgeben. Bei der Analyse der Schnittstelle BankServer haben wir festgestellt, daß zwei Methoden Exceptions auslösen können. Die neuen Signaturen für diese Methoden, die so geändert wurden, daß die beschriebenen Exceptions ausgelöst werden können, lauten folgendermaßen: void registriereBank(in Bank bank) raises (UngueltigeBankException); void aufhebenRegistrierungBank(in Bank bank) raises (UngueltigeBankException); Exceptions in der Schnittstelle Bank Als nächstes werden wir die Schnittstelle Bank untersuchen und Funktionen zur ExceptionBehandlung hinzufügen. Auch hier habe ich aus praktischen Gründen noch einmal das ursprüngliche Listing der Schnittstelle Bank.idl vom 6. Tag wiederholt: Listing 7.2: Ursprüngliche Schnittstelle Bank.idl. 1: 2: 3: 4: 5: 6:
// Bank.idl // Forward-Deklaration der Schnittstelle Bank. interface Bank; #ifndef Bank_idl
7: #define Bank_idl 8: 9: // Banksequenzen 10: typedef sequence BankenListe; 11: 12: #include "Kunde.idl" 13: #include "Konto.idl" 14: 15: // Bank ermöglicht Zugriff auf Konten und kann für 16: // einen Kunden ein Konto erzeugen, ein Konto löschen, 17: // oder die aktuellen Konten der Banken auflisten. 18: interface Bank { 19: 20: // Name dieser Bank. 21: attribute string name; 22: 23: // Adresse dieser Bank. 24: attribute string adresse; 25: 26: // Ein Konto für den angegebenen Kunden erzeugen, unter Berücksichtigung 27: // der angegebenen Kontoart ("Sparkonto" oder "Girokonto", wobei auf die 28: // Schreibweise zu achten ist) und des angegebenen Saldos. 29: Konto erstelleKonto(in Kunde Kunde, in string 30: KontoArt, in float eroeffnungsSaldo); 31: 32: // Angegebenes Konto löschen. Gehört das Konto nicht zu dieser 33: // Bank, führt diese Funktion nichts aus. 34: void loescheKonto(in Konto Konto); 35: 36: // Alle Konten dieser Bank auflisten. 37: KontoListe getKonten(); 38: }; 39: 40: #endif Die Schnittstelle Bank enthält drei Methoden: erstelleKonto() (Zeilen 26-30), die keine Exceptions auslösen muß, loescheKonto() (Zeilen 32-34), die eine Exception auslöst, wenn das angegebene Konto in der Bank nicht vorhanden ist, und die Methode getKonten() (Zeilen 36-37), für die ebenfalls keine Exceptions ausgelöst werden müssen. Unter Verwendung eines selbsterklärenden Namens sieht die geänderte Signatur für loescheKonto() wie folgt aus: void loescheKonto(in Konto Konto)
raises (UngueltigesKontoException); Exceptions in der Schnittstelle Konto Listing 7.3 enthält (zur Wiederholung) die Definition der Schnittstelle Konto vom 6. Tag. Dies ist der nächste Kandidat, in dem Methoden vorhanden sind, die ggf. Exceptions auslösen können. Listing 7.3: Ursprüngliche Schnittstelle Konto.idl. 1: // Konto.idl 2: 3: // Forward-Deklaration der Schnittstelle Konto. 4: interface Konto; 5: 6: #ifndef Konto_idl 7: #define Konto_idl 8: 9: // Konten-Sequenz 10: typedef sequence KontoListe; 11: 12: #include "Kunde.idl" 13: 14: // Ein Konto ist eine Einheit, die zu einer Bank gehört und von einem Kunden geführt wird 15: // (oder mehreren Kunden). Ein Konto hat einen Saldo, welcher durch 16: // Einzahlungen und Abhebungen geändert wird. 17: interface Konto { 18: 19: // Dies ist die Nummer des Kontos. 20: readonly attribute string KontoNummer; 21: 22: // Das Erstellungsdatum des Kontos. 23: readonly attribute string EroefffnungsDatum; 24: 25: // Der aktuelle Kontostand. 26: readonly attribute float Kontostand; 27: 28: // Liste der Kunden zurückliefern, die dieses Konto führen. 29: KundenListe getKunden(); 30: 31: // Angegebenen Betrag von diesem Konto abheben. Rückgabewert ist der 32: // neue Kontostand. 33: float Abhebung(in float Betrag); 34:
35: ist 36: 37: 38: 39: 40:
// Angegebenen Betrag auf dieses Konto einzahlen. Rückgabewert der // neue Kontostand. float Einzahlung(in float Betrag); }; #endif
Bei der Analyse der Methoden, die für die Schnittstelle Konto definiert wurden, stoßen wir in den Zeilen 28-29 auf die Methode getKunden(), für die keine Exception ausgelöst zu werden braucht (auch hier kann eine leere Liste zurückgegeben werden, wenn keine Kunden vorhanden sind, obwohl dies nie der Fall sein dürfte). In den Zeilen 31-33 ist die Methode Abhebung() enthalten, die eine Exception auslöst, wenn der Kontostand für die Abhebung nicht ausreicht. Schließlich enthält die Schnittstelle noch die Methode Einzahlung() (Zeilen 35-37), die eine Exception auslöst, wenn ein ungültiger Betrag angegeben wird (z.B. ein negativer Wert). Auch bei Abhebung() wird eine Exception ausgelöst, wenn ein ungültiger Wert angegeben wird. Diese Analyse führt zu den folgenden Methodensignaturen: float Abhebung(in float Betrag) raises (UngueltigerBetragException, UnzureichendeGeldmittelException); float Einzahlung(in float Betrag) raises (UngueltigerBetragException); Exceptions in der Schnittstelle GiroKonto Da über die Schnittstelle GiroKonto keine neuen Methoden zur Schnittstelle Konto hinzugefügt werden, braucht hier nicht untersucht zu werden, welche Exceptions ggf. ausgelöst werden könnten. Zur Erinnerung hier noch einmal das Listing für die Schnittstelle GiroKonto.idl: Listing 7.4: Ursprüngliche Schnittstelle GiroKonto.idl. // GiroKonto.idl #ifndef GiroKonto_idl #define GiroKonto_idl #include "Konto.idl" // GiroKonto stellt ein Girokonto dar. Es bringt im Gegensatz // zu seinem Gegenstück, dem Sparkonnto, keine Zinsen // ein. interface GiroKonto : Konto {
}; #endif Exceptions in der Schnittstelle SparKonto Listing 7.5 zeigt noch einmal das Listing für die Schnittstelle SparKonto.idl vom 6. Tag. Listing 7.5: Ursprüngliche Schnittstelle SparKonto.idl. 1: // SparKonto.idl 2: 3: #ifndef SparKonto_idl 4: #define SparKonto_idl 5: 6: #include "Konto.idl" 7: 8: // Ein SparKonto ist ein Konto, das Sparbuch-Merkmale, 9: // wie z.B. Zinsen, unterstützt. 10: interface SparKonto : Konto { 11: 12: // Der Zinsatz für dieses Konto. 13: readonly attribute float ZinsSatz; 14: 15: // Zinsatz für dieses Konto auf angegebenen Wert setzen. 16: // Rückgabewert ist der vorherige Zinssatz. 17: float setZinsSatz(in float Satz); 18: }; 19: 20: #endif In der Schnittstelle SparKonto wird eine weitere Methode, nämlich setZinsSatz() definiert (Zeilen 1517), die eine Exception auslösen kann, wenn ein ungültiger Zinssatz (z.B. ein negativer Wert) angegeben wird. Damit hier nicht zu viele verschiedene Exceptions verwendet werden, können Sie hier UngueltigerBetragException aus der Schnittstelle Konto verwenden: float setZinsSatz(in float Satz) raises (UngueltigerBetragException); Exceptions in der Schnittstelle Kunde In der Schnittstelle Kunde wird nur eine weitere Methode definiert: getKonten(). Für diese Methode braucht keine Exception ausgelöst zu werden. Zur Wiederholung hier noch einmal das Listing der ursprünglichen Schnittstelle Kunde.idl.
Listing 7.6: Ursprüngliche Schnittstelle Kunde.idl. 1: // Kunde.idl 2: 3: // Forward-Deklaration der Schnittstelle Kunde. 4: interface Kunde; 5: 6: #ifndef Kunde_idl 7: #define Kunde_idl 8: 9: // Kundensequenz 10: typedef sequence KundenListe; 11: 12: #include "Konto.idl" 13: 14: // Ein Kunde kann über ein (oder mehrere) Konten verfügen. Der 15: // Kunde ist wohl das Kernelement der Anwendung. 16: interface Kunde { 17: 18: // Der Name dieses Kunden. 19: attribute string name; 20: 21: // Die Personalausweisnummer dieses Kunden. 22: readonly attribute string PersonalAusweisNummer; 23: 24: // Die Anschrift dieses Kunden. 25: attribute string adresse; 26: 27: // Der Mädchenname der Mutter dieses Kunden. 28: readonly attribute string MuttersMaedchenName; 29: 30: // Eine Kontoliste zurückgeben, die von diesem 31: // Kunden (mit-) geführt wird. 32: KontoListe getKonten(); 33: }; 34: 35: #endif
Änderung der Server-IDL zur Verwendung von Exceptions Ein Teil der zum Hinzufügen von Exceptions nötigen Arbeit (Hinzufügen der raises-Klauseln zu den Methoden, die Exceptions auslösen) ist damit schon erledigt. Damit haben Sie aber die Exceptions selbst noch nicht definiert. Aus praktischen Gründen sollen alle Exceptions in einer einzigen Datei namens Exceptions.idl definiert werden, die in allen übrigen IDL-Dateien über #include-Anweisungen eingebunden werden kann. Listing 7.7 zeigt die Datei Exceptions.idl.
Listing 7.7: Exceptions.idl. 1: // Exceptions.idl 2: 3: #ifndef Exceptions_idl 4: #define Exceptions_idl 5: 6: // Diese Exception wird ausgelöst, wenn einer Methode ein ungültiger 7: // Betrag übergeben wird; wenn z.B. versucht wird, einen negativen 8: // Betrag auf ein Konto einzuzahlen. 9: exception UngueltigerBetragException { 10: 11: }; 12: 13: // Diese Exception wird ausgelöst, wenn ein ungültiges Konto an eine 14: // Methode übergeben wird, die ein Konto-Objekt erwartet. 15: exception UngueltigesKontoException { 16: 17: }; 18: 19: // Diese Exception wird ausgelöst, wenn eine ungültige Bank an eine 20: // Methode übergeben wird, die ein Bank-Objekt erwartet. 21: exception UngueltigeBankException { 22: 23: }; 24: 25: // Diese Exception wird ausgelöst, wenn keine ausreichende Deckung 26: // vorhanden ist, um eine Transaktion durchzuführen; wenn z.B. bei einer Abhebung 27: // versucht wird, einen Betrag abzuheben, der durch das Guthaben nicht gedeckt ist. 28: exception UnzureichendeGeldmittelException { 29: 30: }; 31: 32: #endif Das Einbringen der betreffenden Änderungen in die einzelnen IDL-Dateien ist nicht weiter schwierig. Die Listings 7.8 bis 7.13 zeigen die geänderten IDL-Dateien für die einzelnen Schnittstellen, wobei die Änderungen gegenüber der ursprünglichen Version jeweils durch Fettschrift gekennzeichnet sind.
Listing 7.8: Geänderte Schnittstelle BankServer.idl. 1: // BankServer.idl 2: 3: #ifndef BankServer_idl 4: #define BankServer_idl 5: 6: #include "Bank.idl" 7: #include "Exceptions.idl" 8: 9: // BankServer erlaubt Clients den Zugriff auf Bank-Objekte. 10: interface BankServer { 11: 12: // Angegebene Bank bei diesem BankServer registrieren. Die Bank wird von 13: // getBanken() aufgelistet, bis aufhebenRegistrierungBank() mit dieser 14: // Bank aufgerufen wird. 15: void registriereBank(in Bank bank) 16: raises (UngueltigeBankException); 17: 18: // Registrierung der angegebenen Bank vom BankServer aufheben. Falls die Bank 19: // zuvor nicht registriert wurde, wird nichts ausgeführt. 20: void aufhebenRegistrierungBank(in Bank bank) 21: raises (UngueltigeBankException); 22: 23: // Liste aller Banken liefern, die aktuell auf diesem 24: // BankServer registriert sind. 25: BankenListe getBanken(); 26: }; 27: 28: #endif Listing 7.9: Geänderte Schnittstelle Bank.idl. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// Bank.idl // Forward-Deklaration der Schnittstelle Bank. interface Bank; #ifndef Bank_idl #define Bank_idl // Banksequenzen
10: typedef sequence BankenListe; 11: 12: #include "Kunde.idl" 13: #include "Konto.idl" 14: #include "Exceptions.idl" 15: 16: // Bank ermöglicht Zugriff auf Konten und kann für 17: // einen Kunden ein Konto erzeugen, ein Konto löschen 18: // oder die aktuellen Konten der Banken auflisten. 19: interface Bank { 20: 21: // Name dieser Bank. 22: attribute string name; 23: 24: // Adresse dieser Bank. 25: attribute string adresse; 26: 27: // Ein Konto für den angegebenen Kunden erzeugen, unter Berücksichtigung 28: // der angegebenen Kontoart ("Sparkonto" oder "Girokonto", wobei auf die 29: // Schreibweise zu achten ist) und des angegebenen Eröffnungssaldos. 30: Konto erstelleKonto(in Kunde Kunde, in string 31: KontoArt, in float eroeffnungsSaldo); 32: 33: // Angegebenes Konto löschen. Gehört das Konto nicht zu dieser 34: // Bank, führt diese Funktion nichts aus. 35: void loescheKonto(in Konto Konto) 36: raises (UngueltigesKontoException); 37: 38: // Alle Konten dieser Bank auflisten. 39: KontoListe getKonten(); 40: }; 41: 42: #endif Listing 7.10: Geänderte Schnittstelle Konto.idl. 1: 2: 3: 4: 5: 6: 7:
// Konto.idl // Forward-Deklaration der Schnittstelle Konto. interface Konto; #ifndef Konto_idl #define Konto_idl
8: 9: // Konten-Sequenz 10: typedef sequence KontoListe; 11: 12: #include "Kunde.idl" 13: #include "Exceptions.idl" 14: 15: // Ein Konto ist eine Einheit, die zu einer Bank gehört und von einem Kunden geführt wird 16: // (oder mehreren Kunden). Ein Konto hat einen Saldo, welcher durch 17: // Einzahlungen und Abhebungen geändert wird. 18: interface Konto { 19: 20: // Dies ist die Nummer des Kontos. 21: readonly attribute string KontoNummer; 22: 23: // Das Erstellungsdatum des Kontos. 24: readonly attribute string EroefffnungsDatum; 25: 26: // Der aktuelle Kontostand. 27: readonly attribute float Kontostand; 28: 29: // Liste der Kunden zurückliefern, die dieses Konto führen. 30: KundenListe getKunden(); 31: 32: // Angegebenen Betrag von diesem Konto abheben. Rückgabewert ist der 33: // neue Kontostand. 34: float Abhebung(in float Betrag) 35: raises (UngueltigerBetragException, 36: UnzureichendeGeldmittelException); 37: 38: // Angegebenen Betrag auf dieses Konto einzahlen. Rückgabewert ist der 39: // neue Kontostand. 40: float Einzahlung(in float Betrag) 41: raises (UngueltigerBetragException); 42: }; 43: 44: #endif Listing 7.11: Geänderte Schnittstelle GiroKonto.idl. 1: // GiroKonto.idl 2:
3: #ifndef GiroKonto_idl 4: #define GiroKonto_idl 5: 6: #include "Konto.idl" 7: 8: // GiroKonto stellt ein Girokonto dar. Es bringt im Gegensatz 9: // zu seinem Gegenstück, dem Sparkonnto, keine Zinsen 10: // ein. 11: interface GiroKonto : Konto { 12: 13: }; 14: 15: #endif Listing 7.12: Geänderte Schnittstelle SparKonto.idl. 1: // SparKonto.idl 2: 3: #ifndef SparKonto_idl 4: #define SparKonto_idl 5: 6: #include "Konto.idl" 7: #include "Exceptions.idl" 8: 9: // Ein SparKonto ist ein Konto, das Sparbuch-Merkmale, 10: // wie z.B. Zinsen, unterstützt. 11: interface SparKonto : Konto { 12: 13: // Der Zinsatz für dieses Konto. 14: readonly attribute float ZinsSatz; 15: 16: // Zinsatz für dieses Konto auf angegebenen Wert setzen. 17: // Rückgabewert ist der vorherige Zinssatz. 18: float setZinsSatz(in float Satz) 19: raises (UngueltigerBetragException); 20: }; 21: 22: #endif Listing 7.13: Geänderte Schnittstelle Kunde.idl. 1: // Kunde.idl 2: 3: // Forward-Deklaration der Schnittstelle Kunde. 4: interface Kunde; 5:
6: #ifndef Kunde_idl 7: #define Kunde_idl 8: 9: // Kundensequenz 10: typedef sequence KundenListe; 11: 12: #include "Konto.idl" 13: 14: // Ein Kunde kann über ein (oder mehrere) Konten verfügen. Der 15: // Kunde ist wohl das Kernelement der Anwendung. 16: interface Kunde { 17: 18: // Der Name dieses Kunden. 19: attribute string name; 20: 21: // Die Personalausweisnummer dieses Kunden. 22: readonly attribute string PersonalAusweisNummer; 23: 24: // Die Anschrift dieses Kunden. 25: attribute string adresse; 26: 27: // Der Mädchenname der Mutter dieses Kunden. 28: readonly attribute string MuttersMaedchenName; 29: 30: // Eine Kontoliste zurückgeben, die von diesem 31: // Kunden (mit-) geführt wird. 32: KontoListe getKonten(); 33: }; 34: 35: #endif Nachdem Sie die IDL-Schnittstellendefinitionen so geändert haben, daß die gewünschten Exceptions für die jeweiligen Methoden ausgelöst werden können, können Sie die IDL-Dateien erneut kompilieren, um neue Client-Stubs und Server-Skeletons zu generieren. Anschließend können Sie den Quelltext so ändern, daß die neuen Exceptions verwendet werden.
Änderungen am Server-Quelltext zum Auslösen von Exceptions Über die IDL-Schnittstellendefinitionen wird zwar festgelegt, welche Exceptions von den betreffenden Methoden ausgelöst werden sollen, aber die Umstände, unter denen dies erfolgen soll, werden nicht definiert. Aus diesem Grund müssen Sie nach dem Ändern der IDL-Definitionen den Server-Quelltext so ändern, daß die richtige Exception zum richtigen Zeitpunkt ausgelöst wird. BankServerImpl
Wir werden zunächst einmal den einfacheren Server bearbeiten (BankServer). Wie zuvor ermittelt, weist die Schnittstelle BankServer zwei Methoden auf, die Exceptions auslösen: registriereBank() und aufhebenRegistrierungBank(). Die Änderungen müssen sowohl in BankServerImpl.h als auch in BankServerImpl.cpp vorgenommen werden; sie sind in den Listings 7.14 und 7.15 durch Fettschrift gekennzeichnet. In C++ ist es zulässig, für eine Methode die Auslösung einer Exception zu definieren, ohne zuvor zu deklarieren, daß dies geschehen soll (Verwendung der throw-Klausel in der Methodensignatur). Diese Vorgehensweise gilt in einigen Programmiererkreisen als schlechter Stil. Ich empfehle daher, alle von einer C++-Methode ausgelösten Exceptions im Header der betreffenden Methode zu deklarieren. Dadurch wird für das die Methode aufrufende Objekt deutlicher, daß hier bestimmte Exceptions ausgelöst werden können. (Im Gegensatz zu C++ ist diese Vorgehensweise bei Java obligatorisch.)
Listing 7.14: Geänderter Server-Quelltext für BankServerImpl.h. 1: // BankServerImpl.h 2: 3: #ifndef BankServerImpl_h 4: #define BankServerImpl_h 5: 6: #include 7: 8: #include "../BankServer_s.h" 9: 10: class BankServerImpl : public _sk_BankServer { 11: 12: public: 13: 14: // Konstruktor. 15: BankServerImpl(const char* name); 16: 17: // Destruktor. 18: ~BankServerImpl(); 19: 20: // Diese Methoden werden in BankServer.idl beschrieben. 21: virtual void registriereBank(Bank_ptr bank) throw 22: (UngueltigeBankException); 23: virtual void aufhebenRegistrierungBank(Bank_ptr bank) throw 24: (UngueltigeBankException); 25: virtual BankenListe* getBanken(); 26:
27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
private: // Standard-Konstruktor. BankServerImpl(); // Die Bankenliste dieses BankServers. std::vector meineBanken; }; #endif
Listing 7.15: Geänderter Server-Quelltext für BankServerImpl.cpp. 1: // BankServerImpl.cpp 2: 3: #include "BankServerImpl.h" 4: 5: #include 6: #include 7: 8: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Banken gleich sind. 9: class IstBankGleich : public std::unary_function { 10: public: 11: IstBankGleich(argument_type bank) { meineBank = bank; } 12: result_type operator()(argument_type bank) { return bank-> 13: _is_equivalent(meineBank) != 0; } 14: private: 15: argument_type meineBank; 16: }; 17: 18: // Konstruktor. 19: BankServerImpl::BankServerImpl(const char* name) : 20: _sk_BankServer(name), meineBanken() { 21: 22: } 23: 24: // Destruktor. 25: BankServerImpl::~BankServerImpl() { 26: 27: } 28: 29: void BankServerImpl::registriereBank(Bank_ptr bank) throw 30: (UngueltigeBankException) { 31:
32: // Sicherstellen, daß angegebene Bank nicht bereits vorhanden ist. 33: std::vector::iterator first = meineBanken.begin(); 34: std::vector::iterator last = meineBanken.end(); 35: IstBankGleich predicate(bank); 36: 37: std::vector::iterator uebereinstimmendeBank = std:: 38: find_if(first, last, predicate); 39: if (uebereinstimmendeBank == last) { 40: 41: // Bank nicht gefunden, daher angegebene Bank an das 42: // Listenende einfügen. 43: cout << "BankServerImpl: Registriere Bank \"" << bank-> 44: name() << "\"." << endl; 45: meineBanken.push_back(Bank::_duplicate(bank)); 46: return; 47: } else { 48: 49: // Bank ist bereits registriert, daher eine Exception auslösen. 50: throw UngueltigeBankException(); 51: } 52: } 53: 54: void BankServerImpl::aufhebenRegistrierungBank(Bank_ptr bank) throw 55: (UngueltigeBankException) { 56: 57: std::vector::iterator first = meineBanken.begin(); 58: std::vector::iterator last = meineBanken.end(); 59: IstBankGleich predicate(bank); 60: 61: std::vector::iterator uebereinstimmendeBank = std:: 62: find_if(first, last, predicate); 63: if (uebereinstimmendeBank == last) { 64: 65: // Ungültige Bank; eine Exception auslösen. 66: cout << "BankServerImpl: Es wurde versucht, die Registrierung einer ungültigen Bank " 67: "aufzuheben." << endl; 68: throw UngueltigeBankException(); 69: } 70: cout << "BankServerImpl: Registrierung wird aufgehoben \"" << bank->name() 71: << "\"." << endl; 72: 73: // Angegebene Bank löschen.
74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
meineBanken.erase(uebereinstimmendeBank); bank->_release(); } BankenListe* BankServerImpl::getBanken() { BankenListe* list = new BankenListe(meineBanken.size()); CORBA::Long i; for (i = 0; i < meineBanken.size(); i++) { (*list)[i] = Bank::_duplicate(meineBanken[i]); } return list; }
Die Version der Methode registriereBank(), in der noch keine Exceptions vorgesehen waren, reagierte nicht, wenn ein Client versucht hat, ein Objekt vom Typ Bank doppelt zu registrieren. Die neue, verbesserte Version behandelt diesen Fall als Fehlerbedingung; eine doppelte Registrierung eines Bank-Objekts führt dazu, daß die Exception UngueltigeBankException ausgelöst wird. Analog dazu löst aufhebenRegistrierungBank() nun die Exception UngueltigeBankException aus, wenn ein Client versucht, die Registrierung einer Bank rückgängig zu machen, die gar nicht beim BankServer registriert ist. KontoImpl Nachdem wir die Implementierung für die Schnittstelle BankServer bearbeitet haben, können wir unsere Aufmerksamkeit nun den verschiedenen Schnittstellen in der Bankanwendung zuwenden. Wir werden mit KontoImpl.h und KontoImpl.cpp beginnen. Analog zu BankServerImpl.h werden Sie jetzt die Methodensignaturen in KontoImpl.h so ändern, daß die Exceptions, die für die Methoden in KontoImpl ausgelöst werden sollen, darin enthalten sind. Analog dazu gibt KontoImpl.cpp die Bedingungen an, unter denen die Exceptions ausgelöst werden sollen. Die geänderten ServerQuelltexts für KontoImpl.h und KontoImpl.cpp sind in den Listings 7.16 und 7.17 dargestellt. Listing 7.16: Geänderter Server-Quelltext für KontoImpl.h. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// KontoImpl.h #ifndef KontoImpl_h #define KontoImpl_h #include "../Konto_s.h" class KontoImpl : public _sk_Konto {
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55:
// erlaubt GiroKontoImpl und SparKontoImpl den Zugriff auf den // geschützten Konstruktor. friend class GiroKontoImpl; friend class SparKontoImpl; public: // Destruktor. ~KontoImpl(); // Diese Methoden werden in Konto.idl beschrieben. virtual char* KontoNummer(); virtual char* EroefffnungsDatum(); virtual CORBA::Float Kontostand(); virtual KundenListe* getKunden(); virtual CORBA::Float Abhebung(CORBA::Float Betrag) throw (UngueltigerBetragException, UnzureichendeGeldmittelException); virtual CORBA::Float Einzahlung(CORBA::Float Betrag) throw (UngueltigerBetragException); protected: // Konstruktor. // // KontoNummer - Kontonnummer. // EroefffnungsDatum - Datum an dem das Konto angelegt wurde. // anfangsSaldo - Der anfängliche Kontostand. // Kunde - Der erste Inhaber des Kontos. KontoImpl(const char* KontoNummer, const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr Kunde); private: // Standard-Konstruktor. KontoImpl(); // Dies ist die Nummer des Kontos. char* meineKontoNummer; // Das Erstellungsdatum des Kontos. char* meinEröffnungsdatum; // Der aktuelle Kontostand. CORBA::Float meinKontostand;
56: 57: 58: 59: 60:
// Die Inhaber dieses Kontos. KundenListe meineInhaber; }; #endif
Listing 7.17: Geänderter Server-Quelltext für KontoImpl.cpp. 1: // KontoImpl.cpp 2: 3: #include "KontoImpl.h" 4: 5: #include 6: #include <string.h> 7: 8: // Konstruktor. 9: // 10: // KontoNummer - Kontonnummer. 11: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 12: // anfangsSaldo - Der anfängliche Kontostand. 13: // Kunde - Der erste Inhaber des Kontos. 14: KontoImpl::KontoImpl(const char* KontoNummer, const char* 15: EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr 16: Kunde) : _sk_Konto(KontoNummer), 17: meineKontoNummer(strdup(KontoNummer)), 18: meinEröffnungsdatum(strdup(EroefffnungsDatum)), 19: meinKontostand(anfangsSaldo), meineInhaber() { 20: 21: // Kunden zur Inhaberliste hinzufügen. 22: meineInhaber.length(1); 23: meineInhaber[0] = Kunde::_duplicate(Kunde); 24: } 25: 26: // Standard-Konstruktor. 27: KontoImpl::KontoImpl() : meineKontoNummer(NULL), 28: meinEröffnungsdatum(NULL), meinKontostand(0.0), meineInhaber() { 29: 30: } 31: 32: // Destruktor. 33: KontoImpl::~KontoImpl() { 34: 35: free(meineKontoNummer); 36: free(meinEröffnungsdatum); 37: } 38:
39: char* KontoImpl::KontoNummer() { 40: 41: return CORBA::strdup(meineKontoNummer); 42: } 43: 44: char* KontoImpl::EroefffnungsDatum() { 45: 46: return CORBA::strdup(meinEröffnungsdatum); 47: } 48: 49: CORBA::Float KontoImpl::Kontostand() { 50: 51: return meinKontostand; 52: } 53: 54: KundenListe* KontoImpl::getKunden() { 55: 56: return &meineInhaber; 57: } 58: 59: CORBA::Float KontoImpl::Abhebung(CORBA::Float Betrag) throw 60: (UngueltigerBetragException, UnzureichendeGeldmittelException) { 61: 62: // Das Abheben von negativen Beträgen nicht zulassen und 63: // eine Exception auslösen, falls dies versucht wird. 64: if (Betrag < 0.0) { 65: cout << "KontoImpl: Es wurde versucht, einen ungültigen " 66: << "Betrag abzuheben." << endl; 67: throw UngueltigerBetragException(); 68: } 69: 70: // Abheben von Beträgen, die das aktuelle Guthaben überschreiten, nicht 71: // zulassen und eine Exception auslösen, wenn dies versucht wird. 72: if (Betrag > meinKontostand) { 73: cout << "KontoImpl: Vorhandene Geldmittel für Abhebung des angegebenen " 74: << "Betrags nicht ausreichend." << endl; 75: throw UnzureichendeGeldmittelException(); 76: } 77: 78: meinKontostand -= Betrag; 79: 80: return meinKontostand; 81: }
82: 83: CORBA::Float KontoImpl::Einzahlung(CORBA::Float Betrag) throw 84: (UngueltigerBetragException) { 85: 86: // Einzahlung negativer Beträge nicht zulassen und eine 87: // Exception auslösen, falls dies versucht wird. 88: if (Betrag < 0.0) { 89: cout << "KontoImpl: Es wurde versucht, einen ungültigen Betrag einzuzahlen." 90: << endl; 91: throw UngueltigerBetragException(); 92: } 93: 94: meinKontostand += Betrag; 95: 96: return meinKontostand; 97: } Die geänderte Methode Abhebung() in KontoImpl prüft zunächst den abzuhebenden Betrag. Wenn es sich um einen negativen Wert handelt, wird er von der Methode Abhebung() zurückgewiesen, und die Exception UngueltigerBetragException wird ausgelöst. Wenn der Wert für den Betrag nicht negativ ist, wird der Stand des Kontos auf ausreichende Dekkung geprüft. (Beachten Sie allerdings, daß es derzeit keine Überziehungsschutzfunktion im System gibt.) Wenn der Kontostand nicht ausreicht, löst Abhebung() die Exception UnzureichendeGeldmittelException aus. Die Methode Einzahlung() von KontoImpl funktioniert analog, denn die Exception UngueltigerBetragException wird ausgelöst, wenn versucht wird, einen negativen Betrag auf das Konto einzuzahlen (dabei würde es sich nämlich um eine Abhebung handeln). Die Einzahlung kann ein beliebiger Betrag sein, deshalb gibt es keinen Grund, warum über die Methode Einzahlung() die Exception UnzureichendeGeldmittelException ausgelöst werden sollte. GiroKontoImpl Die Listings 7.18 und 7.19 zeigen weitere Änderungen, die zur Exception-Funktionalität der Bankanwendung hinzugefügt werden müssen. Listing 7.18: Geänderter Server-Quelltext für GiroKontoImpl.h. 1: 2: 3: 4: 5: 6: 7:
// GiroKontoImpl.h #ifndef GiroKontoImpl_h #define GiroKontoImpl_h #include "../GiroKonto_s.h" #include "KontoImpl.h"
8: 9: class GiroKontoImpl : public _sk_GiroKonto { 10: 11: public: 12: 13: // Konstruktor. 14: // 15: // KontoNummer - Kontonnummer. 16: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 17: // anfangsSaldo - Der anfängliche Kontostand. 18: // Kunde - Der erste Inhaber des Kontos. 19: GiroKontoImpl(const char* KontoNummer, const char* 20: EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr 21: Kunde); 22: 23: // Destruktor. 24: ~GiroKontoImpl(); 25: 26: // Diese Methoden werden in Konto.idl beschrieben. 27: virtual char* KontoNummer(); 28: virtual char* EroefffnungsDatum(); 29: virtual CORBA::Float Kontostand(); 30: virtual KundenListe* getKunden(); 31: virtual CORBA::Float Abhebung(CORBA::Float Betrag) throw 32: (UngueltigerBetragException, UnzureichendeGeldmittelException); 33: virtual CORBA::Float Einzahlung(CORBA::Float Betrag) throw 34: (UngueltigerBetragException); 35: 36: private: 37: 38: // Standard-Konstruktor. 39: GiroKontoImpl(); 40: 41: // Unser verknüpftes KontoImpl-Objekt. 42: KontoImpl meinKonto; 43: }; 44: 45: #endif Listing 7.19: Geänderter Server-Quelltext für GiroKontoImpl.cpp. 1: // GiroKontoImpl.cpp 2: 3: #include "GiroKontoImpl.h" 4: 5: // Konstruktor.
6: // 7: // KontoNummer - Kontonnummer. 8: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 9: // anfangsSaldo - Der anfängliche Kontostand. 10: // Kunde - Der erste Inhaber des Kontos. 11: GiroKontoImpl::GiroKontoImpl(const char* KontoNummer, 12: const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, 13: Kunde_ptr Kunde) : meinKonto(KontoNummer, 14: EroefffnungsDatum, anfangsSaldo, Kunde) { 15: 16: } 17: 18: // Standard-Konstruktor. 19: GiroKontoImpl::GiroKontoImpl() : meinKonto(NULL, NULL, 20: 0.0, Kunde::_nil()) { 21: 22: } 23: 24: // Destruktor. 25: GiroKontoImpl::~GiroKontoImpl() { 26: 27: } 28: 29: char* GiroKontoImpl::KontoNummer() { 30: 31: return meinKonto.KontoNummer(); 32: } 33: 34: char* GiroKontoImpl::EroefffnungsDatum() { 35: 36: return meinKonto.EroefffnungsDatum(); 37: } 38: 39: CORBA::Float GiroKontoImpl::Kontostand() { 40: 41: return meinKonto.Kontostand(); 42: } 43: 44: KundenListe* GiroKontoImpl::getKunden() { 45: 46: return meinKonto.getKunden(); 47: } 48: 49: CORBA::Float GiroKontoImpl::Abhebung(CORBA::Float Betrag) 50: throw (UngueltigerBetragException, UnzureichendeGeldmittelException)
51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
{ return meinKonto.Abhebung(Betrag); } CORBA::Float GiroKontoImpl::Einzahlung(CORBA::Float Betrag) throw (UngueltigerBetragException) { return meinKonto.Einzahlung(Betrag); }
Auch hier kann über die Methoden Abhebung() und Einzahlung() die Exception UngueltigerBetragException oder UnzureichendeGeldmittelException bzw. UngueltigerBetragException ausgelöst werden. Beachten Sie jedoch, daß keine dieser Exceptions explizit innerhalb der Methoden ausgelöst wird. Wie bereits erwähnt, lösen die Operationen Abhebung() und Einzahlung() im Datenelement meinKonto (bei dem es sich um ein Objekt vom Typ KontoImpl handelt) diese Exceptions aus. Da diese Exceptions nicht von den Methoden in GiroKontoImpl abgefangen werden, werden sie einfach an das Objekt zurückgegeben, das die Methode GiroKontoImpl aufgerufen hat. SparKontoImpl Die Änderungen am Quelltext für SparKontoImpl sind in den Listings 7.20 und 7.21 gezeigt; sie ähneln den Änderungen an GiroKontoImpl. Listing 7.20: Geänderter Server-Quelltext für SparKontoImpl.h. 1: // SparKontoImpl.h 2: 3: #ifndef SparKontoImpl_h 4: #define SparKontoImpl_h 5: 6: #include "../SparKonto_s.h" 7: #include "KontoImpl.h" 8: 9: class SparKontoImpl : public KontoImpl { 10: 11: public: 12: 13: // Konstruktor. 14: // 15: // KontoNummer - Kontonnummer. 16: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 17: // anfangsSaldo - Der anfängliche Kontostand. 18: // Kunde - Der erste Inhaber des Kontos.
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
// ZinsSatz - Anfänglicher Zinssatz des Kontos. SparKontoImpl(const char* KontoNummer, const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, Kunde_ptr Kunde, CORBA::Float ZinsSatz); // Destruktor. ~SparKontoImpl(); // Diese Methoden werden in Konto.idl beschrieben. virtual char* KontoNummer(); virtual char* EroefffnungsDatum(); virtual CORBA::Float Kontostand(); virtual KundenListe* getKunden(); virtual CORBA::Float Abhebung(CORBA::Float Betrag) throw (UngueltigerBetragException, UnzureichendeGeldmittelException); virtual CORBA::Float Einzahlung(CORBA::Float Betrag) throw (UngueltigerBetragException); // Diese Methoden werden in SparKonto.idl beschrieben. virtual CORBA::Float ZinsSatz(); virtual CORBA::Float setZinsSatz(CORBA::Float Satz) throw (UngueltigerBetragException); private: // Standard-Konstruktor. SparKontoImpl(); // Der Zinsatz für dieses Konto. CORBA::Float meinZinsSatz; // Unser verknüpftes KontoImpl-Objekt. KontoImpl meinKonto; }; #endif
Listing 7.21: Geänderter Server-Quelltext für SparKontoImpl.cpp. 1: 2: 3: 4: 5: 6: 7:
// SparKontoImpl.cpp #include "SparKontoImpl.h" // Konstruktor. // // KontoNummer - Kontonnummer.
8: // EroefffnungsDatum - Datum, an dem das Konto angelegt wurde. 9: // anfangsSaldo - Der anfängliche Kontostand. 10: // Kunde - Der erste Inhaber des Kontos. 11: // ZinsSatz - Anfänglicher Zinssatz des Kontos. 12: SparKontoImpl::SparKontoImpl(const char* KontoNummer, 13: const char* EroefffnungsDatum, CORBA::Float anfangsSaldo, 14: Kunde_ptr Kunde, CORBA::Float ZinsSatz) : 15: meinKonto(KontoNummer, EroefffnungsDatum, anfangsSaldo, 16: Kunde), meinZinsSatz(ZinsSatz) { 17: 18: } 19: 20: // Standard-Konstruktor. 21: SparKontoImpl::SparKontoImpl() : meinKonto(NULL, NULL, 22: 0.0, Kunde::_nil()), meinZinsSatz(0.0) { 23: 24: } 25: 26: // Destruktor. 27: SparKontoImpl::~SparKontoImpl() { 28: 29: } 30: 31: char* SparKontoImpl::KontoNummer() { 32: 33: return meinKonto.KontoNummer(); 34: } 35: 36: char* SparKontoImpl::EroefffnungsDatum() { 37: 38: return meinKonto.EroefffnungsDatum(); 39: } 40: 41: CORBA::Float SparKontoImpl::Kontostand() { 42: 43: return meinKonto.Kontostand(); 44: } 45: 46: KundenListe* SparKontoImpl::getKunden() { 47: 48: return meinKonto.getKunden(); 49: } 50: 51: CORBA::Float SparKontoImpl::Abhebung(CORBA::Float Betrag) 52: throw (UngueltigerBetragException, UnzureichendeGeldmittelException)
53: { 54: 55: return meinKonto.Abhebung(Betrag); 56: } 57: 58: CORBA::Float SparKontoImpl::Einzahlung(CORBA::Float Betrag) throw 59: (UngueltigerBetragException) { 60: 61: return meinKonto.Einzahlung(Betrag); 62: } 63: 64: CORBA::Float SparKontoImpl::ZinsSatz() { 65: 66: return meinZinsSatz; 67: } 68: 69: CORBA::Float SparKontoImpl::setZinsSatz(CORBA::Float Satz) throw 70: (UngueltigerBetragException) { 71: 72: // Negative Zinssätze nicht zulassen und eine Exception auslösen, falls 73: // dies versucht wird. 74: if (Satz < 0.0) { 75: 76: throw UngueltigerBetragException(); 77: } 78: 79: CORBA::Float alterZinsSatz = meinZinsSatz; 80: 81: meinZinsSatz = Satz; 82: 83: return alterZinsSatz; 84: } Die Änderungen an den Methoden Abhebung() und Einzahlung() in SparKontoImpl sind die exakten Gegenstücke zu den Methoden in GiroKontoImpl. Zusätzlich wird die Methode setZinsSatz() so geändert, daß keine negativen Zinssätze möglich sind (dies hieße ja, daß das Geld auf dem Sparkonto mit der Zeit weniger würde). Wenn versucht wird, für ein SparKonto einen negativen Zinssatz anzugeben, würde die Exception UngueltigerBetragException ausgelöst. BankImpl Die letzte zu ändernde Server-Komponente ist BankImpl selbst. Hier braucht nur eine einzige Methode, nämlich loescheKonto(), geändert zu werden, damit sie eine Exception auslösen kann. Der geänderte Server-Quelltext zu BankImpl.h und BankImpl.cpp ist in den Listings 7.22 bzw. 7.23
enthalten. Listing 7.22: Geänderter Server-Quelltext für BankImpl.h. 1: // BankImpl.h 2: 3: #ifndef BankImpl_h 4: #define BankImpl_h 5: 6: #include 7: 8: #include "../Bank_s.h" 9: 10: class BankImpl : public _sk_Bank { 11: 12: public: 13: 14: // Konstruktor. 15: // 16: // name - Name dieser Bank. 17: BankImpl(const char* name); 18: 19: // Destruktor. 20: ~BankImpl(); 21: 22: // Diese Methoden werden in Bank.idl beschrieben. 23: virtual char* name(); 24: virtual void name(const char* val); 25: virtual char* adresse(); 26: virtual void adresse(const char* val); 27: virtual Konto_ptr erstelleKonto(Kunde_ptr Kunde, 28: const char* KontoArt, CORBA::Float eroeffnungsSaldo); 29: virtual void loescheKonto(Konto_ptr Konto) throw 30: (UngueltigesKontoException); 31: virtual KontoListe* getKonten(); 32: 33: protected: 34: 35: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird 36: // in einem statischen Puffer zurückgegeben. 37: char* getNaechsteKontoNummer(); 38: 39: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis 40: // wird in einem statischen Puffer zurückgeliefert.
41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61:
char* getCurrentDate(); private: // Standard-Konstruktor. BankImpl(); // Name dieser Bank. char* meinName; // Adresse dieser Bank. char* meineAdresse; // Die Konten dieser Bank. std::vector meineKonten; // Die nächste verfügbare Kontonummer. unsigned int meineNaechsteKontoNummer; }; #endif
Listing 7.23: Geänderter Server-Quelltext für BankImpl.cpp. 1: // BankImpl.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: #include <string.h> 7: #include 8: #include 9: #include 10: 11: #include "SparKontoImpl.h" 12: #include "GiroKontoImpl.h" 13: 14: extern CORBA::BOA_var boa; 15: 16: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Konten 17: // gleich sind. 18: class IstKontoGleich : public std::unary_function { 20: public: 21: IstKontoGleich(argument_type Konto) { meinKonto = Konto; }
22: result_type operator()(argument_type Konto) { return Konto-> 23: _is_equivalent(meinKonto) != 0; } 24: private: 25: argument_type meinKonto; 26: }; 27: 28: // Konstruktor. 29: // 30: // name - Name dieser Bank. 31: BankImpl::BankImpl(const char* name) : _sk_Bank(name), meineKonten(), 32: meinName(strdup(name)), meineAdresse(strdup("123 Elm Street, " 33: "Anywhere USA 12345")), meineNaechsteKontoNummer(0) { 34: 35: } 36: 37: // Standard-Konstruktor. 38: BankImpl::BankImpl() : meineKonten(), meinName(NULL), meineAdresse(NULL), 39: meineNaechsteKontoNummer(0) { 40: 41: } 42: 43: // Destruktor. 44: BankImpl::~BankImpl() { 45: 46: cout << "Bank \"" << name() << "\" wird gelöscht." << endl; 47: free(meinName); 48: free(meineAdresse); 49: } 50: 51: char* BankImpl::name() { 52: 53: return CORBA::strdup(meinName); 54: } 55: 56: void BankImpl::name(const char* val) { 57: 58: free(meinName); 59: meinName = strdup(val); 60: } 61: 62: char* BankImpl::adresse() { 63: 64: return CORBA::strdup(meineAdresse); 65: }
66: 67: void BankImpl::adresse(const char* val) { 68: 69: free(meineAdresse); 70: meineAdresse = strdup(val); 71: } 72: 73: Konto_ptr BankImpl::erstelleKonto(Kunde_ptr Kunde, 74: const char* KontoArt, CORBA::Float eroeffnungsSaldo) { 75: 76: Konto_ptr neuesKonto; 77: 78: if (strcmp(KontoArt, "savings") == 0) { 79: 80: // Erzeuge ein neues SparKontoImpl-Objekt für das Konto. 81: cout << "BankImpl: Erzeuge neues Sparkonto für " 82: "Kunde " << Kunde->name() << "." << endl; 83: neuesKonto = new SparKontoImpl(getNaechsteKontoNummer(), 84: getCurrentDate(), eroeffnungsSaldo, Kunde, 10.0); 85: } else if (strcmp(KontoArt, "checking") == 0) { 86: 87: // Erzeuge ein neues GiroKontoImpl-Objekt für das Konto. 88: cout << "BankImpl: Erzeuge neues Girokonto für " 89: "Kunde " << Kunde->name() << "." << endl; 90: neuesKonto = new GiroKontoImpl(getNaechsteKontoNummer(), 91: getCurrentDate(), eroeffnungsSaldo, Kunde); 92: } else { 93: 94: // Ungültige Kontoart; keine Aktion ausführen. 95: cout << "BankImpl: Kunde " << Kunde->name() << 96: " hat ungültige Kontoart angefordert \"" << KontoArt 97: << "\"." << endl; 98: return Konto::_nil(); 99: } 100: 101: // Das erzeugte Konto an das Listenende einfügen und zurückliefern. 102: ::boa->obj_is_ready(neuesKonto); 103: meineKonten.push_back(Konto::_duplicate(neuesKonto)); 104: return neuesKonto; 105: } 106: 107: void BankImpl::loescheKonto(Konto_ptr Konto) throw 108: (UngueltigesKontoException) { 109: 110: std::vector::iterator first = meineKonten.begin(); 111: std::vector::iterator last = meineKonten.end();
112: IstKontoGleich predicate(Konto); 113: 114: std::vector::iterator uebereinstimmendesKonto = std:: 115: find_if(first, last, predicate); 116: if (uebereinstimmendesKonto == last) { 117: 118: // Ungültiges Konto; eine Exception auslösen. 119: cout << "BankImpl: Versuch, ein ungültiges Konto zu löschen." << 120: endl; 121: throw UngueltigesKontoException(); 122: } 123: cout << "BankImpl: Lösche Konto \"" << Konto-> 124: KontoNummer() << "\"." << endl; 125: 126: // Angegebenes Konto löschen. 127: meineKonten.erase(uebereinstimmendesKonto); 128: Konto->_release(); 129: } 130: 131: KontoListe* BankImpl::getKonten() { 132: 133: KontoListe* list = new KontoListe(meineKonten.size()); 134: CORBA::Long i; 135: 136: for (i = 0; i < meineKonten.size(); i++) { 137: (*list)[i] = Konto::_duplicate(meineKonten[i]); 138: } 139: 140: return list; 141: } 142: 143: // Die nächste verfügbare Kontonummer zurückliefern. Das Ergebnis wird in einem 144: // statischen Puffer zurückgegeben. 145: char* BankImpl::getNaechsteKontoNummer() { 146: 147: static char KontoNummer[16] = "Konto "; 148: 149: sprintf(KontoNummer + 7, "%08u", meineNaechsteKontoNummer++); 150: 151: return KontoNummer; 152: } 153: 154: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis
155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169:
// wird in einem statischen Puffer zurückgeliefert. char* BankImpl::getCurrentDate() { static char aktuellesDatum[12] = " "; time_t ltime; time(<ime); char* ctimeResult = ctime(<ime); memcpy(aktuellesDatum, ctimeResult + 4, 3); memcpy(aktuellesDatum + 4, ctimeResult + 8, 2); memcpy(aktuellesDatum + 7, ctimeResult + 20, 4); return aktuellesDatum; }
Die Logik für die Methode loescheKonto() entspricht der, die ich bereits bei der Methode aufhebenRegistrierungBank() für BankServerImpl gezeigt habe: Wenn versucht wird, ein Konto zu löschen, das in der Bank nicht vorhanden ist, löst loescheKonto() die Exception UngueltigesKontoException aus. Herzlichen Glückwunsch! Sie haben die Verbesserungen auf der Server-Seite der Bankanwendung erfolgreich durchgeführt!
Änderungen am Client-Quelltext zum Behandeln von Exceptions Bisher haben Sie nur eine Hälfte des Ganzen gesehen. Nachdem Sie den Server-Quelltext so geändert haben, daß in bestimmten Situationen Exceptions ausgelöst werden, muß der Client-Quelltext so angepaßt werden, daß er diese Exceptions behandeln kann, wenn sie ausgelöst werden. Obwohl der Quelltext für die Exception-Behandlung oft die ganze Client-Anwendung durchdringt, werden Sie hier - aufgrund des einfachen Aufbaus des Clients für die Bankanwendung - nur relativ wenig Quelltext für die Exception-Behandlung begegnen. Zunächst läßt sich feststellen, daß keine Änderungen an den Quelldateien KundenImpl.h bzw. KundenImpl.cpp notwendig sind, weil die Methoden der Klasse KundenImpl weder Exceptions auslösen noch abfangen. Die Client-Anwendung für den Endbenutzer, NeuerKundeHauptmodul.cpp, muß jedoch verschiedene Exceptions abfangen. Um die ganze Angelegenheit ein bißchen spannender zu gestalten, soll NeuerKundeHauptmodul.cpp absichtlich einige nicht zulässige Operationen durchführen, nur damit Sie einmal sehen können, wie der Exception-Mechanismus funktioniert. Die geänderte Datei NeuerKundeHauptmodul.cpp ist in Listing 7.24 dargestellt. Listing 7.24: Geänderter Client-Quelltext für NeuerKundeHauptmodul.cpp. 1: // NeuerKundeHauptmodul.cpp 2:
3: #include 4: 5: #include "../Kunde/KundenImpl.h" 6: 7: #include "../Bank_c.h" 8: 9: int main(int argc, char *const *argv) { 10: 11: // Anzahl der Parameter überprüfen; es sollten genau vier vorliegen 12: // (zusammen mit dem Programmnamen fünf). 13: if (argc != 5) { 14: cout << "Verwendung: NeuerKunder " 15: " <Mädchenname der Mutter>" << endl; 16: return 1; 17: } 18: 19: // Befehlszeilenparameter den Attributen des Kunden zuweisen. 20: const char* name = argv[1]; 21: const char* PersonalAusweisNummer = argv[2]; 22: const char* adresse = argv[3]; 23: const char* MuttersMaedchenName = argv[4]; 24: 25: // ORB und BOA initialisieren. 26: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 27: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 28: 29: // Ein Objekt Kunde erzeugen. 30: cout << "NeuerKunde: Erzeuge neuen Kunden:" << endl; 31: cout << " Name: " << name << endl; 32: cout << " Personalausweisnummer: " << PersonalAusweisNummer << 33: endl; 34: cout << " Adresse: " << adresse << endl; 35: cout << " Mädchenname der Mutter: " << MuttersMaedchenName << endl; 36: KundenImpl Kunde(name, PersonalAusweisNummer, adresse, 37: MuttersMaedchenName); 38: 39: // BOA mitteilen, daß das Objekt KundenImpl bereit ist. 40: boa->obj_is_ready(&Kunde); 41: 42: // Bank-Objekt finden und damit registrieren. 43: Bank_var bank; 44: try { 45: bank = Bank::_bind(); 46: } catch (const CORBA::Exception& ex) {
47: 48: // Die Bindung ist mißlungen... 49: cout << "NeuerKunde: Kann Bindung mit Bank nicht erzeugen." << endl; 50: cout << ex << endl; 51: return 1; 52: } 53: 54: // Einige interessante Dinge ausführen. 55: 56: cout << "NeuerKunde: Verbunden mit Bank \"" << bank->name() << 57: "\"." << endl; 58: Konto_var Konto; 59: 60: try { 61: Konto = bank->erstelleKonto(&Kunde, "checking", 0.0); 62: } catch (const CORBA::Exception& ex) { 63: 64: // erstelleKonto() ist mißlungen... 65: cout << "NeuerKunde: Kann Konto nicht erzeugen." << endl; 66: cout << ex << endl; 67: return 1; 68: } 69: 70: try { 71: 72: // Einige Kontostatistiken ausgeben. 73: cout << "NeuerKunde: Neues Konto wurde eröffnet:" << endl; 74: cout << " Kontonummer: " << Konto->KontoNummer() << 75: endl; 76: cout << " EroefffnungsDatum: " << Konto->EroefffnungsDatum() << 77: endl; 78: cout << " Kontostand: " << Konto->Kontostand() << endl; 79: 80: // Einige Kontotransaktionen durchführen. 81: cout << "NeuerKunde: Transaktionen werden durchgeführt." << endl; 82: cout << " Einzahlung in Höhe von 250,00 DM; neuer Kontostand: DM" << 83: Konto->Einzahlung(250.00) << endl; 84: cout << " Abhebung: DM 500,00...; Neuer Kontostand ist $"; 85: try { 86: cout << Konto->Abhebung(500.00) << endl; 87: } catch (const UnzureichendeGeldmittelException&) { 88: 89: // Für die Operation Abhebung() stand keine ausreichende 90: // Deckung zur Verfügung.
91: cout << endl << "NeuerKunde: Exception abgefangen: " << 92: "Unzureichende Deckung" << endl; 93: } 94: 95: // Das Konto löschen. 96: cout << " Konto wird gelöscht." << endl; 97: bank->loescheKonto(Konto); 98: 99: // Versuch, das Konto erneut zu löschen, nur zum Spaß. 100: // Dies sollte zur Auslösung einer Exception führen. 101: cout << " Versuch, durch nochmaliges Löschen des Kontos eine " << 102: " Exception auszulösen." << endl; 103: try { 104: bank->loescheKonto(Konto); 105: } catch (const UngueltigesKontoException&) { 106: 107: // Sicher, daß die Exception ausgelöst wurde. 108: cout << "NeuerKunde: Exception abgefangen: Ungültiges " << 109: "Konto (wie erwartet)" << endl; 110: } 111: } catch (const CORBA::Exception& ex) { 112: 113: // Eine Kontofunktion konnte nicht ausgeführt werden... 114: cout << endl << "NeuerKunde: Fehler beim Versuch auf Konto zuzugreifen." << 115: endl; 116: cout << ex << endl; 117: return 1; 118: } 119: 120: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 121: return 0; 122: } Beachten Sie die zusätzlich hinzugekommenen Anweisungen try und catch in den Zeilen 85-93 bzw. 99-110 der Datei NeuerKundeHauptmodul.cpp. Zuvor wurden die Anweisungen try und catch verwendet, um CORBA::Exceptions abzufangen, nun dienen sie zum Überprüfen auf BenutzerExceptions. Auf die erste treffen wir bei Verwendung der Methode Abhebung(). Wie Sie sich erinnern werden, waren auf das Konto 250,00 DM eingezahlt worden, aber das Programm hat versucht, 500,00 DM abzuheben. Man müßte also erwarten, daß bei der Ausführung der Anwendung eine Exception ausgelöst wird. Auch wird die Methode loescheKonto() zweimal für dasselbe Konto aufgerufen (einmal in Zeile 97 und dann nochmals in Zeile 104). Der erste Aufruf ist erfolgreich, aber beim zweiten müßte man erwarten, daß nochmals eine Exception ausgelöst wird, weil das Konto, das ja bereits gelöscht wurde, in der Bank nicht mehr vorhanden ist. Im nächsten Abschnitt werden Sie sehen, ob das Programm so ausgeführt wird, wie Sie es erwarten.
In diesem Beispiel ist der erste Aufruf der Methode loescheKonto() nicht in einem try ... catch-Block für UngueltigesKontoException enthalten. Für unsere Beispielanwendung mag eine solche Vorgehensweise akzeptabel sein, weil die Exception von der Behandlungsroutine catch (const CORBA::Exception ex) behandelt würde. Unter Umständen wäre es sinnvoll, den Aufruf in seinen eigenen try ... catch-Block zu stellen. Bei der Anordnung dieser Exception-Behandlungsroutinen kommt es in hohem Maße auf den Zweck der Anwendung an, aber es wird kaum Fälle geben, in denen die Behandlung einer BenutzerException durch eine globale Exception-Behandlungsroutine wie CORBA::Exception im vorherigen Beispiel sinnvoll ist. Gute Techniken zur Exception-Behandlung sind eine Frage der Übung und des sorgfältigen Anwendungsentwurfs.
Ausführen der verbesserten Beispielanwendung Herzlichen Glückwunsch! Sie haben erfolgreich einen Mechanismus zur Exception-Behandlung zu unserer Bankanwendung hinzugefügt! Jetzt brauchen Sie die Anwendung nur noch zu kompilieren und auszuführen, um zu prüfen, ob die Anwendung wie erwartet funktioniert. Denken Sie daran, daß Sie den IDL-Compiler für alle neuen und geänderten IDL-Quelldateien ausführen müssen. Unter Umständen müssen Sie auch berücksichtigen, daß der IDL-Compiler zum Generieren des Quelltexts für die Exception-Behandlung spezielle Befehlszeilenargumente benötigt. (Zufällig ist dies bei VisiBroker für C++, Version 3.0, von Visigenic nicht der Fall.) Überprüfen Sie auch die Einstellungen Ihres C++-Compilers daraufhin, ob er die Exception-Behandlung unterstützt (bei einigen Compilern ist diese Funktion nicht standardmäßig aktiviert). Die Reihenfolge beim Starten der Anwendungskomponenten entspricht der, die Sie am 6. Tag eingehalten haben: Zunächst müssen Sie den Bezeichnungsdienst starten (oder bei Verwendung von VisiBroker das Dienstprogramm osagent), dann den Server BankServer, den Server Bank und zum Schluß die Client-Anwendung NeuerKunde. Auch diesmal sehen Sie beim Starten der Anwendung BankServer die folgende Ausgabe: BankServer bereit. Wenn diese Meldung erscheint, können Sie einen Server vom Typ Bank starten. Es erfolgt folgende Ausgabe: Bank "Ducksches Bankhaus" bereit. Inzwischen hat die Anwendung BankServer folgendes ausgegeben:
BankServerImpl: Registriere Bank "Ducksches Bankhaus". Jetzt können sie die Anwendung NeuerKunde starten. Es erfolgt die folgende Ausgabe: NeuerKunde: Neue Kundendaten werden erstellt: Name: Gustav Gans Personalausweisnummer: 123456789 Adresse: Erpelstraße 12, 12345 Entenhausen Mädchenname der Mutter: Gössel NeuerKunde: Verbindung mit Bank "Ducksches Bankhaus". NeuerKunde: Neues Konto wurde eröffnet: Kontonummer: Konto00000000 Eröffnungsdatum: Oct 14 1997 Kontostand: 0 NeuerKunde: Transaktionen werden durchgeführt. Einzahlung 250,00 DM; neuer Kontostand: DM 250,00 Abhebung 500,00 DM...; neuer Kontostand: DM NeuerKunde: Exception abgefangen: Unzureichende Deckung Konto wird gelöscht. Versuch, durch nochmaliges Löschen des Kontos eine Exception auszulösen. NeuerKunde: Exception abgefangen: Ungültiges Konto (wie erwartet) In der Zwischenzeit erfolgt die folgende Ausgabe durch den Server Bank: BankImpl: Erzeuge neues Girokonto für Kunde Gustav Gans. KontoImpl: Vorhandene Geldmittel für Abhebung des angegebenen Betrags nicht ausreichend. BankImpl: Lösche Konto "Konto00000000". Ist dies die erwartete Ausgabe? Wie Sie ja wissen, wird durch NeuerKunde ein neues Konto eröffnet, wobei die in der Befehlszeile angegebenen Informationen verwendet werden. Danach zahlt unser Beispielkunde 250,00 DM auf das Konto ein und versucht, 500,00 DM abzuheben. Die Anwendung NeuerKunde meldet, daß auf dem Konto nicht genügend Geld für die Abhebung vorhanden ist (diese Meldung erfolgt dank der Exception UnzureichendeGeldmittelException). Ferner erwartet man, daß beim nochmaligen Löschen eines bereits gelöschten Kontos Schwierigkeiten auftreten. Auch hier gibt NeuerKunde an, daß versucht wurde, ein ungültiges Konto zu löschen, als loescheKonto() zum zweiten Mal aufgerufen wird. Dies bedeutet, daß unsere Exception-Programmierung funktioniert!
Zusammenfassung In diesem Kapitel haben Sie die Bankanwendung so geändert, daß sie Exceptions behandeln kann. Hierzu haben Sie zunächst ermittelt, welche Exceptions sinnvollerweise in den verschiedenen Teilen der Anwendung ausgelöst werden können, und dann die betreffenden IDL-Schnittstellen der
Anwendung so geändert, daß diese die Exceptions verwenden können. Dazu haben Sie angegeben, welche Exceptions von welchen Methoden ausgelöst werden können. Dann haben Sie die relevanten C++-Header- und Implementierungsdateien für die CORBA-Server-Komponenten geändert und die exakten Bedingungen angegeben, unter denen eine bestimmte Exception ausgelöst werden soll. Schließlich haben Sie eine CORBA-Client-Anwendung geschrieben, die diese Exceptions »intelligent« behandeln kann. Ergebnis: Eine Anwendung, die robust ist, was hier bedeutet, daß sie mit außergewöhnlichen Situationen zurechtkommt, wobei dies Umstände sind, die bei der normalen Ausführung der Anwendung gar nicht auftreten sollten. Obwohl ich Ihnen in diesem Buch gezeigt habe, wie Sie eine funktionierende Anwendung entwerfen und erstellen, bevor Sie sich um die Exception-Behandlung Gedanken machen, werden Sie in der Regel die Exceptions gleich beim Entwurf der Anwendung berücksichtigen. In der Phase, in der Sie festlegen, welche Methoden für eine Schnittstelle benötigt werden, sollten Sie sich auch überlegen, wie diese Methoden falsch verwendet werden könnten und mit welchen Mechanismen - in Form von Exceptions - die betreffende Fehlerbedingung dem Client mitgeteilt werden kann. Wie Sie gesehen haben, ist es zwar immer möglich, Exceptions nachträglich zur Anwendung hinzuzufügen, aber es ist dringend anzuraten, daß Sie sich beizeiten in der Anwendungsentwicklungsphase zumindest einige Gedanken über diese Problematik machen.
In den nächsten Kapiteln werden Sie die Bankanwendung noch weiter ändern und nochmals weitere Funktionen hinzufügen. Am 8. Tag werden Sie insbesondere zusätzliche Systemanforderungen definieren, nämlich die Funktionalität zur Unterstützung von Geldautomaten. Selbstverständlich geht es dabei nur um virtuelle Geldautomaten und nicht um reale Geldautomaten, aber die Behandlung dieses Themas soll Ihnen den Entwicklungsprozeß für eine ernsthafte CORBA-Anwendung zeigen.
Fragen & Antworten Frage: Woher weiß ich, ob eine anormale Bedingung so wichtig ist, daß ich dafür eine Exception definieren sollte? Anders gefragt: Sollten Exceptions nur für schwerwiegende Fehlerbedingungen reserviert sein? Antwort: Zu diesem Thema gibt es zwei Ansichten. Die einen sind der Ansicht, daß Exceptions nur verwendet werden sollten, um wirklich gravierende Fehlerbedingungen zu signalisieren. Wenn also nach diesem Ansatz eine Exception auftritt, kann bestenfalls versucht werden, »aufzuräumen« und dann die Anwendung ohne Schaden zu beenden. Der zweite Ansatz - und dieser wird offensichtlich
durch die CORBA-Architektur verfolgt - besteht darin, mit Exceptions jede nur denkbare anormale Bedingung zu signalisieren. Das Beispiel in diesem Kapitel, bei dem für eine Abhebung nicht genügend Geld auf dem Konto war, ist für die Anwendung selbst in keiner Weise schwerwiegend, in einer solchen Situation braucht die aktuelle Transaktion einfach nur abgebrochen zu werden. Natürlich kann man es mit diesem Ansatz auch übertreiben. Da CORBA-Exceptions über das Netzwerk weitergeleitet werden können, ist es möglich, daß sie, im Vergleich zu nativen C++Exceptions, einen hohen Systemaufwand verursachen. Wie überall gilt auch hier: Übermaß schadet nur. Frage: Wie legt man Exception-Namen am sinnvollsten fest? Antwort: Namen von Exceptions sollten selbsterklärend sein. Also sollte der Name einer Exception die Situation, zu deren Bereinigung sie eingesetzt wird, ausreichend beschreiben. Andererseits sollten Sie vermeiden, zu spezielle Namen zu wählen, was dazu führen könnte, daß Sie mehrere Exceptions definieren, die eigentlich fast identische Bedeutungen haben. So könnten beispielsweise die Exceptions UngueltigerEinzahlungsBetragException, UngueltigerAbhebungsBetragException, UngueltigerTransferBetragException, UngueltigerZinsSatzException usw. sinnvollerweise zu einer Exception mit der Bezeichnung UngueltigerBetragException zusammengefaßt werden, deren Name immer noch aussagekräftig genug ist, damit Sie in etwa wissen, was die Exception auslöst.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A. Quiz 1. Was versteht man unter »eine Exception auslösen«? 2. Was bedeutet »eine Exception abfangen«? 3. Wozu dienen Exceptions? Übungen 1. Ändern Sie die folgende Schnittstellendefinition so ab, daß in den gewünschten Situationen die geeigneten Exceptions ausgelöst werden. exception UngueltigeZahlException { }; exception KeinEingehenderAnrufException { }; exception HoererNichtAbgehobenException { };
interface Telefon { void HoererAbgehoben(); void HoererNichtAbgehoben(); void waehleNummer(in string TelefonNummer); void AnrufEntgegennehmen(); }; 2. Implementieren Sie die Schnittstelle aus Übung 1, und lösen Sie in den betreffenden Situationen die geeigneten Exceptions aus. (Bei den meisten Methoden passiert wahrscheinlich nichts, außer bei waehleNummer(), mit der wahrscheinlich die Gültigkeit der angegebenen Telefonnummer geprüft wird.)
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 8 Hinzufügen von Geldautomat-Fähigkeit Definieren zusätzlicher Anforderungen Modifizierung des Klassendiagramms Modifizieren der IDL-Spezifikation Implementierung der neuen Funktionen Ausführen der Anwendung Zusammenfassung F&A Workshop
An Tag 7 haben Sie mit der grundlegenden Bank-Anwendung begonnen und an dieser erste Verbesserungen vorgenommen, indem Sie sie um Fehlerprüffunktionen über den CORBA-ExceptionMechanismus erweitert haben. Das Endergebnis war eine Anwendung mit gleicher Basisfunktionalität wie in der am 6. Tag implementierten, jedoch mit robusterer Fehlerbehandlung. Heute werden Sie auf derselben Anwendung aufbauen und dabei weitere Funktionen hinzufügen. Insbesondere werden Sie die Schnittstelle für einen virtuellen Geldautomaten definieren und die Anwendung Bank erweitern, so daß diese mit diesem Gerät wechselwirkt. Sie werden in folgender Reihenfolge vorgehen: ■
Zusätzliche Anforderungen definieren. Die ursprünglichen Anforderungen für die Anwendung
■
■
■
Bank sagten nichts über Geldautomaten aus, so daß Sie die Anforderungen modifizieren werden, um diese neue Funktion zu definieren. Den Systementwurf modifizieren. Das Hinzufügen von Geldautomat-Funktionalität wirkt sich in der Weise aus, daß eine Anzahl neuer Klassen dem System hinzugefügt wird. Sie werden diese Klassen in der Analyse- und Entwurfsphase identifizieren. (Das erneute Durchlaufen dieser Phase untermauert das Konzept, daß Software-Entwurf ein iterativer Prozeß ist.) Die IDL-Definitionen modifizieren. Nachdem Sie die neu hinzuzufügenden Klassen definiert und einige bestehende Klassen modifiziert haben, werden Sie die Schnittstellendefinitionen für diese Klassen erstellen oder aktualisieren. Die neue Funktionalität implementieren. Nach Beendigung des Entwurfs und seiner Realisierung in IDL werden Sie in der Lage sein, die neuen Funktionen zu implementieren und zu sehen, wie die neue Anwendung funktioniert.
Definieren zusätzlicher Anforderungen Erinnern Sie sich zunächst rückblickshalber, daß Sie am 5. Tag folgende Systemanforderungen für die Anwendung Bank definiert haben: ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
■ ■ ■
Unterstützung mehrerer Banken Unterstützung mehrerer Konten innerhalb der Banken Unterstützung mehrerer Kunden als Kontoinhaber Unterstützung von Kunden, die mehrere Konten haben Unterstützung für das Eröffnen (Anlegen) neuer Konten Unterstützung für das Schließen (Löschen) bestehender Konten Unterstützung für das Abzählen der Konten einer Bank Unterstützung für die Bestimmung des Inhabers/der Inhaber eines Kontos Unterstützung für das Abheben von Beträgen von einem Konto Unterstützung für das Einzahlen von Beträgen auf ein Konto Unterstützung für das Überweisen von Beträgen zwischen Konten innerhalb einer einzelnen Bank Unterstützung für das Überweisen von Beträgen zwischen Konten bei verschiedenen Banken Unterstützung der Prüfung von Konten (die keine Zinsen einbringen) Unterstützung der Speicherung von Konten (die Zinsen einbringen)
Die ursprünglichen Anforderungen sagten nichts über die Unterstützung von Geldautomaten. Was aber, wenn Sie die Anwendung modifizieren möchten, um das Geldautomat-Konzept zu unterstützen? Sicherlich ist es möglich, diese Fähigkeit der Anwendung hinzuzufügen; dieses Kapitel demonstriert, wie dies zu tun ist. Um Geldautomat-Fähigkeit zu integrieren, definieren Sie zunächst einen Satz von Anforderungen, die exakt beschreiben, welche Funktionalität erforderlich ist. Um Geldautomat-Funktionalität zu unterstützen, könnten die Anforderungen ungefähr so aussehen: ■
Unterstützung der Einzahlung von Beträgen durch einen Kunden über einen Geldautomaten auf ein bestehendes Konto
■
■
■
■
Unterstützung der Abhebung von Beträgen durch einen Kunden über einen Geldautomaten von einem bestehenden Konto Unterstützung der Abholung eines Kontoauszugs durch einen Kunden über einen Geldautomaten von einem bestehenden Konto Unterstützung der Autorisierung von Kunden durch einen Geldautomaten anhand einer Bankkarte und einer Geheimzahl (PIN) Unterstützung der Zugriffsmöglichkeit einer Bankkarte auf mehrere Konten, die einem Kunden bei einer bestimmten Bank gehören
Mit anderen Worten, ein Geldautomat stellt drei Grundfunktionen zur Verfügung: Einzahlen von Beträgen, Abheben von Beträgen und Ausgabe von Kontoauszügen. Auf den Geldautomat wird über eine Bankkarte zugegriffen, die in Verbindung mit der PIN des Kunden diesen für den Zugriff auf das Konto (oder die Konten) autorisiert, für welche(s) die Bankkarte ausgestellt ist. Natürlich werden Sie nicht mit einer echten Bankkarte hantieren; vielmehr werden Sie den Betrieb eines Geldautomaten über IDL-Schnittstellen simulieren. Der Betrieb des Geldautomaten wird dadurch erheblich gegenüber einem tatsächlichen Geldautomaten vereinfacht, der mit einer Geldausgabe, einem Bildschirm, einer Tastatur, einem Netzwerk und anderen Komponenten zusammenarbeiten muß. Ihre Geldautomat-Schnittstelle wird den gesamten Geldautomat abstrahieren, indem angenommen wird, daß alle diese Komponenten als Einheit zusammenarbeiten.
Modifizierung des Klassendiagramms Nun, da Sie die mit der Bereitstellung von Geldautomat-Funktionalität verknüpften Anforderungen identifiziert haben, sind Sie in der Lage, zu entscheiden, welche bestehenden Klassen im System modifiziert werden müssen, welche Klassen neu hinzugefügt werden müssen und was die neuen Klassen tun sollen. Zwei neue Klassen GeldAutomat und BankKarte Natürlich kommen durch die Erweiterung der Anwendung Bank zur Integration von GeldAutomatFunktionalität neue Klassen zum System hinzu. Eine offensichtliche Klasse ist der GeldAutomat selbst; eine weitere Klasse ist BankKarte, das Objekt, das der Kunde verwendet, um sich gegenüber dem Geldautomaten zu identifizieren. Sehen Sie sich nun die Operationen an, welche diese Klassen unterstützen müssen. Erinnern Sie sich, daß der GeldAutomat Einzahlungs-, Abhebungs-, und Kontoauszugsoperationen unterstützen muß, zusätzlich zur Authentifizierung des Kunden mittels Bankkarte. BankKarte muß demgegenüber angeben, für welchem Konto sie autorisiert ist, soll die Möglichkeit des Hinzufügens und Entfernens von Konten ihrer Liste autorisierter Konten unterstützen und muß für die Zwecke dieser Anwendung die mit dieser BankKarte verknüpfte PIN als Datenelement enthalten. Die Schnittstelle für die Klasse GeldAutomat sieht folgendermaßen aus: name : string
Abhebung(Karte : BankKarte, Konto : Konto, pin : integer, Betrag : float) : float Einzahlung(Karte : BankKarte, Konto : Konto, pin : integer, Betrag : float) : float getKontostand(Karte : BankKarte, Konto : Konto, pin : integer) : float Beachten Sie, daß jede dieser Operationen in der Klasse GeldAutomat eine BankKarte erfordert, um den Kunden und die Konten zu identifizieren, für die er autorisiert ist, ein Konto, mit dem gearbeitet werden soll, sowie eine pin (bei der es sich für die Zwecke dieser Anwendung einfach um einen Integer handelt) zur Authentifizierung des Kunden. Die Schnittstelle von BankKarte sieht etwa so aus: pin : integer getKonten() : Konto[] hinzufuegenKonto(Konto : Konto) : void entfernenKonto(Konto : Konto) : void istAuthentifiziert(Konto : Konto) : boolean Modifizieren bestehender Klassen Natürlich können Sie nicht einfach die Klassen GeldAutomat und BankKarte erstellen und erwarten, daß die GeldAutomat-Fähigkeit in der restlichen Anwendung integriert ist; Sie müssen auch einige der bestehenden Klassen modifizieren. Beispielsweise muß eine BankKarte irgendwo herkommen; üblicherweise wird sie von einer Bank ausgegeben. Außerdem muß ein Kunde einige Kenntnisse der BankKarte haben, da die BankKarte in erster Linie für die Verwendung durch Kunden gedacht ist. Und schließlich sollte ein Kunde in der Lage sein, Geldautomaten zu lokalisieren und auf sie zuzugreifen, so daß es vernünftig wäre, BankServer so zu modifizieren, daß Standorte von Geldautomaten und Banken angegeben sind. Betrachten Sie zunächst die Klasse Bank, die in der Lage sein muß, BankKarten an Kunden auszugeben. Ein sinnvoller Weg zum Einrichten dieser Funktionalität besteht darin, folgende Methode zu Bank hinzuzufügen: ausgebenBankKarte(pin : integer, Konto : Konto) : BankKarte Hier wird angenommen, daß bei der Ausgabe der BankKarte eine erste pin eingerichtet wird und daß die BankKarte anfangs autorisiert wird, auf das angegebene Konto zuzugreifen. Beachten Sie, daß in dieser Anwendung der Aufrufer (vermutlich ein Kunde) dafür verantwortlich ist, die BankKarte nach ihrer Erstellung zu verwahren.
Kunde sollte wiederum so modifiziert werden, daß es BankKarten wahrnimmt und somit ein Kunde diese verwenden kann. Es zeigt sich, daß die einzige in Kunde erforderliche Hinzufügung eine interne Liste der diesem Kunden gehörenden BankKarten ist; aus Gründen der Bequemlichkeit könnten Sie eine Methode hinzufügen, die es anderen Objekten ermöglicht, auf diese Liste von BankKarten zuzugreifen. Sie können jedoch für die Zwecke dieser Anwendung annehmen, daß andere Objekte keinen Zugriff auf diese Information benötigen (übrigens geben Kunden üblicherweise Informationen über ihre Bankkarten nicht an andere weiter!). Für Ihre Zwecke sind keine Änderungen an der Schnittstelle von Kunde selbst erforderlich (die Änderungen an der Implementierung von Kunde folgen später). Schließlich sollte BankServer so modifiziert werden, daß der Zugriff auf Geldautomaten sowie Banken erleichtert wird. Erinnern Sie sich an die in der Schnittstelle von BankServer angegebenen Operationen, welche den Zugriff auf Banken ermöglichen: registriereBank(bank : Bank) : void aufhebenRegistrierungBank(bank : Bank) : void getBanken() : Bank[] Es erscheint sinnvoll, diese Schnittstelle für Geldautomaten nachzuahmen, was in folgenden Operationen resultiert: registriereGeldautomat(geldautomat : Geldautomat) : void aufhebenRegistrierungGeldautomat(geldautomat : Geldautomat) : void getGeldautomaten() : Geldautomat[] Das geänderte Klassendiagramm erweitert um die neuen Klassen Geldautomat und BankKarte nebst der modifizierten Klasse Bank ist in Abbildung 8.1 zu sehen.
Abbildung 8.1: Das Klassendiagramm der geänderten Bank-Anwendung Zwei neue Exceptions AuthentifizierungException und UngueltigerGeldautomatException Die Operationen in der Klasse Geldautomat lassen eine neue Exception als erforderlich erscheinen. Um diesem Erfordernis zu entsprechen, definieren Sie eine neue Exception (nennen Sie diese AuthentifizierungException), die von jeder Geldautomat-Methode ausgelöst
werden kann. Später werden Sie diese Methoden implementieren, um eine AuthentifizierungException auszulösen, wenn ein Problem bei der Authentifizierung des Kunden (beispielsweise, wenn die vom Kunden eingegebene PIN nicht der auf der BankKarte gespeicherten pin entspricht) oder bei der Autorisierung des Kunden auftritt (beispielsweise, wenn das in Geldautomat an eine Methode übergebene Konto nicht auf der jeweiligen BankKarte autorisiert ist). Erinnern Sie sich auch an die neuen, der Schnittstelle von BankServer hinzugefügten Methoden. Da diese im wesentlichen den Bank-bezogenen Operationen gleichen, welche UngueltigeBankExceptions auslösen, folgt, daß es eine analoge Exception für die Geldautomat-bezogenen Operationen geben muß. Daher werden Sie UngueltigerGeldautomatException definieren wollen, die in der von Ihnen erwarteten Weise verwendet wird.
Modifizieren der IDL-Spezifikation Die Änderung der IDL-Spezifikationen ist unkompliziert. Beginnen Sie mit der geänderten BankSchnittstelle, die in Listing 8.1 mit fett markierten Änderungen erscheint. Listing 8.1: Die geänderte Bank.idl. 1: // Bank.idl 2: 3: // Forward-Deklaration der Schnittstelle Bank. 4: interface Bank; 5: 6: #ifndef Bank_idl 7: #define Bank_idl 8: 9: // Banksequenzen 10: typedef sequence BankenListe; 11: 12: #include "Kunde.idl" 13: #include "Konto.idl" 14: #include "BankKarte.idl" 15: #include "Exceptions.idl" 16: 17: // Bank ermöglicht Zugriff auf Konten und kann für 18: // einen Kunden ein Konto erzeugen, ein Konto löschen 19: // oder die aktuellen Konten der Banken auflisten. 20: interface Bank { 21: 22: // Name dieser Bank. 23: attribute string name;
24: 25: // Adresse dieser Bank. 26: attribute string adresse; 27: 28: // Ein Konto für den angegebenen Kunden erzeugen, unter Berücksichtigung 29: // der angegebenen Kontoart ("Sparkonto" oder "Girokonto", wobei auf die 30: // Schreibweise zu achten ist) und des angegebenen Eröffnungssaldos. 31: Konto erstelleKonto(in Kunde Kunde, in string 32: KontoArt, in float eroeffnungsSaldo); 33: 34: // Angegebenes Konto löschen. Gehört das Konto nicht zu dieser 35: // Bank, führt diese Funktion nichts aus. 36: void loescheKonto(in Konto Konto) 37: raises (UngueltigesKontoException); 38: 39: // Alle Konten dieser Bank auflisten. 40: KontoListe getKonten(); 41: 42: // Eine BankKarte mit der anfänglichen PIN und Berechtigung 43: // für das angegebene Konto ausgeben. Ist das Konto nicht 44: // bei dieser Bank registriert, führt diese Funktion nichts aus. 45: BankKarte ausgebenBankKarte(in short pin, in Konto Konto) 46: raises (UngueltigesKontoException); 47: }; 48: 49: #endif Richten Sie Ihre Aufmerksamkeit auf die veränderte Exceptions.idl, die jetzt die neu erstellten AuthentifizierungException und UngueltigerGeldautomatException enthält. Die geänderte Datei erscheint in Listing 8.2. Listing 8.2: Die geänderte Exceptions.idl. 1: // Exceptions.idl 2: 3: #ifndef Exceptions_idl 4: #define Exceptions_idl 5: 6: // Diese Exception wird ausgelöst, wenn die Authentifizierung fehlschlägt; z.B. 7: // wenn ein Kunde eine PIN eingibt, die nicht zur 8: // BankKarte gehört.
9: exception AuthentifizierungException { 10: 11: }; 12: 13: // Diese Exception wird ausgelöst, wenn einer Methode ein ungültiger 14: // Betrag übergeben wird; wenn z.B. versucht wird, einen negativen 15: // Betrag auf ein Konto einzuzahlen. 16: exception UngueltigerBetragException { 17: 18: }; 19: 20: // Diese Exception wird ausgelöst, wenn ein ungültiges Konto an eine 21: // Methode übergeben wird, die ein Konto-Objekt erwartet. 22: exception UngueltigesKontoException { 23: 24: }; 25: 26: // Diese Exception wird ausgelöst, wenn eine ungültige Bank an eine 27: // Methode übergeben wird, die ein Bank-Objekt erwartet. 28: exception UngueltigeBankException { 29: 30: }; 31: 32: // Diese Exception wird ausgelöst, wenn ein ungültiger Geldautomat an eine 33: // Methode übergeben wird, die ein Geldautomat-Objekt erwartet. 34: exception UngueltigerGeldautomatException { 35: 36: }; 37: 38: // Diese Exception wird ausgelöst, wenn keine ausreichende Deckung 39: // vorhanden ist, um eine Transaktion durchzuführen; wenn z.B. bei einer Abhebung 40: // versucht wird, einen Betrag abzuheben, der durch das Guthaben nicht gedeckt ist. 41: exception UnzureichendeGeldmittelException { 42: 43: }; 44: 45: #endif
Betrachten Sie nun die in den Listings 8.3 und 8.4 gezeigten IDL-Abbildungen für die neu erstellten Klassen Geldautomat und BankKarte. Die IDL-Schnittstellendefinitionen enthalten keine Überraschungen; alle Elementdaten und -methoden sind in der zu erwartenden Weise abgebildet. Listing 8.3: Geldautomat.idl. 1: // Geldautomat.idl 2: 3: // Forward-Deklaration der Schnittstelle Geldautomat. 4: interface Geldautomat; 5: 6: #ifndef Geldautomat_idl 7: #define Geldautomat_idl 8: 9: // Geldautomat-Sequenzen 10: typedef sequence GeldautomatListe; 11: 12: #include "Konto.idl" 13: #include "BankKarte.idl" 14: #include "Exceptions.idl" 15: 16: // Ein Geldautomat wird zum (indirekten) Kontozugriff verwendet. Jede Geldautomat-Operation 17: // wird durch die gegebene BankKarte überprüft; z.B. 18: // muß ein Konto durch die jeweilige BankKarte 19: // authorisiert werden. 20: interface Geldautomat { 21: 22: // Der Geldautomat-Name. 23: attribute string name; 24: 25: // Abheben des angegebenen Betrags vom angegebenen Konto. Liefert den 26: // neuen Kontostand zurück. Hat die angegebene BankKarte keine 27: // Zugriffsberechtigung für das Konto, oder ist die angegebene PIN 28: // nicht korrekt, führt diese Operation nichts aus. 29: float Abhebung(in BankKarte Karte, in Konto Konto, in short 30: pin, in float Betrag) raises (AuthentifizierungException, 31: UngueltigerBetragException, UnzureichendeGeldmittelException); 32: 33: // Einzahlung des angegebenen Betrags auf das angegebene Konto. Liefert den 34: // neuen Kontostand zurück. Hat die angegebene BankKarte keine 35: // Zugriffsberechtigung für das Konto, oder ist die angegebene PIN
36: 37: 38: 39: 40: 41: 42: 43: PIN 44: 45: 46: 47: 48: 49:
// nicht korrekt, führt diese Operation nichts aus. float Einzahlung(in BankKarte Karte, in Konto Konto, in short pin, in float Betrag) raises (AuthentifizierungException, UngueltigerBetragException); // Den aktuellen Kontostand des angegebenen Kontos zurückgeben. // Hat die angegebene BankKarte keine // Zugriffsberechtigung für das Konto, oder ist die angegebene // nicht korrekt, führt diese Operation nichts aus. float getKontostand(in BankKarte Karte, in Konto Konto, in short pin) raises (AuthentifizierungException); }; #endif
Listing 8.4: BankKarte.idl. 1: // BankKarte.idl 2: 3: // Forward-Deklaration der Schnittstelle BankKarte. 4: interface BankKarte; 5: 6: #ifndef BankKarte_idl 7: #define BankKarte_idl 8: 9: // BankKarten-Sequenz 10: typedef sequence BankKartenListe; 11: 12: #include "Konto.idl" 13: #include "Exceptions.idl" 14: 15: // Eine BankKarte wird zum Zugriff auf einen Geldautomaten verwendet. Sie wartet eine Liste von 16: // Konten, auf die es zugreifen kann, sowie eine PIN, die angegeben werden 17: // muß, wenn die BankKarte verwendet werden soll. 18: interface BankKarte { 19: 20: // Die PIN dieser BankKarte. 21: attribute short pin; 22: 23: // Listet alle Konten auf, für die diese BankKarte über eine Zugriffsberechtigung verfügt. 24: KontoListe getKonten(); 25:
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
// Angegebenes Konto in die Liste der Konten einfügen, für // die diese BankKarte über eine Zugriffsberechtigung verfügt. void hinzufuegenKonto(in Konto Konto) raises (UngueltigesKontoException); // Angegebenes Konto aus der Liste der Konten entfernen, für // die diese BankKarte über eine Zugriffsberechtigung verfügt. void entfernenKonto(in Konto Konto) raises (UngueltigesKontoException); // True zurückliefern, wenn das angegebene Konto mit dieser // BankKarte verwendet werden kann. boolean istAuthentifiziert(in Konto Konto); }; #endif
Damit ist der Analyse- und Entwurfsteil der Geldautomat-Funktionalitätserweiterungen abgeschlossen; Sie können nun mit der Implementierung der Änderungen am Quelltext selbst beginnen.
Implementierung der neuen Funktionen Um die Geldautomat-Erweiterungen zu implementieren, müssen Sie die Implementierungsdateien ändern, um ihnen neue Funktionalität zu verleihen. Es folgt ein kurzer Überblick der Änderungen, die Sie vornehmen müssen: ■
■
■
■
Erweitern von BankServer. Im wesentlichen müssen Sie alles, was BankServer momentan für Banken erledigt, so erweitern, daß dies auch für Geldautomaten getan wird. Erweitern von Bank. Über die geforderte Fähigkeit zum Ausgeben von BankKarten hinaus bietet Bank auch die Implementierung von BankKarte. Implementieren des Geldautomat-Servers. Im wesentlichen nimmt Geldautomat Transaktionsanforderungen von Kunden an, überprüft die Autorisierung von Kunde gegenüber einer BankKarte und leitet, falls die Autorisierung gegeben ist, die Anforderungen an die jeweiligen Konten weiter. Implementieren des Geldautomat-Clients. Diese Anwendung ist eine Erweiterung des früheren Bank-Clients, die so modifiziert wurde, daß ein Konto erstellt und auf dieses anschließend anstatt direkt über einen Geldautomat zugegriffen wird.
Erweiterung von BankServer Der erste Schritt bei der Implementierung von Geldautomat-Funktionalität besteht darin,
BankServer so zu erweitern, daß es Geldautomaten ebenso wie Banken unterstützt. Da die Unterstützung für jeden Objekttyp exakt gleich ist (so können sich sowohl Geldautomaten als auch Banken beim BankServer an- und abmelden), können die Geldautomat-Methoden fast direkt von ihren Bank-Gegenstücken kopiert werden. Die notwendigen Änderungen an den Implementierungsdateien von BankServer erscheinen in den Listings 8.5-8.7, wobei die Änderungen gegenüber der vorigen Version durch Fettdruck hervorgehoben sind. Listing 8.5: BankServerImpl.h. 1: // BankServerImpl.h 2: 3: #ifndef BankServerImpl_h 4: #define BankServerImpl_h 5: 6: #include 7: 8: #include "../BankServer_s.h" 9: 10: class BankServerImpl : public _sk_BankServer { 11: 12: public: 13: 14: // Konstruktor. 15: BankServerImpl(const char* name); 16: 17: // Destruktor. 18: ~BankServerImpl(); 19: 20: // Diese Methoden werden in BankServer.idl beschrieben. 21: virtual void registriereBank(Bank_ptr bank) throw 22: (UngueltigeBankException); 23: virtual void aufhebenRegistrierungBank(Bank_ptr bank) throw 24: (UngueltigeBankException); 25: virtual BankenListe* getBanken(); 26: virtual void registriereGeldautomat(Geldautomat_ptr geldautomat) throw 27: (UngueltigerGeldautomatException); 28: virtual void aufhebenRegistrierungGeldautomat(Geldautomat_ptr geldautomat) throw 29: (UngueltigerGeldautomatException); 30: virtual GeldautomatListe* getGeldautomaten(); 31: 32: private: 33: 34: // Standard-Konstruktor.
35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
BankServerImpl(); // Die Bankenliste dieses BankServers. std::vector meineBanken; // Die Liste der Geldautomaten dieses BankServers. std::vector meineGeldautomaten; }; #endif
Beachten Sie in Listing 8.5 die Ähnlichkeit zwischen den Methoden registriereBank() (Zeilen 21-22) und registriereGeldautomat() (Zeilen 26-27) ebenso wie zwischen anderen Paaren korrespondierender Methoden. So wie BankServerImpl ein std::vector verwendet, um Referenzen auf Bank-Objekte zu speichern (wie in den Zeilen 37-38 zu sehen), so verwendet es jetzt ein std::vector, um Referenzen auf GeldautomatObjekte zu speichern (Zeilen 40-41). Listing 8.6 illustriert die weiteren Ähnlichkeiten zwischen den zuvor bestehenden Methodenimplementierungen in BankServerImpl und den Geldautomat-Methoden, die hinzugefügt werden. Die Implementierungen sind exakt gleich, wobei Referenzen auf Banken in Referenzen auf Geldautomaten umgeändert sind, usw. (Da die Semantiken der Operationen für Geldautomaten und Banken gleich sind, ist es keine Überraschung, daß die Implementierungen annähernd identisch sind.) Listing 8.6: BankServerImpl.cpp. 1: // BankServerImpl.cpp 2: 3: #include "BankServerImpl.h" 4: 5: #include 6: #include 7: 8: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Banken gleich sind. 9: class IstBankGleich : public std::unary_function { 10: public: 11: IstBankGleich(argument_type bank) { meineBank = bank; } 12: result_type operator()(argument_type bank) { return bank-> 13: _is_equivalent(meineBank) != 0; } 14: private: 15: argument_type meineBank;
16: }; 17: 18: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Geldautomaten gleich sind. 19: class IstGeldautomatGleich : public std::unary_function { 20: public: 21: IstGeldautomatGleich(argument_type geldautomat) { meinGeldautomat = geldautomat; } 22: result_type operator()(argument_type geldautomat) { return geldautomat-> 23: _is_equivalent(meinGeldautomat) != 0; } 24: private: 25: argument_type meinGeldautomat; 26: }; 27: 28: // Konstruktor. 29: BankServerImpl::BankServerImpl(const char* name) : 30: _sk_BankServer(name), meineBanken(), meineGeldautomaten() { 31: 32: } 33: 34: // Destruktor. 35: BankServerImpl::~BankServerImpl() { 36: 37: } 38: 39: void BankServerImpl::registriereBank(Bank_ptr bank) throw 40: (UngueltigeBankException) { 41: 42: // Sicherstellen, daß angegebene Bank nicht bereits vorhanden ist. 43: std::vector::iterator first = meineBanken.begin(); 44: std::vector::iterator last = meineBanken.end(); 45: IstBankGleich predicate(bank); 46: 47: std::vector::iterator uebereinstimmendeBank = std:: 48: find_if(first, last, predicate); 49: if (uebereinstimmendeBank == last) { 50: 51: // Bank nicht gefunden, daher angegebene Bank an das 52: // Listenende einfügen. 53: cout << "BankServerImpl: Registriere Bank \"" << bank-> 54: name() << "\"." << endl; 55: meineBanken.push_back(Bank::_duplicate(bank)); 56: return;
57: } else { 58: 59: // Bank ist bereits registriert, daher eine Exception auslösen. 60: throw UngueltigeBankException(); 61: } 62: } 63: 64: void BankServerImpl::aufhebenRegistrierungBank(Bank_ptr bank) throw 65: (UngueltigeBankException) { 66: 67: std::vector::iterator first = meineBanken.begin(); 68: std::vector::iterator last = meineBanken.end(); 69: IstBankGleich predicate(bank); 70: 71: std::vector::iterator uebereinstimmendeBank = std:: 72: find_if(first, last, predicate); 73: if (uebereinstimmendeBank == last) { 74: 75: // Ungültige Bank; eine Exception auslösen. 76: cout << "BankServerImpl: Es wurde versucht die Registrierung einer ungültigen Bank " 77: "aufzuheben." << endl; 78: throw UngueltigeBankException(); 79: } 80: cout << "BankServerImpl: Registrierung wird aufgehoben \"" << bank->name() 81: << "\"." << endl; 82: 83: // Angegebene Bank löschen. 84: meineBanken.erase(uebereinstimmendeBank); 85: bank->_release(); 86: } 87: 88: BankenListe* BankServerImpl::getBanken() { 89: 90: BankenListe* list = new BankenListe(); 91: CORBA::Long i; 92: 93: list->length(meineBanken.size()); 94: for (i = 0; i < meineBanken.size(); i++) { 95: (*list)[i] = Bank::_duplicate(meineBanken[i]); 96: } 97: 98: cout << "BankServerImpl: Auflistung von " << meineBanken.size() 99: << " Bank(en) wird ausgegeben." << endl;
100: 101: return BankenListe::_duplicate(list); 102: } 103: 104: void BankServerImpl::registriereGeldautomat(Geldautomat_ptr geldautomat) throw 105: (UngueltigerGeldautomatException) { 106: 107: // Sicherstellen, daß angegebener Geldautomat nicht bereits vorhanden ist. 108: std::vector::iterator first = meineGeldautomaten.begin(); 109: std::vector::iterator last = meineGeldautomaten.end(); 110: IstGeldautomatGleich predicate(geldautomat); 111: 112: std::vector::iterator uebereinstimmenderGeldautomat = std::find_if(first, 113: last, predicate); 114: if (uebereinstimmenderGeldautomat == last) { 115: 116: // Geldautomat nicht gefunden, daher angegebenen Geldautomaten an das 117: // Listenende einfügen. 118: cout << "BankServerImpl: RegistriereGeldautomat \"" << geldautomat-> 119: name() << "\"." << endl; 120: meineGeldautomaten.push_back(Geldautomat::_duplicate(geldautomat)); 121: return; 122: } else { 123: 124: // Geldautomat ist bereits registriert, daher eine Exception auslösen. 125: throw UngueltigerGeldautomatException(); 126: } 127: } 128: 129: void BankServerImpl::aufhebenRegistrierungGeldautomat(Geldautomat_ptr geldautomat) throw 130: (UngueltigeBankException) { 131: 132: std::vector::iterator first = meineGeldautomaten.begin(); 133: std::vector::iterator last =
meineGeldautomaten.end(); 134: IstGeldautomatGleich predicate(geldautomat); 135: 136: std::vector::iterator uebereinstimmenderGeldautomat = std::find_if(first, 137: last, predicate); 138: if (uebereinstimmenderGeldautomat == last) { 139: 140: // Ungültiger Geldautomat; eine Exception auslösen. 141: cout << "BankServerImpl: Es wurde versucht die Registrierung eines ungültigen Geldautomaten " 142: "aufzuheben." << endl; 143: throw UngueltigerGeldautomatException(); 144: } 145: cout << "BankServerImpl: Registrierung wird aufgehoben \"" << geldautomat->name() 146: << "\"." << endl; 147: 148: // Angegebenen Geldautomat löschen. 149: meineGeldautomaten.erase(uebereinstimmenderGeldautomat); 150: geldautomat->_release(); 151: } 152: 153: GeldautomatListe* BankServerImpl::getGeldautomaten() { 154: 155: GeldautomatListe* list = new GeldautomatListe(); 156: CORBA::Long i; 157: 158: list->length(meineGeldautomaten.size()); 159: for (i = 0; i < meineGeldautomaten.size(); i++) { 160: (*list)[i] = Geldautomat::_duplicate(meineGeldautomaten[i]); 161: } 162: 163: cout << "BankServerImpl: Auflistung von " << meineGeldautomaten.size() 164: << " Geldautomaten wird ausgegeben." << endl; 165: 166: return GeldautomatListe::_duplicate(list); 167: } Wie in Listing 8.7 zu sehen, sind in BankServerHaupt.cpp keine Änderungen erforderlich; die Anwendung BankServer startet in exakt der gleichen Weise wie an Tag 7. Listing 8.7: BankServerHaupt.cpp.
1: // BankServerHaupt.cpp 2: 3: #include "BankServerImpl.h" 4: #include 5: 6: int main(int argc, char *const *argv) { 7: 8: // ORB und BOA initialisieren. 9: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 10: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 11: 12: // Ein BankServerImpl-Objekt erzeugen. 13: BankServerImpl bankServer("BankServer"); 14: 15: // BOA informieren, daß das BankServerImpl-Objekt bereit ist. 16: boa->obj_is_ready(&bankServer); 17: 18: // Auf CORBA-Ereignisse warten. 19: cout << "BankServer bereit." << endl; 20: boa->impl_is_ready(); 21: 22: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 23: return 0; 24: } Erweiterung der Bank In Listings 8.8 und 8.9 ist die Klasse BankImpl im wesentlichen unverändert, abgesehen von der Einfügung von ausgebenBankKarte() in den Zeilen 32-33. Diese Methode nimmt eine Anforderung zur Ausgabe einer BankKarte für ein gegebenes Konto an. ausgebenBankKarte() prüft zunächst das Objekt Konto, um zu prüfen, ob es bei dieser Bank existiert. Ist dies der Fall, so wird die BankKarte ausgegeben; falls nicht, wird eine UngueltigesKontoException ausgelöst. Listing 8.8: BankImpl.h. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// BankImpl.h #ifndef BankImpl_h #define BankImpl_h #include #include "../Bank_s.h"
10: class BankImpl : public _sk_Bank { 11: 12: public: 13: 14: // Konstruktor. 15: // 16: // name - Name dieser Bank. 17: BankImpl(const char* name); 18: 19: // Destruktor. 20: ~BankImpl(); 21: 22: // Diese Methoden werden in Bank.idl beschrieben. 23: virtual char* name(); 24: virtual void name(const char* val); 25: virtual char* adresse(); 26: virtual void adresse(const char* val); 27: virtual Konto_ptr erstelleKonto(Kunde_ptr Kunde, 28: const char* KontoArt, CORBA::Float eroeffnungsSaldo); 29: virtual void loescheKonto(Konto_ptr Konto) throw 30: (UngueltigesKontoException); 31: virtual KontoListe* getKonten(); 32: virtual BankKarte_ptr ausgebenBankKarte(CORBA::Short pin, Konto_ptr 33: Konto) throw (UngueltigesKontoException); 34: 35: protected: 36: 37: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird 38: // in einem statischen Puffer zurückgegeben. 39: char* getNaechsteKontoNummer(); 40: 41: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis 42: // wird in einem statischen Puffer zurückgeliefert. 43: char* getCurrentDate(); 44: 45: private: 46: 47: // Standard-Konstruktor. 48: BankImpl(); 49: 50: // Name dieser Bank. 51: char* meinName; 52:
53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63:
// Adresse dieser Bank. char* meineAdresse; // Die Konten dieser Bank. std::vector meineKonten; // Die nächste verfügbare Kontonummer. unsigned int meineNaechsteKontoNummer; }; #endif
Listing 8.9: BankImpl.cpp. 1: // BankImpl.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: #include <string.h> 7: #include 8: #include 9: #include 10: 11: #include "SparKontoImpl.h" 12: #include "GiroKontoImpl.h" 13: #include "BankKarteImpl.h" 14: 15: extern CORBA::BOA_var boa; 16: 17: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Konten 18: // gleich sind. 19: class IstKontoGleich : public std::unary_function { 21: public: 22: IstKontoGleich(argument_type Konto) { meinKonto = Konto; } 23: result_type operator()(argument_type Konto) { return Konto-> 24: _is_equivalent(meinKonto) != 0; } 25: private: 26: argument_type meinKonto; 27: }; 28: 29: // Konstruktor. 30: // 31: // name - Name dieser Bank.
32: BankImpl::BankImpl(const char* name) : _sk_Bank(name), meineKonten(), 33: meinName(strdup(name)), meineAdresse(strdup("123 Elm Street, " 34: "Anyware USA 12345")), meineNaechsteKontoNummer(0) { 35: 36: } 37: 38: // Standard-Konstruktor. 39: BankImpl::BankImpl() : meineKonten(), meinName(NULL), meineAdresse(NULL), 40: meineNaechsteKontoNummer(0) { 41: 42: } 43: 44: // Destruktor. 45: BankImpl::~BankImpl() { 46: 47: cout << "Bank \"" << name() << "\" wird gelöscht." << endl; 48: free(meinName); 49: free(meineAdresse); 50: } 51: 52: char* BankImpl::name() { 53: 54: return CORBA::strdup(meinName); 55: } 56: 57: void BankImpl::name(const char* val) { 58: 59: free(meinName); 60: meinName = strdup(val); 61: } 62: 63: char* BankImpl::adresse() { 64: 65: return CORBA::strdup(meineAdresse); 66: } 67: 68: void BankImpl::adresse(const char* val) { 69: 70: free(meineAdresse); 71: meineAdresse = strdup(val); 72: } 73: 74: Konto_ptr BankImpl::erstelleKonto(Kunde_ptr Kunde, 75: const char* KontoArt, CORBA::Float eroeffnungsSaldo) {
76: 77: Konto_ptr neuesKonto; 78: 79: if (strcmp(KontoArt, "savings") == 0) { 80: 81: // Erzeuge eine neues SparKontoImpl-Objekt für das Konto. 82: cout << "BankImpl: Erzeuge neues Sparkonto für " 83: "Kunde " << Kunde->name() << "." << endl; 84: neuesKonto = new SparKontoImpl(getNaechsteKontoNummer(), 85: getCurrentDate(), eroeffnungsSaldo, Kunde, 10.0); 86: } else if (strcmp(KontoArt, "checking") == 0) { 87: 88: // Erzeuge ein neues GiroKontoImpl-Objekt für das Konto. 89: cout << "BankImpl: Erzeuge neues Girokonto für " 90: "Kunde " << Kunde->name() << "." << endl; 91: neuesKonto = new GiroKontoImpl(getNaechsteKontoNummer(), 92: getCurrentDate(), eroeffnungsSaldo, Kunde); 93: } else { 94: 95: // Ungültige Kontoart; keine Aktion ausführen. 96: cout << "BankImpl: Kunde " << Kunde->name() << 97: " hat ungültige Kontoart angefordert \"" << KontoArt 98: << "\"." << endl; 99: return Konto::_nil(); 100: } 101: 102: // Das erzeugte Konto an das Listenende einfügen und zurückliefern. 103: ::boa->obj_is_ready(neuesKonto); 104: meineKonten.push_back(Konto::_duplicate(neuesKonto)); 105: return neuesKonto; 106: } 107: 108: void BankImpl::loescheKonto(Konto_ptr Konto) throw 109: (UngueltigesKontoException) { 110: 111: std::vector::iterator first = meineKonten.begin(); 112: std::vector::iterator last = meineKonten.end(); 113: IstKontoGleich predicate(Konto); 114: 115: std::vector::iterator uebereinstimmendesKonto = std:: 116: find_if(first, last, predicate); 117: if (uebereinstimmendesKonto == last) { 118: 119: // Ungültiges Konto; eine Exception auslösen.
120: cout << "BankImpl: Versuch ein ungültiges Konto zu löschen." << 121: endl; 122: throw UngueltigesKontoException(); 123: } 124: cout << "BankImpl: Lösche Konto \"" << Konto-> 125: KontoNummer() << "\"." << endl; 126: 127: // Angegebenes Konto löschen. 128: meineKonten.erase(uebereinstimmendesKonto); 129: Konto->_release(); 130: } 131: 132: KontoListe* BankImpl::getKonten() { 133: 134: KontoListe* list = new KontoListe(meineKonten.size()); 135: CORBA::Long i; 136: 137: for (i = 0; i < meineKonten.size(); i++) { 138: (*list)[i] = Konto::_duplicate(meineKonten[i]); 139: } 140: 141: return list; 142: } 143: 144: BankKarte_ptr BankImpl::ausgebenBankKarte(CORBA::Short pin, Konto_ptr 145: Konto) throw (UngueltigesKontoException) { 146: 147: // Zunächst prüfen, ob das Konto bei dieser Bank vorhanden ist. 148: std::vector::iterator first = meineKonten.begin(); 149: std::vector::iterator last = meineKonten.end(); 150: IstKontoGleich predicate(Konto); 151: 152: std::vector::iterator uebereinstimmendesKonto = std:: 153: find_if(first, last, predicate); 154: if (uebereinstimmendesKonto == last) { 155: 156: // Ungültiges Konto; Exception auslösen. 157: throw UngueltigesKontoException(); 158: } 159: 160: // Falls wir bis hierher gelangen, bedeutet dies, daß das Konto bei 161: // dieser Bank existiert, so daß wir fortfahren können. 162: BankKarte_ptr neueKarte = new BankKarteImpl(pin, Konto);
163: 164: return BankKarte::_duplicate(neueKarte); 165: } 166: 167: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird 168: // in einem statischen Puffer zurückgegeben. 169: char* BankImpl::getNaechsteKontoNummer() { 170: 171: static char KontoNummer[16] = "Konto "; 172: 173: sprintf(KontoNummer + 7, "%08u", meineNaechsteKontoNummer++); 174: 175: return KontoNummer; 176: } 177: 178: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis wird 179: // in einem statischen Puffer zurückgegeben. 180: char* BankImpl::getCurrentDate() { 181: 182: static char aktuellesDatum[12] = " "; 183: 184: time_t ltime; 185: time(<ime); 186: char* ctimeResult = ctime(<ime); 187: 188: memcpy(aktuellesDatum, ctimeResult + 4, 3); 189: memcpy(aktuellesDatum + 4, ctimeResult + 8, 2); 190: memcpy(aktuellesDatum + 7, ctimeResult + 20, 4); 191: 192: return aktuellesDatum; 193: } Die Logik von BankKarteImpl (Listings 8.10 und 8.11) ist der in BankImpl sehr ähnlich; hinzufuegenKonto(), entfernenKonto() und getKonten() sind in exakt der gleichen Weise implementiert wie in BankImpl (mit der Ausnahme, daß entfernenKonto() und hinzufuegenKonto() die Funktion istAuthentifiziert() aufrufen, um festzustellen, ob das Konto in der Kontenliste enthalten ist, anstatt diese Funktionalität zu kopieren). Listing 8.10: BankKarteImpl.h. 1: // BankKarteImpl.h 2: 3: #ifndef BankKarteImpl_h
4: #define BankKarteImpl_h 5: 6: #include 7: 8: #include "../BankKarte_s.h" 9: 10: class BankKarteImpl : public _sk_BankKarte { 11: 12: public: 13: // Konstuktor. 14: // 15: // pin - die anfängliche PIN für diese BankKarte. 16: // anfaenglichesKonto - das Konto, für welches diese BankKarte als erstes 17: // authorisiert wird. 18: BankKarteImpl(CORBA::Short pin, Konto_ptr anfaenglichesKonto); 19: 20: // Destruktor. 21: ~BankKarteImpl(); 22: 23: // Diese Methoden werden in BankKarte.idl beschrieben. 24: virtual CORBA::Boolean istAuthentifiziert(Konto_ptr Konto); 25: virtual CORBA::Short pin(); 26: virtual void pin(CORBA::Short val); 27: virtual void entfernenKonto(Konto_ptr Konto) throw 28: (UngueltigesKontoException); 29: virtual void hinzufuegenKonto(Konto_ptr Konto) throw 30: (UngueltigesKontoException); 31: virtual KontoListe* getKonten(); 32: 33: private: 34: 35: // Standard-Konstruktor. 36: BankKarteImpl(); 37: 38: // Die PIN dieser BankKarte. 39: CORBA::Short meinePIN; 40: 41: // Die Liste der Konten, für die dieser Geldautomat über eine Zugriffsberechtigung verfügt. 42: std::vector meineKonten; 43: }; 44: 45: #endif Listing 8.11: BankKarteImpl.cpp.
1: // BankKarteImpl.cpp 2: 3: #include "BankKarteImpl.h" 4: 5: #include 6: #include 7: #include 8: 9: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Konten 10: // gleich sind. 11: class IstKontoGleich : public std::unary_function { 13: public: 14: IstKontoGleich(argument_type Konto) { meinKonto = Konto; } 15: result_type operator()(argument_type Konto) { return Konto-> 16: _is_equivalent(meinKonto) != 0; } 17: private: 18: argument_type meinKonto; 19: }; 20: 21: // Konstuktor. 22: // 23: // pin - die anfängliche PIN für diese BankKarte. 24: // anfaenglichesKonto - das Konto, für welches diese BankKarte als erstes 25: // authorisiert wird. 26: BankKarteImpl::BankKarteImpl(CORBA::Short pin, Konto_ptr 27: anfaenglichesKonto) : meinePIN(pin), meineKonten() { 28: 29: // Konto zur Zugriffsberechtigungsliste hinzufügen. 30: meineKonten.push_back(Konto::_duplicate(anfaenglichesKonto)); 31: } 32: 33: // Standard-Konstruktor. 34: BankKarteImpl::BankKarteImpl() : meinePIN(0), meineKonten() { 35: 36: } 37: 38: // Destruktor. 39: BankKarteImpl::~BankKarteImpl() { 40: 41: } 42: 43: CORBA::Short BankKarteImpl::pin() {
44: 45: return meinePIN; 46: } 47: 48: void BankKarteImpl::pin(CORBA::Short val) { 49: 50: meinePIN = val; 51: } 52: 53: CORBA::Boolean BankKarteImpl::istAuthentifiziert(Konto_ptr Konto) { 54: 55: std::vector::iterator first = meineKonten.begin(); 56: std::vector::iterator last = meineKonten.end(); 57: IstKontoGleich predicate(Konto); 58: 59: std::vector::iterator uebereinstimmendesKonto = std:: 60: find_if(first, last, predicate); 61: if (uebereinstimmendesKonto == last) { 62: 63: // Konto nicht gefunden; false zurückgeben. 64: return 0; 65: } else { 66: 67: // Konto gefunden; true zurückgeben. 68: return 1; 69: } 70: } 71: 72: void BankKarteImpl::entfernenKonto(Konto_ptr Konto) throw 73: (UngueltigesKontoException) { 74: 75: if (!istAuthentifiziert(Konto)) { 76: 77: // Ungültiges Konto; Exception auslösen. 78: throw UngueltigesKontoException(); 79: } 80: 81: // Angegebenes Konto löschen. 82: meineKonten.erase(&Konto); 83: Konto->_release(); 84: } 85: 86: void BankKarteImpl::hinzufuegenKonto(Konto_ptr Konto) throw 87: (UngueltigesKontoException) { 88:
89: if (istAuthentifiziert(Konto)) { 90: 91: // Konto wurde bereits hinzugefügt, daher eine Exception auslösen. 92: throw UngueltigesKontoException(); 93: } 94: 95: // Das erzeugte Konto an das Listenende einfügen. 96: meineKonten.push_back(Konto::_duplicate(Konto)); 97: } 98: 99: KontoListe* BankKarteImpl::getKonten() { 100: 101: KontoListe* list = new KontoListe(meineKonten.size()); 102: CORBA::Long i; 103: 104: for (i = 0; i < meineKonten.size(); i++) { 105: (*list)[i] = Konto::_duplicate(meineKonten[i]); 106: } 107: 108: return list; 109: } Da nichts anderes getan werden muß, wenn ein BankImpl-Objekt erzeugt wird, sind keine Änderungen gegenüber der ursprünglichen BankHaupt.cpp erforderlich, aus Gründen der Einheitlichkeit erscheint sie jedoch in Listing 8.12. Listing 8.12: BankHaupt.cpp. 1: // BankHaupt.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: 7: #include "../BankServer_c.h" 8: 9: CORBA::BOA_var boa; 10: 11: int main(int argc, char *const *argv) { 12: 13: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 14: // (zwei, mit dem Namen des Programms zusammen). 15: if (argc != 2) {
16: cout << "Verwendung: Bank " << endl; 17: return 1; 18: } 19: 20: // Namen der Bank dem ersten Parameter zuweisen. 21: const char* bankName = argv[1]; 22: 23: // ORB und BOA initialisieren. 24: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 25: ::boa = orb->BOA_init(argc, argv); 26: 27: // Ein Bank-Objekt erzeugen. 28: BankImpl bank(bankName); 29: 30: // BOA benachrichtigen, daß das BankImpl-Objekt bereit ist. 31: ::boa->obj_is_ready(&bank); 32: 33: // Ein BankServer-Objekt finden und damit registrieren. 34: BankServer_var bankServer; 35: try { 36: bankServer = BankServer::_bind(); 37: } catch (const CORBA::Exception& ex) { 38: 39: // Die Bindung ist mißlungen... 40: cout << "BankImpl: Bindung mit einem BankServer nicht möglich." << endl; 41: cout << ex << endl; 42: return 1; 43: } 44: try { 45: bankServer->registriereBank(&bank); 46: } catch (const CORBA::Exception& ex) { 47: 48: // Befehl registriereBank() mißlungen... 49: cout << "BankImpl: Bank konnte nicht registriert werden." << endl; 50: cout << ex << endl; 51: return 1; 52: } 53: 54: // Auf CORBA-Ereignisse warten. 55: cout << "Bank \"" << bankName << "\" bereit." << endl; 56: ::boa->impl_is_ready(); 57: 58: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 59: return 0;
60: } Implementierung des Geldautomat-Servers Die Implementierung von GeldautomatImpl (siehe Listings 8.13 und 8.14) ist klar. GeldautomatImpl leitet einfach Anfragen zum Abheben von Beträgen von einem Konto, zum Einzahlen auf dasselbe und zum Holen von Kontoauszügen desselben weiter. Jede Operation verwendet BankKarte, um die Transaktion zu autorisieren, und falls BankKarte zustimmt, wird die Transaktion an das Konto weitergeleitet. Da die Transaktion selbst von Konto vorgenommen wird, sind die Implementierungen für Abhebung(), Einzahlung() und getKontostand() einfach. Listing 8.13: GeldautomatImpl.h. 1: // GeldautomatImpl.h 2: 3: #ifndef GeldautomatImpl_h 4: #define GeldautomatImpl_h 5: 6: #include "../Geldautomat_s.h" 7: 8: class GeldautomatImpl : public _sk_Geldautomat { 9: 10: public: 11: // Konstuktor. 12: // 13: // name - der Name dieses Geldautomaten. 14: GeldautomatImpl(const char* name); 15: 16: // Destruktor. 17: ~GeldautomatImpl(); 18: 19: // Diese Methoden werden in Geldautomat.idl beschrieben. 20: virtual char* name(); 21: virtual void name(const char* val); 22: virtual CORBA::Float Abhebung(BankKarte_ptr Karte, Konto_ptr 23: Konto, CORBA::Short pin, CORBA::Float Betrag) throw 24: (AuthentifizierungException, UngueltigerBetragException, 25: UnzureichendeGeldmittelException); 26: virtual CORBA::Float Einzahlung(BankKarte_ptr Karte, Konto_ptr 27: Konto, CORBA::Short pin, CORBA::Float Betrag) throw 28: (AuthentifizierungException, UngueltigerBetragException); 29: virtual CORBA::Float getKontostand(BankKarte_ptr Karte, Konto_ptr 30: Konto, CORBA::Short pin) throw
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:
(AuthentifizierungException); private: // Standard-Konstruktor. GeldautomatImpl(); // Der Geldautomat-Name. char* meinName; }; #endif
Listing 8.14: GeldautomatImpl.cpp. 1: // GeldautomatImpl.cpp 2: 3: #include "GeldautomatImpl.h" 4: 5: #include 6: 7: // Konstruktor. 8: // 9: // name - der Name dieses Geldautomaten. 10: GeldautomatImpl::GeldautomatImpl(const char* name) : _sk_Geldautomat(name), 11: meinName(strdup(name)) { 12: 13: } 14: 15: // Standard-Konstruktor. 16: GeldautomatImpl::GeldautomatImpl() : _sk_Geldautomat(""), meinName(NULL) { 17: 18: } 19: 20: // Destruktor. 21: GeldautomatImpl::~GeldautomatImpl() { 22: 23: free(meinName); 24: } 25: 26: char* GeldautomatImpl::name() { 27: 28: return CORBA::strdup(meinName); 29: }
30: 31: void GeldautomatImpl::name(const char* val) { 32: 33: free(meinName); 34: meinName = strdup(val); 35: } 36: 37: CORBA::Float GeldautomatImpl::Abhebung(BankKarte_ptr Karte, Konto_ptr 38: Konto, CORBA::Short pin, CORBA::Float Betrag) throw 39: (AuthentifizierungException, UngueltigerBetragException, 40: UnzureichendeGeldmittelException) { 41: 42: cout << "Geldautomat: Authentifiziere Konto für Abhebungen." << endl; 43: if (pin != Karte->pin() || !Karte->istAuthentifiziert(Konto)) { 44: 45: // Falsche PIN oder Karte nicht authentifiziert; eine 46: // Exception auslösen. 47: cout << " Authentifizierung fehlgeschlagen." << endl; 48: throw AuthentifizierungException(); 49: } 50: 51: cout << " Authentifizierung erfolgreich; Abhebung wird an Konto " << 52: "weitergeleitet." << endl; 53: return Konto->Abhebung(Betrag); 54: } 55: 56: CORBA::Float GeldautomatImpl::Einzahlung(BankKarte_ptr Karte, Konto_ptr 57: Konto, CORBA::Short pin, CORBA::Float Betrag) throw 58: (AuthentifizierungException, UngueltigerBetragException) { 59: 60: cout << "Geldautomat: Authentifiziere Konto für Einzahlung." << endl; 61: if (pin != Karte->pin() || !Karte->istAuthentifiziert(Konto)) { 62: 63: // Falsche PIN oder Karte nicht authentifiziert; eine 64: // Exception auslösen. 65: cout << " Authentifizierung fehlgeschlagen." << endl; 66: throw AuthentifizierungException(); 67: } 68: 69: cout << " Authentifizierung erfolgreich; Einzahlung wird an Konto " <<
70: "weitergeleitet." << endl; 71: return Konto->Einzahlung(Betrag); 72: } 73: 74: CORBA::Float GeldautomatImpl::getKontostand(BankKarte_ptr Karte, Konto_ptr 75: Konto, CORBA::Short pin) throw (AuthentifizierungException) { 76: 77: cout << "Geldautomat: Authentifiziere Konto für Kontostandabfrage" << endl; 78: if (pin != Karte->pin() || !Karte->istAuthentifiziert(Konto)) { 79: 80: // Falsche PIN oder Karte nicht authentifiziert; eine 81: // Exception auslösen. 82: cout << " Authentifizierung fehlgeschlagen." << endl; 83: throw AuthentifizierungException(); 84: } 85: 86: cout << " Authentifizierung erfolgreich; Kontostandabfrage wird an Konto " << 87: "weitergeleitet." << endl; 88: return Konto->Kontostand(); 89: } Wiederum kann die Implementierung in GeldautomatHaupt.cpp überwiegend von anderen Stellen übernommen werden. In diesem Fall können Sie, da ein Geldautomat mit dem BankServer in ähnlicher Weise interagiert wie eine Bank, den Quelltext aus BankHaupt.cpp übernehmen und ihn geringfügig modifizieren (so daß ein GeldautomatImpl anstelle eines BankImpl erzeugt und registriert wird). GeldautomatHaupt.cpp ist in Listing 8.15 zu sehen, vergleichen Sie es jedoch mit BankHaupt.cpp (siehe Listing 8.12), und beachten Sie die Ähnlichkeit. Listing 8.15: GeldautomatHaupt.cpp. 1: // GeldautomatHaupt.cpp 2: 3: #include "GeldautomatImpl.h" 4: 5: #include 6: 7: #include "../BankServer_c.h" 8: 9: CORBA::BOA_var boa; 10: 11: int main(int argc, char *const *argv) {
12: 13: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 14: // (zwei, mit dem Namen des Programms zusammen). 15: if (argc != 2) { 16: cout << "Verwendung: Geldautomat " << endl; 17: return 1; 18: } 19: 20: // Namen des Geldautomaten dem ersten Parameter zuweisen. 21: const char* geldautomatName = argv[1]; 22: 23: // ORB und BOA initialisieren. 24: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 25: ::boa = orb->BOA_init(argc, argv); 26: 27: // Ein Geldautomat-Objekt erzeugen. 28: GeldautomatImpl geldautomat(geldautomatName); 29: 30: // BOA benachrichtigen, daß das GeldautomatImpl-Objekt bereit ist. 31: ::boa->obj_is_ready(&geldautomat); 32: 33: // Ein BankServer-Objekt finden und damit registrieren. 34: BankServer_var bankServer; 35: try { 36: bankServer = BankServer::_bind(); 37: } catch (const CORBA::Exception& ex) { 38: 39: // Die Bindung ist mißlungen... 40: cout << "GeldautomatImpl: Bindung mit einem BankServer nicht möglich." << endl; 41: cout << ex << endl; 42: return 1; 43: } 44: try { 45: bankServer->registriereGeldautomat(&geldautomat); 46: } catch (const CORBA::Exception& ex) { 47: 48: // Befehl registriereGeldautomat() mißlungen... 49: cout << "GeldautomatImpl: Geldautomat konnte nicht registriert werden." << endl; 50: cout << ex << endl; 51: return 1; 52: } 53:
54: // Auf CORBA-Ereignisse warten. 55: cout << "Geldautomat \"" << geldautomatName << "\" bereit." << endl; 56: ::boa->impl_is_ready(); 57: 58: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 59: return 0; 60: } Beachten Sie, daß in Zeile 36 wieder der Aufruf der Methode _bind() erscheint. Obwohl diese Funktion in vielen ORBs auftaucht, handelt es sich dabei nicht um Standard-CORBA. An Tag 12 werden Sie diesen Aufruf unter Verwendung des CORBA-Bezeichnungsdiensts durch den NichtStandardaufruf _bind() ersetzen, in der Zwischenzeit werden Sie jedoch weiterhin der Einfachheit halber _bind() verwenden. Implementierung des Geldautomat-Clients Der nächste Schritt besteht darin, eine Client-Anwendung zu implementieren, welche von der neu hinzugekommenen Geldautomat-Funktionalität Gebrauch macht. Der Client kann in seiner Funktion der Anwendung BankClient aus früheren Kapiteln ähneln, abgesehen davon, daß er auf ein Konto nicht direkt zugreift, sondern über die Geldautomat-Schnittstelle. Die Implementierung von GeldautomatClient ist in Listing 8.16 zu sehen. Listing 8.16: GeldautomatClientHaupt.cpp. 1: // GeldautomatClientHaupt.cpp 2: 3: #include 4: #include <stdlib.h> 5: 6: #include "../Kunde/KundenImpl.h" 7: 8: #include "../Bank_c.h" 9: #include "../BankServer_c.h" 10: #include "../Geldautomat_c.h" 11: 12: int main(int argc, char *const *argv) { 13: 14: // Anzahl der Parameter überprüfen; es sollten genau fünf vorliegen 15: // (zusammen mit dem Programmnamen sechs). 16: if (argc != 6) { 17: cout << "Verwendung: GeldautomatClient "
18: " <mädchenname der mutter> " << endl; 19: return 1; 20: } 21: 22: // Befehlszeilenparameter den Attributen des Kunden zuweisen. 23: const char* name = argv[1]; 24: const char* PersonalAusweisNummer = argv[2]; 25: const char* adresse = argv[3]; 26: const char* MuttersMaedchenName = argv[4]; 27: CORBA::Short pin = atoi(argv[5]); 28: 29: // ORB und BOA initialisieren. 30: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 31: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 32: 33: // Objekt Kunde erzeugen. 34: cout << "GeldautomatClient: Erzeuge neuen Kunden:" << endl; 35: cout << " Name: " << name << endl; 36: cout << " Personalausweisnummer: " << PersonalAusweisNummer << 37: endl; 38: cout << " Adresse: " << adresse << endl; 39: cout << " Mädchenname der Mutter: " << MuttersMaedchenName << endl; 40: KundenImpl Kunde(name, PersonalAusweisNummer, adresse, 41: MuttersMaedchenName); 42: 43: // BOA informieren, daß das KundenImpl-Objekt bereit ist. 44: boa->obj_is_ready(&Kunde); 45: 46: // BankServer-Objekt suchen und versuchen, eine Liste der Banken 47: // und Geldautomaten zu beziehen. 48: BankServer_var bankServer; 49: try { 50: bankServer = BankServer::_bind(); 51: } catch (const CORBA::Exception& ex) { 52: 53: // Die Bindung ist mißlungen... 54: cout << "GeldautomatClient: Bindung mit einem BankServer nicht möglich:" << 55: endl; 56: cout << ex << endl; 57: return 1; 58: } 59: cout << "GeldautomatClient: Bindung mit einem BankServer erfolgreich durchgeführt." << 60: endl;
61: 62: BankenListe_ptr banks; 63: GeldautomatListe_ptr Geldautomaten; 64: 65: try { 66: banks = bankServer->getBanken(); 67: Geldautomaten = bankServer->getGeldautomaten(); 68: } catch (const CORBA::Exception& ex) { 69: 70: // Der Versuch ist fehlgeschlagen... 71: cout << "GeldautomatClient: Liste der Banken und Geldautomaten konnte nicht abgerufen werden:" 72: << endl; 73: cout << ex << endl; 74: return 1; 75: } 76: 77: // Erste Bank und ersten Geldautomaten der Liste 78: // verwenden. 79: if (banks->length() == 0) { 80: 81: // Es sind keine Banken verfügbar. 82: cout << "GeldautomatClient: Es sind keine Banken verfügbar." << endl; 83: return 1; 84: } 85: if (Geldautomaten->length() == 0) { 86: 87: // Es sind keine Geldautomaten verfügbar. 88: cout << "GeldautomatClient: Es sind keine Geldautomaten verfügbar." << endl; 89: return 1; 90: } 91: Bank_var bank = (*banks)[0]; 92: Geldautomat_var geldautomat = (*Geldautomaten)[0]; 93: cout << "GeldautomatClient: Verwende Bank \"" << bank->name() << "\" und" 94: << " Geldautomat \"" << geldautomat->name() << "\"." << endl; 95: 96: // Einige interessante Dinge ausführen. 97: 98: Konto_var Konto; 99: BankKarte_var BankKarte; 100: 101: try { 102: Konto = bank->erstelleKonto(&Kunde, "checking", 0.0);
103: } catch (const CORBA::Exception& ex) { 104: 105: // Funktion erstelleKonto() fehlgeschlagen... 106: cout << "GeldautomatClient: Kann Konto nicht erzeugen." << endl; 107: cout << ex << endl; 108: return 1; 109: } 110: 111: try { 112: 113: // Einige Kontostatistiken ausgeben. 114: cout << "GeldautomatClient: Neues Konto wurde eröffnet:" << endl; 115: cout << " Kontonummer: " << Konto->KontoNummer() << 116: endl; 117: cout << " Eröffnungsdatum: " << Konto->EroefffnungsDatum() << 118: endl; 119: cout << " Kontostand: " << Konto->Kontostand() << endl; 120: 121: // Bank anfordern, eine BankKarte für das neu erzeugte Konto 122: // auszugeben. 123: cout << "GeldautomatClient: BankKarte wird von Bank bezogen." << endl; 124: try { 125: BankKarte = bank->ausgebenBankKarte(pin, Konto); 126: } catch (const UngueltigesKontoException&) { 127: 128: // Aus irgendeinem Grund war das Konto ungültig (dies 129: // sollte nicht vorkommen). 130: cout << "GeldautomatClient: Exception abgefangen: Ungültiges Konto" 131: << endl; 132: return 1; 133: } 134: 135: // Einige Kontotransaktionen mit dem Geldautomaten 136: // durchführen. 137: cout << "GeldautomatClient: Transaktionen werden durchgeführt." << endl; 138: try { 139: cout << " Einzahlung DM 250,00..." << endl; 140: cout << " Neuer Kontostand: DM" << geldautomat>Einzahlung(BankKarte, 141: Konto, pin, 250.00) << endl; 142:
143: // Dies wird eine Exception auslösen, da wir versuchen viel 144: // abzuheben. 145: cout << " Abhebung: DM 500,00..." << endl; 146: cout << " Neuer Kontostand: DM" << geldautomat>Abhebung(BankKarte, 147: Konto, pin, 500.00) << endl; 148: } catch (AuthentifizierungException&) { 149: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 150: << "Keine Authorisierung (wie erwartet)" << endl; 151: } catch (UngueltigerBetragException&) { 152: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 153: << endl; 154: } catch (UnzureichendeGeldmittelException&) { 155: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 156: "Geldmittel" << endl; 157: } 158: 159: // Weitere Kontotransaktionen mit dem Geldautomaten 160: // durchführen. 161: cout << "GeldautomatClient: Weitere Transaktionen durchführen." << endl; 162: try { 163: cout << " Einzahlung DM 500,00..." << endl; 164: cout << " Neuer Kontostand: DM" << 165: geldautomat->Einzahlung(BankKarte, Konto, pin, 500.00) << 166: endl; 167: 168: // Dies wird eine Exception auslösen, da wir die 169: // falsche PIN verwenden. 170: cout << " Abhebung DM 250,00 mit falscher PIN..." 171: << endl; 172: cout << " Neuer Kontostand: DM" << geldautomat>Abhebung(BankKarte, 173: Konto, pin + 1, 250.00) << endl; 174: } catch (AuthentifizierungException&) { 175: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 176: << "Keine Authorisierung (wie erwartet)" << endl; 177: } catch (UngueltigerBetragException&) { 178: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 179: << endl; 180: } catch (UnzureichendeGeldmittelException&) {
181: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 182: "Geldmittel" << endl; 183: } 184: 185: // Konto löschen. 186: try { 187: cout << " Konto wird gelöscht." << endl; 188: bank->loescheKonto(Konto); 189: 190: // Versuch, das Konto erneut zu löschen, nur zum Spaß. 191: // Dies sollte zur Auslösung einer Exception führen. 192: cout << " Versuch, durch nochmaliges Löschen des Kontos eine " << 193: " Exception auszulösen." << endl; 194: bank->loescheKonto(Konto); 195: } catch (const UngueltigesKontoException&) { 196: 197: // Sicher, daß die Exception ausgelöst wurde. 198: cout << "GeldautomatClient: Exception abgefangen: Ungültiges " << 199: "Konto (wie erwartet)" << endl; 200: } 201: } catch (const CORBA::Exception& ex) { 202: 203: // Eine Kontofunktion konnte nicht ausgeführt werden... 204: cout << "GeldautomatClient: Fehler beim Versuch, auf Konto zuzugreifen:" << endl; 205: cout << ex << endl; 206: return 1; 207: } 208: 209: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 210: return 0; 211: } GeldautomatClient.cpp ist stärker beteiligt als die Anwendung NeuerKunde.cpp des vorigen Kapitels (vgl. Listing 8.16). Es dürfte lehrreich sein, den Quelltext Schritt für Schritt durchzugehen, um zu sehen, was genau geschieht: Die ersten drei #include-Anweisungen (Zeilen 3-6) dürften Ihnen inzwischen bekannt vorkommen. Da GeldautomatClient ein Client einer Bank, eines BankServer und eines Geldautomaten ist, sind in den Zeilen 8-10 die Header-Dateien dieser Client-Rümpfe ebenfalls durch #include-Anweisungen eingebunden.
In den ersten Zeilen von main() (Zeilen 12-20) prüft der GeldautomatClient zunächst die Anzahl der in der Befehlszeile übergebenen Argumente. Die fünf Argumente sind der Name des Kunden, Personalausweisnummer, Adresse, Mädchenname der Mutter und Geheimzahl (PIN). Ist die Anzahl der Argumente nicht korrekt, so bricht das Programm ab. In den Zeilen 22-31 setzt GeldautomatClient seine internen Parameter auf die in der Befehlszeile übergebenen und initialisiert dann ORB und BOA. (Dieser Quelltextabschnitt ist beinahe eine exakte Kopie des entsprechenden Quelltextes in der Anwendung NeuerKunde.) Sodann erzeugt GeldautomatClient ein Objekt des Typs Kunde (Zeilen 33-41) und meldet es beim BOA an (Zeilen 43-44). Da es keine Änderungen gegenüber der Schnittstelle von Kunde von Tag 7 gibt, gleicht die Erzeugung eines Objekts vom Typ Kunde exakt der in NeuerKunde. Der nächste Schritt von GeldautomatClient in den Zeilen 46-60 besteht darin, ein Objekt vom Typ BankServer zu lokalisieren und sich mit diesem zu verbinden (in Zeile 50). Kann kein BankServer lokalisiert werden, so kann GeldautomatClient nicht fortfahren und bricht daher ab (diese Bedingung wird vom Konstrukt catch in den Zeilen 51-58 behandelt). In den Zeilen 62-75 empfängt GeldautomatClient die Liste verfügbarer Banken und Geldautomaten vom zuvor lokalisierten BankServer. Wiederum bricht der GeldautomatClient ab, falls beim Einholen dieser Informationen ein Problem auftritt. Als nächstes wählt GeldautomatClient in den Zeilen 77-94 die erste Bank und den ersten Geldautomaten aus, die in den vom BankServer empfangenen Listen erscheinen. Falls nicht jeweils mindestens ein Objekt verfügbar ist (die tatsächlichen Überprüfungen werden in den Zeilen 79 und 85 vorgenommen), so bricht GeldautomatClient ab. Der nächste Schritt besteht in der Eröffnung eines Kontos bei der gewählten Bank unter Verwendung des zuvor erzeugten Kunde-Objekts, was GeldautomatClient in den Zeilen 96109 durchführt. In den Zeilen 111-119 gibt GeldautomatClient einige Daten des Kontos aus: Kontonummer, Eröffnungsdatum und aktuellen Kontostand (dieser sollte Null sein). In den Zeilen 121-133 fordert GeldautomatClient eine BankKarte von der Bank an, wobei er die pin verwendet, die in der Befehszeile übergeben wurde. Da die BankKarte für das gerade bei der gleichen Bank eröffnete Konto ausstellt wurde, ist nicht zu erwarten, daß hier eine UngueltigesKontoException auftritt, doch als sichere Praxis versucht GeldautomatClient dennoch, diese Exception in Zeile 126 abzufangen. GeldautomatClient fährt fort, indem er einige Transaktionen auf dem Konto durchführt wie das Einzahlen und das Abheben von Beträgen in den Zeilen 135-157. Wie die Anwendung NeuerKunde versucht GeldautomatClient, zuviel vom Konto abzuheben (in Zeile 146), wodurch eine
UnzureichendeGeldmittelException ausgelöst wird (abgefangen in Zeile 154). Dann, in den Zeilen 159-183, führt GeldautomatClient eine weitere Transaktion durch; diesmal versucht er, eine Einzahlung auf das Konto zu tätigen, wobei er die falsche PIN verwendet. (Er erhält eine inkorrekte PIN durch Addieren von eins zur PIN, wie in Zeile 173 zu sehen.) Wie erwartet, führt dies dazu, daß eine AuthentifizierungException ausgelöst wird (die in Zeile 174 abgefangen wird). Schließlich, in den Zeilen 185-211, löscht GeldautomatClient das Konto. Da das Konto von derselben Bank angelegt wurde, die GeldautomatClient zum Löschen des Kontos verwendet, sollten Sie wiederum nicht erwarten, daß die UngueltigesKontoException ausgelöst wird, dennoch wurde eine catch-Anweisung aus Gründen der Gleichbehandlung eingefügt. Wie sein Vorgänger versucht auch GeldautomatClient, das Konto zweimal zu löschen (Zeile 194), um die UngueltigesKontoException zu demonstrieren (abgefangen in Zeile 195).
Ausführen der Anwendung Nachdem Sie die verschiedenen Komponenten der Anwendung erfolgreich kompiliert haben, können Sie diese zusammen ausführen, um die Ergebnisse zu sehen. Beginnen Sie wie an Tag 7, indem Sie die Anwendung BankServer starten; geben Sie folgendes ein: BankServer Wiederum gibt BankServer folgendes aus: BankServer bereit. Sie sind nun bereit zum Starten der Anwendung Bank. Tatsächlich können Sie zuerst die Anwendung Geldautomat starten, da die Objekte Bank und Geldautomat voneinander unabhängig sind (beide hängen jedoch von der Lokalisierung eines BankServer-Objekts ab). Starten Sie BankServer durch Eingabe von: BankServer "Ducksches Bankhaus" was wiederum zu folgender Ausgabe führt: Bank "Ducksches Bankhaus" bereit. Inzwischen hat BankServer dies ausgegeben: BankServerImpl: Registriere Bank "Ducksches Bankhaus".
Sie können die Anwendung Geldautomat starten: Geldautomat "Ducksches Bankhaus Geldautomat" Die Anwendung Geldautomat zeigt folgendes an: Geldautomat "Ducksches Bankhaus Geldautomat" bereit. Der BankServer meldet sich ebenfalls mit dieser Botschaft: BankServerImpl: RegistriereGeldautomat "Ducksches Bankhaus Geldautomat". Schließlich sind Sie in der Lage, die Anwendung GeldautomatClient auszuführen. Sie erreichen dies, indem Sie folgendes eingeben: GeldautomatClient "Jeremy Rosenberger" 123456789 "123 Main Street" Doe 1234 Der GeldautomatClient zeigt dann seine Zauberkunst, indem er folgendes ausgibt: GeldautomatClient: Erzeuge neuen Kunden: Name: Jeremy Rosenberger Personalausweisnummer: 123456789 Adresse: 123 Main Street Mädchenname der Mutter: Doe GeldautomatClient: Bindung mit einem BankServer erfolgreich durchgeführt. GeldautomatClient: Verwende Bank "Ducksches Bankhaus" und Geldautomat "Ducksches Bankhaus Geldautomat". GeldautomatClient: Neues Konto wurde eröffnet: Kontonummer: Konto00000000 Eröffnungsdatum: Oct 20 1997 Kontostand: 0 GeldautomatClient: BankKarte wird von Bank bezogen. GeldautomatClient: Transaktionen werden durchgeführt. Einzahlung DM 250,00... Neuer Kontostand ist 250,00 DM Abhebung DM 500,00... GeldautomatClient: Exception abgefangen: Unzureichende Geldmittel GeldautomatClient: Weitere Transaktionen durchführen. Einzahlung DM 500,00... Neuer Kontostand ist 750,00 DM Abhebung DM 250,00 mit falscher PIN...
GeldautomatClient: Exception abgefangen: Ungültige PIN oder keine Authorisierung (wie erwartet) Konto wird gelöscht. Versuch, eine Exception auszulösen, durch erneutes Löschen des Kontos. GeldautomatClient: Exception abgefangen: Ungültiges Konto (wie erwartet) All dies läuft sehr schnell ab, doch nachdem alles vorbei ist, können Sie zu den anderen Anwendungsfenstern wechseln und einige Belege dafür sehen, was hier geschehen ist. Wenn Sie zunächst nach der Anwendung BankServer sehen, finden Sie folgendes (wobei die neuen Meldungen durch Fettdruck hervorgehoben sind): BankServer bereit. BankServerImpl: Auflistung von 1 Bank(en) wird ausgegeben. BankServerImpl: Auflistung von 1 Geldautomat(en) wird ausgegeben. Wenn Sie sich an die Implementierung von BankServerImpl erinnern, dann werden Sie bemerken, daß diese Nachrichten ausgegeben werden, wenn die Methoden getBanken() und getGeldautomaten()aufgerufen werden. Wenn Sie Ihre Aufmerksamkeit nun auf die Anwendung Bank richten, so sehen Sie folgende Meldungen: Bank "Ducksches Bankhaus" bereit. BankImpl: Erzeuge neues Girokonto für Kunde Jeremy Rosenberger. KontoImpl: Unzureichende Geldmittel zur Abhebung des angegebenen Betrags. BankImpl: Lösche Konto "Konto00000000". BankImpl: Versuch ungültiges Konto zu löschen. Durch Untersuchung der Ausgabe der Anwendung Bank können Sie die Aktionen der Anwendung GeldautomatClient verfolgen: Es wird ein neues GiroKonto erzeugt; zu einem späteren Zeitpunkt wird ein erfolgloser Versuch unternommen, einen Betrag vom Konto abzuheben, und später wird Konto gelöscht. Erinnern Sie sich auch daran, daß der GeldautomatClient ebenso wie zuvor die Anwendung NeuerKunde versucht, das Konto zweimal zu löschen und daß auch über diesen Anlauf von der Anwendung Bank eine Nachricht ausgegeben wird. Sie werden sich erinnern, daß GeldautomatClient ebenfalls versuchte, eine Transaktion mit inkorrekter PIN durchzuführen. Beachten Sie, daß hier keine derartige Nachricht über diesen Versuch erscheint; tatsächlich hat die Bank den Transaktionsversuch nie gesehen, da der Geldautomat die Transaktion blockierte, als der Kunde nicht durch die BankKarte autorisiert werden konnte.
Wenden Sie Ihre Aufmerksamkeit schließlich noch der Ausgabe der Anwendung Geldautomat zu: Geldautomat "Ducksches Bankhaus Geldautomat" bereit. Geldautomat: Authentifiziere Konto für Einzahlung. Authentifizierung erfolgreich; Einzahlung wird an Konto weitergeleitet. Geldautomat: Authentifiziere Konto für Abhebungen. Authentifizierung erfolgreich; Abhebung wird an Konto weitergeleitet. Geldautomat: Authentifiziere Konto für Einzahlung. Authentifizierung erfolgreich; Einzahlung wird an Konto weitergeleitet. Geldautomat: Authentifiziere Konto für Abhebungen. Authentifizierung fehlgeschlagen. Die Ausgabe des Geldautomaten liefert die meisten Einblicke darüber, was GeldautomatClient tut. Jede Operation, ob erfolgreich oder nicht, wird von der Anwendung Geldautomat aufgezeichnet. Wie Sie sehen, ist jede Transaktion erfolgreich, außer der letzten, bei welcher GeldautomatClient absichtlich versucht, die falsche PIN für die Transaktion zu verwenden.
Zusammenfassung In diesem Kapitel haben Sie der Anwendung Bank ein erhebliches Maß an Funktionalität hinzugefügt. Das meiste davon wurde in Form einer neuen Systemkomponente, der Anwendung Geldautomat und ihres Gegenstücks, BankKarte, hinzugefügt. Auch die anderen Komponenten des Systems haben Sie so modifiziert, daß diese mit dem Geldautomaten zusammenarbeiten. So wurde der BankServer so verändert, daß Geldautomaten sich bei ihm anmelden können, die Bank wurde in einer Weise modifiziert, daß sie auf Anforderung BankKarten ausstellt, und natürlich wurde die Anwendung NeuerKunde so überarbeitet, daß sie Geldautomat und BankKarte für all ihre Transaktionen verwendet. Dieses Kapitel gibt Ihnen die Gelegenheit, mit einer komplexeren CORBA-Anwendung zu arbeiten, die eine Reihe miteinander interagierender Komponenten beinhaltet. An Tag 9 werden Sie die endgültige Installation der Anwendung Bank implementieren. Bislang steuert der Client-Teil der Anwendung (gestern NeuerKunde, heute GeldautomatClient) größtenteils den Prozeß. Mit anderen Worten, die Server-Komponenten verbringen ihre Zeit mit dem Warten darauf, daß von der Client-Komponente eine Transaktion initiiert wird. Dies wird gewöhnlich als Pull-Modell bezeichnet, da der Client aus den Servern nach Bedarf Informationen »zieht«. An Tag 9 werden Sie eine neue Fähigkeit hinzufügen: Durch Callbacks können die ServerKomponenten Nachrichten zum Client »drücken« (»Push«). Mit anderen Worten, falls ein Server (beispielsweise eine Bank) eine Nachricht an Kunde sendet, so muß er nicht darauf warten, daß
Kunde Informationen aus der Bank zieht; er kann die Informationen jederzeit zum Kunde drücken. Dies ist nützlich, falls Kunde über die Bank einen Dienst abonniert, der beispielsweise periodisch Aktienkurse aktualisiert.
F&A Frage: Mir scheint, daß die Schnittstelle Geldautomat kaum mehr ist als eine Durchgangsstation für Kunden beim Zugriff auf ihre Konten. Antwort: Falls Sie diese Eigenschaft der Schnittstelle Geldautomat erkannt haben, können Sie sich auf die Schulter klopfen. Der Geldautomat bringt tatsächlich kaum neue Funktionalität für die Anwendung, zumindest was Konto-Operationen anbelangt, sondern bietet einfach einen weiteren Weg des Kontozugriffs. Frage: Wenn tatsächlich jemand diese Anwendung zu entwickeln hätte, wäre dann nicht Sicherheit ein wichtigeres Thema? Zum Beispiel sieht es aus, als wäre es leicht, die PIN eines Kunden mit Gewalt zu erraten. Antwort: Wenn Sie diese Frage stellen, so haben Sie zweifelsfrei das vollständige Fehlen von Sicherheitsmechanismen in der Anwendung festgestellt (abgesehen vom PIN-Mechanismus selbst). Natürlich müßten Sie in einer Produktionsanwendung weitere Sicherheitsüberlegungen anstellen, um das Ausspionieren von Kennwörtern, Gewaltattacken und dergleichen zu verhindern. (Möglicherweise möchten Sie den CORBA Security Service oder eigene Mechanismen verwenden, um Sicherheitsmaßnahmen zu implementieren und in Kraft zu setzen.)
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A. Quiz Was sind die vier Schritte, nach denen Sie üblicherweise vorgehen, um Erweiterungen an einer CORBA-Anwendung vorzunehmen?
Übung Fügen Sie der Schnittstelle Geldautomat eine Operation hinzu, die es ermöglicht, Beträge zwischen Konten zu verschieben. Achten Sie darauf, auch geeignete Exceptions bereitzustellen.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 9 Verwendung von Callbacks zum Hinzufügen von PushFähigkeit Definieren zusätzlicher Anforderungen Modifizierung des Klassendiagramms Modifizieren der IDL-Spezifikation Implementierung der neuen Funktionen Ausführen der Anwendung Ideen für zukünftige Erweiterungen Zusammenfassung F&A Workshop
An Tag 8, »Hinzufügen von Geldautomat-Fähigkeit«, fuhren Sie mit der Entwicklung der Beispielanwendung Bank fort, indem Sie diese um Geldautomat-Fähigkeit erweiterten. Nun werden Sie die letzten Verbesserungen vornehmen, und zwar durch Hinzufügen von Push-Fähigkeit. Obwohl »Push« eines der neuesten Schlagworte der Industrie zu sein scheint (insbesondere soweit vom Internet die Rede ist), gibt es das Konzept schon seit einiger Zeit. (Das Internet ist schlicht eine der am ehesten sichtbaren Anwendungen dafür.) Client-Server-Anwendungen gehen üblicherweise nach dem Pull-Modell vor, bei dem der Client Informationen aus dem Server zieht, wenn er es wünscht (mit anderen Worten, der Client initialisiert die Transaktion). Beim Push-Modell drückt der Server Informationen zum Client, d.h., der Server initialisiert die Transaktion. Wenn sich beispielsweise ein Aktienkurs ändert, kann ein Server die aktualisierten Informationen per Push an seine Clients senden. Push kann wesentlich effizienter sein als Pull, wenn Informationen unregelmäßig aktualisiert werden, besonders wenn mehrere Clients einem System angehören.
Push-Fähigkeit kann auf viele Arten verwendet werden, bei der Anwendung Bank verwenden Sie sie zum Implementieren eines Kontoaktualisierungssystems. Mit diesem System erhalten die Kunden jede Minute aktualisierte Kontoauszüge. (Das wäre natürlich bei einer echten Bankanwendung sinnlos, es dient jedoch zur Demonstration des Konzepts.) Die Vorgehensweise ist dieselbe wie im vorigen Kapitel: ■
■
■
■
Zusätzliche Anforderungen definieren. Modifizieren Sie die Systemanforderungen, um eine Anforderung für die Kontoaktualisierungsfähigkeit zu definieren. Den Systementwurf modifizieren. Übersetzen Sie die zusätzlichen Anforderungen in Änderungen am Systementwurf, was wiederum die Vorstellung vom Software-Entwurf als iterativem Prozeß untermauert. Die IDL-Definitionen modifizieren. Erstellen oder aktualisieren Sie Schnittstellendefinitionen für neue Klassen oder für Klassen, die sich seit der letzten Iteration geändert haben. Die neue Funktionalität implementieren. Implementieren Sie nach Beendigung des Entwurfs und der Realisierung in IDL die neuen Funktionen, und prüfen Sie, wie die neue Anwendung funktioniert.
Definieren zusätzlicher Anforderungen Erinnern Sie sich an die Systemanforderungen für die Anwendung Bank von Tag 8. So wie Sie es beim Einarbeiten der Geldautomat-Fähigkeit in die Anwendung taten, werden Sie einen Satz von Anforderungen definieren, der den gewünschten Funktionsumfang definiert. Gehen Sie bei der Kontoaktualisierungsfähigkeit von folgenden Anforderungen aus: ■
Unterstützung für das periodische und automatische Senden aktualisierter Kontoauszüge an
■
Kunden durch einen Server Unterstützung der Anforderbarkeit der Kontoaktualisierungsfunktion durch Kunden
Die Anforderungen sind klar; sie formalisieren die bereits beschriebene Funktionalität. Beachten Sie, daß nur eine Anforderung zum Bestellen der Kontoaktualisierungsfunktion vorliegt; es gibt keine Anforderung zum Abbestellen dieser Funktion. Das Hinzufügen einer solchen Möglichkeit ist nicht schwierig, um die Beispielanwendung einfach zu halten, wurde diese Funktion weggelassen. (Vielleicht gäbe das eine gute Übung ab.)
Modifizierung des Klassendiagramms Wieder sind Sie so weit, einen veränderten Satz von Anforderungen in einen aktualisierten Systementwurf zu übersetzen. Diesmal wird es nicht nötig sein, zusätzliche Klassen zu erstellen, und Sie werden nur einige bestehende Klassen geringfügig abändern müssen. Modifizieren bestehender Klassen Die Änderungen, die zum Einbau der Kontoaktualisierung in die Anwendung erforderlich sind, liegen klar auf der Hand. Zunächst muß ein Kunde den Dienst abonnieren, was bedeutet, daß Bank so modifiziert werden muß, um dies zu ermöglichen. Die einzige zum Abonnieren des Dienstes erforderliche Information ist das Konto, so daß die Methode dieses als Parameter enthalten sollte. Keine weiteren Parameter sind zwingend erforderlich; Bank kann die Kunden, Kunde, über Konto feststellen, falls erforderlich. Ein Rückgabewert ist nicht erforderlich. Die Signatur für die neue Methode lautet: anfordernAktualisierungsDienst(Konto : Konto) : void Das andere Erfordernis besteht darin, der Klasse Kunde eine Methode hinzuzufügen, welche die Bank in die Lage versetzt, Aktualisierungen zu senden, wenn dies erforderlich ist. Damit Kunde weiß, für welches Konto die Aktualisierung gilt (schließlich können Kunden mehrere Konten haben), sollte Konto ein Parameter sein. Allein aus der Information Konto kann der Kunde den aktuellen Kontostand ermitteln, doch aus Gründen der Bequemlichkeit erhält die Methode auch den Kontostand als Parameter. Auch hier ist kein Rückgabewert erforderlich, und deklarieren Sie spaßeshalber die Methode ebenfalls als oneway, so daß die Bank in der Lage sein wird, Aktualisierungen zu senden, ohne auf Antworten der Kunden zu warten. Die Signatur für die Methode lautet: aktualisiereKontostand(Konto : Konto, Kontostand : float) : void
Im Falle der Operation aktualisiereKontostand() ist die Verwendung des oneway-Aufrufmechanismus eine sinnvolle Wahl. Erinnern Sie sich, daß der Mechanismus oneway unzuverlässig ist, das heißt, die Auslieferung von oneway-Nachrichten ist nicht garantiert. Dies ist akzeptabel für aktualisiereKontostand(), da die Kontoaktualisierungsmeldungen nicht als kritisch anzusehen ist. Mit anderen Worten, falls gelegentlich eine Aktualisierungsbotschaft nicht ausgeliefert wird, so sind die Auswirkungen der Operation auf die Anwendung minimal.
Abbildung 9.1 zeigt das geänderte Klassendiagramm der Anwendung Bank, in dem diese Hinzufügungen wiedergegeben sind.
Abbildung 9.1: Das Klassendiagramm der geänderten Bank-Anwendung
Modifizieren der IDL-Spezifikation Wie an Tag 8 sind die Veränderungen der IDL-Schnittstelle offensichtlich. Beginnen Sie mit der veränderten Schnittstelle Bank, die in Listing 9.1 zu sehen ist, wobei die Änderungen durch Fettdruck hervorgehoben sind. Listing 9.1: Die geänderte Bank.idl. 1: // Bank.idl 2: 3: // Forward-Deklaration der Schnittstelle Bank. 4: interface Bank; 5: 6: #ifndef Bank_idl 7: #define Bank_idl 8: 9: // Banksequenzen 10: typedef sequence BankenListe; 11: 12: #include "Kunde.idl" 13: #include "Konto.idl" 14: #include "BankKarte.idl" 15: #include "Exceptions.idl" 16: 17: // Bank ermöglicht Zugriff auf Konten und kann für 18: // einen Kunden ein Konto erzeugen, ein Konto löschen, 19: // oder die aktuellen Konten der Banken auflisten. 20: interface Bank { 21: 22: // Name dieser Bank. 23: attribute string name; 24: 25: // Adresse dieser Bank. 26: attribute string adresse; 27: 28: // Ein Konto für den angegebenen Kunden erzeugen, unter
Berücksichtigung 29: // der angegebenen Kontoart ("Sparkonto" oder "Girokonto", wobei auf die 30: // Schreibweise zu achten ist) und des angegebenen Eröffnungssaldos. 31: Konto erstelleKonto(in Kunde Kunde, in string 32: KontoArt, in float eroeffnungsSaldo); 33: 34: // Angegebenes Konto löschen. Gehört das Konto nicht zu dieser 35: // Bank, führt diese Funktion nichts aus. 36: void loescheKonto(in Konto Konto) 37: raises (UngueltigesKontoException); 38: 39: // Alle Konten dieser Bank auflisten. 40: KontoListe getKonten(); 41: 42: // Eine BankKarte mit der anfänglichen PIN und Berechtigung 43: // für das angegebene Konto ausgeben. Ist das Konto nicht 44: // bei dieser Bank registriert, führt diese Funktion nichts aus. 45: BankKarte ausgebenBankKarte(in short pin, in Konto Konto) 46: raises (UngueltigesKontoException); 47: 48: // Automatischen Aktualisierungsdienst für das angegebene Konto anfordern. 49: // Kontostände werden in regelmäßigen Abständen an die Inhaber 50: // des Kontos gesendet. 51: void anfordernAktualisierungsDienst(in Konto Konto) 52: raises (UngueltigesKontoException); 53: }; 54: 55: #endif Beachten Sie, daß die Änderungen an Bank.idl minimal sind und lediglich aus der neuen Methode anfordernAktualisierungsDienst() bestehen. Beachten Sie auch, daß UngueltigesKontoException hier wieder ins Spiel kommt, für den Fall, daß ein nicht zur Bank gehörendes Konto übergeben wird. Die anderen Änderungen werden an Kunde.idl vorgenommen, welches in Listing 9.2 zu sehen ist. Listing 9.2: Die veränderte Kunde.idl. 1: // Kunde.idl 2: 3: // Forward-Deklaration der Schnittstelle Kunde. 4: interface Kunde;
5: 6: #ifndef Kunde_idl 7: #define Kunde_idl 8: 9: // Kundensequenz 10: typedef sequence KundenListe; 11: 12: #include "Konto.idl" 13: 14: // Ein Kunde kann über ein (oder mehrere) Konten verfügen. Der 15: // Kunde ist wohl das Kernelement der Anwendung. 16: interface Kunde { 17: 18: // Der Name dieses Kunden. 19: attribute string name; 20: 21: // Die Personalausweisnummer dieses Kunden. 22: readonly attribute string PersonalAusweisNummer; 23: 24: // Die Anschrift dieses Kunden. 25: attribute string adresse; 26: 27: // Der Mädchenname der Mutter dieses Kunden. 28: readonly attribute string MuttersMaedchenName; 29: 30: // Eine Kontoliste zurückgeben, die von diesem 31: // Kunden (mit-) geführt wird. 32: KontoListe getKonten(); 33: 34: // Eine Aktualisierung an diesen Kunden bezüglich des angegebenen 35: // Kontos und dessen neuem Kontostand senden. 36: oneway void aktualisiereKontostand(in Konto Konto, in float 37: Kontostand); 38: }; 39: 40: #endif Wieder sind die Änderungen an Kunde.idl minimal, denn es wird nur die Methode aktualisiereKontostand() hinzugefügt. Beachten Sie die Verwendung des Modifizierers oneway, was besagt, daß beim Aufruf dieser Methode sofort zum Aufrufer zurückgekehrt wird. Sie sind nun bereit, die Änderungen an der Implementierung selbst vorzunehmen.
Implementierung der neuen Funktionen
Das Implementieren der Kontoaktualisierungsfunktion ist ebenfalls ein einfacher Vorgang. Da nur zwei Methoden dem gesamten System hinzugefügt wurden, sind nur wenige Schritte nötig: ■
■
Implementieren Sie anfordernAktualisierungsDienst() in der Klasse BankImpl. Diese muß alle Konten verfolgen, für welche der Dienst aktiviert ist; wenn es so weit ist, Aktualisierungsbotschaften an die Kunden des Kontos zu senden, kann BankImpl einfach diese Liste von Konten durchgehen. Implementieren Sie aktualisiereKontostand() in der Klasse KundenImpl. Diese Methode kann so trivial sein, daß sie einfach eine Botschaft ausgibt, welche das Konto und seinen neuen Stand ausgibt.
Erweiterung von BankImpl Zunächst werden Sie BankImpl so modifizieren, daß diese Klasse über die Funktionalität von anfordernAktualisierungsDienst() verfügt. Wie bereits erwähnt, muß BankImpl eine Liste von Konten enthalten, für welche die automatische Aktualisierung aktiviert ist. Wie dies zu tun ist, werden Sie in Listing 9.3 sehen, BankImpl.h, wobei Änderungen durch Fettdruck hervorgehoben sind. Listing 9.3: BankImpl.h. 1: // BankImpl.h 2: 3: #ifndef BankImpl_h 4: #define BankImpl_h 5: 6: #include 7: 8: #include "../Bank_s.h" 9: 10: class BankImpl : public _sk_Bank { 11: 12: public: 13: 14: // Konstruktor. 15: // 16: // name - Name dieser Bank. 17: BankImpl(const char* name); 18: 19: // Destruktor. 20: ~BankImpl(); 21: 22: // Diese Methoden werden in Bank.idl beschrieben. 23: virtual char* name();
24: virtual void name(const char* val); 25: virtual char* adresse(); 26: virtual void adresse(const char* val); 27: virtual Konto_ptr erstelleKonto(Kunde_ptr Kunde, 28: const char* KontoArt, CORBA::Float eroeffnungsSaldo); 29: virtual void loescheKonto(Konto_ptr Konto) throw 30: (UngueltigesKontoException); 31: virtual KontoListe* getKonten(); 32: virtual BankKarte_ptr ausgebenBankKarte(CORBA::Short pin, Konto_ptr 33: Konto) throw (UngueltigesKontoException); 34: virtual void anfordernAktualisierungsDienst(Konto_ptr Konto) throw 35: (UngueltigesKontoException); 36: 37: protected: 38: 39: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird 40: // in einem statischen Puffer zurückgegeben. 41: char* getNaechsteKontoNummer(); 42: 43: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis 44: // wird in einem statischen Puffer zurückgeliefert. 45: char* getCurrentDate(); 46: 47: private: 48: 49: // Standard-Konstruktor. 50: BankImpl(); 51: 52: // Name dieser Bank. 53: char* meinName; 54: 55: // Adresse dieser Bank. 56: char* meineAdresse; 57: 58: // Die Konten dieser Bank. 59: std::vector meineKonten; 60: 61: // Die Konten, die durch den automatischen Aktualisierungsdienst 62: // angezeigt werden. 63: std::vector meineAktualisiertenKonten; 64: 65: // Die nächste verfügbare Kontonummer.
66: unsigned int meineNaechsteKontoNummer; 67: }; 68: 69: #endif Beachten Sie in BankImpl.h die hinzugefügte Methode anfordernAktualisierungsDienst() und das Datenelement meineAktualisiertenKonten. meineAktualisiertenKonten ist ein vector der C++Standardschablonenbibliothek, ebenso wie das Element meineKonten, das alle zu einer bestimmten Bank gehörenden Konten enthält. In Listing 9.4, BankImpl.cpp, werden Sie sehen, wie die Elemente von meineAktualisiertenKonten verwaltet werden. Listing 9.4: BankImpl.cpp. 1: // BankImpl.cpp 2: 3: #include "BankImpl.h" 4: 5: #include <process.h> 6: 7: #include 8: #include <string.h> 9: #include 10: #include 11: #include 12: 13: #include "SparKontoImpl.h" 14: #include "GiroKontoImpl.h" 15: #include "BankKarteImpl.h" 16: 17: extern CORBA::BOA_var boa; 18: 19: // Von STL abgeleitete unäre Funktion, die True ergibt, wenn Konten 20: // gleich sind. 21: class IstKontoGleich : public std::unary_function { 23: public: 24: IstKontoGleich(argument_type Konto) { meinKonto = Konto; } 25: result_type operator()(argument_type Konto) { return Konto-> 26: _is_equivalent(meinKonto) != 0; } 27: private: 28: argument_type meinKonto; 29: }; 30:
31: void updateAccountThreadFunction(LPVOID pParam) { 32: 33: std::vector* Konten = (std:: 34: vector*)pParam; 35: 36: while (1) { 37: Sleep(60000); 38: cout << "BankImpl: Aktualisiere Konten" << endl; 39: 40: // Iterate through the list of Konten. 41: for (int i = 0; i < Konten->size(); i++) { 42: 43: // Für jedes Konto die Kundenliste ermitteln 44: // und an jeden eine Aktualisierung senden. 45: KundenListe* Kunden = (*Konten)[i]-> 46: getKunden(); 47: for (CORBA::Long j = 0; j < Kunden->length(); j++) { 48: try { 49: (*Kunden)[i]-> 50: aktualisiereKontostand((*Konten)[i], 51: (*Konten)[i]->Kontostand()); 52: } catch (const CORBA::Exception&) { 53: 54: // Ignoriere die Exception; es interessiert uns nicht 55: // falls es mit der Aktualisierung Probleme gibt. 56: } 57: } 58: } 59: } 60: } 61: 62: // Konstruktor. 63: // 64: // name - Name dieser Bank. 65: BankImpl::BankImpl(const char* name) : _sk_Bank(name), meineKonten(), 66: meineAktualisiertenKonten(), meinName(strdup(name)), 67: meineAdresse(strdup("123 Elm Street, Anywhere USA 12345")), 68: meineNaechsteKontoNummer(0) { 69: 70: _beginthread(&updateAccountThreadFunction, 0, 71: &meineAktualisiertenKonten); 72: } 73: 74: // Standard-Konstruktor. 75: BankImpl::BankImpl() : meineKonten(),
meineAktualisiertenKonten(), 76: meinName(NULL), meineAdresse(NULL), meineNaechsteKontoNummer(0) { 77: 78: } 79: 80: // Destruktor. 81: BankImpl::~BankImpl() { 82: 83: cout << "Bank \"" << name() << "\" wird gelöscht." << endl; 84: free(meinName); 85: free(meineAdresse); 86: } 87: 88: char* BankImpl::name() { 89: 90: return CORBA::strdup(meinName); 91: } 92: 93: void BankImpl::name(const char* val) { 94: 95: free(meinName); 96: meinName = strdup(val); 97: } 98: 99: char* BankImpl::adresse() { 100: 101: return CORBA::strdup(meineAdresse); 102: } 103: 104: void BankImpl::adresse(const char* val) { 105: 106: free(meineAdresse); 107: meineAdresse = strdup(val); 108: } 109: 110: Konto_ptr BankImpl::erstelleKonto(Kunde_ptr Kunde, 111: const char* KontoArt, CORBA::Float eroeffnungsSaldo) { 112: 113: Konto_ptr neuesKonto; 114: 115: if (strcmp(KontoArt, "savings") == 0) { 116: 117: // Erzeuge eine neues SparKontoImpl-Objekt für das Konto. 118: cout << "BankImpl: Erzeuge neues Sparkonto für " 119: "Kunde " << Kunde->name() << "." << endl;
120: neuesKonto = new SparKontoImpl(getNaechsteKontoNummer(), 121: getCurrentDate(), eroeffnungsSaldo, Kunde, 10.0); 122: } else if (strcmp(KontoArt, "checking") == 0) { 123: 124: // Erzeuge ein neues GiroKontoImpl-Objekt für das Konto. 125: cout << "BankImpl: Erzeuge neues Girokonto für " 126: "Kunde " << Kunde->name() << "." << endl; 127: neuesKonto = new GiroKontoImpl(getNaechsteKontoNummer(), 128: getCurrentDate(), eroeffnungsSaldo, Kunde); 129: } else { 130: 131: // Ungültige Kontoart; keine Aktion ausführen. 132: cout << "BankImpl: Kunde " << Kunde->name() << 133: " hat ungültige Kontoart angefordert \"" << KontoArt 134: << "\"." << endl; 135: return Konto::_nil(); 136: } 137: 138: // Das erzeugte Konto an das Listenende einfügen und zurückliefern. 139: ::boa->obj_is_ready(neuesKonto); 140: meineKonten.push_back(Konto::_duplicate(neuesKonto)); 141: return neuesKonto; 142: } 143: 144: void BankImpl::loescheKonto(Konto_ptr Konto) throw 145: (UngueltigesKontoException) { 146: 147: std::vector::iterator first = meineKonten.begin(); 148: std::vector::iterator last = meineKonten.end(); 149: IstKontoGleich predicate(Konto); 150: 151: std::vector::iterator uebereinstimmendesKonto = std:: 152: find_if(first, last, predicate); 153: if (uebereinstimmendesKonto == last) { 154: 155: // Ungültiges Konto; Exception auslösen. 156: cout << "BankImpl: Versuch ein ungültiges Konto zu löschen." << 157: endl; 158: throw UngueltigesKontoException(); 159: } 160: cout << "BankImpl: Lösche Konto \"" << Konto-> 161: KontoNummer() << "\"." << endl; 162: 163: // Angegebenes Konto löschen.
164: meineKonten.erase(uebereinstimmendesKonto); 165: Konto->_release(); 166: } 167: 168: KontoListe* BankImpl::getKonten() { 169: 170: KontoListe* list = new KontoListe(meineKonten.size()); 171: CORBA::Long i; 172: 173: for (i = 0; i < meineKonten.size(); i++) { 174: (*list)[i] = Konto::_duplicate(meineKonten[i]); 175: } 176: 177: return list; 178: } 179: 180: BankKarte_ptr BankImpl::ausgebenBankKarte(CORBA::Short pin, Konto_ptr 181: Konto) throw (UngueltigesKontoException) { 182: 183: // Zunächst prüfen, ob das Konto bei dieser Bank vorhanden ist. 184: std::vector::iterator first = meineKonten.begin(); 185: std::vector::iterator last = meineKonten.end(); 186: IstKontoGleich predicate(Konto); 187: 188: std::vector::iterator uebereinstimmendesKonto = std:: 189: find_if(first, last, predicate); 190: if (uebereinstimmendesKonto == last) { 191: 192: // Ungültiges Konto; Exception auslösen. 193: throw UngueltigesKontoException(); 194: } 195: 196: // Falls wir bis hierher gelangen, bedeutet dies, daß das Konto bei 197: // dieser Bank existiert, so daß wir fortfahren können. 198: BankKarte_ptr neueKarte = new BankKarteImpl(pin, Konto); 199: 200: return BankKarte::_duplicate(neueKarte); 201: } 202: 203: void BankImpl::anfordernAktualisierungsDienst(Konto_ptr Konto) throw 204: (UngueltigesKontoException) { 205:
206: // Feststellen, ob das Konto bei dieser Bank geführt wird. 207: std::vector::iterator first = meineKonten.begin(); 208: std::vector::iterator last = meineKonten.end(); 209: IstKontoGleich predicate(Konto); 210: 211: std::vector::iterator uebereinstimmendesKonto = std:: 212: find_if(first, last, predicate); 213: if (uebereinstimmendesKonto == last) { 214: 215: // Ungültiges Konto; Exception auslösen. 216: throw UngueltigesKontoException(); 217: } 218: 219: // Falls wir bis hierher gelangen, bedeutet dies, daß das Konto bei 220: // dieser Bank existiert, so daß wir fortfahren können. 221: meineAktualisiertenKonten.push_back(Konto::_duplicate(Konto)); 222: } 223: 224: // Die nächste verfügbare Kontonummer zurückgeben. Das Ergebnis wird returned 225: // in einem statischen Puffer zurückgegeben. 226: char* BankImpl::getNaechsteKontoNummer() { 227: 228: static char KontoNummer[16] = "Konto "; 229: 230: sprintf(KontoNummer + 7, "%08u", meineNaechsteKontoNummer++); 231: 232: return KontoNummer; 233: } 234: 235: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. Das Ergebnis wird 236: // in einem statischen Puffer zurückgegeben. 237: char* BankImpl::getCurrentDate() { 238: 239: static char aktuellesDatum[12] = " "; 240: 241: time_t ltime; 242: time(<ime); 243: char* ctimeResult = ctime(<ime); 244: 245: memcpy(aktuellesDatum, ctimeResult + 4, 3); 246: memcpy(aktuellesDatum + 4, ctimeResult + 8, 2); 247: memcpy(aktuellesDatum + 7, ctimeResult + 20, 4);
248: 249: return aktuellesDatum; 250: } BankImpl.cpp, gezeigt in Listing 9.4, führt die Verwendung von Threads in die Server-Anwendung ein. Abhängig von Ihrem Betriebssystem wird die Datei jedoch möglicherweise nicht wie dargestellt kompiliert werden. BankImpl.cpp benutzt Win32-APIs zur Verwendung von Threads, so daß es unter Windows 95 und NT kompiliert wird. Benutzer anderer Plattformen, insbesondere von Unix, müssen den Quelltext geringfügig modifizieren, um die Thread-API (beispielsweise POSIX-Threads) auf ihrem Betriebssystem zu verwenden. Dabei handelt es sich um eine triviale Angelegenheit, da in der Implementierung von BankImpl.cpp nur ein neuer Thread erzeugt wird. Behalten Sie auch in Erinnerung, daß der hier präsentierte Quelltext nicht Thread-sicher ist, der Klarheit halber werden keine Prüfungen durchgeführt, die ausschließen, daß beide Threads gleichzeitig auf den Vektor meineAktualisiertenKonten zugreifen. Dieser nicht Thread-sichere Quelltext dient Demonstrationszwecken, bei einem kommerziellen Produkt hingegen werden Sie definitiv sicherstellen wollen, daß das gesamte Programm Thread-sicher ist, wenn Multithreading in einer Anwendung verwendet wird.
Betrachten Sie nun Listing 9.4 genauer. Was Sie als erstes in den Zeilen 31-60 feststellen werden, ist eine neu eingefügte Funktion namens aktualisiereKontoThreadFunktion(), die in einem zweiten Thread ausgeführt wird. Zunächst erwartet aktualisiereKontoThreadFunktion(), wie Sie in den Zeilen 31-34 sehen können, daß es sich bei dem Argument um einen Zeiger auf einen STL-Vektor von Konten handelt (Sie werden später sehen, daß dies das Argument ist, mit dem die Funktion tatsächlich aufgerufen wird). Was in den Zeilen 36-37 geschieht, ist, daß der Thread so eingerichtet wird, daß er so lange läuft wie die Server-Anwendung (daher das while (1), das niemals endet). Außerdem wird die Schleife so eingerichtet, daß sie 60.000 Millisekunden (eine Minute) zwischen Ausführungen pausiert. Jede Minute bewirkt die Anweisung in Zeile 41, daß der Thread seine Liste von Konten durchgeht (Zeilen 43-46) und dann jeden der Kunden, die zu diesen Konten gehören, wie Sie in den Zeilen 4751 sehen können.
Wie Sie außerdem in Zeile 51 sehen können, wird die Botschaft aktualisiereKontostand() an jeden Kunden gesendet. Wenn schließlich aus irgendeinem Grund eine Exception von der Remote-Methode aufgerufen wird, so wird diese ignoriert, wie Sie in den Zeilen 52-56 sehen können. (aktualisiereKontoThreadFunktion() fängt die Exception ab, tut jedoch nichts mit ihr.) Erweitern von KundenImpl Die Erweiterungen von KundenImpl sind einfach. KundenImpl muß nur die Botschaft aktualisiereKontostand() akzeptieren und eine Botschaft ausgeben, welche den neuen Kontostand nennt. Die modifizierten KundenImpl.h und KundenImpl.cpp sind in den Listings 9.5 und 9.6 zu sehen. Listing 9.5: KundenImpl.h. 1: // KundenImpl.h 2: 3: #ifndef KundenImpl_h 4: #define KundenImpl_h 5: 6: #include "../Kunde_s.h" 7: #include "../BankKarte_c.h" 8: 9: class KundenImpl : public _sk_Kunde { 10: 11: public: 12: 13: // Konstruktor. 14: // 15: // name - Name des Kunden. 16: // PersonalAusweisNummer - Die Personalausweisnummer des Kunden. 17: // adresse - Die Anschrift des Kunden. 18: // MuttersMaedchenName - Der Mädchenname der Mutter dieses Kunden. 19: KundenImpl(const char* name, const char* PersonalAusweisNummer, 20: const char* adresse, const char* MuttersMaedchenName); 21: 22: // Destruktor. 23: ~KundenImpl(); 24: 25: // Diese Methoden werden in Kunde.idl beschrieben. 26: virtual char* name(); 27: virtual void name(const char* val); 28: virtual char* PersonalAusweisNummer();
29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
virtual char* adresse(); virtual void adresse(const char* val); virtual char* MuttersMaedchenName(); virtual KontoListe* getKonten(); virtual void aktualisiereKontostand(Konto_ptr Konto, CORBA:: Float Kontostand); private: // Standard-Konstruktor. KundenImpl(); // Der Name dieses Kunden. char* meinName; // Die Personalausweisnummer dieses Kunden. char* meinePersonalAusweisNummer; // This Die Anschrift des Kunden. char* meineAdresse; // Der Mädchenname der Mutter dieses Kunden. char* meineMuttersMaedchenName; // Die Konten dieses Kunden. KontoListe meineKonten; // Die BankKarten dieses Kunden. BankKartenListe meineBankKarten; }; #endif
Listing 9.6: KundenImpl.cpp. 1: // KundenImpl.cpp 2: 3: #include "KundenImpl.h" 4: 5: #include 6: #include <string.h> 7: 8: // Konstruktor. 9: // 10: // name - Name des Kunden. 11: // PersonalAusweisNummer - Die Personalausweisnummer des Kunden.
12: // adresse - Die Anschrift des Kunden. 13: // MuttersMaedchenName - Der Mädchenname der Mutter dieses Kunden. 14: KundenImpl::KundenImpl(const char* name, const char* 15: PersonalAusweisNummer, const char* adresse, const char* 16: MuttersMaedchenName) : _sk_Kunde(PersonalAusweisNummer), 17: meinName(strdup(name)), 18: meinePersonalAusweisNummer(strdup(PersonalAusweisNummer)), 19: meineAdresse(strdup(adresse)), 20: meineMuttersMaedchenName(strdup(MuttersMaedchenName)), 21: meineKonten(), meineBankKarten() { 22: 23: } 24: 25: // Standard-Konstruktor. 26: KundenImpl::KundenImpl() : meinName(NULL), 27: meinePersonalAusweisNummer(NULL), meineAdresse(NULL), 28: meineMuttersMaedchenName(NULL), meineKonten(), meineBankKarten() { 29: 30: } 31: 32: // Destruktor. 33: KundenImpl::~KundenImpl() { 34: 35: free(meinName); 36: free(meinePersonalAusweisNummer); 37: free(meineAdresse); 38: free(meineMuttersMaedchenName); 39: } 40: 41: char* KundenImpl::name() { 42: 43: return CORBA::strdup(meinName); 44: } 45: 46: void KundenImpl::name(const char* val) { 47: 48: free(meinName); 49: meinName = strdup(val); 50: } 51: 52: char* KundenImpl::PersonalAusweisNummer() { 53: 54: return CORBA::strdup(meinePersonalAusweisNummer); 55: }
56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83:
char* KundenImpl::adresse() { return CORBA::strdup(meineAdresse); } void KundenImpl::adresse(const char* val) { free(meineAdresse); meineAdresse = strdup(val); } char* KundenImpl::MuttersMaedchenName() { return CORBA::strdup(meineMuttersMaedchenName); } KontoListe* KundenImpl::getKonten() { return &meineKonten; } void KundenImpl::aktualisiereKontostand(Konto_ptr Konto, CORBA::Float Kontostand) { cout << "KundenImpl: Kontoaktualisierung erhalten:" << endl << " Neuer Kontostand: DM" << Kontostand << endl; }
Erweitern von GeldautomatClient Die Änderungen an GeldautomatClientHaupt.cpp sind leicht nachzuvollziehen (siehe Listing 9.7). Die einzigen Hinzufügungen bestehen darin, daß GeldautomatClient nun den Kontoaktualisierungsdienst von der Bank anfordert, wenn das Konto angelegt wird, und wenn GeldautomatClient beendet wird, wartet es zwei Minuten, um der Bank ein- oder zweimal eine Chance zum Aufruf von aktualisiereKontostand() zu geben, bevor GeldautomatClient endet. (Wie BankImpl verwendet GeldautomatClient die Win32API, um zu bewirken, daß der aktuelle Thread pausiert; wieder müssen Nicht-Windows-Entwickler hier den jeweiligen Methodenaufruf ersetzen.) Listing 9.7: GeldautomatClientHaupt.cpp. 1: // GeldautomatClientHaupt.cpp 2: 3: #include
4: #include <stdlib.h> 5: 6: #include "../Kunde/KundenImpl.h" 7: 8: #include "../Bank_c.h" 9: #include "../BankServer_c.h" 10: #include "../Geldautomat_c.h" 11: 12: int main(int argc, char *const *argv) { 13: 14: // Anzahl der Parameter überprüfen; es sollten genau fünf vorliegen 15: // (zusammen mit dem Programmnamen sechs). 16: if (argc != 6) { 17: cout << "Verwendung: GeldautomatClient " 18: " <mädchenname der mutter> " << endl; 19: return 1; 20: } 21: 22: // Befehlszeilenparamater den Attributen des Kunden zuweisen. 23: const char* name = argv[1]; 24: const char* PersonalAusweisNummer = argv[2]; 25: const char* adresse = argv[3]; 26: const char* MuttersMaedchenName = argv[4]; 27: CORBA::Short pin = atoi(argv[5]); 28: 29: // ORB und BOA initialisieren. 30: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 31: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 32: 33: // Objekt Kunde erzeugen. 34: cout << "GeldautomatClient: Erzeuge neuen Kunden:" << endl; 35: cout << " Name: " << name << endl; 36: cout << " Personalausweisnummer: " << PersonalAusweisNummer << 37: endl; 38: cout << " Adresse: " << adresse << endl; 39: cout << " Mädchenname der Mutter: " << MuttersMaedchenName << endl; 40: KundenImpl Kunde(name, PersonalAusweisNummer, adresse, 41: MuttersMaedchenName); 42: 43: // BOA informieren, daß das KundenImpl-Objekt bereit ist. 44: boa->obj_is_ready(&Kunde); 45: 46: // BankServer-Objekt suchen und versuchen eine Liste der Banken
47: // und Geldautomaten zu beziehen. 48: BankServer_var bankServer; 49: try { 50: bankServer = BankServer::_bind(); 51: } catch (const CORBA::Exception& ex) { 52: 53: // Die Bindung ist mißlungen... 54: cout << "GeldautomatClient: Bindung mit einem BankServer nicht möglich:" << 55: endl; 56: cout << ex << endl; 57: return 1; 58: } 59: cout << "GeldautomatClient: Bindung mit einem BankServer erfolgreich durchgeführt." << 60: endl; 61: 62: BankenListe_ptr banks; 63: GeldautomatListe_ptr Geldautomaten; 64: 65: try { 66: banks = bankServer->getBanken(); 67: Geldautomaten = bankServer->getGeldautomaten(); 68: } catch (const CORBA::Exception& ex) { 69: 70: // Der Versuch ist fehlgeschlagen... 71: cout << "GeldautomatClient: Liste der Banken und Geldautomaten konnte nicht abgerufen werden:" 72: << endl; 73: cout << ex << endl; 74: return 1; 75: } 76: 77: // Ducksches Bankhaus und ersten Geldautomat der Liste 78: // verwenden. 79: if (banks->length() == 0) { 80: 81: // Es sind keine Banken verfügbar. 82: cout << "GeldautomatClient: Es sind keine Banken verfügbar." << endl; 83: return 1; 84: } 85: if (Geldautomaten->length() == 0) { 86: 87: // Es sind keine Geldautomaten verfügbar. 88: cout << "GeldautomatClient: Es sind keine Geldautomaten
verfügbar." << endl; 89: return 1; 90: } 91: Bank_var bank = (*banks)[0]; 92: Geldautomat_var geldautomat = (*Geldautomaten)[0]; 93: cout << "GeldautomatClient: Verwende Bank \"" << bank->name() << "\" und" 94: << " Geldautomat \"" << geldautomat->name() << "\"." << endl; 95: 96: // Einige interessante Dinge ausführen. 97: 98: Konto_var Konto; 99: BankKarte_var BankKarte; 100: 101: try { 102: Konto = bank->erstelleKonto(&Kunde, "checking", 0.0); 103: } catch (const CORBA::Exception& ex) { 104: 105: // Funktion erstelleKonto() fehlgeschlagen... 106: cout << "GeldautomatClient: Kann Konto nicht erzeugen." << endl; 107: cout << ex << endl; 108: return 1; 109: } 110: 111: try { 112: 113: // Automatischen Kontoaktualisierungsdienst von der 114: // Bank anfordern. 115: bank->anfordernAktualisierungsDienst(Konto); 116: } catch (const CORBA::Exception& ex) { 117: 118: // Die Funktion anfordernAktualisierungsDienst() ist fehlgeschlagen... 119: cout << "GeldautomatClient: Kann Konto nicht erzeugen." << endl; 120: cout << ex << endl; 121: return 1; 122: } 123: 124: try { 125: 126: // Einige Kontostatistiken ausgeben. 127: cout << "GeldautomatClient: Neues Konto wurde eröffnet:" << endl; 128: cout << " Kontonummer: " << Konto->KontoNummer() <<
129: endl; 130: cout << " Eröffnungsdatum: " << Konto->EroefffnungsDatum() << 131: endl; 132: cout << " Kontostand: " << Konto->Kontostand() << endl; 133: 134: // Bank anfordern eine BankKarte für das neu erzeugte Konto 135: // auszugeben. 136: cout << "GeldautomatClient: BankKarte wird von Bank bezogen." << endl; 137: try { 138: BankKarte = bank->ausgebenBankKarte(pin, Konto); 139: } catch (const UngueltigesKontoException&) { 140: 141: // Aus irgendeinem Grund war das Konto ungültig (dies 142: // sollte nicht vorkommen). 143: cout << "GeldautomatClient: Exception abgefangen: Ungültiges Konto" 144: << endl; 145: return 1; 146: } 147: 148: // Einige Kontotransaktionen mit dem Geldautomat 149: // durchführen. 150: cout << "GeldautomatClient: Transaktionen werden durchgeführt." << endl; 151: try { 152: cout << " Einzahlung DM 250,00..." << endl; 153: cout << " Neuer Kontostand: DM" << geldautomat>Einzahlung(BankKarte, 154: Konto, pin, 250.00) << endl; 155: 156: // Dies wird eine Exception auslösen, da wir versuchen zu viel 157: // abzuheben. 158: cout << " Abhebung: DM 500,00..." << endl; 159: cout << " Neuer Kontostand: DM" << geldautomat>Abhebung(BankKarte, 160: Konto, pin, 500.00) << endl; 161: } catch (AuthentifizierungException&) { 162: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 163: << "Keine Authorisierung (wie erwartet)" << endl; 164: } catch (UngueltigerBetragException&) { 165: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 166: << endl; 167: } catch (UnzureichendeGeldmittelException&) {
168: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 169: "Geldmittel" << endl; 170: } 171: 172: // Weitere Kontotransaktionen mit dem Geldautomaten 173: // durchführen. 174: cout << "GeldautomatClient: Weitere Transaktionen durchführen." << endl; 175: try { 176: cout << " Einzahlung DM 500,00..." << endl; 177: cout << " Neuer Kontostand: DM" << 178: geldautomat->Einzahlung(BankKarte, Konto, pin, 500.00) << 179: endl; 180: 181: // Dies wird eine Exception auslösen, da wir die 182: // falsche PIN verwenden. 183: cout << " Abhebung DM 250,00 mit falscher PIN..." 184: << endl; 185: cout << " Neuer Kontostand: DM" << geldautomat>Abhebung(BankKarte, 186: Konto, pin + 1, 250.00) << endl; 187: } catch (AuthentifizierungException&) { 188: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 189: << "Keine Authorisierung (wie erwartet)" << endl; 190: } catch (UngueltigerBetragException&) { 191: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 192: << endl; 193: } catch (UnzureichendeGeldmittelException&) { 194: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 195: "Geldmittel" << endl; 196: } 197: 198: // Konto löschen. 199: try { 200: cout << " Konto wird gelöscht." << endl; 201: bank->loescheKonto(Konto); 202: 203: // Versuch das Konto erneut zu löschen, nur zum Spaß. 204: // Dies sollte zur Auslösung einer Exception führen. 205: cout << " Versuch, durch nochmaliges Löschen des Kontos eine " << 206: " Exception auszulösen." << endl;
207: bank->loescheKonto(Konto); 208: } catch (const UngueltigesKontoException&) { 209: 210: // Sicher, daß die Exception ausgelöst wurde. 211: cout << "GeldautomatClient: Exception abgefangen: Invalid " << 212: "Konto (wie erwartet)" << endl; 213: } 214: } catch (const CORBA::Exception& ex) { 215: 216: // Eine Kontofunktion konnte nicht ausgeführt werden... 217: cout << "GeldautomatClient: Fehler beim Versuch auf Konto zuzugreifen:" << endl; 218: cout << ex << endl; 219: return 1; 220: } 221: 222: // Lange genug warten, um eine oder zwei Kontoaktualisierungen 223: // abzufangen. 224: Sleep(120000); 225: 226: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 227: return 0; 228: }
Ausführen der Anwendung Erneut sind Sie bereit zum Ausführen der modifizierten Anwendung. Der Vorgang ist exakt der gleiche wie im vorigen Kapitel, doch wird die Ausgabe der verschiedenen Anwendungen geringfügig anders sein, wie nicht anders zu erwarten. Starten Sie wieder durch Ausführen der Anwendung BankServer: BankServer Wieder ist dies die Ausgabe von BankServer: BankServer bereit. Sie können nun die Anwendung Bank starten: Bank "Ducksches Bankhaus" Diese gibt folgendes aus: Bank "Ducksches Bankhaus" bereit.
Inzwischen gibt BankServer dies aus: BankServerImpl: Registriere Bank "Ducksches Bankhaus". Starten Sie nun die Anwendung Geldautomat : Geldautomat "Ducksches Bankhaus Geldautomat" Die Anwendung Geldautomat wird folgendes ausgeben: Geldautomat "Ducksches Bankhaus Geldautomat" bereit. BankServer gibt wieder folgende Botschaft aus: BankServerImpl: RegistriereGeldautomat "Ducksches Bankhaus Geldautomat". Schließlich sind Sie bereit, die Anwendung GeldautomatClient laufen zu lassen. Sie können dies tun, indem Sie folgendes eingeben: GeldautomatClient "Jeremy Rosenberger" 123456789 "123 Main Street" Doe 1234 GeldautomatClient gibt wiederum folgendes aus: GeldautomatClient: Erzeuge neuen Kunden: Name: Jeremy Rosenberger Personalausweisnummer: 123456789 Adresse: 123 Main Street Mädchenname der Mutter: Doe GeldautomatClient: Bindung mit einem BankServer erfolgreich durchgeführt. GeldautomatClient: Verwende Bank "Ducksches Bankhaus" und Geldautomat "Ducksches Bankhaus Geldautomat". GeldautomatClient: Neues Konto wurde eröffnet: Kontonummer: Konto00000000 Eröffnungsdatum: Oct 20 1997 Kontostand: 0 GeldautomatClient: BankKarte wird von Bank bezogen. GeldautomatClient: Transaktionen werden durchgeführt. Einzahlung DM 250,00... Neuer Kontostand ist 250,00 DM Abhebung DM 500,00...
GeldautomatClient: Exception abgefangen: GeldautomatClient: Weitere Transaktionen Einzahlung DM 500,00... Neuer Kontostand ist 750,00 DM Abhebung DM 250,00 mit falscher PIN... GeldautomatClient: Exception abgefangen: Authorisierung (wie erwartet) Konto wird gelöscht. Versuch eine Exception auszulösen, durch Kontos. GeldautomatClient: Exception abgefangen: erwartet)
Unzureichende Geldmittel durchführen.
Ungültige PIN oder Keine
erneutes Löschen des Ungültiges Konto (wie
An diesem Punkt pausiert GeldautomatClient zwei Minuten lang, während er auf Botschaften von Bank wartet. Haben Sie etwas Geduld, und irgendwann gibt GeldautomatClient folgendes aus: KundenImpl: Kontoaktualisierung erhalten: Neuer Kontostand ist 750,00 DM All dies geschieht sehr schnell, doch wenn alles vorüber ist, können Sie zu den anderen Anwendungsfenstern wechseln und Belege dafür sehen, was dort geschehen ist. Wenn Sie zuerst nach der Anwendung BankServer sehen, so finden Sie dies vor (wobei neue Meldungen durch Fettdruck hervorgehoben sind): BankServer bereit. BankServerImpl: Auflistung von 1 Bank(en) wird ausgegeben. BankServerImpl: Auflistung von 1 Geldautomat(en) wird ausgegeben. Die Ausgabe der anderen Anwendungen gleicht denen vom letzten Mal, ausgenommen die der Anwendung Bank. Wenden Sie Ihre Aufmerksamkeit demjenigen Fenster zu, in welchem Bank ausgeführt wird, und Sie werden folgende vertraute Ausgabe sehen: Bank "Ducksches Bankhaus" bereit. BankImpl: Erzeuge neues Girokonto für Kunde Jeremy Rosenberger. KontoImpl: Unzureichende Geldmittel zur Abhebung des angegebenen Betrags. BankImpl: Lösche Konto "Konto00000000". BankImpl: Versuch ungültiges Konto zu löschen. Warten Sie ein paar Augenblicke, und Sie werden folgendes sehen (wenn es etwa eine Minute gedauert hat, bevor das Ausgabefenster von Bank erschien, wird es auf Ihrem Bildschirm möglicherweise bereits angezeigt):
BankImpl: Aktualisiere Konten Erinnern Sie sich daran, daß diese Botschaft ausgegeben wird, unmittelbar bevor der zweite Thread in der Anwendung Bank die Aktualisierungsbotschaften an alle Eigentümer des Kontos sendet.
Ideen für zukünftige Erweiterungen Sie haben gerade erst damit begonnen, an der Oberfläche dessen zu kratzen, was Sie mit CORBA machen können. Was die Anwendung Bank angeht, gibt es eine Reihe möglicher Erweiterungen. Wenn Sie in den kommenden Tagen zu fortgeschrittenen CORBA-Themen vordringen, dann werden Sie einige weitere Erweiterungen der Anwendung Bank vornehmen, doch die Erweiterungsmöglichkeiten sind unbegrenzt. Falls Sie weiter mit der Anwendung Bank experimentieren möchten, sind hier ein paar Ideen: ■
■
■
■
SparKontos unterstützen bereits einen Zinssatz, der Zins wird jedoch niemals den Kontoständen hinzuaddiert. Implementieren Sie einen Mechanismus, wahrscheinlich unter Verwendung eines dritten Threads in der Anwendung Bank, der periodisch Zinsen zu jedem Sparkonto addiert. GiroKontos verfügen normalerweise über einen Überziehungsschutz; das heißt, das Abheben eines Betrags, der größer ist als der auf dem Konto verfügbare, schöpft automatisch von der Kreditlinie des Kunden. Wie bereits mehrmals zuvor erwähnt, ist der hier präsentierte Quelltext nicht Thread-sicher. Modifizieren Sie den Quelltext so, daß er Thread-sicher ist. Fügen Sie eine neue Kontoart hinzu, vielleicht ein Kapitalanlage- oder Wertpapierkonto. Ermöglichen Sie es Kunden als Extra, einen Dienst zu abonnieren, der diese automatisch, unter Verwendung von Push-Botschaften, über die Entwicklung des Kontos informiert.
Wieder sind die Erweiterungsmöglichkeiten endlos. Durch selbständiges Hinzufügen von Funktionalität oder Robustheit zur Anwendung Bank können Sie an Ihren CORBAEntwicklungsfähigkeiten feilen.
Zusammenfassung Heute haben Sie die Anwendung Bank um eine einfache Fähigkeit erweitert, das Übermitteln aktualisierter Informationen an Bankkunden per Push durch den Bank-Server. Wenn auch die Implementierung dieser Fähigkeit einfach ist, so ist doch das Potential der Push-Architektur sehr groß. Tatsächlich wetteifern einige Firmen im Moment, da dieses Buch geschrieben wird, um die Erstellung des De-facto-Standards für das Pushen von Inhalten an Benutzer im Internet. Daneben wurden Sie, wenn auch in knapper Form, an die Bedeutung des Schreibens von Threadsicheren Programmen in einer Multithread-Umgebung erinnert. Es ist sehr wichtig, daß Sie beim Schreiben von Multithread-Anwendungen auf Thread-Sicherheit achten. Bei einer Beispielanwendung mit nur einem Benutzer ist Thread-Sicherheit wohl kein Thema, da die Wahrscheinlichkeit gering ist,
daß zwei Threads dieselben Daten gleichzeitig verwenden. Bei einem kommerziellen Produkt hingegen, besonders im Falle eines unternehmensweiten Systems, kann die Strafe für das Schreiben nicht Thread-sicheren Quelltexts hart sein und resultiert üblicherweise in Datenverlusten. An Tag 10 werden Sie einen Gang höher auf fortgeschrittenere CORBA-Themen schalten, eine Reihe von Entwurfsfragen, die mit CORBA zusammenhängen, nebst einiger Vorschläge darüber, wie diese Themen zu behandeln sind. Sie werden eine kurze Pause in der weiteren Entwicklung der Anwendung Bank machen, das Beispiel wird jedoch in späteren Kapiteln wiederkehren, wenn Sie zusätzliche fortgeschrittene CORBA-Themen studieren wie die Verwendung der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII), CORBAservices und CORBAfacilities sowie die Verwendung von Java mit CORBA.
F&A Frage: Was ist denn nun das Tolle an der Push-Technologie? Antwort: Bei richtiger Implementierung kann eine Push-Architektur den Benutzern das aktive Suchen nach gewünschten Informationen abnehmen. (Wenn die Anwendung es jedoch mit dem InformationsPush an den Benutzer übertreibt, könnte der Benutzer unter dem neuen Problem der Informationsüberlast leiden.) Die Push-Technologie verfügt außerdem über das Potential zum Sparen von System-Ressourcen. Informationen können an Benutzer ausgeliefert werden, sobald sie aktualisiert werden (im Gegensatz zur Notwendigkeit seitens des Benutzers, periodisch nach aktualisierten Daten zu suchen, was ineffizient sein kann, wenn sich die Daten nicht allzuoft ändern). Frage: Mir scheint, Push-Methodenaufrufe können fast immer oneway-Aufrufe sein. Stimmt das? Antwort: Sofern die per Push übermittelten Informationen nicht als wesentlich für die Zwecke der Anwendung anzusehen sind, trifft dies zu. Die Verwendung von oneway-Aufrufen ermöglicht effizientere Server-Implementierungen (da der Server nicht auf Antworten der Clients warten muß) auf Kosten der Verläßlichkeit beim Ausliefern von Botschaften. Im Falle eines Servers, der Kontostand- oder Börsenkursaktualisierungen an Abonnenten ausliefert, die nicht ständig darauf angewiesen sind, ist es unerheblich, wenn gelegentlich eine Aktualisierungsbotschaft verlorengeht. Wird die zu aktualisierende Information als wesentlich angesehen, so ist oneway für gewöhnlich keine gute Wahl.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A. Quiz 1. Warum wird das Thema Thread-Sicherheit bei der in diesem Kapitel entwickelten Beispielanwendung bedeutsam? 2. Können Sie sich anstelle der Verwendung von oneway-Methoden zur Benachrichtigung von Clients über Aktualisierungen einen anderen Weg des effizienten Sendens von Aktualisierungsbotschaften an Clients denken? (Hinweis: Multithreading könnte hierbei von Nutzen sein.) Übungen 1. Früher im Kapitel wurde angemerkt, daß zur Zeit keine Möglichkeit besteht, den automatischen Kontoaktualisierungsdienst abzubestellen. Erstellen Sie eine IDLMethodensignatur für eine solche Operation. Vergessen Sie nicht, passende Exceptions vorzusehen, falls erforderlich. 2. Implementieren Sie die Abbestellmethode für den Kontoaktualisierungsdienst von Übung 1.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 10 Lernen über CORBAEntwurfsfragen IDL-Creep Einfach-Thread-Anwendungen Lebensdauer von Objekten Fehlen der Übergabe als Wert CORBA und das System X Window Zusammenfassung F&A Workshop
Inzwischen dürften Sie für sich selbst festgestellt haben, daß CORBA eine komplexe Architektur hat. Wie bei jeder komplexen Architektur gehört zu CORBA ein eigener Satz von Gesichtspunkten, die Entwurf und Implementierung von CORBA-Systemen betreffen. Die Tatsache beispielsweise, daß alle Schnittstellen unter CORBA in IDL angegeben sind, hat Auswirkungen für Entwickler, die bestehende Systeme in CORBA integrieren möchten - üblicherweise müssen IDL-Schnittstellen für eine Reihe bestehender Klassen geschrieben werden (kein triviales Unterfangen). Dieses Kapitel führt Sie in solche Entwurfsfragen ein und bietet Vorschläge an, wie Sie diese handhaben können.
IDL-Creep
Der Begriff IDL creep (deutsch: Widerling) bezieht sich nicht auf den Burschen im Büro unten in der Halle, der glaubt, er wisse alles über Interface Definition Language. Vielmehr wurde der Begriff geprägt, um die Tendenz von IDL zum Durchdringen eines Systementwurfs zu beschreiben - und es durchdringt wahrhaftig. Denken Sie nach: Damit eine Klasse von CORBA verstanden werden kann, muß ihre Schnittstelle in IDL formuliert sein. Müssen außerdem Klassen, die von dieser Klasse referenziert oder in anderer Weise für CORBA-Komponenten zugänglich sein, so müssen auch die Schnittstellen dieser Klassen in IDL formuliert sein. Konsequenterweise ist es nicht ungewöhnlich, wenn die meisten, wenn nicht alle, Klassen in einem System IDL-Spezifikationen benötigen. Wenn Sie eine Anwendung von Grund auf entwerfen, ist es nicht allzu schwierig, IDL-Schnittstellen für die meisten (oder sogar alle) Klassen definieren zu müssen. Wenn Sie jedoch eine bestehende Anwendung nach CORBA konvertieren, kann der Vorgang tatsächlich mühsam sein. Da die bestehenden Klassen wahrscheinlich nicht im Gedanken an CORBA - oder irgendeiner anderen verteilten Architektur - geschrieben wurden, müssen die Schnittstellen für diese Klassen möglicherweise etwas modifiziert werden, um mit IDL ineinanderzugreifen. Obwohl die Änderungen selbst wohl keine allzu großen Anstrengungen erfordern, bedenken Sie, daß durch die Änderung der Schnittstelle eines Objekts etwaiger Quelltext, der dieses Objekt verwendet, ebenfalls verändert werden muß. Dieser Kaskadeneffekt kann leicht ein paar Schnittstellenänderungen an relativ wenigen Klassen in ein frustrierendes Durcheinander von Änderungen überall in einer Anwendung verwandeln. Dieses Szenario berücksichtigt noch nicht einmal die Möglichkeit, daß die Architektur der NichtCORBA-Anwendung eventuell mit der CORBA-Architektur unvereinbar ist. Besonders das zur Zeit bestehende Unvermögen von CORBA zur Übergabe von Objekten als Werte kann speziell den Entwurf einer Anwendung betreffen (dies ist später in diesem Kapitel erläutert). Manchmal kann der Versuch, eine bestehende Anwendung so zu ändern, daß diese CORBA verwendet, wie ein Versuch sein, einen quadratischen Pflock in ein rundes Loch zu stecken. Die Moral ist, daß IDL durchdringend ist - es verfügt über einen Weg, in einen Systementwurf hineinzukriechen und ihn langsam zu übernehmen. Dies ist nicht unbedingt etwas Schlechtes an sich, kann jedoch die »CORBAtisierung« zu übernehmender Anwendungen zu einem schwierigen Unterfangen machen. Stellen Sie sich darauf ein, IDL für eine jede Klasse einer Anwendung zu schreiben, die möglicherweise von Anwendungskomponenten gemeinsam benutzt wird. Es ist nicht immer möglich, die Einführung von IDL in die meisten Klassen einer bestehenden Anwendung zu vermeiden, ohne daß eine erhebliche Entwurfsänderung von Teilen oder der gesamten Anwendung die Folge ist. Beim Entwurf einer Anwendung von Grund auf gibt es jedoch eine Leitlinie insbesondere zum Minimieren der Auswirkungen von IDL auf die restliche Anwendung: Achten Sie genau darauf, welche Klassen wahrscheinlich von Anwendungskomponenten gemeinsam verwendet werden und welche nicht. Die zugrundeliegenden Klassen (z.B. Klassen, die nicht zu einer Schnittstelle zwischen Komponenten gehören) erfordern üblicherweise keine IDL-Schnittstellen.
Einfach-Thread-Anwendungen Obgleich die meisten modernen Betriebssysteme Multithread-Anwendungen - also Anwendungen, bei
denen mehrere Threads gleichzeitig in einem einzelnen Programm ablaufen können - unterstützen, ist die Verwendung von Multithreading noch begrenzt. Dafür gibt es verschiedene Gründe: Nicht alle Betriebssysteme unterstützen Threads vollständig, nicht alle Entwickler verwenden aktuellere Versionen von Betriebssystemen, die Threads unterstützen, und nicht alle Entwickler kommen damit klar. Darüber hinaus führt die Verwendung von Multithreading in Anwendungen zu neuen Problemen (von denen die Notwendigkeit der Verwaltung konkurrierender Zugriffe auf Objekte nicht das kleinste ist), welche Entwurf und Entwicklung einer Anwendung erschweren. Folglich ist die Verwendung von Multithreading nicht so weit verbreitet, wie sie sein könnte. CORBA zwingt Entwickler nicht in ein Multithreading-Entwicklungsparadigma; tatsächlich ist es ohne weiteres möglich, Einfach-Thread-CORBA-Entwicklungen zu erstellen. Dennoch muß wegen der Natur des Betriebs verteilter Anwendungen große Sorgfalt auf Entwurf und Entwicklung von CORBAAnwendungen für eine Einfach-Thread-Umgebung verwendet werden. Dieses Kapitel erklärt Ihnen warum, wobei sowohl Fragen bezüglich der Server- als auch der Client-Seite einer Anwendung erörtert werden. Server-Anwendungen Die Berechtigung für die Verwendung von Multithreads in Server-Anwendungen ist klar: Auf einen CORBA-Server kann zu einem gegebenen Zeitpunkt von mehreren Clients zugegriffen werden. Ein Einfach-Thread-Server kann jeweils nur eine Anfrage von einem Client verarbeiten - falls ein Client versucht, auf den Server zuzugreifen, während der Server mit der Verarbeitung eines zweiten Clients beschäftigt ist, muß ersterer Client warten, bis der Server fertig ist (siehe Abbildung 10.1). Der offensichtliche Nachteil dieser Architektur besteht darin, daß für den Fall, daß der Server zeitaufwendige Transaktionen durchführt, die scheinbare Antwortzeit des Systems leidet (soweit die Endbenutzer betroffen sind).
Abbildung 10.1: Arbeitsweise eines Einfach-Tread-Servers Ein Ansatz zur Linderung dieses Problems besteht darin, mehrere Server in einer Anwendung zu verwenden. Normalerweise verwendet eine unternehmensweite Anwendung sowieso mehrere Server, um Lasten und Redundanzen auszugleichen. Es ist jedoch schlichtweg nicht praktisch, einen Server pro konkurrierenden Client einzusetzen, berücksichtigt man den Bedarf jeder Server-Anwendung. Viel effizienter ist es, wenn ein einzelner Server mehrere Clients gleichzeitig bedient, und das ist exakt die von einer Multithread-Architektur geforderte Fähigkeit. Bei einer Multithread-Server-Architektur (siehe Abbildung 10.2) kann der Server einen neuen Ausführungs-Thread für jede Transaktion starten, anstatt nur jeweils eine Client-Anfrage zu verarbeiten. Da stets ein Thread auf neue Anfragen wartet, müssen andere Clients nicht mehr warten, bis der Server eine Transaktion abschließt, bevor er die nächste annehmen kann. Das Ergebnis ist, daß der Server schneller antwortbereit scheint, da er sofort auf eingehende Anfragen antworten kann.
Abbildung 10.2: Arbeitsweise eines Multithread-Servers Obwohl die Multithread-Architektur die Illusion einer höheren Server-Leistung erzeugen kann, indem sie die Server-Antwortbereitschaft erhöht, so macht doch die Architektur Server nicht auf magische Weise schneller. Falls der Server mehrere Transaktionen gleichzeitig verarbeitet, sinkt die Geschwindigkeit, mit der jede Transaktion verarbeitet wird, wahrscheinlich gegenüber einem Server, der nur jeweils eine Transaktion bearbeitet. (Multithread-Anwendungen können effizienter sein als Einfach-Thread-Anwendungen, ein Server, der jeweils nur eine Transaktion verarbeitet, kann jedoch nicht doppelt so schnell sein wie ein Server, der zwei Transaktionen gleichzeitig verarbeitet.) Obwohl also die Verwendung von Multithreading nicht dafür gedacht ist, die Verwendung mehrerer Server zu ersetzen, ist üblicherweise der Einsatz mehrerer Multithread-Server mehreren Einfach-Thread-Servern vorzuziehen. Außerdem sind in den meisten Fällen weniger Multithread-Server erforderlich, um die gleiche Antwortbereitschaft für Endbenutzer zu erzielen. Da es Multithreading nicht umsonst gibt, ist es wichtig zu verstehen, wann es geeignet ist. Die Verwaltung mehrerer Threads kann in zusätzlichen Kosten resultieren, besonders wenn es notwendig ist, mehrere Threads davon abzuhalten, gleichzeitig auf die gleichen Daten zuzugreifen (was oft der Fall ist). Folglich macht auf einer Maschine mit einer einzelnen CPU das Hinzufügen mehrerer Threads zu einer Anwendung, die bereits CPU-gebunden ist, diese nur langsamer (obwohl sich die Antwortzeit für die einzelnen Clients dennoch erhöhen kann, wie zuvor erläutert). Andererseits kann sich auf einer Mehrprozessormaschine die Leistung durch Verwendung von Multithreading erhöhen, da jeder Thread potentiell auf einer eigenen CPU laufen kann, selbst bei einer CPU-gebundenen Anwendung. Wo Mehrprozessorverarbeitung hingegen
wirklich glänzt, ist in E/A-gebundenen Server-Anwendungen oder in Anwendungen, die gleichzeitig als Clients und Server fungieren (wie Sie im nächsten Anschnitt sehen werden). Bei einer E/Agebundenen Server-Anwendung mit mehreren Client-Verbindungen steigt die Wahrscheinlichkeit, daß ein gegebener Thread während des Wartens auf E/A blockiert ist. Daher erhalten andere Threads die Gelegenheit zum Durchführen nützlicher Aufgaben, wodurch die Nutzung der vorhandenen CPU(s) effizienter ist. Die Quintessenz hinsichtlich der Verwendung von Threads in CORBA-Servern ist diese: Falls der Server Transaktionen schnell genug verarbeiten kann, so daß Antwortzeiten kein Thema sind, so wird voraussichtlich ein Einfach-Thread-Server genügen. Wenn die Antwortzeit einer Einfach-ThreadServer-Anwendung unangemessen ist, so ist die Verwendung von Multithreading wahrscheinlich eine bessere Alternative. Beachten Sie, daß dies nur zutrifft, wenn der Server nicht als Client fungiert. Spielt er beide Rollen, so können weitere Fragen von Bedeutung sein, wie Sie bald sehen werden. Client-Anwendungen Anders als eine Server-Anwendung muß sich eine Client-Anwendung nicht um vernünftige Antwortzeiten für andere Clients kümmern. Wenn ein Client eine Remote-Methode auf einem Server aufruft, ist es meist absolut sinnvoll, daß der Client auf die Antwort vom Server wartet, bevor er fortfährt. Dies trifft zu, solange eine Annahme gültig bleibt - daß es sich bei der Client-Anwendung um einen reinen Client handelt; das heißt, die Anwendung erzeugt weder CORBA-Objekte, noch übergibt sie Referenzen auf diese Objekte an andere Anwendungen. Gemischte Server-/Client-Anwendungen Die Richtlinien für die Verwendung von Threads in reinen Servern (Anwendungen, die nur als Server auftreten, nie als Clients) sind klar: Wenn die Anforderungen an die Antwortzeiten es gewährleisten, ist Multithreading vorzuziehen; ansonsten sind Einfach-Threads angebracht. Die Richtlinien für reine Clients sind ebenfalls einfach: In den meisten Fällen ist Multithreading nicht erforderlich. Daraus können Sie schließen, daß Einfach-Thread-Architekturen für die meisten CORBA-Anwendungen angemessen sind. Wären all Ihre Anwendungen reine Server und Clients, so wäre dies richtig. Wenn eine Anwendung Verhalten eines Servers und eines Clients zeigt - Sie können dies in Ermangelung eines besseren Begriffs als gemischte Server-/Client-Anwendung bezeichnen - , werden die mit der Verwendung der Einfach-Thread-Architektur verknüpften Entwurfserörterungen tückisch. Das Grundproblem ist in Abbildung 10.3 illustriert: Wenn eine Einfach-Thread-, gemischte Server/Client-Anwendung eines ihrer Objekte einer zweiten Anwendung in einem Remote-Methodenaufruf übergibt und die zweite Anwendung versucht, auf das ihr übergebene Objekt zuzugreifen, werden beide Anwendungen blockiert.
Abbildung 10.3: Arbeitsweise einer Einfach-Tread-, gemischte Server-/Client-Anwendung Natürlich wird dieses Problem durch Verwendung von Multithreading in der gemischten Server/Client-Anwendung schön gelöst, wie in Abbildung 10.4 gezeigt. Falls Multithreading aus irgendeinem Grund keine Option ist, wird eine andere Lösung benötigt. Der Rest dieses Abschnitts erläutert potentielle Lösungen für Probleme, die in Zusammenhang mit der Entwicklung von Einfach-ThreadAnwendungen stehen, welche die Rollen sowohl von Client als auch Server spielen müssen.
Abbildung 10.4: Multithreading in einer gemischten Server-/Client-Anwendung Hier werden zwei nützliche Entwurfsmuster für das Entwerfen von CORBA-Anwendungen präsentiert, die nur Einfach-Thread-Komponenten verwenden. Wieder ist die Verwendung von Multithreading wahrscheinlich die sauberste Methode des Implementierens von CORBAAnwendungen, doch wenn Multithreading nicht zur Wahl steht, sollten Sie eines dieser Entwurfsmuster in Erwägung ziehen. Das Muster Object Factory Erinnern Sie sich daran, daß Einfach-Thread-Anwendungen, bei denen es sich um reine Server oder reine Clients handelt, nicht an dem in Abbildung 10.3 illustrierten Verriegelungsproblem leiden. Das Muster Object Factory (Objektfabrik) macht sich diese Eigenschaft reiner Client- und reiner ServerAnwendungen zunutze, indem es Funktionalität zur entsprechenden Komponente der Anwendung verschiebt, was in Anwendungskomponenten resultiert, die entweder reine Clients oder reine Server sind. Beim Entwurfsmuster Object Factory ist ein Factory- (Fabrik-) Objekt verantwortlich für die Erstellung von Objekten eines bestimmten Typs. Beispielsweise kann das Objekt Bank als Kontofabrik angesehen werden, da Banken für das Erstellen aller Kontoobjekte im System verantwortlich sind.
Um zu verstehen, wie dies möglich ist, betrachten Sie folgendes Szenario: Ein Client will eine RemoteMethode auf einem Server aufrufen. Die Methode nimmt einen bestimmten Objekttyp als Parameter an; nennen Sie ihn Dingsda. Nehmen Sie nun an, daß Dingsda vom Client erzeugt wird. Falls der Client dem Server mit Hilfe einer Remote-Methode ein Dingsda übergeben müßte, den er erzeugt hat, und diese Methode versuchte, eine Methode für Dingsda aufzurufen, so wäre das Ergebnis ein Verriegelungsszenario, wie in Abbildung 10.3 beschrieben. Das sind die typischen Anwendungssemantiken, über die Einfach-Thread-Anwendungen stolpern. Es wäre sehr nützlich, wenn diese Arten von Semantiken in einer Weise erreicht werden könnten, die mit Einfach-ThreadAnwendungen funktioniert. Das ist der Punkt, an dem das Muster Object Factory ins Spiel kommt (siehe Abbildung 10.5). Dieses Muster nimmt die Stelle des Objekterzeugungsschrittes ein. Anstatt Dingsda selbst zu erzeugen, fragt der Client bei der Factory an (bei der es sich um das gleiche Objekt handelt wie bei dem Server, dessen Methode der Client aufrufen möchte), um Dingsda auf Seiten des Clients zu erzeugen. Der Client kann dann Dingsda manipulieren, falls gewünscht, und kann schließlich die gewünschte Remote-Methode auf dem Server aufrufen, wobei dieser Dingsda als Parameter übergibt. Da nun Dingsda im selben Adreßraum existiert wie der Rest des Servers, kann der Server Dingsda nach Belieben manipulieren.
Abbildung 10.5: Das Muster Object Factory Das Muster Object Factory hat den Vorteil, daß es Einfach-Thread-Clients in die Lage versetzt, Objekte als Parameter an Remote-Methoden zu übergeben. Diese Fähigkeit hat ihren Preis: Der Server muß Methoden zum Erstellen jeder Art von CORBA-Objekt, die ein Client erzeugen könnte, zur Verfügung stellen. Obwohl es leicht ist, Implementierungen für diese Methoden zur Verfügung zu stellen, kann es anstrengend sein, besonders bei Anwendungen, die eine große Anzahl von Klassen enthalten. Außerdem kann es für Clients weniger bequem sein, zusätzliche Methoden zum Erzeugen von Objekten aufzurufen, anstatt direkt Konstruktoren zu verwenden, auch wenn diese Unbequemlichkeit minimal sein kann. Diese Nachteile werden jedoch oft von den Vorteilen des Musters Object Factory mehr als aufgewogen. Das Muster Ausschließlicher oneway-Aufruf Ein weiterer Ansatz zur Herstellung von Harmonie zwischen Einfach-Thread-Anwendungen und CORBA ist das, was Sie als ausschließliches oneway-Aufrufsmuster bezeichnen könnten. Dieses
Muster verlangt die ausschließliche Verwendung von oneway-Aufrufen in der gesamten Anwendung. Da oneway-Methoden nicht blockieren, ist das mit Einfach-Thread-CORBA-Anwendungen verknüpfte Verriegelungsproblem beseitigt. Dieser Vorteil hat jedoch seinen Preis, wie Sie bald sehen werden. Das Entwurfsmuster Ausschließlicher oneway-Aufruf verlangt die ausschließliche Verwendung von oneway-Methoden für die Kommunikation zwischen CORBA-Objekten. Um einen Rückblick über die oneway-Methoden unter CORBA zu erhalten, lesen Sie Tag 3, »Beherrschen der Sprache Interface Definition Language (IDL)«.
Die ausschließliche Verwendung von oneway-Methodenaufrufen in einer CORBA-Anwendung verlangt eine potentiell hohe Strafe: Zunächst geht das Konzept eines klar ausgelegten Programmflusses verloren. Betrachten Sie eine typische Client-Anwendung. Allgemein gibt es einen wohldefinierten Kontrollfluß durch das Programm. Das bedeutet, es gibt eine Methode main - dabei kann es sich sehr wohl um das main() von C/C++/Java handeln - , bei der das Programm beginnt und mit dem Aufrufen anderer Methoden fortfährt. Üblicherweise kann der Programmfluß durch Verfolgen der Reihenfolge der Methodenaufrufe bestimmt werden. Mit anderen Worten, das Verhalten der Anwendung ist zumindest einigermaßen vorhersagbar, da der Ausführungsfluß relativ leicht verfolgt werden kann. Die ausschließliche Verwendung von oneway-Methoden ändert jedoch die Landschaft einer Anwendung radikal, wie in Abbildung 10.6 illustriert. Anstelle eines wohldefinierten Kontrollflusses, der in der gesamten Anwendung verfolgt werden kann, zeigt der Client eine Reihe scheinbar zusammenhangloser oneway-Methoden. Der Austausch zwischen Client und Server ist, anstatt dem vertrauten Muster »Client ruft Server/Server gibt Ergebnis zurück« zu folgen, in einen Hagel aus oneway-Aufrufen von einer Anwendung zur anderen transformiert. Der Client startet, indem er eine Methode auf dem Server aufruft. Diese Methode ruft nach Beendigung eine Methode auf dem Client auf, welche den zweiten Schritt in der Anwendung leistet. Diese Methode ruft wiederum eine andere Methode auf dem Server auf, die irgendwann eine Methode auf dem Client aufruft usw.
Abbildung 10.6: Aufrufmuster der ausschließlichen Verwendung von oneway-Methoden Da jede Methode als oneway aufgerufen wird, gibt es wieder kein Blockieren durch ein eventuelles Hängen von Client oder Server. Folglich hat jede Anwendung die Möglichkeit, beliebige CORBAObjekte zu übergeben - einschließlich von ihr selbst angelegter Objekte - ohne Gefahr des Blockierens. Die Kehrseite dieser Architektur besteht darin, daß der Kontrollfluß viel schwieriger zu verfolgen ist, da er nun zwischen zwei Anwendungen aufgeteilt ist. Eine weitere Strafe, die von Entwicklern zu zahlen ist, welche das Muster Ausschließlicher onewayAufruf verwenden möchten, stammt aus einer Charakteristik der oneway-Methoden, an die Sie sich erinnern sollten - nämlich daß nicht garantiert werden kann, daß oneway-Botschaften an ihrem Ziel abgeliefert werden. Das Aufbauen einer gesamten Anwendung basierend auf diesem Mechanismus wäre sicherlich eine unangenehme Erfahrung, hauptsächlich weil für den Fall, daß Methoden in der Anwendung existieren, die verläßlich abgeliefert werden müssen, der Entwickler einen Mechanismus implementieren muß, um festzustellen, ob ein oneway-Methodenaufruf tatsächlich erfolgreich ausgeführt wurde. Mit anderen Worten, der Entwickler muß im wesentlichen verläßliche Auslieferungssemantiken über dem unverläßlichen oneway-Mechanismus implementieren, zusätzlich zur Implementierung der sonstigen Funktionalität der Anwendung. Um das Muster Ausschließlicher oneway-Aufruf zusammenzufassen, ist zu betonen, daß dieses
Muster für die meisten kommerziellen Systeme der realen Welt extrem schwer zu implementieren wäre. Folglich seien Entwickler ermutigt, eine andere Methode der Vermählung von Client- und Server-Funktionen in einer Einfach-Thread-Anwendung zu wählen, sofern möglich.
Lebensdauer von Objekten In einer nicht-verteilten Anwendung ist die Verwaltung der Lebensdauer von Objekten kein Thema: Wenn die Anwendung ein Objekt nicht mehr verwendet, zerstört sie es einfach. Bei Sprachen mit Garbage Collection (Speicherbereinigung) wie Java muß der Benutzer nicht einmal Quelltext eingeben, um dies zu erledigen - die Anwendung erledigt dies automatisch. Falls die Anwendung abstürzt, wird der von ihren Objekten verwendete Speicher ans Betriebssystem zurückgegeben. Da alle Objekte in der Anwendung selbst enthalten sind, gibt es keine mit der Lebensdauer von Objekten verknüpften Probleme. In einer verteilten Anwendung sind die Umstände anders. Eine Anwendung kann Objekte verwenden, die für die Anwendung lokal sind - das heißt, Objekte, die im selben Adreßraum wie die Anwendung existieren -, oder sie kann Remote-Objekte verwenden, die in einem anderen Prozeß auf derselben Maschine oder auf einer anderen Maschine irgendwo im Netzwerk residieren. Zahlreiche CORBAORB-Produkte verwenden Referenzzählung zur Verwaltung der Lebensdauer von Objekten - ein Mechanismus, der gut funktioniert, wenn sich Anwendungen normal verhalten. Obwohl die Verwendung eines Referenzzählungsmechanismus von CORBA nicht diktiert wird, verdient seine Integration in einigen größeren ORB-Produkten hier eine Erläuterung. Erinnern Sie sich, daß bei einem solchen Referenzzählungsmechanismus Objektreferenzen dupliziert werden - das heißt, ihr Referenzzähler wird erhöht -, wenn die Referenz einer Anwendung übergeben wird. In ähnlicher Weise gibt eine Anwendung, wenn diese ein Objekt nicht mehr verwendet, die Objektreferenz frei, das heißt, sie dekrementiert den Referenzzähler. Wenn der Referenzzähler null erreicht, wird das Objekt nicht länger verwendet und kann sicher zerstört werden. Was geschieht jedoch, wenn eine Anwendung, die eine Objektreferenz hält, abstürzt, bevor sie diese Objektreferenz freigeben kann? An diesem Punkt beginnt der Referenzzählungsmechanismus einzustürzen. Unter diesen Umständen erreicht der Referenzzähler des Objekts niemals null, und das Objekt wird nie zerstört. Sie können sich dies als eine Art von Speicherleck in einer verteilten Anwendung vorstellen. Folglich müssen Entwickler möglicherweise über den Referenzzählungsmechanismus hinausdenken und sich Gedanken über einen Mechanismus für Unvorhergesehenes machen, der dort einsetzt, wo die Referenzzählung aufhört. Da eine verteilte Anwendung nicht wirklich wissen kann, wann alle anderen Anwendungen ihre Objekte nicht mehr verwenden (da sie nicht weiß, ob eine dieser Anwendungen abgestürzt ist), wird ein Mechanismus über den grundlegenden Referenzzählungsmechanismus hinaus benötigt. Ein solcher Mechanismus verwaltet die Lebenszeit von CORBA-Objekten, wobei er automatisch bestimmt, ob ein Objekt verwendet wird, und dieses zerstört, falls nicht. Wie jedoch stellt dieser Mechanismus fest, ob ein Objekt noch verwendet wird? Tatsächlich kann er nur raten. Um zu verstehen, wie ein solcher Mechanismus arbeitet, betrachten Sie folgende Fallstudie. Ein solcher Mechanismus, der als Evictor bekannt ist, verwaltet die Lebensdauer von Objekten, indem
er die Verwendung eines jeden CORBA-Objekts innerhalb eines Servers verfolgt (jeder CORBAServer müßte einen eigenen Evictor beinhalten). Wenn ein Objekt über einen zuvor festgelegten Zeitraum (beispielsweise einen Tag) nicht mehr verwendet wurde, vertreibt Evictor dieses Objekt. Obwohl es für den Vertreibungsprozeß möglich ist, das nicht verwendete Objekt einfach zu löschen, erinnern Sie sich, daß Evictor nicht sicher weiß, daß das Objekt nicht mehr verwendet wird. Es ist möglich, daß einer der das Objekt verwendenden Clients eine Zeitlang schläft, und wenn das Objekt zerstört würde, könnte der Client nach dem Aufwachen nicht mehr auf das Objekt zugreifen. Um diese Möglichkeit einzubeziehen, zerstört Evictor ein CORBA-Objekt nicht wirklich nach einer Zeit der Nichtbenutzung, sondern vertreibt es in einen persistenten Speicher, etwa eine Datenbank. Falls das Objekt später benötigt wird, kann es aus dem persistenten Speicher wieder auferstehen. All dies geschieht transparent für Clients, die es nicht wahrnehmen, daß Objekte nach Bedarf vertrieben werden und wieder auferstehen. Natürlich verschiebt ein Mechanismus wie Evictor, anstatt potentiell unbenutzten Objekten den Verbrauch von Speicher im Adreßraum eines Server-Prozesses zu ermöglichen, die unbenutzten Objekte einfach in eine Form persistenten Speichers. Das Ergebnis ist, daß unbenutzte Objekte immer noch irgendwo existieren - in diesem Fall im persistenten Speicher anstatt im Adreßraum der ServerAnwendung. Die Verwendung persistenten Speichers für diesen Zweck gestattet, neben dem Vorteil der Freisetzung von Speicher-Ressourcen einer Maschine, eine einfachere Wartung. Das heißt, die Objekte im persistenten Speicher können periodisch gelöscht werden, vielleicht als Teil des periodischen Wartungszyklus des Systems. Das Reinigen der Datenbank von unbenutzten Objekten in dieser Weise würde es unnötig machen, daß Systemadministratoren den Server herunterfahren, und somit die Verfügbarkeit des Servers erhöhen.
Fehlen der Übergabe als Wert Vielleicht eines der kniffligsten mit CORBA-Entwicklung verknüpften Probleme besteht darin, daß CORBA zur Zeit nicht die Übergabe von Objekten als Wert unterstützt. (Es wird erwartet, daß diese Beschränkung in einer späteren Version von CORBA beseitigt wird; lesen Sie Anhang C, »Was steht bevor? Die Zukunft von CORBA«, um weitere Details über dieses und andere zukünftige Erweiterungen der CORBA-Architektur zu erfahren.) Manchmal ist es weitaus effizienter für einen Server, ein Objekt als Wert an einen Client zurückzugeben, so daß der Client lokal mit dem Objekt arbeiten kann, anstatt eine Reihe von Remote-Methoden - von denen jede den Aufwand für den Remote-Aufruf einer Methode verursacht - für das Objekt aufzurufen. Sehen Sie sich Abbildung 10.7 an, um eine Illustration dieses Szenarios zu erhalten.
Abbildung 10.7: Übergabe über Referenz im Verlgeich zur Übergabe über Wert Wenn selbstverständlich ein Client eine größere Zahl von Methoden für ein Objekt aufrufen möchte,
so ist es im Sinne der Effizienz vorzuziehen, an einer lokalen Kopie des Objekts zu arbeiten statt an einem Remote-Objekt. Dies gilt insbesondere dann, wenn es sich bei den Parametern oder Rückgabewerten der Methode(n) um komplexe Werte handelt. Das Übermitteln solcher Werte über das Netzwerk ist noch teurer als es bei Remote-Methoden der Fall ist. Wenn die Übergabe von Objekten als Wert manchmal eine gute Idee ist, CORBA diese Möglichkeit jedoch nicht anbietet, ist dann nicht die gesamte Diskussion eine strittige Sache? Wie sich herausstellt, gibt es verschiedene Ansätze zur Emulation dieses Verhaltens, auch wenn CORBA dieses Verhalten nicht direkt anbietet. ORBstreams.h++ von Rogue Wave Rogue Wave Software bietet ein Produkt an, das CORBA-Anwendungen in die Lage versetzt, C++Objekte als Wert zu übergeben. Das Produkt, das auf Tools.h++ von Rogue Wave aufbaut, bietet die Möglichkeit, viele der Klassen von Tools.h++ als Wert zu übergeben, ebenso wie benutzerdefinierte Klassen, die aus bestimmten Klassen von Tools.h++ abgeleitet sind oder den jeweiligen, von Rogue Wave bereitgestellten Schnittstellen entsprechen. ORBstreams.h++ hat einige Nachteile. Zum einen unterstützt es nur C++. (Wenn Sie eine CORBAAnwendung vollständig in C++ implementieren - was insgesamt nicht unwahrscheinlich ist -, so ist dies wahrscheinlich kein Thema für Sie.) Außerdem unterstützt das Produkt derzeit nur einen ORB Orbix von IONA Technologies -, was Ihre Auswahl von Entwicklungswerkzeugen weiter einschränkt. Da schließlich ORBstreams.h++ auf dem Produkt Tools.h++ aufbaut, ist das zusätzliche, mit der Verwendung von Tools.h++ verknüpfte Gepäck möglicherweise eine Last für Sie, falls Sie nicht sowieso vorhatten, dieses Produkt zu verwenden. Trotz all seiner Nachteile ist ORBstreams.h++ jedoch ein guter Notbehelf für das momentane Fehlen einer Möglichkeit zur Übergabe als Wert in CORBA. Verwendung von CORBA-structs Ein weiterer Ansatz zum Erreichen eines Verhaltens, das der Übergabe als Wert ähnelt, besteht in der Verwendung von CORBA-structs. Für jede CORBA-Schnittstelle, die als Wert übergeben werden muß, erstellt der Entwickler einen struct, der Elemente enthält, die allen Datenelementen der die Schnittstelle implementierenden Klasse entsprechen. Sobald diese structs definiert sind, funktioniert der Mechanismus etwa so: 1. Eine Methode, die wie gewöhnlich Schnittstellentypen für Parameter und Rückgabewert verwendet anstelle der entsprechenden struct-Typen. 2. Vor dem Aufruf einer solchen Methode erzeugt der Client struct-Versionen der Objekte, die er als Wert übergeben möchte. Dann ruft er die Methode mit diesen Parametern auf. 3. Der Server erzeugt den structs entsprechende Objekte (falls erforderlich), führt seine Verarbeitung durch und erzeugt structs für alle Ausgabeparameter oder Rückgabewerte, die als Wert übergeben werden. 4. Der Client empfängt die Ausgabeparameter und/oder Rückgabewerte vom Server und, falls
erforderlich, erzeugt Objekte, die den struct-Parametern entsprechen. Dieser Ansatz enthält ein paar Nachteile. Der wichtigste ist, daß Vererbung kein Merkmal von structs ist; Daher wird Polymorphismus nicht unterstützt. Mit anderen Worten, falls eine bestimmte Methode eine bestimmte Art von struct als Parameter erhält, ist es nicht möglich, eine andere Art von struct an seiner Stelle anzugeben. Auf das Beispiel Bank bezogen, ähnelt dies der Erzeugung eines Objekts vom Typ GiroKonto und seiner Rückgabe anstelle eines Parameters vom Typ Konto. Dabei kann jedoch der CORBA-Typ any von Nutzen sein, um den Preis erhöhter Komplexität. Verwenden von Konversionskonstruktoren Ein dritter Ansatz zur Emulation der Fähigkeit zur Übergabe als Wert in CORBA besteht darin, daß das Objekt normal übergeben wird, der Client (oder Server, abhängig davon, ob es sich bei dem Objekt um einen Eingabe- oder Ausgabeparameter handelt) kopiert dann jedoch den Zustand des Objekts unmittelbar nach dem Erhalt. Der Vorgang ist folgender: 1. Ein Client ruft eine Methode auf einem Server auf, welche eine Objektreferenz zurückgibt. Der Server gibt einfach wie gewöhnlich die Objektreferenz zurück. 2. Nach Erhalten der Objektreferenz erzeugt der Client ein neues Objekt vom selben Typ, indem er den Status des Remote-Objekts in ein lokales Objekt kopiert. Dies wird allgemein durch Verwendung eines Konstruktors bewerkstelligt, welcher das Remote-Objekt als Argument übernimmt, um dieses Objekt in ein lokales umzuwandeln (daher der Begriff Konversionskonstruktor). 3. Der Client gibt das Remote-Objekt frei und arbeitet mit dem lokalen weiter. Gegenüber dem struct-Ansatz hat der Konversionskonstruktor-Ansatz den Vorteil, daß er mit Objekten geerbter Klassentypen arbeiten kann. Zusätzlich erfordert dieser Ansatz nicht die Entwicklung separater IDL-Schnittstellen und structs - es ist möglich, exakt die gleichen Implementierungsklassen für lokale und Remote-Objekte zu verwenden. Ein potentieller Nachteil dieses Mechanismus ist, daß das lokale Objekt eine Reihe von Methoden für das Remote-Objekt aufrufen muß, um dessen Anfangszustand zu erhalten (dies ist oft dem Durchführen einer Reihe von Remote-Aufrufen während der Lebensdauer eines Objekts vorzuziehen). Des weiteren erfordert dieser Ansatz, daß zur Übergabe eines Objekts als Wert dessen Schnittstelle Methoden bereitstellen muß, die das Lesen seines vollständigen Zustands durch ein anderes Objekt ermöglichen. Dieses Erfordernis steht dem Konzept der Kapselung entgegen, eines der Ziele objektorientierten Entwurfs. Es kann auch das Eingeben von mehr Quelltext erforderlich machen.
CORBA und das System X Window Ein letzter Punkt dreht sich um die Verwendung von CORBA mit Anwendungen, die für das System X Window geschrieben wurden. Sowohl bei Einfach-Thread- als auch bei Multithread-Anwendungen verursacht die Verwendung von CORBA und X eine Reihe von Überlegungen.
Einfach-Thread-Anwendungen unter Verwendung von CORBA und X Das Hauptproblem beim Schreiben von Einfach-Thread-Anwendungen, die CORBA und X verwenden, besteht darin, daß beide Produkte versuchen, das zu installieren, was als Ereignisschleife bekannt ist. Eine Ereignisschleife ist das, was der Name andeutet: eine Schleife im Quelltext der Anwendung (genaugenommen im System-Code, der im Fall von X mit dem Anwendungs-Code verknüpft ist), die auf ein Ereignis wartet, es verarbeitet und zurückkehrt, um auf ein weiteres Ereignis zu warten und so weiter. Eine solche Ereignisschleife existiert für X wie für CORBA. Im Falle von X empfängt die Ereignisschleife Ereignisse vom X-Server und verarbeitet sie; im Falle von CORBA verarbeitet die Schleife Ereignisse aus anderen CORBA-Anwendungen. In beiden Fällen ist die Ereignisschleife die Hauptschleife der Anwendung, die so entworfen wurde, daß sie davon ausgeht, die ganze Zeit in ihrem eigenen Thread abzulaufen. Darin liegt das Problem von Einfach-ThreadAnwendungen: Sowohl CORBA als auch X erwarten, daß sie ihre eigenen Ereignisschleifen verwenden, und beide gehen davon aus, daß sie in ihrem eigenen Thread ablaufen, es gibt jedoch nur einen Thread in der Anwendung. Glücklicherweise haben ORB-Produkte für gewöhnlich einen Mechanismus zur Integration der CORBA-Ereignisschleife in einer X-Ereignisschleife. In diesen Fällen werden die CORBA-Ereignisse bei der X-Ereignisschleife angemeldet, so daß die eine Ereignisschleife Ereignisse für beide Produkte behandeln kann. Sie können in der Dokumentation Ihres Produkts nachsehen, um weitere Informationen zu erhalten, wie dies geschieht. Multithread-Anwendungen unter Verwendung von CORBA und X In einer Multithread-Umgebung ist es ohne weiteres möglich, separate Ereignisschleifen für X und CORBA auszuführen, so daß die Probleme, die für Einfach-Thread-Anwendungen gelten, nicht für Multithread-Anwendungen gültig sind. Es gibt jedoch einige Punkte, die zu bedenken sind: Ältere Revisionen von X - Versionen vor X11R6.1 - sind nicht Thread-sicher und sind daher in einer Multithreading-Umgebung mit Vorsicht zu verwenden. Dies bedeutet, daß der Entwickler zusätzliche Maßnahmen ergreifen muß, um zu gewährleisten, daß nicht mehrere Threads gleichzeitig auf XBibliotheksaufrufe zugreifen. (Ab X11R6.1 ist X jedoch Thread-sicher und von dieser Restriktion nicht betroffen.) Ein verwandtes Thema ist Motif, eine verbreitete Benutzerschnittstellenbibliothek für X. Während dies geschrieben wird, existiert noch keine Thread-sichere Version der Bibliothek Motif. Daher müssen Entwickler selbst unter dem Thread-sicheren X11R6.1 oder höher darauf achten, daß mehrere Threads nicht gleichzeitig Motif-Bibliotheksaufrufe ausführen. Im Ergebnis erfordert die Integration von CORBA in einer Motif-Anwendung, zumindest mit der aktuellen Version von Motif, so viel Anstrengung wie die Integration von CORBA in einer nicht Thread-sicheren X-Bibliothek unter einer Multithread-Umgebung. Die Integration von Multithreading, CORBA und nicht Thread-sicherem X und/oder Motif ist sicherlich möglich, auch wenn Sie erwarten können, daß dies einige Arbeit erfordert. Alle nicht Threadsicheren Aufrufe müssen in Methoden verpackt werden, die sicherstellen, daß jeweils nur ein Thread eine solche Methode zu einem gegebenen Zeitpunkt aufrufen kann. Ein Weg, dies sicherzustellen, ist anhand einer Thread-Warteschlange, eines Mechanismus, der es ermöglicht, daß mehrere Threads in
eine Warteschlange gestellt werden (mit anderen Worten, diese müssen anstehen), um auf nicht Threadsicheren Code zuzugreifen. Sowie sich Thread-sichere Versionen von X und Motif verbreiten, wird dies ein weniger wichtiges Thema werden, im Moment jedoch sollten CORBA-Entwickler vorsichtig sein.
Zusammenfassung Heute haben Sie verschiedene mit der Entwicklung von CORBA-Entwicklungen verbundene Fragen untersucht. Die wichtigsten sind die mit der Entwicklung von CORBA-Anwendungen in einer EinfachThread-Umgebung verknüpften und jene, die sich aus der in CORBA zur Zeit fehlenden Möglichkeit der Übergabe als Wert ergeben. Sie haben auch einige, bei weitem nicht alle, Behelfe für diese Probleme kennengelernt. An Tag 11 werden Sie beim nächsten Thema der fortgeschrittenen CORBA-Entwicklung fortfahren: bei der Verwendung der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII). DII ermöglicht es CORBA-Anwendungen, dynamisch etwas über einander zu lernen (mit anderen Worten, zur Laufzeit) und auf neu entdeckte Dienste zuzugreifen.
F&A Frage: Wenn nicht-triviale Einfach-Thread-CORBA-Anwendungen so viele Entwurfsprobleme verursachen, warum sollte man dann nicht einfach Multithreading verwenden? Antwort: Auch wenn Multithreading oft die vorzuziehende Alternative beim Ringen mit den sich aus EinfachThread-Anwendungen ergebenden Problemen ist, gibt es Gelegenheiten, bei denen Multithreading einfach nicht verfügbar ist, etwa wenn Einfach-Threading durch die Wahl anderer Anwendungen oder Entwicklungswerkzeuge diktiert ist. Für diese Fälle sind die mit Einfach-ThreadAnwendungen in Zusammenhang stehenden Entwurfsmuster gedacht. Frage: Früher im Kapitel wurde erwähnt, daß einige CORBA-Produkte Referenzzählung implementieren, um die Lebensdauer von Objekten zu verwalten. Auf welche andere Weise kann dies geschehen? Antwort: Ein weiterer Mechanismus, der für die Verwaltung der Lebensdauer von Objekten verwendet werden kann, ist für jedes Remote-Objekt brauchbar, das einen Herzschlag hat. Andere Objekte oder die ORBs selbst können bei jedem Remote-Objekt klingeln, um festzustellen, ob dieses Objekt noch lebt. Falls ein Objekt auf das Klingeln nicht innerhalb einer vorgegebenen Zeit antwortet, so
kann das andere Objekt davon ausgehen, daß die dieses Objekt enthaltende Anwendung abgestürzt ist. (Wie sich zeigt, wird ein ähnlicher Mechanismus von anderen ORB-ähnlichen Produkten wie DCOM von Microsoft und Voyager von ObjectSpace verwendet.)
Workshop Folgender Abschnitt hilft Ihnen beim Testen Ihres Verständnisses des heute präsentierten Materials und beim Umsetzen des Erlernten in die Praxis. Die Antworten auf das Quiz finden Sie in Anhang A. An den meisten Tagen gehören einige Übungen zum Quiz; da heute kein echtes Arbeitsmaterial präsentiert wurde, gibt es keine Übungen. Quiz 1. Was ist das mit dem Mischen von Client- und Server-Funktionen in einer Einfach-ThreadCORBA-Anwendung verknüpfte Hauptproblem? 2. Wie kann die Verwendung von Referenzzählung in einer CORBA-Anwendung zu Problemen führen? 3. Welche Version von X11 (dem System X Window) wäre notwendig, um X-basierte Multithread-Anwendungen sicher auszuführen? 4. Warum ist die Möglichkeit der Übergabe von Objekten als Wert manchmal nützlich? 5. Warum ist es üblicherweise nicht ratsam, das früher in diesem Kapitel eingeführte Entwurfsmuster Ausschließlicher oneway-Aufruf zu verwenden?
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 11 Verwendung der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII) Vorstellung des Dynamic Invocation Interface Verwenden von DII: Eine Übersicht Zusammenfassung F&A Workshop
An Tag 10, »Lernen über CORBA-Entwurfsfragen«, haben Sie kurz in der Entwicklung der Beispielanwendung Bank pausiert, um einige von der CORBA-Architektur aufgeworfene Entwurfsfragen zu studieren. Wie Sie sahen, erfordert die Verwendung von CORBA es gelegentlich von Ihnen, den Entwurf einiger Anwendungen teilweise (oder ganz!) zu überdenken, da CORBA selbst einige Restriktionen einbringt.
Heute werden Sie mit der Beispielanwendung Bank fortfahren - diesmal, um einen einfachen Client zu studieren, der über die dynamische Aufrufschnittstelle (Dynamic Invocation Interface, DII) auf ein Server-Objekt zugreift. DII versetzt einen Client in die Lage, auf Dienste zuzugreifen, ohne zuvor mit einem Client-Rumpf für diese Dienste kompiliert worden zu sein. (Erinnern Sie sich daran, daß Sie in der Vergangenheit mit Ihren Anwendungen Client-Rümpfe kompilieren und verknüpfen mußten, die CORBA-Objektdienste verwendeten.) Wie funktioniert dies? DII ermöglicht es CORBA-Clients, Schnittstellen dynamisch zu entdecken (mit anderen Worten, zur Laufzeit anstatt zur Zeit der Kompilierung) und Methoden für Objekte aufzurufen, die diese Schnittstellen implementieren. Heute werden Sie etwas über DII erfahren und warum es nützlich ist, und dann werden Sie sehen, wie eine Client-Anwendung implementiert wird, um DII-Dienste zu verwenden.
Vorstellung des Dynamic Invocation Interface Bislang waren die von Ihnen implementierten CORBA-Clients in gewisser Hinsicht statisch, denn sie wußten nur von denjenigen Schnittstellen, deren Client-Rümpfe zusammen mit dem Rest der ClientAnwendung zum Zeitpunkt der Kompilierung enthalten waren. Daher wußten die Anwendungen BankClient und GeldautomatClient nur von den Schnittstellen Bank, Geldautomat und Konto usw. Für die Zwecke der Anwendung Bank funktioniert die statische Kenntnis von Schnittstellen gut; es gibt keinen Grund für einen Client, auf andere, ihm zum Kompilierungszeitpunkt unbekannte Objekte zuzugreifen. Es gibt jedoch Gelegenheiten, bei denen dynamischere Client-Anwendungen gefragt sind, und das ist der Punkt, an dem DII ins Spiel kommt. (Sie werden sehr bald die Situationen kennenlernen, welche die von DII zur Verfügung gestellte Fähigkeit verlangen.) Es sollte betont werden, daß die dynamische Aufrufschnittstelle gut und gerne die am wenigsten nützliche Funktion von CORBA sein könnte. Sie werden später in diesem Kapitel sehen, daß nur eine Handvoll Anwendungsarten existieren, die wirklich von DII profitieren; in fast allen anderen Fällen stehen die Chancen gut, daß Sie DII niemals benutzen müssen. Der Vollständigkeit halber - und für den Fall, daß Sie durch einen dummen Zufall jemals in die Lage kommen, sie zu verwenden - folgt hier jedoch eine Beschreibung von DII. Der Zweck von DII Wie zuvor erwähnt, versetzt DII eine CORBA-Client-Anwendung in die Lage, Schnittstellen zur Laufzeit zu entdecken. Das bedeutet, daß eine solche Client-Anwendung ohne jeden Client-Rumpf kompiliert werden kann und so über keinerlei vorheriges Wissen über irgendeine Art von CORBAServer-Objekt verfügt. (Der Client muß jedoch nicht alle Server-Schnittstellen dynamisch entdecken; er kann mit ein paar Client-Rümpfen für einige Schnittstellen kompiliert werden und andere Schnittstellen dynamisch finden.) In jedem Fall ist DII diejenige Funktion von CORBA, die es Clients gestattet, Dienste von Objekten zu verwenden, die dem Client zum Kompilierungszeitpunkt unbekannt waren. (Beachten Sie, daß die Verwendung der DII sich nur auf einen Client bezieht; wenn eine Server-Methode aufgerufen wird, hat der Server keine Kenntnis darüber, ob eine Methode über den konventionellen statischen Mechanismus aufgerufen wurde oder über DII.)
Warum ist die dynamische Aufrufschnittstelle also nützlich? Sollte der Entwickler einer Anwendung letzten Endes nicht wissen, auf welche Art von Objekten die Anwendung wird zugreifen müssen? In den meisten Fällen trifft dies zu, doch einige, wenn auch unübliche, Anwendungen profitieren von der Fähigkeit, neue Objekttypen zu entdecken und zu verwenden. Es gibt mindestens zwei praktische Beispiele: ein CORBA-Entwicklungswerkzeug und eine generische Objekt-Browser-Anwendung. Ein hypothetisches CORBA-Entwurfswerkzeug unter Verwendung von DII Eine potentielle Verwendung für DII besteht in einem Entwicklungswerkzeug für die Erstellung von CORBA-Anwendungen. Ein solches Werkzeug hätte Zugriff auf die zum Erstellen der CORBAObjekte verwendeten IDL-Schnittstellen, da Entwickler sich im allgemeinen nicht mit CORBAObjekten beschäftigen, für welche sie keinen Quelltext oder IDL haben. Da Sie jedoch niemals sicher sein können, was Entwickler vorhaben, wäre es ein netter Zug des Werkzeugs, wenn dieses die Fähigkeit besäße, existierende CORBA-Schnittstellen zu entdecken und Client-Quelltext zum Verwenden dieser Objekte zu erzeugen. Das Werkzeug könnte es dem Entwickler auch ermöglichen, CORBA-Server-Objekte basierend entweder auf ihren IDL-Schnittstellen oder den mittels DII festgestellten Schnittstellen zu integrieren (ähnlich wie Steuerelemente in Entwicklungswerkzeuge wie Visual Basic und JavaBeans-sensitive Java-Entwicklungswerkzeuge integriert werden können). Ein generischer Objekt-Browser unter Verwendung von DII Eine weitere mögliche Verwendung für die dynamische Aufrufschnittstelle besteht in Form einer esoterischen, manchmal jedoch praktischen Anwendung: eine, die Objekte im Netzwerk anzeigt. Es gibt viele Gründe, warum Sie dies in manchen Anwendungsarten vorhaben könnten. Stellen Sie sich beispielsweise eine automatisierte Testanwendung vor, die Objekte entdeckt, ihre Schnittstellen erkennt und jede Methode in der Schnittstelle mit einem Satz von Dummy-Parametern aufruft. Die Ergebnisse könnten dann aufgezeichnet und später analysiert werden, um die Konformität der Objekte mit einem Testplan festzustellen. Da die Testanwendung Objektschnittstellen dynamisch mittels DII entdecken würde, müßte sie nicht neu kompiliert werden, um neue Objekttypen zu bearbeiten, wodurch sie als generisches Testwerkzeug nützlich sein kann. Ein weiteres Objektanzeigewerkzeug könnte nach Objekten Ausschau halten, die bestimmte Methoden implementieren und es dann einem Benutzer ermöglichen, auf diese Objekte zuzugreifen. Dies wäre hilfreich, falls ein bestimmter Entwurf einen Satz von Standardmethoden verlangen würde, die von allen Objekten im System zu implementieren wären, aus irgendeinem Grund jedoch keine Basisklasse definiert wurde, von der alle Schnittstellen ableiten könnten (dies ist nicht immer möglich, wenn Schnittstellen aus verschiedenen Quellen gemischt werden, etwa verschiedene Produkte oder verschiedene Projekte). Müßte beispielsweise jedes Objekt eine Methode killObject() implementieren, so könnte ein Objekt-Browser DII verwenden, um nach Objekten Ausschau zu halten, die eine solche Methode definiert haben, welche Benutzer in die Lage versetzen würde, Objekte nach Belieben zu löschen. Obgleich dies ein triviales Beispiel ist, bereitet es einen komplexeren Satz vor, der beispielsweise Systemadministratorfunktionen zur Verfügung stellen könnte. Entwickelt man diese Idee einen Schritt weiter, so wäre es möglich, einen Browser für Web-Objekte
zu entwerfen, der Objekte anzeigen könnte anstelle von Web-Seiten, die aus statischem HTML, dynamischem HTML, Java-Anwendungen usw. bestehen. Die Objekte könnten eine grundlegende Schnittstelle unterstützen und optional zusätzliche Möglichkeiten in Form von Methoden anbieten, die der Browser mittels DII entdecken könnte. Da eine solche Anwendung höchstwahrscheinlich interaktiv wäre, könnte der Benutzer durch eine Inspektion feststellen, welche Methoden interessant wären. Tatsächlich könnte eine intelligente Browser-Anwendung feststellen, welche Methoden interessant sind, basierend auf ihren Namen und Parameterlisten. Betrachten Sie als abschließendes Beispiel eine interpretierte Skriptsprache wie Perl oder Tcl (oder eine andere fiktive Skriptsprache). DII könnte verwendet werden, um eine Schnittstelle zwischen einer solchen Sprache und CORBA-Objekten zu bilden. Würde ein Skript auf eine Operation auf einem CORBA-Objekt zugreifen, so könnte der Übersetzer DII zum »Assemblieren« eines RemoteMethodenaufrufs aus der IDL-Schnittstellendefinition verwenden, die jeweiligen Argumente an die Methode zu übergeben und das Ergebnis an den Skript zurückzugeben. Hier würde der Sprachübersetzer im wesentlichen den IDL-Compiler ersetzen, welcher üblicherweise für das Herstellen einer Schnittstelle zwischen CORBA-Objekten und verschiedenen Sprachen verantwortlich ist, wie Sie sich erinnern dürften. Der Sprachübersetzer müßte jedoch ebenfalls zu einer Art IDLCompiler werden, denn er müßte IDL-Definitionen in DII-Methodenaufrufe übersetzen. Vergleich mit statischen Schnittstellen/IDL Bislang wurde gezeigt, daß DII zumindest einen Vorteil gegenüber dem konventionellen statischen Prozeß des Aufrufs von Methoden für Objekte hat. Sie sahen auch einige potentielle Anwendungen, in denen DII nützlich wäre. Auch wenn der Bereich der Anwendungen zugegebenermaßen begrenzt sein mag, so ist DII doch wesentlich, um diese möglich zu machen - ohne DII könnten solche Anwendungen überhaupt nicht existieren. Um die Vorteile von DII zusammenzufassen: ■
■
Ein Client muß zum Zeitpunkt der Kompilierung nichts über Server-Schnittstellen wissen; tatsächlich muß die Schnittstellendefinition für das Server-Objekt nicht einmal existieren, wenn der Client kompiliert wird. Dies macht eine enorme Flexibilität in Anwendungen möglich, die DII verwenden (falls Sie eine Anwendung finden können, die eine solche Flexibilität erfordert). Die DII bietet verschiedene Optionen, um Rückgabeparameter von einer Methode zu erhalten. Die Client-Anwendung kann das Ergebnis normal erhalten, die Methode unter Verwendung von oneway-Übergaben aufrufen (selbst wenn die IDL der Schnittstelle die Methode nicht als oneway deklariert hat, auch wenn dies nicht ratsam wäre) oder ein Ergebnis abfragen. Diese Optionen ermöglichen noch höhere Flexibilität in DII-Anwendungen als im Falle ihrer statischen aufrufausführenden Gegenstücke.
Es existieren jedoch einige mit der Verwendung der dynamischen Aufrufschnittstelle verknüpfte Nachteile: ■
Anwendungen, die DII verwenden, sind komplexer als ihre Client-Rümpfe verwendenden Gegenstücke. Das liegt daran, daß ein Methodenaufruf mittels DII ein Eingabeargument nach dem anderen auf dem Stack ablegen, die Methode aufrufen und dann jedes Rückgabeargument
■
■
■
zurückholen muß. Wird dies von Hand erledigt, so kann dies ein anstrengender und fehlerträchtiger Prozeß sein. Während die Möglichkeit der statischen Typprüfung im statischen Methodenaufrufsmechanismus eingebaut ist, gibt es für DII-Methodenaufrufe im Grunde keine. Folglich können geringfügige Unterschiede der Schnittstellendefinitionen dazu führen, daß eine Anwendung ungewollt inkorrekte Methoden aufruft - selbst wenn der Quelltext der Anwendung korrekt kompiliert wurde! Solche Fehler zurückzuverfolgen kann sehr schwierig sein, was sie besonders heimtückisch macht. Da jedes Argument einer Methode einzeln auf den Stack gegeben werden muß, wird bei jedem DII-Methodenaufruf zusätzlicher Aufwand verursacht. Natürlich gibt es auch noch den mit der tatsächlichen Entdeckung von Schnittstellen verknüpften Aufwand. Ein DII-Client muß typischerweise mit einem Server (oder mehreren Servern) »verhandeln«, um die Schnittstelle (oder die Schnittstellen) zu finden, an denen dieser Client interessiert ist.
Verwenden von DII: Eine Übersicht Nun, da Sie wissen, wofür DII gedacht ist, müssen Sie wissen, wie sie verwendet wird. Der Prozeß des Aufrufs einer DII-Methode unterscheidet sich von dem einer statischen Methode, wie Sie wohl erwarten dürften. Drei hauptsächliche Konzepte sind mit der Verwendung von DII verknüpft: das Objekt Request, die Verwendung von Anys und TypeCodes (an die Sie sich von Tag 3 erinnern werden) und verfügbare Optionen zum Senden von Anfragen sowie zum Empfangen von Antworten. Vorstellung von Request Bei Request handelt es sich um ein »Pseudo-Objekt« (das heißt, es repräsentiert kein CORBAObjekt, sondern es ist als Objekt implementiert, was die Implementierungssprache betrifft), welches eine Methode für ein CORBA-Objekt aufruft. Um eine Methode unter Verwendung von DII aufzurufen, erhalten Sie zuerst eine Referenz auf ein Objekt, für das die Methode aufgerufen werden soll. Dann erstellen Sie unter Verwendung der Methode request() oder create_request() des Objekts ein Objekt des Typs Request. Abhängig davon, welche Methode Sie wählen, füllen Sie Request mit Argumenten, Rückgabetypen und Exceptions, wozu Sie die Methoden add_value() und result() des Objekts Request verwenden. Dann rufen Sie die Methode auf (unter Verwendung einer der später in diesem Abschnitt beschriebenen Methoden), und schließlich holen Sie das Ergebnis des Methodenaufrufs ab (wieder unter Verwendung einer der später beschriebenen Methoden). Der Zweck von Request besteht im Kapseln von Eingabeparametern für Methodenaufrufe und im Zurückgeben von Werten aus diesen. Ein Request kann auf eine von zwei Arten erstellt werden, abhängig davon, wie Sie Request zu verwenden beabsichtigen. (Beachten Sie, daß ein Objekt des Typs Request nur für einen Methodenaufruf für ein Objekt verwendet werden kann; um eine Methode zweimal aufzurufen, rufen Sie zwei separate Methoden für dasselbe Objekt auf, oder, um Methoden für zwei verschiedene Objekte aufzurufen, müssen Sie zwei Objekte des Typs Request
erstellen.) Die zwei Methoden zum Erzeugen eines Request-Objekts sind folgende: ■
■
Aufrufen der Methode request() für das CORBA-Objekt. Die Methode request() erhält den Namen der aufzurufenden Methode als Parameter, beispielsweise request("getBanken"). Dann rufen Sie add_value() für das zurückgegebene Request-Objekt auf, um Eingabeparameter hinzuzufügen. Schließlich rufen Sie result() für das Request-Objekt auf, um den Typ des Rückgabeparameters anzugeben. Rufen Sie die Methode create_request() für das CORBA-Objekt auf. Diese Methode erhält verschiedene Parameter, die das Request-Objekt belegen, bevor es zurückgegeben wird. Die beim Aufruf von create_request() verwendeten Argumente können wiederverwendet werden, was eventuell die Leistung verbessert.
Anys und TypeCodes: Ein Rückblick Sie erinnern sich an die Diskussion der Typen Any und TypeCode von Tag 3. Ein Objekt des Typs Any kann jeden Objekttyp enthalten; ein TypeCode ist ein Objekt, das den Typ eines Objekts beschreibt. Ein Any besteht aus einem TypeCode (das den Typ von Any beschreibt) und dem Wert selbst. Höchstwahrscheinlich werden Sie bei Verwendung von DII die TypeCodes über die ORBMethoden create_struct_tc() und create_exception_tc() erhalten. Ein weiteres Objekt, dem Sie bei Verwendung von DII begegnen werden, ist NVList (NV steht für Named Value). Eine NVList ist eine Liste korrespondierender Namen, Werte und Flags, die angeben, ob der Wert ein in- oder out-Parameter ist. Bei den in der NVList gespeicherten Werten handelt es sich um Anys. Bei Verwendung der Methode request() eines Objekts zum Generieren eines Requests fügen Sie einfach die Argumente eins nach dem anderen mittels add_value() hinzu; wenn Sie jedoch die Methode create_request() verwenden, so benutzen Sie eine NVList, um die Eingabeargumente für die aufgerufene Methode anzugeben. In jedem Falle wird der Rückgabewert des Methodenaufrufs in einer NVList übergeben. Optionen für Request und Reply Es existiert eine Reihe von Optionen zum Senden eines Request an ein CORBA-Objekt. Nachdem das Pseudo-Objekt Request erzeugt wurde und die Argumente der Methode angegeben wurden, rufen Sie die Methode auf eine der folgenden Arten auf: ■
■
Rufen Sie die Methode invoke() für Request auf. Diese Methode verhält sich so, wie Sie es wahrscheinlich gewohnt sind (von nicht-oneway-Aufrufen), indem sie blockiert, bis die Antwort empfangen ist. Rufen Sie die Methode send_deferred() für Request auf. Diese Methode ruft die Anfrage auf, kehrt jedoch sofort zurück; Sie können dann die Antwort unter Verwendung einer der folgenden beiden Methoden abholen:
■
Rufen Sie die Methode send_oneway() für Request auf. Dies sollten Sie nur dann tun, falls die aufgerufene Methode in der IDL-Definition als oneway deklariert ist. Sie können auch send_multiple_requests_deferred() und send_multiple_requests_oneway() für den ORB verwenden, um eine Reihe von Methoden für verschiedene Objekte parallel aufzurufen.
Je nachdem, wie eine Methode aufgerufen wird, gibt es mehrere verschiedene Wege, die Ergebnisse des Methodenaufrufs zu erhalten: ■
■
■
Rufen Sie zunächst die Methode env() für Request auf, um festzustellen, ob eine Exception ausgelöst wurde. Gab es keine Exception, dann rufen Sie die Methode result() für das Objekt Request auf, um die NVList zu erhalten, welche die Ergebnisse des Methodenaufrufs enthält. Verwenden Sie diese Methode, falls Sie die Methode unter Verwendung der Methode invoke() für Request aufgerufen haben. Rufen Sie die Methode poll_response() für Request auf, um periodisch auf eine Antwort des Methodenaufrufs zu prüfen. Nachdem poll_response() angibt, daß das Ergebnis empfangen wurde oder daß der Client blockieren will, während er auf das Ergebnis wartet, rufen Sie die Methode get_response() für das Objekt Request auf. Verwenden Sie diese Methode, falls Sie die Methode unter Verwendung der Methode send_deferred() aufgerufen haben. Wenn Sie die Methode send_oneway() verwendet haben, gibt es natürlich keinen Rückgabewert, folglich rufen Sie keine Methode auf, um ihn zu erhalten.
Nun, da Sie mit dem Prozeß des Aufrufs einer Methode über DII vertraut sind, ist es Zeit für ein Beispiel. Beispiel zu DII Die Verwendung von DII in einem einfachen Beispiel ist unkomplizierter, als es sich anhören mag. Sie können eine Objektreferenz unter Verwendung des bind()-Mechanismus des ORB (bedenken Sie dabei, daß bind(), obwohl es nicht Standard ist, nützlich zum Entwickeln einfacher Beispiele ist) auf ähnliche Weise erhalten wie zuvor. Bei der Objektreferenz, die Sie erhalten, wird es sich jedoch um eine generische Objektreferenz handeln, die auf ein generisches CORBA::Object handelt, anstatt um eine Instanz eines spezifischen Objekttyps. Die folgenden Beispiele sind einige Schlaglichter eines CORBA-Clients, der DII verwendet: CORBA::ORB_ptr orb; CORBA::Object_ptr helloWorld; Natürlich benötigen Sie als erstes einen Zeiger auf ein ORB-Objekt. Außerdem brauchen Sie einen Zeiger auf das Objekt, mit dem Sie eine Verbindung herstellen möchten. In diesem Fall ist das Objekt ein fiktives »Hello World«-Objekt, das eine einzelne Methode helloWorld() enthält, die einen
CORBA::String als Parameter benötigt und ein CORBA::Long zurückgibt. Die Methode löst keinerlei Exceptions aus (erinnern Sie sich jedoch, daß eine CORBA-System-Exception von jeder Remote-Methode ausgelöst werden kann). Beachten Sie, daß der Zeiger auf das HelloWorldObjekt ein generischer CORBA::Object-Zeiger ist. CORBA::NamedValue_ptr resultVal; CORBA::Any_ptr resultValAny; CORBA::Long returnValue = 0; CORBA::Request_ptr request; CORBA::Any string; Als nächstes benötigen Sie einige weitere Zeiger. Einer enthält den vom DII-Methodenaufruf zurückgegebenen NamedValue. Ein weiterer Zeiger beinhaltet den in NamedValue enthaltenen Wert Any. Es wird eine CORBA::Long-Variable erstellt, um den aktuellen Rückgabewert zu speichern. Außerdem wird ein Zeiger angelegt, um das vom invoke()-Aufruf zurückgegebene Request-Objekt aufzunehmen. Schließlich wird ein Zeiger für den Eingabeparameter (das ist die Zeichenkette "Hello World") erzeugt. orb = CORBA::ORB_init(argc, argv); Sie sollten inzwischen mit diesem Aufruf vertraut sein; er initialisiert einfach den ORB. Dies ist in einer DII-Anwendung ähnlich wie in einer Anwendung mit statischen Aufrufen. try { helloWorld = orb->bind("IDL:HelloWorld:1.0"); } catch (const CORBA::Exception& ex) { cout << "Could not bind to HelloWorld" << endl; cout << ex << endl; return 1; } Nun versucht die Client-Anwendung, sich mit dem gewünschten HelloWorld-Objekt zu verbinden. Beachten Sie, daß die Anwendung, da sie keinen Client-Rumpf für das HelloWorld-Objekt hat, anstatt die Methode HelloWorld::_bind() zu verwenden, die Methode ORB::bind() mit einer geringfügig veränderten Syntax verwendet, um eine Referenz auf ein HelloWorld-Objekt zu erhalten. try { request = helloWorld->_request("helloWorld"); string <<= "Hello World"; CORBA::NVList_ptr arguments = request->arguments(); arguments->add_value("string", string, CORBA::ARG_IN );
Die Client-Anwendung fordert das HelloWorld-Objekt auf, ein Request-Objekt zu erstellen, indem sie die Methode request() aufruft. Der Name der Methode, die der Client für das Objekt HelloWorld aufrufen will, ist die Methode helloWorld(), die als Parameter der Methode request() übergeben wird. Dann erhält der Client eine Parameterliste (eine NVList) vom Objekt Request über seine Methode arguments() und fährt damit fort, die NVList mit dem einzigen Argument der Methode helloWorld() zu belegen: einem CORBA::String mit dem Wert "Hello World". Dieses Argument wird als Eingabeparameter unter Verwendung der Methode add_value() der NVList hinzugefügt. resultVal = request->result(); resultValAny = result->value(); resultAny->replace(CORBA::_tc_long, &resultVal); } catch (const CORBA::Exception& ex) { cout << "Konnte Anfrage nicht erzeugen" << endl; cout << ex << endl; return 1; } Bevor Request übermittelt wird, muß der Client noch etwas tun: den erwarteten Rückgabewert für die Methode angeben. Da erwartet wird, daß von der Methode erwartet wird, daß sie ein CORBA::Long zurückgibt, wird der TypeCode für diesen Typ in das Ergebnis gepusht. Um dies zu tun, ruft der Client result() für das Objekt Request auf, erhält value() (wobei es sich um ein Any handelt) aus dem Ergebnis und setzt Typ und Wert des zurückgegebenen Any auf CORBA::Long. Der Client ist nun bereit zum Aufruf der Methode über DII. try { request->invoke(); CORBA::Environment_ptr env = request->env(); if (env->exception()) { cout << "Es ist eine Exception eingetreten: " << *env->exception() << endl; returnValue = 0; } else { returnValue = *(CORBA::Long*)resultValAny->value(); } } catch (const CORBA::Exception& ex) { cout << "Konnte Anfrage nicht aufrufen" << endl; cout << ex << endl; return 1; } cout << "Der Rückgabewert war " << returnValue << endl;
Schließlich ruft der Client die Methode helloWorld() für das Objekt HelloWorld über die Methode invoke() für Request auf. Dann prüft der Client auf etwaige ausgelöste Exceptions, indem er env() für das Objekt Request aufruft und die zurückgegebene Umgebung durch Aufruf von exception() prüft. Falls dieser Aufruf ein Nicht-NULL-Ergebnis zurückgibt, dann wurde durch den Methodenaufruf eine Exception ausgelöst, und die Exception wird gemeldet. Gab es keine Exception, so kann der Client value() für das zuvor von der Methode result() zurückgegebene Any aufrufen und den Wert von Any (erhalten durch Aufruf von value()) in ein CORBA::Long übergeben, was der Typ von Any sein sollte. Verglichen mit dem äquivalenten statischen Aufruf können Sie erkennen, daß die dynamische Aufrufschnittstelle viel stärker involviert ist. Daher meiden die meisten CORBA-Entwickler DII; die erhöhte Flexibilität rechtfertigt oft nicht die zusätzliche Komplexität der Programmierung. Wenn die von DII angebotene Funktionalität benötigt wird, so ist DII notwendig; ansonsten läßt man wahrscheinlich am besten die Finger davon. Es ist wohl wert, noch einmal wiederholt zu werden, daß die meisten Entwickler niemals mit DII in Berührung zu kommen brauchen. Abgesehen von der Art von Beispielen, die weiter oben in diesem Kapitel angegeben wurden, ist DII für die meisten Anwendungen nicht nützlich und würde allgemein lediglich unnötige zusätzliche Komplexität für die meisten Anwendungen bringen. Wenn Sie Werkzeuge entwickeln - beispielsweise Werkzeuge für Entwicklung und Systemverwaltung -, die mit unbekannten CORBA-Objekten in generischer Weise arbeiten müssen, wird DII möglicherweise für Sie nützlich sein; ansonsten werden Sie finden, daß es wenig Nutzen bringt, wenn überhaupt.
Zusammenfassung Heute lernten Sie einen neuen Weg zum Aufrufen von Methoden für Remote-CORBA-Objekte kennen - über die dynamische Aufrufschnittstelle (Dynamic Invocation Interface, DII). Sie haben auch etwas über die Vor- und Nachteile des DII-Mechanismus verglichen mit der traditionellen Methode des statischen Aufrufs erfahren, ebenso wie einige potentiell praktische Anwendungen des DII-Mechanismus. Dann lernten Sie, wie die DII-Funktionen in einer Anwendung zu verwenden sind, wobei Sie in erster Linie Zeuge ihrer extremen Komplexität wurden, verglichen mit dem Ihnen vertrauten statischen Aufruf. An Tag 12, »Erkunden von CORBAservices und CORBAfacilities«, werden Sie das nächste in der Reihe von fortgeschrittenen CORBA-Themen erkunden - die Verwendung von CORBAservices und CORBAfacilities. Da diese Dienste eine große Zahl von Funktionen anbieten, ist in diesem Buch jeweils nur Platz für eine kurze Beschreibung. Sie werden jedoch auch zur Beispielanwendung Bank zurückkehren und die Möglichkeit erhalten, die Verwendung eines CORBAservice in die Anwendung Bank zu integrieren - nämlich den CORBA Naming Service.
F&A Frage:
Ich glaube, ich muß DII verwenden. Antwort: Das müssen Sie nicht. DII lädt zu vielen Gelegenheiten für Verwirrung und schwer zu verfolgender Fehler bei Ihren Anstrengungen in der Anwendungsentwicklung ein. Sofern Sie nicht eine Anwendung ähnlich den in den Beispielen dieses Kapitels entwickeln und absolut eine von DII angebotene Funktion nutzen müssen, ist es fast sicher, daß Sie nicht einmal an DII denken müssen. Frage: Wenn DII für die meisten Anwendungen so unnötig ist, warum nervt die CORBA-Spezifikation dann überhaupt damit? Antwort: In den frühen Tagen von CORBA gab es zwei Lager mit unterschiedlichen Ansichten in Hinblick darauf, wie Methoden aufgerufen werden sollten. Aus der Sicht des einen Lagers entwickelte sich der statische Aufrufmechanismus, und aus dem anderen kam DII.
Workshop Der folgende Abschnitt wird Ihnen dabei helfen, Ihr Verständnis des heute präsentierten Materials zu testen und das Erlernte in die Praxis umzusetzen. Sie finden die Antworten auf das Quiz in Anhang A. An den meisten Tagen werden einige Übungen das Quiz begleiten; da heute kein »working knowledge«-Material präsentiert wurde, gibt es keine Übungen. Quiz 1. Würden Sie erwarten, daß DII für die meisten CORBA-Anwendungsentwickler von Nutzen ist? Warum oder warum nicht? 2. Was sind die Vorteile von DII gegenüber statischen Methodenaufrufen? 3. Was sind die Nachteile von DII, verglichen mit statischen Methodenaufrufen?
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 12 CORBAservices und CORBAfacilities Die CORBAservices Die CORBAfacilities Die Einrichtungen für den vertikalen Markt Die Bankanwendung mit Hilfe von CORBAservices verbessern Zusammenfassung Fragen & Antworten Workshop
Inzwischen sind Ihnen die grundlegenden Mechanismen, die von CORBA zur Entwicklung verteilter, objektorientierter Anwendungen zur Verfügung gestellt werden, bestens bekannt. Der ORBMechanismus erleichtert die Kommunikation zwischen CORBA-Objekten und ermöglicht es den Objekten, sich gegenseitig zu finden, wobei der BOA die Kernfunktionalität für alle CORBA-Objekte zur Verfügung stellt. Allerdings reichen die Funktionen, die über den ORB und den BOA zur Verfügung gestellt werden, bei weitem noch nicht auf, um robuste, für Unternehmen geeignete verteilte Anwendungen zu erstellen. Es wäre von großem Vorteil, wenn unabhängig von der Branche, in der die Anwendung eingesetzt wird, noch zusätzliche Funktionen zur Verfügung stehen würden, wie beispielsweise Verzeichnisdienste, Objektpersistenzfunktionen, ein Transaktionsmechanismus, Benutzeroberflächen-
und Präsentationsfunktionen usw. In vielen Branchen, wie beispielsweise im Gesundheitswesen, im Finanzwesen oder in der Telekommunikation, werden Anwendungen benötigt, die besonders gut auf der Basis von CORBA entwickelt werden können. Daher wäre es auch von Vorteil, wenn Funktionen zur Verfügung gestellt würden, welche die Belange für diesen vertikalen Markt speziell berücksichtigen. In der Tat bietet die OMG solche horizontalen und vertikalen Funktionen in Form der CORBAservices und CORBAfacilities an. An den vergangenen Tagen haben Sie bereits eine, wenn auch knappe, Einführung in die CORBAservices und CORBAfacilities erhalten. Dieses Wissen soll heute weiter vertieft werden, und Sie werden Gelegenheit haben, einige der CORBAservices in unserer Bankanwendung einzusetzen.
Die CORBAservices In der OMA (Object Management Architecture), deren Bestandteil CORBA ist, sind eine Reihe von Diensten definiert, die für Anwendungen allgemein hilfreich sind. Diese Dienste reichen vom nahezu unverzichtbaren Bezeichnungsdienst über Dienste höherer Ebenen wie beispielsweise dem Transaktionsdienst. Wie bei allen ihren Spezifikationen hat die OMG (Objekt Management Group) auch für CORBA keine Implementierungen für diese Dienste definiert, stellt aber die Schnittstellen zur Verfügung, über welche auf die Dienste zugegriffen werden kann. Die eigentliche Implementierung der Dienste ist dann Sache der verschiedenen Hersteller von CORBA-Produkten. Hierbei ist es wichtig zu wissen, daß die Produkte zur Implementierung der CORBAservices oft getrennt von den CORBA-ORB-Produkten angeboten werden und daß die Implementierung der CORBAservices nicht Voraussetzung dafür ist, daß ein Produkt mit CORBA 2 kompatibel ist. In diesem Abschnitt möchte ich Ihnen die einzelnen CORBAservices kurz vorstellen. Anhang B enthält eine Übersicht darüber, welche Hersteller derzeit Implementierungen für diese Dienste anbieten. Der Concurrency-Steuerungsdienst Dieser Dienst stellt eine Schnittstelle zur Verwaltung von parallelen Abläufen in gemeinsam benutzten CORBA-Objekten zur Verfügung. Dies erfolgt durch Verwendung unterschiedlicher Typen von Sperren, die der Dienst unterstützt. Hierzu gehören Readers-Writers-Sperren und IntentionSperren. Entwickler von Multithread-Anwendungen werden die Funktionen kennen, die von diesem Dienst zur Verfügung gestellt werden. Der Ereignisdienst Dieser Dienst stellt einen Mechanismus zur Verfügung, mit dem CORBA-Objekte Ereignisse senden und empfangen können. Hierzu gehören folgende Funktionen: ■
■ ■
Zuverlässige Übermittlung, die (vereinfacht gesagt) sicherstellt, daß ein Ereignis sein(e) Ziel(e) erreicht. Unterstützung für Push- und Pull-Modelle bei der Ereignisübermittlung. Anonymes Messaging, bei dem der Erzeuger eines Ereignisses nichts von der Identität des
■
Nutzers weiß und umgekehrt. Ereigniskanäle; dieser Mechanismus entspricht dem System »Herausgeber/Interessent« (Publisher/Subscriber), bei dem der Nutzer bestimmte Ereignistypen quasi abonnieren kann.
Der Auslagerungsdienst Dieser Dienst stellt Schnittstellen für die Auslagerung (d.h. Serialisierung) und die Einlagerung von Objekten zur Verfügung. Wenn ein Objekt ausgelagert wurde, kann es innerhalb desselben oder eines anderen Prozesses eingelagert werden. Ferner können Objekte in einem portierbaren Dateiformat ausgelagert werden (dieses ist in der Spezifikation für den Auslagerungsdienst festgelegt). Eine mögliche Anwendung für diesen Dienst ist ein Übergabe-durch-Wert-Mechanismus für CORBAObjekte. Der Lizenzierungsdienst Über diesen Dienst kann der Hersteller Strategien definieren, mit denen die Verwendung der Dienste eingeschränkt werden kann. Der Dienst unterstützt drei Lizenzierungsstrategien: ■
■
■
Über die Zeitstrategie wird festgelegt, daß für eine Lizenz ein Anfangszeitpunkt, ein Ablaufdatum und eine Dauer gelten. Über die Wertzuordnung wird die Lizenzierung anhand von Einheiten (Messung der Ressourcenauslastung, Anzahl der gleichzeitigen Benutzer usw.) festgelegt. Über die Nutzerstrategie werden Dienste zur Nutzung durch einen bestimmten Benutzer oder Computer verfügbar gemacht.
Funktionen, wie sie über diesen Dienst zur Verfügung gestellt werden, werden sich in Zukunft noch weiter verbreiten, wenn Konzepte wie die nutzungsabhängige Zahlung oder das Mieten von Software realisiert werden. So wäre es beispielsweise denkbar, daß ein gelegentlicher Benutzer einer Bildbearbeitungssoftware diese ausgehend von der Häufigkeit der Nutzung bestimmter Grafikfilter bezahlt. In dem Maße, in dem ein Gerüst für den elektronischen Handel verfügbar wird, könnte es sein, daß mehr Software unter diesen Bedingungen angeboten wird. Der Lebenszyklusdienst Dieser Dienst bietet Funktionen zum Erstellen, Löschen, Kopieren und Verschieben von CORBAObjekten. Mit diesem Dienst wird ferner auch das Konzept einer »Objektfabrik« unterstützt, bei der es sich um ein CORBA-Objekt handelt, welches andere CORBA-Objekte erstellt. Der Bezeichnungsdienst Der Bezeichnungsdienst ermöglicht es CORBA-Objekten, registriert und über ihre Bezeichnung von anderen Objekten gefunden zu werden. Hierbei wird das Konzept des Bezeichnungskontexts verwirklicht, der eine Gruppe von eindeutigen Namen enthält. Der Bezeichnungsdienst unterstützt auch eine verbundartig aufgebaute Architektur, was bedeutet, daß die Bezeichnungs-Server im Netzwerk verteilt sein und zusammenarbeiten können.
Sie werden sich erinnern, daß CORBA-Objekten bei der Durchführung des Standard-Bindevorgangs eine Bezeichnung zugeordnet wird, über die sie von anderen Objekten gefunden werden können. Diese Funktion stellt einen verkleinerten Bezeichnungsdienst dar; der tatsächliche Bezeichnungsdienst kann weitaus umfangreichere Funktionen zur Verfügung stellen. Der Objektvermittlungsdienst Wie auch der Bezeichnungsdienst ermöglicht der Objektvermittlungsdienst das Auffinden von CORBA-Objekten durch andere Objekte. In diesem Fall erfolgt die Suche nicht anhand einer Bezeichnung, sondern die Suche des Client-Objekts nach Diensten erfolgt über Operationsnamen, Parameter und Ergebnistypen. Der Hauptunterschied zwischen dem Vermittlungsdienst und dem Bezeichnungsdienst entspricht dem Unterschied zwischen dem Branchentelefonbuch und dem normalen Telefonbuch. Der Bezeichnungsdienst entspricht dem normalen Telefonbuch, in dem ein bestimmter Dienst gesucht werden kann, wenn man seinen genauen Namen kennt. Der Objektvermittlungsdienst ähnelt eher dem Branchenbuch, in dem ein Dienst anhand der Ortsangabe, seiner Funktion oder seines Namens gefunden werden kann. So können Sie im normalen Telefonbuch nach »Hegners Autowerkstatt« suchen, in den Gelben Seiten nach den Autowerkstätten in Rastatt. In unserer Beispielbankanwendung aus den vorherigen Kapiteln könnte beispielsweise der Bezeichnungsdienst verwendet werden, um eine Bank über ihren Namen zu finden (wie z.B. das Ducksche Bankhaus), oder der Vermittlungsdienst, um Objekte anhand von Funktionen zu suchen (z.B. eine Bank oder einen Geldautomaten). Der Objektpersistenzdienst Dieser Dienst stellt eine Reihe von Schnittstellen zur Verfügung, mit denen die Objektpersistenz verwaltet wird. In der Regel werden die Implementierungen für diesen Dienst von den Datenbankherstellern zur Verfügung gestellt. Unter persistenten Objekten versteht man Objekte, die während eines bestimmten Zeitraums bestehen. Dies bedeutet, daß die Lebensdauer des Objekts die einer jeden Anwendung überdauern kann. Während der Zeit, in der das Objekt nicht verwendet wird, befindet es sich in einem permanenten Speicher, wie einer Datenbank oder einer unstrukturierten Datei; es kann dann bei Bedarf wieder »aufgeweckt« werden. So wäre z.B. ein mit einem Textverarbeitungsprogramm erstelltes Dokument ein persistentes Objekt, weil es möglich ist, das Programm zu schließen und später wieder auszuführen, wobei das Objekt dann erneut aufgerufen werden kann. In einer CORBA-Anwendung ist es manchmal hilfreich, CORBA-Objekte als persistent zu definieren. In unserer Bankanwendung beispielsweise wäre es denkbar, die Objekte vom Typ Bank als persistente Objekte zu konzipieren. Dies bedeutet, daß diese, wenn sie keine Transaktionen verarbeiten, vorübergehend deaktiviert werden können, wobei ihr Status in einer Datenbank gespeichert wird, bis sie wieder benötigt werden. Der Eigenschaftendienst
Über diesen Dienst können Objekte Gruppen von Eigenschaften über Definitionen nach dem Schema »Name/Wert« definieren. Bei jeder Definition ist der Name einfach eine CORBA-Zeichenkette, der Wert ein CORBA-Objekt vom Typ any. Der Zugriff auf die Eigenschaften kann beschränkt werden, so daß eine Eigenschaft nur gelesen bzw. nicht geändert werden kann. Die Verwendung von Eigenschaften zur Beschreibung von Objekten findet immer weitere Verbreitung, insbesondere dadurch, daß Objektmodelle wie JavaBeans immer mehr an Bedeutung gewinnen. Für die Objekte einer umfangreichen Anwendung oder einer Gruppe von Anwendungen könnten eine Reihe von Standardeigenschaften definiert werden, was die Objektverwaltung stark vereinfachen würde. Wenn beispielsweise in der Bankanwendung für jedes Objekt eine Eigenschaft »Standort« definiert wäre, könnten die Standorte der Objekte vom Typ Bank, Geldautomat, Kunde usw. einheitlich für den gesamten Anwendungs-Quelltext definiert werden. Der Abfragedienst Dieser Dienst unterstützt die Verwendung von Abfragen für Objekte. In diesen Abfragen können Prädikate zur Angabe der Objekte enthalten sein, auf die sich die Operation beziehen soll, wobei Attributwerte angegeben werden. Dieser Dienst unterstützt ferner sowohl die Indexierung von Objekten als auch verschachtelte Abfragen. Die Abfragefunktion stellt eine datenbankähnliche Semantik für CORBA-Objekte zur Verfügung. Genauso wie in einer Anwendung Abfragen für Tabellen und Datensätze einer relationalen Datenbank durchgeführt werden können, ermöglicht der Abfragedienst einer Anwendung die Durchführung von Abfragen für CORBA-Objekte. Der Beziehungsdienst Der Beziehungsdienst dient zur Darstellung der Beziehungen zwischen Objekten. Er stellt eine vollständige Prüfung der Integritätsbedingungen für Beziehungstypen und Beziehungsarten (Eins-zuViele, Eins-zu-Eins usw.) und wird auch zusammen mit dem Lebenszyklusdienst zum Kopieren, Verschieben und Entfernen miteinander in Beziehung stehender Objekte verwendet. Die Verwaltung der Beziehungen zwischen Objekten ist natürlich auch ohne den Beziehungsdienst möglich, seine Verwendung verringert allerdings die Komplexität bei der Verwaltung komplexer Beziehungen. Der Sicherheitsdienst Über den Sicherheitsdienst werden Schnittstellen für die folgenden Sicherheitsfunktionen definiert: ■
■
■
■
■
Identifikation und Identitätsüberprüfung der Benutzer, durch die festgestellt wird, ob es sich tatsächlich um den angegebenen Benutzer handelt. Zugriffsberechtigungen und Zugriffsbeschränkungen, durch die festgelegt wird, welche Benutzer Zugriff auf welche Dienste oder Objekte haben dürfen. Sicherheitsprotokollierung, durch die Datensätze der Benutzeraktionen zur Verfügung gestellt werden. Kommunikationssicherheit, zu der die Identitätsüberprüfung der Benutzer gegenüber den Diensten (und umgekehrt), Integritätsschutz und Geheimhaltungsschutz gehören. Funktionen zur Unwiderlegbarkeit, die entsprechen denen für digitale Signaturen, d.h., die
■
Herkunft der Daten oder der Empfang der Daten kann unwiderlegbar bewiesen werden. Verwaltung der verschiedenen Sicherheitsstrategien.
Die Sicherheit ist in einer Reihe von Anwendungen ein ganz zentraler Aspekt. So müssen beispielsweise bei kommerziellen Bankanwendungen buchstäblich alle Systemaspekte abgesichert werden, dies geht von der Identitätsüberprüfung und Identifizierung der Kunden bis hin zur Sicherheit der Kommunikation zwischen Banken und Geldautomaten. Der Zeitgeberdienst Der Zeitgeberdienst stellt dem Benutzer die aktuelle Uhrzeit zur Verfügung, er dient zur zeitlichen Ordnung von Ereignissen und ermöglicht das Generieren von Ereignissen mit Hilfe von Zeitgebern. Der Transaktionsdienst Der Transaktionsdienst stellt die Schnittstellen zur Verfügung, die zur Unterstützung von Transaktionsfunktionen benötigt werden. Hierbei werden sowohl flache als auch verschachtelte Transaktionsmodelle und externe Transaktionsverarbeitungs-Monitore unterstützt. Es ist auch möglich, daß die Transaktionsdienste zusammenarbeiten. Die Transaktionssemantik ist ein grundlegender Bestandteil fast jeder ernsthaften Anwendung. Wenn bei unserer Beispielbankanwendung eine bankenübergreifende Überweisung von einem Konto zu einem anderen zu koordinieren ist, muß eine Transaktion eingeleitet werden, die beide beteiligten Banken veranlaßt, die Transaktion entweder festzuschreiben oder abzubrechen. Andernfalls wären inkonsistente Daten (wie z.B. Kontostände) die Folge.
Die CORBAfacilities Die CORBAfacilities decken sowohl horizontale Einrichtungen (Funktionen, die branchenübergreifend für alle Arten von CORBA-Anwendungen hilfreich sind) als auch vertikale Einrichtungen (Funktionen, die besonders für Anwendungen innerhalb bestimmter vertikaler Märkte und Branchen hilfreich sind) ab. Zu den horizontalen Einrichtungen gehören Benutzeroberflächenund Systemverwaltungseinrichtungen, denn diese Funktionalität wird für die meisten Arten von Anwendungen benötigt, unabhängig von der Branche, in der sie eingesetzt werden. Zu den vertikalen Einrichtungen gehören beispielsweise die Hauptbuch- und Amortisierungsfunktionalität für das Buchhaltungswesen oder Einrichtungen zur automatisierten Werkstattsteuerung in der Fertigung. Wie bei den CORBAservices hat die OMG auch für die CORBAfacilities nur die Schnittstellen für diese Einrichtungen definiert, die Implementierungen werden ggf. von den entsprechenden CORBAHerstellern zur Verfügung gestellt. Ferner werden über einige CORBAfacilities nur Anregungen für Schnittstellen gegeben, die für bestimmte Dienste und Arten von Anwendungen verwendet werden können. Horizontale Einrichtungen Bei den horizontalen CORBAfacilities unterscheidet man vier Typen von Einrichtungen:
Benutzeroberflächen, Informationsverwaltung, Systemverwaltung und Aufgabenverwaltung. Diese Kategorien lassen sich noch weiter unterteilen (siehe den folgenden Abschnitt). Hier sei noch einmal betont, daß die horizontalen Einrichtungen branchenunabhängig für alle Arten von Anwendungen verwendet werden können. So wird für die meisten Anwendungen eine Benutzeroberfläche benötigt, Methoden zum Speichern und Abrufen von Informationen, Sicherheitseinrichtungen, Arbeitsablaufund Prozeßverwaltung usw. Die Gruppe der »User Interface Common Facilities« Mit diesen Einrichtungen werden alle Aspekte abgedeckt, welche die Benutzeroberflächen betreffen; dies reicht von den Werkzeugen zu deren Entwicklung bis hin zur Darstellung für den Benutzer. Mit den CORBAfacilities werden die folgenden Komponenten von Benutzeroberflächen definiert: Das Aussehen der Benutzeroberfläche bestimmt, wie sich die Benutzeroberfläche der Anwendung dem Benutzer darstellt. Die Aktivatoren für die Benutzeroberfläche dienen zur Darstellung der Benutzeroberfläche für den Benutzer. Sie sind in folgende Einrichtungen unterteilt: ■ ■ ■
Die Darstellungsverwaltung dient zur Abstrahierung von Benutzeroberflächenobjekten. Die Verbunddarstellung dient zur Darstellung von Verbunddokumenten. Die Benutzerunterstützung bietet Rechtschreibprüfung, Online-Hilfe usw.
Das Arbeitsverwaltungssystem dient zur Verwaltung der Arbeitsumgebung des Benutzers und besteht aus der Arbeitsoberfläche (Desktop) für den Benutzer, der Einzelanmeldung beim System und den Informationen, die der Benutzer verwendet. Die Aufgaben- und Prozeßautomatisierung ermöglicht den Benutzern das Schreiben von Scripts, mit denen sie ihre Aufgaben automatisieren können, sowie die Verwendung von Arbeitsabläufen (Workflows). Die Gruppe der »Information Management Common Facilities« Diese Gruppe umfaßt folgende Einrichtungen: Die Informationsmodellierung beschäftigt sich hauptsächlich mit der Art, wie Daten strukturiert sind. Das Speichern und Abrufen von Informationen mit Hilfe von Datenbanken, Informationsabrufsystemen und Objektablagen. Der Informationsaustausch ermöglicht das Austauschen von Daten zwischen Anwendern und Anwendungen. Hierzu gehören folgende Einrichtungen: Compound Interchange Facility (zum Austausch von Verbunddokumenten), Data Interchange Facility (zum Austausch von Daten) und Information Exchange Facility (zum Austausch von Informationen).
Bei der Datencodierung und -darstellung geht es darum, wie die Daten gespeichert werden, bei Bedarf bis auf Bit-Ebene. Der Hauptgrund dafür, warum dieser Aspekt hier mit einbezogen wird, liegt darin, daß die Portabilität der Daten zwischen Anwendungen, Prozessen, Hardware- und Softwarearchitekturen etc. gewährleistet werden soll. Die Gruppe der »Systems Management Common Facilities« Über diese Einrichtungen werden die Schnittstellen für die Systemverwaltung zur Verfügung gestellt. Die Strategieverwaltung dient zur Steuerung der Erstellung, Löschung und Änderung von verwaltbaren Komponenten. Die Dienstequalitätsverwaltung dient zur Unterstützung der Auswahl von Dienststufen für die Verfügbarkeit, Systemleistung, Zuverlässigkeit und Wiederherstellung. Mit der Instrumentierung werden die Funktionen zur Verfügung gestellt, mit denen Daten bezüglich der Systemauslastung, des Standorts von Objekten, der Antwortzeiten des Systems usw. gesammelt und ausgewertet werden können. Die Datensammlung umfaßt beispielsweise Funktionen zur Protokollierung und zur Datenarchivierung. Die Sicherheitsfunktionen stellen Verwaltungsfunktionen für die Sicherheit von Systemressourcen zur Verfügung. Die Gruppenverwaltung ermöglicht Systemadministratoren die Verwaltung von Gruppen oder Sammlungen von Objekten. Die Instanzenverwaltung ermöglicht das Assoziieren von Objekten mit anderen Objekten zu Verwaltungszwecken. Die Planungsverwaltung ermöglicht das kontrollierte Ausführen von Aufgaben (z.B. zu einem bestimmten Zeitpunkt oder als Reaktion auf ein bestimmtes Ereignis). Über die Anpassung ist es möglich, Objekte dynamisch zu erweitern und dabei deren Typensicherheit zu gewährleisten. Die Ereignisverwaltung stellt diverse Bearbeitungsfunktionen für Ereignisse im System zur Verfügung. Die Gruppe der »Task Management Common Facilities« Diese Einrichtungen unterstützen die Verarbeitung von Benutzeraufgaben. Hierzu gehören die folgenden Einrichtungen:
Arbeitsablauf bietet Verwaltung und Koordinierung von Objekten, die Teil eines Arbeitsprozesses (z.B. eines Beschaffungsprozesses) sind. Es werden sowohl produktionsgestützte Arbeitsabläufe als auch Ad-hoc-Arbeitsabläufe (koordinationsgestützte Arbeitsabläufe) unterstützt. Die Einrichtung Agent unterstützt sowohl statische als auch mobile Agent-Typen. Obwohl die Definition und Erörterung der Verwendung von Agenten den Rahmen dieses Kapitels sprengen würde, möchte ich die agentenbezogenen Einrichtungen zumindest einmal aufzählen. Zu diesen gehören ein Inhaltsdienst, ein Kommunikationsdienst, ein Nachrichtendienst, ein Dienst für Basisinformationen, ein Dienst für einfache Abfragen, Dienste für Abfragen mit mehreren Antworten, Assertion-Dienste, Generierungsdienste, Dienste zur Funktionsdefinition, Benachrichtigungsdienste, Netzwerkdienste, Servo-Dienste, Datenbankdienste, Anpassungsdienste, Fehlerbehandlungsdienste, Dienste für die automatische Neuübertragung, ein Registrierungsdienst, Sicherheitsdienste und Verwaltungsdienste. (Die reine Aufzählung macht schon deutlich, daß dieses Thema weit über den Rahmen dieses Buches hinausgeht.) Die Einrichtung zur Regelverwaltung dient zur Spezifikation und Verarbeitung von Regeln, die auf Ereignissen, Bedingungen und Aktionen basieren. Die Automationseinrichtung liefert Funktionen zur Verwendung von Scripts und Makros bei der Bearbeitung umfangreicher CORBA-Objekte.
Die Einrichtungen für den vertikalen Markt Zusätzlich zu den von der OMA angebotenen horizontalen Diensten und Einrichtungen gibt es eine Vielzahl vertikaler CORBAfacilities, die speziell für die Anforderungen des betreffenden Markts konzipiert sind. Die OMG ist ferner ständig dabei, neue Einrichtungen für den vertikalen Markt hinzuzufügen, wenn in einem bestimmten Spezialgebiet Interesse besteht. Ich möchte Ihnen im Rest dieses Abschnitts die Spezifikationen für Einrichtungen für den vertikalen Markt kurz vorstellen, die verfügbar waren, als ich dieses Buch geschrieben habe. Obwohl die Einrichtungen für den vertikalen Markt derzeit Teil der Facilities Architecture (Systemeinrichtungsarchitektur) der OMG sind, werden sie größtenteils von der Arbeit der verschiedenen Domain Task Forces (Spezialeinheiten) der OMG abgelöst. Jede dieser Spezialeinheiten produziert Spezifikationen für den vertikalen Anwendungsbereich, dem sie zugeordnet ist. Einen Überblick über die zum Zeitpunkt der Bucherstellung beendeten oder laufenden Projekte der einzelnen Spezialeinheiten finden Sie in Anhang C.
Die Einrichtung zur Bilddatenverarbeitung unterstützt den Zugriff auf und den Austausch von Bilddaten sowie zugehörigen Daten.
Die Einrichtung für Datenautobahnen besteht aus einer Reihe von Netzwerken, Protokollen, Regeln und Informationsablagen, die über diese Netzwerke miteinander verbunden sind, sowie einer Sammlung von Werkzeugen, die einen transparenten Zugriff auf diese Daten ermöglichen. Die Einrichtung für die Fertigung bietet die Integration von Fertigungsfunktionen und -Ressourcen mit anderen Geschäftsaspekten. Die Einrichtung für verteilte Simulation unterstützt verteilte Simulationen für die Luftverkehrsüberwachung, Videospiele und Unterhaltung sowie noch weitere Bereiche. Die Einrichtung für die Erschließung und Förderung in der Öl- und Gasindustrie stellt die Grundlage zur Definition von Spezifikationen für die Erschließung und Förderung dar. Die Anforderungen für diesen Bereich umfassen die Handhabung großer Datenmengen und komplexer Algorithmen sowie die Langzeitspeicherung von Daten. Die Einrichtung für die Buchhaltung stellt einen kompatiblen Ansatz für Buchhaltungsschnittstellen zur Verfügung und strebt eine Vereinfachung für die Buchhaltungsdienstleister und Endbenutzer an. Die Einrichtung für die Anwendungsentwicklung deckt Auswahl, Entwicklung, Erstellung und Evolution der Anwendungen ab, die zur Unterstützung der IT-Strategie eines Unternehmens benötigt werden. Die Abbildungseinrichtung stellt ein in sich geschlossenes Mittel zur Einspeisung des Datenstroms aus Datenbanken über konstruierte Analysemodule entweder in Präsentationswerkzeuge oder sekundäre Datenanwendungen zur Verfügung.
Die Bankanwendung mit Hilfe von CORBAservices verbessern Der heutige Tag wäre nicht vollständig, wenn ich nicht beschreiben und zeigen würde, wie die CORBAservices und CORBAfacilities in unsere Beispielanwendung integriert werden können. Dies soll in diesem Abschnitt geschehen. Zunächst werden Sie herausfinden, welche der CORBAservices für die Bankanwendung von Nutzen sind, dann werden Sie die Anwendung so ändern, daß diese Dienste genutzt werden können. In diesem Kapitel (und eigentlich auch in diesem Buch) ist nicht genug Raum, um alle eventuell in Frage kommenden CORBAservices und CORBAfacilities zu behandeln, so werden wir uns auf einige wenige konzentrieren. Welche CORBAservices sollen verwendet werden? Jetzt wollen wir die verfügbaren CORBAservices kurz Revue passieren lassen und dabei überlegen, welche davon für unsere Bankanwendung eingesetzt werden könnten. ■
Der Concurrency-Steuerungsdienst. Da es durchaus möglich wäre, daß andere Objekte auf Objekte in der Bankanwendung (insbesondere vom Typ Bank) gleichzeitig zugreifen könnten, wäre die Verwendung dieses Dienstes sinnvoll. Allerdings könnte der parallele Zugriff besser
■
■
■
■
■
■
■
■
■
■
■
■
■
über den Transaktionsdienst geregelt werden. Der Ereignisdienst. Wenn die Bankanwendung so erweitert werden müßte, daß sie außer den Nachrichten, die von dem am 9. Tag hinzugefügten Aktualisierungsdienst verwendet werden, noch weitere Nachrichten einbinden müßte, wäre die Verwendung des Ereignisdienstes sinnvoll. Der Auslagerungsdienst. Das Auslagern von Objekten ist für die Bankanwendung nicht so besonders wichtig. Der Lizenzierungsdienst. Da Bankdienste in der Regel nicht lizenziert sind, brauchen Sie den Lizenzierungsdienst für die Bankanwendung nicht in Betracht zu ziehen. Der Lebenszyklusdienst. Theoretisch könnte der Lebenszyklusdienst gewinnbringend eingesetzt werden, aber die Bankanwendung profitiert in ausreichendem Maße von den Standard-CORBA-Mechanismen zur Verwaltung der Lebenszyklen von Objekten. Der Bezeichnungsdienst. Wenn unsere Bankanwendung zu einem äußerst weit verteilten System anwachsen würde, könnte sie großen Nutzen aus dem Bezeichnungsdienst ziehen. Anstatt den Standard-CORBA-Mechanismus zum Binden zu verwenden, könnten Sie die Banken über einen verbundartigen Bezeichnungsdienst suchen lassen. Der Objektvermittlungsdienst. Da die Komponenten der Bankanwendung alle bekannt und ihre Standorte bekannt sind, ist es unwahrscheinlich, daß Komponenten Objekte anhand der von diesen zur Verfügung gestellten Dienste suchen müssen. Der Objektvermittlungsdienst ist eher für Anwendungen gedacht, in denen leistungsfähigere Suchfunktionen benötigt werden. (Wenn unsere Bankanwendung umfangreicher wäre, würde es sich lohnen, eine Funktion einzubinden, mit deren Hilfe Banken anhand ihres Standorts gesucht werden können.) Der Objektpersistenzdienst. Die Verwendung des Objektpersistenzdienstes würde sich beispielsweise lohnen, wenn die Kunden- und Kontodaten einer Bank in einem permanenten Speicher gespeichert werden müßten, während sie nicht benötigt werden. Der Eigenschaftendienst. Der Eigenschaftendienst kann zwar verwendet werden, um beispielsweise Kunden- oder Kontoinformationen darzustellen, er ist aber für unsere Bankanwendung nicht besonders gut geeignet. Der Abfragedienst. Nach dem jetzigen Stand der Dinge wäre der Nutzen des Abfragedienstes für die Bankanwendung nicht sonderlich groß. Wenn für die Anwendung allerdings Werkzeuge zur Berichterstellung entwikkelt würden, wäre die Verwendung dieses Dienstes hilfreich. Der Beziehungsdienst. Für die Bankanwendung hat die Verwendung des Beziehungsdienstes zum Modellieren der Beziehungen zwischen den Objekten (z.B. zwischen Banken und Konten oder zwischen Kunden und Konten) wenig Sinn. Wenn jedoch neue Objekte hinzukämen, so daß die Beziehungen komplexer würden, wäre der Beziehungsdienst hilfreich. Der Sicherheitsdienst. Da eine Bank eine äußerst sichere Institution sein muß, kann bei einer professionellen Bankanwendung nicht auf den Sicherheitsdienst verzichtet werden. Es können zahlreiche Funktionen des Sicherheitsdienstes verwendet werden, wie Benutzeridentifikation und Zugriffsberechtigungen, Sicherheitsprotokolle, Kommunikationssicherheit und Unwiderlegbarkeit. Der Sicherheitsdienst ist der ideale Dienst für unsere Bankanwendung. Der Zeitgeberdienst. Der Zeitgeberdienst ist wertvoll, denn er stellt sicher, daß die verschiedenen Banken in zeitlicher Hinsicht perfekt aufeinander abgestimmt sind. Der Transaktionsdienst. Der Transaktionsdienst ist ein weiterer äußerst nützlicher Dienst für eine Bankanwendung. Insbesondere können durch das Zusammenwirken mit einem Transaktionsverarbeitungs-Monitor die Transaktionen zwischen Konten und Banken
koordiniert werden. Wenn man die CORBAservices von der reinen Nützlichkeit für die Bankanwendung her betrachten würde, müßte man wahrscheinlich den Sicherheitsdienst und den Transaktionsdienst wählen. Wenn man allerdings berücksichtigt, welcher Dienst problemlos zur Verfügung steht (und daß dieses Buch nicht übermäßig lang werden soll), so ist der Bezeichnungsdienst eine praktische Wahl. Implementierung der neuen Funktionen Die Entscheidung, wo die über den Bezeichnungsdienst zur Verfügung gestellte Funktionalität einzubinden ist, fällt leicht. Der Bezeichnungsdienst kann anstelle des Standard-CORBAMechanismus zum Binden verwendet werden. Damit werden die Objekte nicht mehr direkt über ihre Namen gebunden, sondern CORBA-Objekte suchen dann andere Objekte über den Bezeichnungsdienst. In der Bankanwendung ist das einzige Objekt, das auf diese Weise gesucht wird, der BankServer, die übrigen Server-Objekte (der Typen Bank und Geldautomat) werden über den BankServer selbst gesucht. Die zum Implementieren durchzuführenden Änderungen sind nicht besonders umfangreich und eignen sich daher gut dafür, in diesem Kapitel gezeigt zu werden. Verwendung des CORBA-Bezeichnungsdienstes Anstatt direkt über den Bindemechanismus von CORBA eine Bindung zum BankServer vorzunehmen, können die Anwendungskomponenten mit einem Bezeichnungskontext verbunden werden und dann diesen Bezeichnungskontext verwenden, um den BankServer über seinen Namen aufzulösen. Die Funktionen des CORBA-Bezeichnungsdienstes werden der Anwendung über ein Bezeichnungskontextobjekt zur Verfügung gestellt. Zu beachten ist hierbei, daß es sich beim Bezeichnungskontext lediglich um ein CORBA-Objekt handelt. Seine Schnittstelle ist in der IDL definiert, und es wird von der Anwendung wie alle übrigen CORBA-Objekte behandelt. Unter einem Bezeichnungskontext versteht man einfach eine Sammlung von Bezeichnungsstrukturen, über die Bezeichnungen entweder mit CORBA-Objektreferenzen oder mit anderen Bezeichnungskontexten assoziiert sind. Zusammengenommen bilden diese gesammelten Assoziationen eine hierarchische Bezeichnungsstruktur, mit deren Hilfe CORBA-Objekte andere Objekte auffinden können.
In den vorherigen Kapiteln haben Sie mit dem Anwendungsquelltext gearbeitet, den Sie jeweils im Kapitel zuvor entwickelt hatten. In diesem Kapitel werden wir in dieser Hinsicht eine Ausnahme machen, denn es soll der Quelltext vom 9. und nicht vom 11. Tag als Ausgangspunkt verwendet werden. Damit soll vermieden werden, daß die DII und die CORBAservices innerhalb derselben Anwendung verwendet werden, was die ganze Angelegenheit unnötig komplex machen würde.
Die erste Änderung an der Bankanwendung besteht darin, die BankServer-Anwendung so zu ändern, daß sie beim Bezeichnungsdienst registriert wird. Keine der Änderungen, weder am BankServer noch an einer anderen Anwendungskomponente, muß direkt in der Objektimplementierung vorgenommen werden. Die Änderungen werden im Abschnitt main jeder Anwendung vorgenommen, in dem das Objekt erstellt wird. Die ersten dieser Änderungen, die in BankServerHaupt.cpp vorgenommen werden, sind im Listing 12.1 durch Fettschrift gekennzeichnet. Listing 12.1: BankServerHaupt.cpp. 1: // BankServerHaupt.cpp 2: 3: #include 4: 5: #include "BankServerImpl.h" 6: #include 7: 8: int main(int argc, char *const *argv) { 9: 10: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 11: // (zwei, mit dem Namen des Programms zusammen). 12: if (argc != 2) { 13: cout << "Verwendung: BankServer " << endl; 14: return 1; 15: } 16: 17: // Den BankServer-Namen dem ersten Parameter zuweisen. 18: const char* bankServerName = argv[1]; 19: 20: // ORB und BOA initialisieren. 21: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 22: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 23: 24: // BankServerImpl-Objekt erzeugen. 25: BankServerImpl bankServer(bankServerName); 26: 27: // BOA informieren, daß das BankServerImpl-Objekt bereit ist. 28: boa->obj_is_ready(&bankServer); 29: 30: // Einen Bezeichnungsdienst suchen und das BankServer-Objekt damit 31: // registrieren. 32: CosNaming::NamingContext_var context; 33: try { 34: CORBA::Object_ptr contextObj = orb->
35: resolve_initial_references("NameService"); 36: CosNaming::NamingContext_var context = CosNaming:: 37: NamingContext::_narrow(contextObj); 38: } catch (const CORBA::Exception& ex) { 39: cout << "BankServerHaupt: Konnte Verbindung zum Bezeichnungsdienst " 40: "nicht herstellen:" << endl << ex << endl; 41: return 1; 42: } 43: CosNaming::Name name; 44: name.length(1); 45: name[0].id = bankServerName; 46: name[0].kind = "BankServer"; 47: context->bind(name, &bankServer); 48: 49: // Auf CORBA-Ereignisse warten. 50: cout << "BankServer bereit." << endl; 51: boa->impl_is_ready(); 52: 53: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 54: return 0; 55: } Beachten Sie hier insbesondere den Prozeß, mit dem das Objekt an den Bezeichnungsdienst gebunden wird. Zuvor wurde für den BankServer keine eigene Bezeichnung benötigt, nun wird diese als Befehlszeilenargument verwendet. Der BankServer sucht dann den Standard-Bezeichnungskontext (dieser wird beim Starten des Bezeichnungsdienstes erstellt), erstellt einen Bezeichnungseintrag für das BankServer-Objekt und führt dann mit Hilfe dieses Bezeichnungseintrags die Bindung zum Bezeichnungsdienst durch. Beachten Sie ferner, daß für den Parameter kind des Objekts die Bezeichnung »BankServer« verwendet wird; es wäre aber auch möglich, jede beliebige Zeichenkette zu verwenden. Nun sollten Sie sich der nächsten zu ändernden Datei, BankHaupt.cpp, zuwenden. Diese ist in Listing 12.2 gezeigt. Auch hier wird für die Bank (und analog für den Geldautomaten) kein direkter Bindevorgang an einen BankServer durchgeführt, sondern die Bank (bzw. der Geldautomat) sucht ein BankServer-Objekt über den Bezeichnungsdienst. Beim Erstellen des Objekts vom Typ Name erhält dieses die ID"BankServer1", und für kind wird »BankServer« festgelegt. Dies bedeutet, daß die Bank erwartet, mit einem Objekt verbunden zu werden, für das bei kind »BankServer« festgelegt ist (dies ist, wie Sie sehen können, bei BankServerHaupt.cpp in Listing 12.1 der Fall) und dessen ID »BankServer1« ist. Da die ID (Bezeichnung) des BankServer über die Befehlszeile zugewiesen wird, ist es sinnvoll, den BankServer zum gegebenen Zeitpunkt mit dem Argument »BankServer1« zu starten. Listing 12.2: BankHaupt.cpp.
1: // BankHaupt.cpp 2: 3: #include "BankImpl.h" 4: 5: #include 6: 7: #include 8: 9: #include "../BankServer_c.h" 10: 11: CORBA::BOA_var boa; 12: 13: int main(int argc, char *const *argv) { 14: 15: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 16: // (zwei, mit dem Namen des Programms zusammen). 17: if (argc != 2) { 18: cout << "Verwendung: Bank " << endl; 19: return 1; 20: } 21: 22: // Namen der Bank dem ersten Parameter zuweisen. 23: const char* bankName = argv[1]; 24: 25: // ORB und BOA initialisieren. 26: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 27: ::boa = orb->BOA_init(argc, argv); 28: 29: // Ein Bank-Objekt erzeugen. 30: BankImpl bank(bankName); 31: 32: // BOA benachrichtigen, daß das BankImpl-Objekt bereit ist. 33: ::boa->obj_is_ready(&bank); 34: 35: // Ein BankServer-Objekt im Bezeichnungsdienst finden. 36: BankServer_var bankServer; 37: try { 38: CORBA::Object_ptr contextObj = orb-> 39: resolve_initial_references("NameService"); 40: CosNaming::NamingContext_var context = CosNaming:: 41: NamingContext::_narrow(contextObj); 42: CosNaming::Name name; 43: name.length(1); 44: name[0].id = "BankServer1"; 45: name[0].kind = "BankServer";
46: CORBA::Object_var object = context->resolve(name); 47: bankServer = BankServer::_narrow(object); 48: } catch (const CORBA::Exception& ex) { 49: 50: // Die Bindung ist mißlungen... 51: cout << "BankImpl: Bindung mit einem BankServer nicht möglich:" << endl; 52: cout << ex << endl; 53: return 1; 54: } 55: 56: // Mit BankServer registrieren. 57: try { 58: bankServer->registriereBank(&bank); 59: } catch (const CORBA::Exception& ex) { 60: 61: // Befehl registriereBank() mißlungen... 62: cout << "BankImpl: Bank konnte nicht registriert werden." << endl; 63: cout << ex << endl; 64: return 1; 65: } 66: 67: // Auf CORBA-Ereignisse warten. 68: cout << "Bank \"" << bankName << "\" bereit." << endl; 69: ::boa->impl_is_ready(); 70: 71: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 72: return 0; 73: } Die Änderungen für GeldautomatHaupt.cpp, die denen in BankHaupt.cpp entsprechen, sind in Listing 12.3 enthalten. Sie können leicht erkennen, daß diese beiden Implementierungen fast identisch sind. Listing 12.3: GeldautomatHaupt.cpp. 1: // GeldautomatHaupt.cpp 2: 3: #include "GeldautomatImpl.h" 4: 5: #include 6: 7: #include 8: 9: #include "../BankServer_c.h" 10:
11: CORBA::BOA_var boa; 12: 13: int main(int argc, char *const *argv) { 14: 15: // Anzahl der Parameter überprüfen; es sollte genau einer vorliegen 16: // (zwei, mit dem Namen des Programms zusammen). 17: if (argc != 2) { 18: cout << "Verwendung: Geldautomat " << endl; 19: return 1; 20: } 21: 22: // Den Geldautomat-Namen dem ersten Parameter zuweisen. 23: const char* geldautomatName = argv[1]; 24: 25: // ORB und BOA initialisieren. 26: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 27: ::boa = orb->BOA_init(argc, argv); 28: 29: // Ein Geldautomat-Objekt erzeugen. 30: GeldautomatImpl geldautomat(geldautomatName); 31: 32: // BOA benachrichtigen, daß das GeldautomatImpl-Objekt bereit ist. 33: ::boa->obj_is_ready(&geldautomat); 34: 35: // Ein BankServer-Objkekt im Bezeichnungsdienst suchen. 36: BankServer_var bankServer; 37: try { 38: CORBA::Object_ptr contextObj = orb-> 39: resolve_initial_references("NameService"); 40: CosNaming::NamingContext_var context = CosNaming:: 41: NamingContext::_narrow(contextObj); 42: CosNaming::Name name; 43: name.length(1); 44: name[0].id = "BankServer1"; 45: name[0].kind = "BankServer"; 46: CORBA::Object_var object = context->resolve(name); 47: bankServer = BankServer::_narrow(object); 48: } catch (const CORBA::Exception& ex) { 49: 50: // Die Bindung ist mißlungen... 51: cout << "GeldautomatImpl: Bindung mit einem BankServer nicht möglich:" << endl; 52: cout << ex << endl; 53: return 1;
54: } 55: 56: // Mit BankServer registrieren. 57: try { 58: bankServer->registriereGeldautomat(&geldautomat); 59: } catch (const CORBA::Exception& ex) { 60: 61: // Befehl registriereGeldautomat() mißlungen... 62: cout << "GeldautomatImpl: Geldautomat konnte nicht registriert werden." << endl; 63: cout << ex << endl; 64: return 1; 65: } 66: 67: // Auf CORBA-Ereignisse warten. 68: cout << "Geldautomat \"" << geldautomatName << "\" bereit." << endl; 69: ::boa->impl_is_ready(); 70: 71: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 72: return 0; 73: } Die geänderte Datei GeldautomatClientHaupt.cpp, die in Listing 12.4 gezeigt ist, weist ähnliche Änderungen auf. Auch der GeldautomatClient sucht über den Bezeichnungsdienst nach einem Objekt namens »BankServer1« vom Typ »BankServer«. Der übrige Quelltext für GeldautomatClientHaupt.cpp bleibt unverändert. Listing 12.4: GeldautomatClientHaupt.cpp. 1: // GeldautomatClientHaupt.cpp 2: 3: #include 4: #include <stdlib.h> 5: 6: #include "../Kunde/KundenImpl.h" 7: 8: #include 9: 10: #include "../Bank_c.h" 11: #include "../BankServer_c.h" 12: #include "../Geldautomat_c.h" 13: 14: int main(int argc, char *const *argv) { 15: 16: // Anzahl der Parameter überprüfen; es sollten genau fünf
vorliegen 17: // (zusammen mit dem Programmnamen sechs). 18: if (argc != 6) { 19: cout << "Verwendung: GeldautomatClient " 20: " <mädchenname der mutter> " << endl; 21: return 1; 22: } 23: 24: // Befehlszeilenparamater den Attributen des Kunden zuweisen. 25: const char* name = argv[1]; 26: const char* PersonalAusweisNummer = argv[2]; 27: const char* adresse = argv[3]; 28: const char* MuttersMaedchenName = argv[4]; 29: CORBA::Short pin = atoi(argv[5]); 30: 31: // ORB und BOA initialisieren. 32: CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 33: CORBA::BOA_var boa = orb->BOA_init(argc, argv); 34: 35: // Ein Objekt Kunde erzeugen. 36: cout << "GeldautomatClient: Erzeuge neuen Kunden:" << endl; 37: cout << " Name: " << name << endl; 38: cout << " Personalausweisnummer: " << PersonalAusweisNummer << 39: endl; 40: cout << " Adresse: " << adresse << endl; 41: cout << " Mädchenname der Mutter: " << MuttersMaedchenName << endl; 42: KundenImpl Kunde(name, PersonalAusweisNummer, adresse, 43: MuttersMaedchenName); 44: 45: // BOA mitteilen, daß das Objekt KundenImpl bereit ist. 46: boa->obj_is_ready(&Kunde); 47: 48: // Ein BankServer-Objekt im Bezeichnungsdienst suchen 49: BankServer_var bankServer; 50: try { 51: CORBA::Object_ptr contextObj = orb-> 52: resolve_initial_references("NameService"); 53: CosNaming::NamingContext_var context = CosNaming:: 54: NamingContext::_narrow(contextObj); 55: CosNaming::Name name; 56: name.length(1); 57: name[0].id = "BankServer1"; 58: name[0].kind = "BankServer"; 59: CORBA::Object_var object = context->resolve(name);
60: bankServer = BankServer::_narrow(object); 61: } catch (const CORBA::Exception& ex) { 62: 63: // Die Bindung ist mißlungen... 64: cout << "GeldautomatClient: Bindung mit einem BankServer nicht möglich:" << 65: endl; 66: cout << ex << endl; 67: return 1; 68: } 69: 70: cout << "GeldautomatClient: Bindung mit einem BankServer erfolgreich durchgeführt." << 71: endl; 72: 73: // Versuchen eine Liste der Banken und Geldautomaten vom BankServer zu beziehen. 74: BankenListe_ptr banks; 75: GeldautomatListe_ptr Geldautomaten; 76: 77: try { 78: banks = bankServer->getBanken(); 79: Geldautomaten = bankServer->getGeldautomaten(); 80: } catch (const CORBA::Exception& ex) { 81: 82: // Der Versuch ist fehlgeschlagen... 83: cout << "GeldautomatClient: Liste der Banken und Geldautomaten konnte nicht abgerufen werden:" 84: << endl; 85: cout << ex << endl; 86: return 1; 87: } 88: 89: // Ducksches Bankhaus und den ersten Geldautomat aus der Liste 90: // verwenden. 91: if (banks->length() == 0) { 92: 93: // Es sind keine Banken verfügbar. 94: cout << "GeldautomatClient: Es sind keine Banken verfügbar." << endl; 95: return 1; 96: } 97: if (Geldautomaten->length() == 0) { 98: 99: // Es sind keine Geldautomaten verfügbar. 100: cout << "GeldautomatClient: Es sind keine Geldautomaten
verfügbar." << endl; 101: return 1; 102: } 103: Bank_var bank = (*banks)[0]; 104: Geldautomat_var geldautomat = (*Geldautomaten)[0]; 105: cout << "GeldautomatClient: Verwende Bank \"" << bank->name() << "\" und" 106: << " Geldautomat \"" << geldautomat->name() << "\"." << endl; 107: 108: // Einige interessante Dinge ausführen. 109: 110: Konto_var Konto; 111: BankKarte_var BankKarte; 112: 113: try { 114: Konto = bank->erstelleKonto(&Kunde, "checking", 0.0); 115: } catch (const CORBA::Exception& ex) { 116: 117: // Die Funktion erstelleKonto() ist fehlgeschlagen... 118: cout << "GeldautomatClient: Kann Konto nicht erzeugen." << endl; 119: cout << ex << endl; 120: return 1; 121: } 122: 123: try { 124: 125: // Automatischen Aktualisierungsdienst für das Konto von der Bank 126: // anfordern. 127: bank->anfordernAktualisierungsDienst(Konto); 128: } catch (const CORBA::Exception& ex) { 129: 130: // Die Funktion anfordernAktualisierungsDienst() ist fehlgeschlagen... 131: cout << "GeldautomatClient: Kann Konto nicht erzeugen." << endl; 132: cout << ex << endl; 133: return 1; 134: } 135: 136: try { 137: 138: // Einige Kontostatistiken ausgeben. 139: cout << "GeldautomatClient: Neues Konto wurde eröffnet:" << endl;
140: cout << " Kontonummer: " << Konto->KontoNummer() << 141: endl; 142: cout << " Eröffnungsdatum: " << Konto->EroefffnungsDatum() << 143: endl; 144: cout << " Kontostand: " << Konto->Kontostand() << endl; 145: 146: // Bank anfordern eine BankKarte für das neu erzeugte Konto 147: // auszugeben. 148: cout << "GeldautomatClient: BankKarte wird von Bank bezogen." << endl; 149: try { 150: BankKarte = bank->ausgebenBankKarte(pin, Konto); 151: } catch (const UngueltigesKontoException&) { 152: 153: // Aus irgendeinem Grund war das Konto ungültig (dies 154: // sollte nicht vorkommen). 155: cout << "GeldautomatClient: Exception abgefangen: Ungültiges Konto" 156: << endl; 157: return 1; 158: } 159: 160: // Einige Kontotransaktionen mit dem Geldautomat 161: // durchführen. 162: cout << "GeldautomatClient: Transaktionen werden durchgeführt." << endl; 163: try { 164: cout << " Einzahlung DM 250,00..." << endl; 165: cout << " Neuer Kontostand ist DM" << geldautomat>Einzahlung(BankKarte, 166: Konto, pin, 250.00) << endl; 167: 168: // Dies wird eine Exception auslösen, da wir versuchen zu viel 169: // abzuheben. 170: cout << " Abhebung DM 500,00..." << endl; 171: cout << " Neuer Kontostand ist DM" << geldautomat>Abhebung(BankKarte, 172: Konto, pin, 500.00) << endl; 173: } catch (AuthentifizierungException&) { 174: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 175: << "Keine Authorisierung (wie erwartet)" << endl; 176: } catch (UngueltigerBetragException&) { 177: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 178: << endl;
179: } catch (UnzureichendeGeldmittelException&) { 180: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 181: "Geldmittel" << endl; 182: } 183: 184: // Einige weitere Kontotransaktionen mit dem Geldautomat 185: // durchführen. 186: cout << "GeldautomatClient: Weitere Transaktionen durchführen." << endl; 187: try { 188: cout << " Einzahlung DM 500,00..." << endl; 189: cout << " Neuer Kontostand ist DM" << 190: geldautomat->Einzahlung(BankKarte, Konto, pin, 500.00) << 191: endl; 192: 193: // Dies wird eine Exception auslösen, da wir die 194: // falsche PIN verwenden. 195: cout << " Abhebung DM 250,00 mit falscher PIN..." 196: << endl; 197: cout << " Neuer Kontostand ist DM" << geldautomat>Abhebung(BankKarte, 198: Konto, pin + 1, 250.00) << endl; 199: } catch (AuthentifizierungException&) { 200: cout << "GeldautomatClient: Exception abgefangen: Ungültige PIN oder " 201: << "Keine Authorisierung (wie erwartet)" << endl; 202: } catch (UngueltigerBetragException&) { 203: cout << "GeldautomatClient: Exception abgefangen: Ungültiger Betrag" 204: << endl; 205: } catch (UnzureichendeGeldmittelException&) { 206: cout << "GeldautomatClient: Exception abgefangen: Unzureichende " << 207: "Geldmittel" << endl; 208: } 209: 210: // Das Konto löschen. 211: try { 212: cout << " Konto wird gelöscht." << endl; 213: bank->loescheKonto(Konto); 214: 215: // Versuch das Konto erneut zu löschen, nur zum Spaß. 216: // Dies sollte zur Auslösung einer Exception führen. 217: cout << " Versuch, durch nochmaliges Löschen des Kontos eine " <<
218: "Exception auszulösen." << endl; 219: bank->loescheKonto(Konto); 220: } catch (const UngueltigesKontoException&) { 221: 222: // Sicher, daß die Exception ausgelöst wurde. 223: cout << "GeldautomatClient: Exception abgefangen: Ungültiges " << 224: "Konto (wie erwartet)" << endl; 225: } 226: } catch (const CORBA::Exception& ex) { 227: 228: // Eine Kontofunktion konnte nicht ausgeführt werden ... 229: cout << "GeldautomatClient: Fehler beim Versuch auf Konto zuzugreifen:" << endl; 230: cout << ex << endl; 231: return 1; 232: } 233: 234: // Lange genug warten, um eine oder zwei Kontoaktualisierungen 235: // abzufangen. 236: Sleep(120000); 237: 238: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 239: return 0; 240: } Damit wurden alle notwendigen Änderungen vorgenommen. Nun können Sie den geänderten Quelltext ausführen. Ausführen der Anwendung Beim Ausführen der verschiedenen Komponenten der Bankanwendung sollten Sie genauso vorgehen wie am 9. Tag, allerdings mit einer Ausnahme: Vor allem anderen müssen Sie den CORBABezeichnungsdienst starten und diesen einen Standard-Bezeichnungskontext erstellen lassen. Wenn Sie die Implementierung des Bezeichnungsdienstes von Visigenic verwenden, müssen Sie folgendes eingeben: NameExtF Für die Bankanwendung können Sie folgende Angaben verwenden: NameExtF NameServer naming.log Die Ausgabe der Bezeichnungsdienstanwendung entspricht der IOR (Interoperable Object Reference, kompatible Objektreferenz), die dem Bezeichnungsdienst zugewiesen ist. Die Ausgabe in Ihrem
System weicht von der hier gezeigten wahrscheinlich ab, sieht aber in etwa wie folgt aus: IOR:012020203700000049444c3a6f72672e6f6d672f436f734e616d696e672f457 874656e6465644e616d696e67436f6e74657874466163746f72793a312e30002002 0000000153495680000000010101201400000069767939362e626577656c6c6e657 42e636f6d005750f8ff010000005300000001504d43000000003700000049444c3a 6f72672e6f6d672f436f734e616d696e672f457874656e6465644e616d696e67436 f6e74657874466163746f72793a312e3000200b0000004e616d6553657276657200 2000000000000000006f000000010100200e0000003135332e33362e3234302e373 90002065300000001504d43000000003700000049444c3a6f72672e6f6d672f436f 734e616d696e672f457874656e6465644e616d696e67436f6e74657874466163746 f72793a312e3000200b0000004e616d6553657276657200 Jetzt können Sie die Anwendung ausführen. Gehen Sie dabei wie am 9. Tag beschrieben vor. Da heute keine Funktionen geändert wurden, erfolgt für alle Anwendungskomponenten genau die gleiche Ausgabe wie am 9. Tag.
Zusammenfassung In diesem Kapitel haben Sie viele der CORBAservices und CORBAfacilities kennengelernt, die von der OMG spezifiziert wurden. Anschließend haben Sie ermittelt, welche der CORBAservices für die in den vorherigen Kapiteln erstellte Bankanwendung sinnvoll eingesetzt werden können, und haben schließlich die Anwendung so geändert, daß einige der Funktionen verwendet werden, die der CORBA-Bezeichnungsdienst zur Verfügung stellt. Zusätzliche Informationen zu den einzelnen CORBAservices (die ich in diesem Kapitel nur sehr kurz angesprochen habe) finden Sie auf der WWW-Seite der OMG unter http://www.omg.org/. Am 13. Tag werden wir noch einmal in einen anderen Themenbereich einsteigen. Diesmal beschäftigen wir uns mit der Programmiersprache/Plattform Java. Wie Ihnen aus den bisherigen Ausführungen sicher deutlich geworden ist, eignet sich die CORBA-Architektur für viele Programmiersprachen, von denen Java nur eine ist. Dennoch handelt es sich bei Java um eine im Zusammenhang mit CORBA besonders interessante Sprache, wenn es um Merkmale wie Schnittstellen und das Entfernen nicht mehr benötigter Objekte (Garbage Collection, Speicherbereinigung) geht. Über diese Themen werden Sie später noch einiges lernen (wenn Sie nicht sowieso bereits damit vertraut sind). Das nächste Kapitel enthält eine kurze Einführung in die Programmiersprache Java und behandelt die Verwendung von Java zusammen mit CORBA. Am 14. Tag werden Sie mit Java und CORBA entwickelten Quelltext im WWW verwenden. Dabei werden Sie die inzwischen wohlbekannte Bankanwendung mit einem Applet-gestützten Front-End-Modul versehen.
Fragen & Antworten Frage:
Ich habe festgestellt, daß für [jeweilige Branche des Fragestellers] keine vertikalen CORBAfacilities angesprochen wurden. Werden wir von der OMG vernachlässigt? Antwort: Die OMG arbeitet mit Hochdruck in einer immer größer werdenden Anzahl von vertikalen Bereichen. Wenn Sie Standard-Einrichtungsschnittstellen für eine bestimmte Branche entwickeln möchten, wenden Sie sich direkt an die OMG. Frage: Was hat die vorhin erwähnte seltsame IOR zu bedeuten? Antwort: Die IOR (Interoperable Object Reference, kompatible Objektreferenz) ist eine Zeichenkette, über die jedes CORBA-Objekt eindeutig identifiziert wird. Die IOR ist so definiert, daß der ORB jedes Herstellers sie übernehmen und auflösen kann, wobei der Standort des Objekts ermittelt wird. In der Regel brauchen Sie sich mit diesem Themenbereich nicht zu befassen, aber gelegentlich ist die IOR eine praktische Funktion zum Auffinden von Objekten. Da es sich um unformatierte Zeichenketten handelt, können diese problemlos über ein Netzwerk übertragen werden, in einer Datei gespeichert werden usw. Frage: Bei Verwendung des Bezeichnungsdienstes wurde die ID »BankServer1« in den Anwendungskomponenten fest codiert. Wäre es auch möglich, daß die Anwendung den Bezeichnungsdienst statt dessen nach einem registrierten BankServer durchsucht? Antwort: Ja. Der Bezeichnungskontext unterstützt die Operation list(), die eine BindingList und einen BindingIterator zurückgibt. Diese wiederum können verwendet werden, um den Inhalt des Bezeichnungskontexts iterativ durchzugehen. Auf diese Weise können Objekte über den Namen oder den Typ gefunden werden. Es ist auch möglich, daß die Anwendung einfach das erste gefundene Objekt verwendet. Obwohl der Bezeichnungsdienst für diesen Zweck genutzt werden kann, wäre es besser, hierfür den Objektvermittlungsdienst zu verwenden, der genau für solche Fälle konzipiert wurde.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A.
Quiz 1. Wer erstellt die Spezifikationen für die CORBAservices und die CORBAfacilities? 2. Wer stellt die Implementierungen für die CORBAservices und die CORBAfacilities zur Verfügung? 3. Welche CORBAservices und/oder CORBAfacilities muß ein Hersteller, wenn überhaupt, mit einem ORB-Produkt zur Verfügung stellen, damit eine Anwendung als mit CORBA 2.0 kompatibel gilt? 4. Warum sind Einrichtungen für vertikale Märkte hilfreich? Übungen 1. Geben Sie an, wie in der Beispielbankanwendung der BankServer durch den Objektvermittlungsdienst ersetzt werden könnte. 2. Beschreiben Sie, wie der Ereignisdienst innerhalb der Bankanwendung verwendet werden könnte (Hinweis: denken Sie an die am 9. Tag hinzugefügte Funktion zur automatischen Kontoaktualisierung). Was wäre der Vorteil eines solchen Ansatzes? 3. (Für besonders Ehrgeizige:) Wenn Ihnen Produkte zur Verfügung stehen, die einen oder mehrere CORBAservices implementieren, versuchen Sie, die Funktionalität eines Dienstes Ihrer Wahl in die Beispielbankanwendung zu integrieren. (Im Abschnitt »Welche CORBAservices sollen verwendet werden?« haben wir überlegt, welche Dienste für unsere Beispielanwendung gut geeignet wären.) Da es für diese Übung zahlreiche Lösungsmöglichkeiten gibt, haben wir auf die Darstellung einer Lösung verzichtet.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 13 Entwicklung von InternetAnwendungen mit CORBA und Java Einführung in Java Die Symbiose zwischen CORBA und Java Entwicklung einer CORBA-Anwendung in Java im Überblick Zusammenfassung Fragen und Antworten Workshop
Am 12. Tag haben Sie zunächst die verschiedenen CORBAservices und CORBAfacilities kennengelernt und wissen nun, welche Funktionalität über diese Spezifikationen zur Verfügung gestellt wird. Dann haben Sie sich darangemacht, die Bankanwendung zu ändern, indem Sie den CORBABezeichnungsdienst in diese eingebunden haben. Inzwischen ist unsere Bankanwendung zu einem komplexen System geworden. Heute werden wir bei der Betrachtung der Bankanwendung hinsichtlich der Funktionalität einen Schritt zurück machen, dabei gleichzeitig aber eine ganz neue Richtung einschlagen. Wir werden die grundlegende Bankanwendung, die wir am 9. Tag erstellt haben, nach Java portieren (das ist lange nicht so schwierig, wie es sich anhört). Falls Sie noch keine Erfahrung mit Java haben, erhalten Sie in diesem Kapitel zunächst eine Einführung in diese Programmiersprache und erfahren, welche Beziehungen
zwischen Java und CORBA bestehen. Java und CORBA sind ideal füreinander geschaffen, und Sie werden in diesem Kapitel sehen, warum dies so ist.
Einführung in Java Java ist in der Computerbranche noch ein Newcomer, aber diese Verbindung aus Programmiersprache und Plattform hat inzwischen einen hohen Reife- und Nützlichkeitsgrad erreicht, sowohl im Hinblick auf die Zuverlässigkeit der Plattform selbst (zumindest in den meisten Fällen), als auch was die Verfügbarkeit von Entwicklungswerkzeugen betrifft. Seit der Einführung im Jahr 1995 (als Java aus seinem Vorgänger Oak hervorgegangen ist) ist Java sprunghaft gewachsen, und trotz einiger rechtlicher Streitereien, die zum Zeitpunkt der Erstellung dieses Buchs noch nicht ausgeräumt waren, wächst es unaufhörlich weiter. Alle, die sich mit der Programmiersprache/Plattform Java noch nicht auskennen, sollten sich die WWWSeite der JavaSoft-Division von Sun Microsystems unter http://www.javasoft.com/ ansehen oder eine der zahlreichen Publikationen durcharbeiten, die über diese Programmiersprache geschrieben wurden. Aus diesen Quellen können Sie, kurz gesagt erfahren, daß sich Java durch folgendes auszeichnet: ■
■
■
■
■
Eine objektorientierte Programmiersprache, deren Syntax an ein vereinfachtes C++ erinnert. Viele der Konzepte sind anderen Sprachen wie Smalltalk und Objective C entlehnt. Ein Compiler, der den Java-Quelltext in ein plattformneutrales Format umsetzt, der als »Bytecode« bezeichnet wird. Die Bytecode-Dateien, die auch als Klassendateien bezeichnet werden, können auf die verschiedensten Rechner verteilt und von der Java-Laufzeitumgebung ausgeführt (siehe auch den nächsten Punkt) werden. Eine Laufzeitumgebung, mit deren Hilfe Java-Anwendungen von der zugrundeliegenden Hardware und dem Betriebssystem abgekoppelt werden. Vor allem diesem Aspekt verdanken die Java-Anwendungen ihre plattformübergreifende Portabilität. Eine Gruppe von Klassenbibliotheken, die eine Vielzahl von Funktionen umfassen; diese reichen von Datei- und Socket-E/A-Funktionen über Multimedia- und 3D-Funktionen bis hin zu Objekten für graphische Benutzeroberflächen. Die Möglichkeit, daß die Anwendungen innerhalb eines WWW-Browsers als Applet ausgeführt werden können. Unter einem Applet versteht man eine Anwendung, die nicht nur (etwa als Komponente einer WWW-Seite) in einem WWW-Browser ausgeführt werden kann, sondern auch verschiedenen sicherheitsbezogenen Einschränkungen unterliegt. Sicherheit ist als Leistungsmerkmal in die Programmiersprache Java integriert; sie macht diese Einschränkungen möglich und bewirkt, daß Java für Internet-Anwendungen besonders gut geeignet ist.
Mit Java besteht die Möglichkeit, daß das Betriebssystem nicht mehr ganz so sehr im Mittelpunkt steht, denn die Sprache stellt eine konsistente API für alle Plattformen zur Verfügung. Ob diese Möglichkeiten jemals voll ausgeschöpft werden können, bleibt abzuwarten. Natürlich ist Java nicht perfekt und muß besonders im Hinblick auf die Leistung, die Portabilität und natürlich die politischen Widerstände noch einige Herausforderungen bestehen. Momentan sieht es allerdings so aus, als ob sich Java auf Dauer etablieren kann, und so wie es sich gezeigt hat, ist Java zur Entwicklung von CORBA-Anwendungen sehr gut geeignet.
Die Symbiose zwischen CORBA und Java Warum passen CORBA und Java so gut zueinander? Wie kommt das zustande? Was hat die CORBAArchitektur für die Sprache/Plattform Java zu bieten und umgekehrt? Welche Symbiose besteht zwischen den beiden? In diesem Abschnitt möchte ich einige der Faktoren behandeln, die das Entwickeln mit CORBA und Java so attraktiv machen. Ähnlichkeiten in der Architektur Java-Programmierer empfinden das Entwickeln mit CORBA als erstaunlich natürlich und leicht erlernbar; fast scheint es, als wären beide füreinander konzipiert worden. (Natürlich ist dies nicht der Fall, denn Java und CORBA haben ganz unterschiedliche Konzeptionsziele.) Es sind die Ähnlichkeiten in der Architektur, die den Eindruck entstehen lassen, daß Java und CORBA nahezu nahtlos ineinandergreifen: ■
■
■
■
Das Konstrukt module aus CORBA, mit dem Schnittstellen gruppiert werden, die zusammengehörende Aufgaben ausführen, entspricht dem Konstrukt package aus Java. Das in Java verwendete Speicherverwaltungsmodell mit Entfernung nicht mehr benötigter Objekte aus dem Speicher (Garbage Collection) erleichtert das Entwickeln von Anwendungen im allgemeinen, und die Entwicklung von CORBA-Anwendungen stellt hierbei keine Ausnahme dar. Solche netten Kleinigkeiten wie die Tatsache, daß keine Zeichenketten verwaltet werden müssen, die von einer CORBA-Schnittstelle zur anderen übergeben werden, machen dem CORBA-Entwickler das Leben ein klein wenig leichter. Das Modell der Exceptions in Java funktioniert mit CORBA gut. Im Gegensatz zu C++ müssen Methoden in Java Exceptions behandeln oder diese explizit auslösen (und dies in der Methodensignatur deklarieren). Aus diesem Grund können CORBA-Exceptions einfacher verwendet werden, und der Programmierer braucht sich keine Sorgen zu machen, daß eventuell Exceptions »durch den Rost fallen«. Der letzte, aber vielleicht praktischste Aspekt ist die fast direkte Entsprechung zwischen der Verwendung der Schnittstellen in Java und in CORBA. Das Abbilden von CORBASchnittstellen auf C++-Klassen kann manchmal ganz schön haarig werden; das Abbilden auf Java-Schnittstellen ist viel einfacher und sauberer möglich.
CORBA oder RMI von Java? Denjenigen, die sich mit Java bereits auskennen, ist RMI (Remote Methode Interface, RemoteMethodenschnittstelle) bereits bekannt. Die von RMI zur Verfügung gestellte Funktionalität ist der von CORBA sehr ähnlich, in der Tat haben CORBA und RMI beide das Ziel, die Entwicklung von verteilten, objektorientierten Anwendungen zu ermöglichen. Abgesehen von einigen oberflächlichen Ähnlichkeiten in der Architektur unterscheiden sich CORBA und RMI in vielen Belangen und sind daher für unterschiedliche Zwecke geeignet. Ein Hauptunterschied zwischen CORBA und RMI besteht darin, daß CORBA sprachenunabhängig ist, RMI jedoch auf Java beschränkt ist. Dies ist bei der Wahl der geeigneten Architektur, auch für Anwendungen, die direkt in Java implementiert werden, ein wichtiger Aspekt. Es ist zwar möglich, daß eine solche Anwendung RMI verwenden kann, der Anwendungsentwickler sollte allerdings
berücksichtigen, daß das System in der Zukunft unter Umständen mit anderen Anwendungen zusammenwirken soll, die nicht in Java programmiert wurden. Sun hat allerdings angegeben, daß spätere Versionen von RMI mit dem IIOP (dem CORBA-Protokoll für die Internet-Kommunikation zwischen ORBs) kompatibel sein werden, so daß die Kompatibilität zwischen RMI und CORBA kein fernes Ziel mehr ist. Im Hinblick auf die Robustheit ist CORBA RMI überlegen. CORBA ist nicht nur bereits einige Jahre länger im Einsatz, sondern bietet auch mehr fertige Lösungen für den Einsatz der Anwendungen im Unternehmen. Während RMI aus einer Kommunikationsschicht und einfachen Bezeichnungsdiensten besteht, ist CORBA Teil einer Architektur, die weitaus mehr Dienste bietet, die bei der Entwicklung von professionellen Anwendungen wertvoll sein können. (Diese von der OMA zur Verfügung gestellten Dienste und Systemeinrichtungen habe ich in Kapitel 12 behandelt.) CORBA bietet Funktionen wie hierarchische Bezeichnungsdienste, Einrichtungen zur Transaktionsverwaltung, Ereignisdienste und eine Vielzahl von Einrichtungen für vertikale Märkte und ist damit hinsichtlich der Robustheit gegenüber RMI klar im Vorteil. CORBA hält jedoch nicht alle Trümpfe in der Hand, denn RMI hat derzeit zumindest in einem Punkt einen deutlichen Vorteil gegenüber der CORBA-Architektur. Da es sich bei RMI um eine reine JavaTechnologie handelt, können die Vorteile der Java-Plattform voll ausgeschöpft werden. Am bemerkenswertesten hierbei ist die nahtlose Integration von RMI mit der Java-Technologie zur Objektserialisierung, die das Übergeben von Objekten durch Werte (passing-by-value) und auch durch Referenzen (passing-by-reference) zwischen Remote-Komponenten ermöglicht. Ferner ist nicht nur die Übergabe von Objekten durch Werte möglich, sondern es können aufgrund der Verwendung eines plattformunabhängigen Bytecodes auch neue Klassen mit RMI durch das Netzwerk gesendet und dann von Client- oder Server-Komponenten genutzt werden. Die Möglichkeit, während der Laufzeit neue Klassen dynamisch in ein System einzuführen, eröffnet ein gewaltiges Potential für neue Arten von Anwendungen. Weitere wichtige Faktoren Es gibt auch noch weitere Faktoren, die eine enge Verbindung zwischen Java und CORBA als sehr sinnvoll erscheinen lassen. Vielleicht ist eines der attraktivsten Merkmale von Java dessen Portabilität, also die Möglichkeit, auf unterschiedlichen Computern und unter verschiedenen Betriebssystemen ausgeführt zu werden. Einer der Faktoren, die für eine verteilte Anwendung wichtig sind, ist die Notwendigkeit, diese auf einer Vielzahl unterschiedlicher Hardware- und Betriebssystemplattformen auszuführen. Auch ist es wahrscheinlicher, daß auf der Client-Seite der Anwendung die Vielfalt größer ist, wenn diverse Typen von Desktop-Computern im Spiel sind, angefangen von Netzcomputern mit geringer Leistung über PCs der mittleren Klasse bis hin zu UNIX-Hochleistungs-Workstations. Sicher ist die Möglichkeit, Client-Anwendungen auf allen potentiellen Endbenutzerplattformen zur Verfügung zu stellen, für die Entwickler professioneller Anwendungen ein Segen. Hier bietet Java für CORBA die Möglichkeit, eine Anwendung für die Client-Seite einmal zu schreiben und dann auf einer Vielzahl von Plattformen auszuführen. (In dem Maß, in dem Java weitere Fortschritte hinsichtlich der Leistung und Robustheit macht, wird es bei verteilten Anwendungen auch auf der Server-Seite stärker verwendet werden, aber das ist ein ganz anderes Thema.) Nicht nur CORBA profitiert von Java, weil dieses für plattformübergreifende Client-Anwendungen
verwendet wird, sondern Java profitiert auch von CORBA. Der Nutzen für Java besteht darin, daß durch CORBA eine sprachenübergreifende Kompatibilität ermöglicht wird. Wie bereits erwähnt, funktioniert die RMI-Einrichtung von Java nur zwischen Java-Anwendungen. Aus diesem Grund müssen die Entwickler zur Kommunikation mit nicht in Java programmierten Anwendungen eigene Kommunikationsschichten implementieren, wobei maschinennahe Netzwerk-Sockets ins Spiel kommen. Mit CORBA sind diese nicht erforderlich, da diese Architektur eine objektorientierte Abstraktion bietet, die es den Java-Anwendungen ermöglicht, mit Anwendungen zu kommunizieren, die in fast jeder beliebigen anderen Sprache geschrieben wurden.
Entwicklung einer CORBA-Anwendung in Java im Überblick Das Entwickeln einer CORBA-Anwendung in Java entspricht in vieler Hinsicht dem Entwickeln derselben Anwendung in C++. Es wird auch in diesem Fall ein IDL-Compiler benötigt, um die IDLDefinitionen in Server-Skeletons und Client-Stubs umzusetzen. Der Vorgang ist an sich gleich, der einzige Unterschied besteht darin, daß der Compiler Java-Code generiert und keinen C++-Code. Zum Entwickeln der Java-Version unserer Bankanwendung, die in diesem Kapitel erstellt werden soll, benötigen Sie einige zusätzliche Werkzeuge: ■
■
■
Ein CORBA-Produkt, das Java unterstützt. Für das Beispiel in diesem (und im nächsten) Kapitel wird VisiBroker for Java von Visigenic Software verwendet, Sie können aber auch jedes andere CORBA-Produkt wie Java IDL von Sun oder OrbixWeb von IONA Technologies verwenden; dabei treten keine oder nur geringe Änderungen am Code auf. Ein Java-Entwicklungswerkzeug. Hierbei kann es sich um eine IDE (Integrated Development Environment, integrierte Entwicklungsumgebung) wie Visual J++ von Microsoft oder Café bzw. Visual Café von Symantec oder aber um JDK (Java Development Kit) von Sun handeln. Je nach verwendetem CORBA-Produkt benötigen Sie ein Werkzeug, das JDK 1.1 oder höher unterstützt, aber es ist gut möglich, daß JDK 1.0.2 ausreicht. Einen (oder mehrere) Computer, auf denen diese Produkte ausgeführt werden können. Wenn Sie über die genannten Produkte verfügen, kann man wahrscheinlich davon ausgehen, daß Sie auch die entsprechenden Computer haben.
Wenn alle genannten Elemente zur Verfügung stehen, können Sie mit dem Entwickeln in Java beginnen. Verwendung des IDL-Compilers Wie bereits erwähnt, ist die Verwendung des IDL-Compilers beim Schreiben einer CORBAAnwendung in C++ oder in Java identisch. Der Befehl zum Ausführen des IDL-Compilers kann allerdings jeweils unterschiedlich sein. Für den mit VisiBroker for Java gelieferten Compiler müssen Sie den folgenden Befehl eingeben: idl2java -package idlGlobal Dateiname
Bevor Sie mit dem Java-IDL-Compiler arbeiten, sollten Sie das Produkt nach den Herstelleranweisungen installiert haben. Insbesondere müssen Sie die Umgebungsvariablen PATH und CLASSPATH gesetzt haben. So muß beispielsweise bei VisiBroker der Eintrag VisiBrokerdirectory/vbj30.jar zur Variablen CLASSPATH hinzugefügt werden. Wenn bei der Verwendung des IDL-Compilers Fehler auftreten, schlagen Sie in der zugehörigen Dokumentation nach.
Dabei ist Dateiname der Name der zu kompilierenden IDL-Datei. Über den Schalter -package idlGlobal wird der IDL-Compiler angewiesen, den generierten Code in ein Verzeichnis namens idlGlobal zu stellen, und die generierten Java-Klassen werden in das Package idlGlobal gestellt. Der IDL-Compiler von VisiBroker generiert den Code für Server und Client-Komponenten. Jetzt bereiten wir die Entwicklung der Java-Bankanwendung vor. Hierzu müssen Sie die IDLQuelldateien, die Sie am 9. Tag erstellt haben, in ein neues Verzeichnis kopieren und diese anschließend mit dem Java-IDL-Compiler kompilieren. Diesen Schritt können Sie zweckmäßigerweise über eine Stapeldatei, eine Shell-Scriptdatei oder mit dem Befehl Makefile automatisieren. Am Ende dieses Arbeitsschritts ist ein Verzeichnis namens idlGlobal vorhanden, in dem einige Dateien mit der Erweiterung .java enthalten sind. Jetzt werden wir mit der Entwicklung fortfahren, wobei als erstes die Server-Funktionen an der Reihe sind. Implementierung der Server-Funktionen Da Sie keinerlei Änderungen an der Bankanwendung vorgenommen haben, wenn man von der Verwendung einer anderen Entwicklungssprache absieht, ist die Architektur gegenüber den vorherigen Kapiteln unverändert geblieben. Aus diesem Grund werden Ihnen die einzelnen Anwendungskomponenten inzwischen sehr bekannt vorkommen. BankServer Der BankServer ist nun in den beiden Dateien BankServerImpl.java und BankServerHaupt. java implementiert, die in den Listings 13.1 bzw. 13.2 gezeigt sind. Ferner wurde die Datei CORBAAlgorithms. java generiert, die einige Dienstprogramm-Methoden enthält und in Listing 13.3 gezeigt ist. Listing 13.1: BankServerImpl.java. 1: 2: 3: 4: 5: 6: 7:
// BankServerImpl.java import java.util.Enumeration; import java.util.Vector; import idlGlobal.Geldautomat; import idlGlobal.Bank;
8: 9: import idlGlobal.UngueltigerGeldautomatException; 10: import idlGlobal.UngueltigeBankException; 11: 12: import util.CORBAAlgorithms; 13: 14: public class BankServerImpl extends idlGlobal._BankServerImplBase { 15: 16: // Die Bankenliste dieses BankServers. 17: private Vector meineBanken; 18: 19: // Liste der Geldautomaten dieses BankServers. 20: private Vector meineGeldautomaten; 21: 22: public BankServerImpl(String name) { 23: 24: super(name); 25: 26: meineBanken = new Vector(); 27: meineGeldautomaten = new Vector(); 28: } 29: 30: public void registriereBank(Bank bank) throws UngueltigeBankException 31: { 32: 33: // Sicherstellen, daß angegebene Bank nicht bereits vorhanden ist. 34: if (!CORBAAlgorithms.contains(meineBanken, bank)) { 35: 36: // Bank nicht gefunden, daher angegebene Bank an das 37: // Listenende einfügen. 38: System.out.println("BankServerImpl: Registriere Bank " 39: + "\"" + bank.name() + "\"."); 40: meineBanken.addElement(bank._duplicate()); 41: return; 42: } 43: 44: // Bank ist bereits registriert, daher eine Exception auslösen. 45: System.out.println("BankServerImpl: Versuch eine registrierte Bank " + 46: "erneut zu registrieren."); 47: throw new UngueltigeBankException(); 48: } 49: 50: public void aufhebenRegistrierungBank(Bank bank) throws
51: UngueltigeBankException { 52: 53: if (!CORBAAlgorithms.contains(meineBanken, bank)) { 54: 55: // Ungültige Bank; eine Exception auslösen. 56: System.out.println("BankServerImpl: Versuch die Registrierung einer " + 57: "ungültigen Bank aufzuheben."); 58: throw new UngueltigeBankException(); 59: } 60: 61: System.out.println("BankServerImpl: UnRegistriereBank \"" 62: + bank.name() + "\"."); 63: 64: // Angegebene Bank löschen. 65: CORBAAlgorithms.removeElement(meineBanken, bank); 66: bank._release(); 67: } 68: 69: public Bank[] getBanken() { 70: 71: Bank[] list = new Bank[meineBanken.size()]; 72: meineBanken.copyInto(list); 73: 74: Enumeration e = meineBanken.elements(); 75: while (e.hasMoreElements()) { 76: ((Bank)e.nextElement())._duplicate(); 77: } 78: 79: return list; 80: } 81: 82: public void registriereGeldautomat(Geldautomat geldautomat) throws UngueltigerGeldautomatException { 83: 84: // Sicherstellen, daß angegebener Geldautomat nicht bereits vorhanden ist. 85: if (!CORBAAlgorithms.contains(meineGeldautomaten, geldautomat)) { 86: 87: // Geldautomat nicht gefunden, daher angegebenen Geldautomat an das 88: // Listenende einfügen. 89: System.out.println("BankServerImpl: RegistriereGeldautomat " 90: + "\"" + geldautomat.name() + "\"."); 91: meineGeldautomaten.addElement(geldautomat._duplicate()); 92: return; 93: } 94:
95: // Geldautomat ist bereits registriert, daher eine Exception auslösen. 96: System.out.println("BankServerImpl: Versuch bereits vorhandenen Geldautomat zu " + 97: "registrieren."); 98: throw new UngueltigerGeldautomatException(); 99: } 100: 101: public void aufhebenRegistrierungGeldautomat(Geldautomat geldautomat) throws UngueltigerGeldautomatException { 102: 103: 104: if (!CORBAAlgorithms.contains(meineGeldautomaten, geldautomat)) { 105: 106: // Ungültiger Geldautomat; eine Exception auslösen. 107: System.out.println("BankServerImpl: Versuch die Registrierung " + 108: "ungültiger Geldautomat aufzuheben."); 109: throw new UngueltigerGeldautomatException(); 110: } 111: 112: System.out.println("BankServerImpl: UnRegistriereGeldautomat \"" 113: + geldautomat.name() + "\"."); 114: 115: // Angegebenen Geldautomat löschen. 116: CORBAAlgorithms.removeElement(meineGeldautomaten, geldautomat); 117: geldautomat._release(); 118: } 119: 120: public Geldautomat[] getGeldautomaten() { 121: 122: GELDAUTOMAT[] list = new GELDAUTOMAT[meineGeldautomaten.size()]; 123: meineGeldautomaten.copyInto(list); 124: 125: Enumeration e = meineGeldautomaten.elements(); 126: while (e.hasMoreElements()) { 127: ((GELDAUTOMAT)e.nextElement())._duplicate(); 128: } 129: 130: return list; 131: } 132: } Listing 13.2: BankServerHaupt.java. 1: // BankServerHaupt.java
2: 3: import org.omg.CORBA.BOA; 4: import org.omg.CORBA.ORB; 5: 6: public class BankServerHaupt { 7: 8: public static ORB meinORB; 9: public static BOA meinBOA; 10: 11: public static void main(String[] args) { 12: 13: try { 14: 15: // ORB und BOA initialisieren. 16: meinORB = ORB.init(args, null); 17: meinBOA = meinORB.BOA_init(); 18: 19: // BankServerImpl-Objekt erzeugen und mit ORB 20: // registrieren 21: BankServerImpl bankServer = new 22: BankServerImpl("BankServer"); 23: meinBOA.obj_is_ready(bankServer); 24: 25: // Auf CORBA-Ereignisse warten. 26: System.out.println("BankServer bereit."); 27: meinBOA.impl_is_ready(); 28: } catch (Exception ex) { 29: 30: // Etwas ist fehlgeschlagen... 31: System.out.println("BankServerImpl: Konnte Bindung mit " + 32: "BankServer nicht herstellen:"); 33: System.out.println(ex); 34: return; 35: } 36: 37: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 38: return; 39: } 40: } Listing 13.3: CORBAAlgorithms.java. 1: // CORBAAlgorithms.java 2: 3: package util; 4: 5: import java.util.Enumeration;
6: import java.util.Vector; 7: 8: public class CORBAAlgorithms { 9: 10: // True zurückgeben, wenn der angegebene Vector das angegebene CORBA-Objekt 11: // enthält, d.h., wenn ein Element im Vektor vorhanden ist, 12: // für das obj._is_equivalent() True ergibt. 13: public static boolean contains(Vector vector, org.omg.CORBA. 14: Object obj) { 15: 16: Enumeration e = vector.elements(); 17: 18: while (e.hasMoreElements()) { 19: if (obj._is_equivalent((org.omg.CORBA.Object)e. 20: nextElement())) { 21: 22: // Eine Übereinstimmung wurde gefunden. 23: return true; 24: } 25: } 26: 27: // Keine Übereinstimmung gefunden. 28: return false; 29: } 30: 31: // Das erste Element aus dem angegebenen Vektor entfernen, für 32: // das obj._is_equivalent() True ergibt. Wird kein solches 33: // Element gefunden, führt diese Methode nichts aus. 34: public static void removeElement(Vector vector, org.omg.CORBA. 35: Object obj) { 36: 37: Enumeration e = vector.elements(); 38: 39: while (e.hasMoreElements()) { 40: org.omg.CORBA.Object cobj = (org.omg.CORBA.Object)e. 41: nextElement(); 42: if (obj._is_equivalent(cobj)) { 43: 44: // Übereinstimmung gefunden; Element entfernen. 45: vector.removeElement(cobj); 46: return; 47: } 48: } 49: } 50: } Die Logik in der Implementierung der BankServer-Komponente ist mit der für C++ identisch. Im
folgenden möchte ich Sie auf einige Besonderheiten der Java-Implementierung hinweisen. Die Anweisung import in Java, die in den Zeilen 3-12 von Listing 13.1 zu sehen ist, ist ein entfernter Verwandter der Präprozessor-Direktive #include in C++. Die Direktive #include dient zum Importieren von Informationen über andere Klassen in eine Quelldatei, die Anweisung import aus Java dient demselben Zweck. Beachten Sie ferner, daß bei der Anweisung import immer der vollständige Klassenname der Klasse, also der Package-Name der Klasse und der eigentliche Klassenname selbst angegeben wird. So wird z.B. angegeben, daß sich die Klasse Enumeration aus den vorherigen Listings im Package java.util befindet. Beachten Sie nun die Verwendung von import für die im Package idlGlobal befindlichen Klassen (Zeilen 6 und 7 in Listing 13.1). Hierbei handelt es sich um die Klassen, die im vorigen Schritt vom IDLCompiler generiert wurden. Sehen Sie sich nun die class-Deklaration in Zeile 14 an. In der Java-Sprachabbildung für IDL wird eine bestimmte Basisklasse durch die Implementierungsklassen für CORBA-Schnittstellen erweitert. Bei der Klasse BankServerImpl ist dies die Basisklasse idlGlobal._BankServerImplBase. Bei Vector (oder genauer gesagt java.util.Vector) handelt es sich um eine integrierte Java-Klasse, welche die Semantik eines größenveränderlichen Arrays zur Verfügung stellt. Für BankServerImpl werden solche Vektor-Elemente verwendet, um die Daten für die Objekte vom Typ Bank und GELDAUTOMAT zu speichern, die beim BankServer registriert werden (siehe Zeilen 16-20). Beachten Sie, daß im Konstruktor für BankServerImpl (in den Zeilen 22-28) ein Aufruf von super() erfolgt. Dahinter verbirgt sich ein spezieller Methodenname, der einen Konstruktor in der übergeordneten Klasse (in diesem Fall _BankServerImplBase) bezeichnet, der das bzw. die angegebenen Argumente akzeptiert. Über den Konstruktor BankServerImpl werden die beiden zuvor deklarierten Vektoren erstellt. Der Rest der BankServerImpl-Implementierung erklärt sich eigentlich von selbst. Daher wollen wir uns nun BankServerHaupt.java ansehen. Mit dieser Komponente wird ein Objekt vom Typ BankServerImpl erstellt und in der CORBA-Umgebung registriert. Wenden wir uns nun dem Listing 13.2 zu. Beachten Sie nun zunächst die import-Anweisungen in den Zeilen 3 und 4. Sie dienen zum Importieren zweier wichtiger Klassen in einer CORBA-Anwendung: dem BOA (Basic Object Adapter) und dem ORB (Object Request Broker). Wir werden gleich noch sehen, wozu sie benötigt werden. Wahrscheinlich dürfte es jetzt nichts Neues mehr sein, daß die statischen Klassenelemente in Java (wie die in den Zeilen 8 und 9 enthaltenen) sich genauso verhalten wie static-Datenelemente in C++: Sie stehen unabhängig von einer bestimmten Instanz der betreffenden Klasse zur Verfügung. In jeder Java-Anwendung muß die Methode public static void main() zumindest in einer Klasse enthalten sein (tatsächlich kann die Methode main() in jeder Klasse vorhanden sein, aber nur mit einer davon kann die Anwendung jeweils aufgerufen werden). Parameter für diese Methode ist immer String[] args (oder das semantische Äquivalent String args[]). Die Methode main() von
BankServerHaupt beginnt in Zeile 11. Bevor CORBA-Objekte erstellt werden können, müssen der ORB und der BOA initialisiert werden. Dieser Vorgang ist in den Zeilen 13-17 enthalten. Dann wird das Objekt BankServerImpl erstellt und beim BOA registriert. Hierzu wird die in den Zeilen 19-23 enthaltene Methode obj_is_ready() verwendet. Mit der in Zeile 27 aufgerufenen Methode impl_is_ready() wird die CORBA-Ereignisschleife begonnen, mit der ankommende CORBA-Ereignisse an die betreffenden Objekte übergeben werden. Schließlich wurde in den Zeilen 28-35 eine catch-Behandlungsroutine hinzugefügt, für den Fall, daß von einer der aufgerufenen Methoden eine Exception ausgelöst wird. Der try...catch-Mechanismus von Java ist mit dem von C++ identisch. Die Klasse CORBAAlgorithms.java ist in Listing 13.3 gezeigt und enthält eine Reihe nützlicher Methoden. Beachten Sie die Verwendung der Anweisung package in Zeile 3. Sie gibt an, daß die Klasse CORBAAlgorithms zum Package util gehört. Beachten Sie ferner, daß die Methode contains(), die in Zeile 10 beginnt, als Methode vom Typ static deklariert ist. Solche Methoden funktionieren genauso wie in C++, d.h. sie sind unabhängig von jeglichen Objektinstanzen vorhanden und werden als freistehende Funktionen aufgerufen, und nicht als Methoden für Objekte. Wie in den Zeilen 16-29 zu sehen ist, führt diese Methode eine Iteration durch das Vektor-Element durch, wobei sie nach einem Objekt sucht, für das _is_equivalent() (eine Standard-CORBA-Methode) den Wert true zurückgibt. Die nächste Methode, removeElement(), arbeitet genauso. Bedenken Sie, daß die Methode _is_equivalent() den Wert true liefert, wenn bekannt ist, daß sich zwei Objektreferenzen auf dasselbe Objekt beziehen. Es ist jedoch auch möglich, daß _is_equivalent() den Wert false zurückgibt, selbst wenn die beiden Referenzen eigentlich identisch sind. Es ist nur garantiert, daß diese Methode niemals true liefert, wenn die Referenzen nicht identisch sind. Verwenden Sie daher diese Methode mit einem gewissen Argwohn.
Bank Die Listings 13.4 bis 13.10 enthalten die Definitionen der Java-Klassen, mit denen die Funktionalität der Anwendung Bank implementiert wird. Sie werden feststellen, daß die von diesen Klassen zur Verfügung gestellten Funktionen Spiegelbilder ihrer Gegenstücke aus den vorhergehenden Kapiteln
sind. Listing 13.4: KontoImpl.java. 1: // KontoImpl.java 2: 3: import java.util.Enumeration; 4: import java.util.Vector; 5: 6: import idlGlobal.Kunde; 7: 8: import idlGlobal.UnzureichendeGeldmittelException; 9: import idlGlobal.UngueltigerBetragException; 10: 11: public class KontoImpl extends idlGlobal._AccountImplBase { 12: 13: // Die Nummer dieses Kontos. 14: private String meineKontoNummer; 15: 16: // Eröffnungsdatum dieses Kontos. 17: private String meinEroeffnungsDatum; 18: 19: // Kontostand dieses Kontos. 20: private float meinKontoStand; 21: 22: // Inhaberliste dieses Kontos (es handelt sich hierbei um Kunden). 23: private Vector meineInhaber; 24: 25: public KontoImpl(String KontoNummer, String EroefffnungsDatum, 26: float anfangsSaldo, Kunde Kunde) { 27: 28: super(KontoNummer); 29: 30: meineKontoNummer = new String(KontoNummer); 31: meinEroeffnungsDatum = new String(EroefffnungsDatum); 32: meinKontoStand = anfangsSaldo; 33: meineInhaber = new Vector(); 34: meineInhaber.addElement(Kunde._duplicate()); 35: } 36: 37: protected KontoImpl() { 38: 39: meineKontoNummer = new String(); 40: meinEroeffnungsDatum = new String(); 41: meinKontoStand = 0.0f; 42: meineInhaber = new Vector();
43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: " + 80: 81: 82: 83: 84: 85: 86: 87: 88: des
} public String KontoNummer() { return meineKontoNummer; } public String EroefffnungsDatum() { return meinEroeffnungsDatum; } public float Kontostand() { return meinKontoStand; } 60: public Kunde[] getKunden() { Kunde[] list = new Kunde[meineInhaber.size()]; meineInhaber.copyInto(list); Enumeration e = meineInhaber.elements(); while (e.hasMoreElements()) { ((Kunde)e.nextElement())._duplicate(); } return list; } public float Abhebung(float Betrag) throws UngueltigerBetragException, UnzureichendeGeldmittelException { // Das Abheben von negativen Beträgen nicht zulassen und // eine Exception auslösen, falls dies versucht wird. if (Betrag < 0.0) { System.out.println("KontoImpl: Es wurde versucht einen ungültigen "Betrag abzuheben."); throw new UngueltigerBetragException(); } // Abheben von Beträgen die das aktuelle Guthaben überschreiten // nichtzulassen, und eine Exception auslösen, wenn // dies versucht wird. if (Betrag > meinKontoStand) { System.out.println("KontoImpl: Vorhandene Geldmittel für Abhebung angegebenen "
89: + " Betrags nicht ausreichend."); 90: throw new UnzureichendeGeldmittelException(); 91: } 92: 93: meinKontoStand -= Betrag; 94: 95: return meinKontoStand; 96: } 97: 98: public float Einzahlung(float Betrag) throws 99: UngueltigerBetragException { 100: 101: // Einzahlung negativer Beträge nicht zulassen und eine 102: // Exception auslösen, falls dies versucht wird. 103: if (Betrag < 0.0) { 104: System.out.println("KontoImpl: Es wurde verucht, einen " 105: + "ungültigen Betrag einzuzahlen."); 106: throw new UngueltigerBetragException(); 107: } 108: 109: meinKontoStand += Betrag; 110: 111: return meinKontoStand; 112: } 113: } Listing 13.5: BankKarteImpl.java. 1: // BankKarteImpl.java 2: 3: import java.util.Enumeration; 4: import java.util.Vector; 5: 6: import idlGlobal.Konto; 7: 8: import idlGlobal.UngueltigesKontoException; 9: 10: import util.CORBAAlgorithms; 11: 12: public class BankKarteImpl extends idlGlobal._GeldautomatCardImplBase { 13: 14: // Die Geheimzahl (PIN) dieser BankKarte. 15: private short meinePIN; 16: 17: // Die Konten die authorisiert sind diese BankKarte zu verwenden. 18: private Vector meineKonten;
19: 20: 21: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66:
public BankKarteImpl(short pin, Konto anfaenglichesKonto) { 22: meinePIN = pin; meineKonten = new Vector(); meineKonten.addElement(anfaenglichesKonto._duplicate()); } private BankKarteImpl() { meinePIN = 0; meineKonten = new Vector(); } public void pin(short pin) { meinePIN = pin; } public short pin() { return meinePIN; } public Konto[] getKonten() { Konto[] list = new Konto[meineKonten.size()]; meineKonten.copyInto(list); Enumeration e = meineKonten.elements(); while (e.hasMoreElements()) { ((Konto)e.nextElement())._duplicate(); } return list; } public void hinzufuegenKonto(Konto Konto) throws UngueltigesKontoException { if (istAuthentifiziert(Konto)) { // Konto wurde bereits hinzugefügt, daher // eine Exception auslösen. throw new UngueltigesKontoException(); } // Das erzeugte Konto an das Listenende einfügen.
67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
meineKonten.addElement(Konto._duplicate()); } public void entfernenKonto(Konto Konto) throws UngueltigesKontoException { if (!istAuthentifiziert(Konto)) { // Ungültiges Konto; Exception auslösen. throw new UngueltigesKontoException(); } // Angegebenes Konto löschen. CORBAAlgorithms.removeElement(meineKonten, Konto); Konto._release(); } public boolean istAuthentifiziert(Konto Konto) { return CORBAAlgorithms.contains(meineKonten, Konto); } }
Listing 13.6: GiroKontoImpl.java. 1: // GiroKontoImpl.java 2: 3: import idlGlobal.Kunde; 4: 5: import idlGlobal.UnzureichendeGeldmittelException; 6: import idlGlobal.UngueltigerBetragException; 7: 8: public class GiroKontoImpl extends idlGlobal. 9: _CheckingAccountImplBase { 10: 11: // KontoImpl an das die meisten Funktionen dieses GiroKontoImpl 12: // delegiert werden. 13: private KontoImpl meinKonto; 14: 15: public GiroKontoImpl(String KontoNummer, String 16: EroefffnungsDatum, float anfangsSaldo, Kunde Kunde) 17: { 18: 19: meinKonto = new KontoImpl(KontoNummer, EroefffnungsDatum, 20: anfangsSaldo, Kunde); 21: } 22:
23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58:
protected GiroKontoImpl() { } public String KontoNummer() { return meinKonto.KontoNummer(); } public String EroefffnungsDatum() { return meinKonto.EroefffnungsDatum(); } public float Kontostand() { return meinKonto.Kontostand(); } public Kunde[] getKunden() { return meinKonto.getKunden(); } public float Abhebung(float Betrag) throws UngueltigerBetragException, UnzureichendeGeldmittelException { return meinKonto.Abhebung(Betrag); } public float Einzahlung(float Betrag) throws UngueltigerBetragException { return meinKonto.Einzahlung(Betrag); } }
Listing 13.7: SparKontoImpl.java. 1: 2: 3: 4: 5: 6: 7: 8:
// SparKontoImpl.java import idlGlobal.Kunde; import idlGlobal.UnzureichendeGeldmittelException; import idlGlobal.UngueltigerBetragException; public class SparKontoImpl extends idlGlobal.
9: _SavingsAccountImplBase { 10: 11: // KontoImpl an das die meisten Funktionen dieses SparKontoImpl 12: // delegiert werden. 13: private KontoImpl meinKonto; 14: 15: // Der Zinssatz für dieses Konto. 16: private float meinZinsSatz; 17: 18: public SparKontoImpl(String KontoNummer, String 19: EroefffnungsDatum, float anfangsSaldo, Kunde Kunde, 20: float ZinsSatz) { 21: 22: meinKonto = new KontoImpl(KontoNummer, EroefffnungsDatum, 23: anfangsSaldo, Kunde); 24: meinZinsSatz = ZinsSatz; 25: } 26: 27: protected SparKontoImpl() { 28: 29: } 30: 31: public String KontoNummer() { 32: 33: return meinKonto.KontoNummer(); 34: } 35: 36: public String EroefffnungsDatum() { 37: 38: return meinKonto.EroefffnungsDatum(); 39: } 40: 41: public float Kontostand() { 42: 43: return meinKonto.Kontostand(); 44: } 45: 46: public Kunde[] getKunden() { 47: 48: return meinKonto.getKunden(); 49: } 50: 51: public float Abhebung(float Betrag) throws 52: UngueltigerBetragException, UnzureichendeGeldmittelException { 53: 54: return meinKonto.Abhebung(Betrag); 55: }
56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
public float Einzahlung(float Betrag) throws UngueltigerBetragException { return meinKonto.Einzahlung(Betrag); } public float ZinsSatz() { return meinZinsSatz; } public float setInterestRate(float rate) throws UngueltigerBetragException { // Das Abheben von negativen Beträgen nicht zulassen und // eine Exception auslösen, falls dies versucht wird. if (rate < 0.0) { throw new UngueltigerBetragException(); } float oldInterestRate = meinZinsSatz; meinZinsSatz = rate; return oldInterestRate; } }
Listing 13.8: BankImpl.java. 1: // BankImpl.java 2: 3: import java.text.SimpleDateFormat; 4: import java.util.Date; 5: import java.util.Enumeration; 6: import java.util.Vector; 7: 8: import idlGlobal.Konto; 9: import idlGlobal.BankKarte; 10: import idlGlobal.Kunde; 11: 12: import idlGlobal.UngueltigesKontoException; 13: 14: import util.CORBAAlgorithms; 15: 16: public class BankImpl extends idlGlobal._BankImplBase { 17:
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
// Name dieser Bank. private String meinName; // Adresse dieser Bank. private String meineAdresse; // Liste der Konten dieser Bank. Vector meineKonten; // Liste mit Konten dieser Bank, die den automatischen // Aktualisierungsdienst angefordert haben. Vector meineAktualisiertenKonten; // Nächste Kontonummer dieser Bank die zugewiesen werden kann. int meineNaechsteKontoNummer; public BankImpl(String name) { meinName = new String(name); meineAdresse = new String("123 Elm Street, Anywhere USA " + "12345"); meineNaechsteKontoNummer = 0; meineKonten = new Vector(); meineAktualisiertenKonten = new Vector(); Thread updateThread = new UpdateAccountThread(meineAktualisiertenKonten); updateThread.start(); } private BankImpl() { meinName = new String(); meineAdresse = new String(); meineKonten = new Vector(); meineAktualisiertenKonten = new Vector(); meineNaechsteKontoNummer = 0; } public void name(String name) { meinName = new String(name); } public String name() { return meinName;
65: } 66: 67: public void adresse(String adresse) { 68: 69: meineAdresse = new String(adresse); 70: } 71: 72: public String adresse() { 73: 74: return meineAdresse; 75: } 76: 77: public Konto erstelleKonto(Kunde Kunde, String 78: KontoArt, float eroeffnungsSaldo) { 79: 80: Konto neuesKonto; 81: 82: if (KontoArt.compareTo("savings") == 0) { 83: 84: // Ein neues SparKontoImpl-Objekt für das 85: // Konto erzeugen. 86: System.out.println("BankImpl: Erzeuge neues " + 87: "Sparkonto für Kunden " + Kunde. 88: name() + "."); 89: neuesKonto = new 90: SparKontoImpl(getNaechsteKontoNummer(), 91: getCurrentDate(), eroeffnungsSaldo, Kunde, 92: 10.0f); 93: } else if (KontoArt.compareTo("checking") == 0) { 94: 95: // Erzeuge ein neues GiroKontoImpl-Objekt 96: // für das Konto. 97: System.out.println("BankImpl: Erzeuge neues Girokonto " + 98: "für Kunden " + Kunde. 99: name() + "."); 100: neuesKonto = new 101: GiroKontoImpl(getNaechsteKontoNummer(), 102: getCurrentDate(), eroeffnungsSaldo, Kunde); 103: } else { 104: 105: // Ungültige Kontoart; keine Aktion ausführen. 106: System.out.println("BankImpl: Kunde " + Kunde. 107: name() + " hat ungültige Kontoart angefordert \"" + 108: KontoArt + "\"."); 109: return null; 110: } 111:
112: // Das erzeugte Konto an das Listenende einfügen 113: // und zurückliefern. 114: BankHaupt.meinBOA.obj_is_ready(neuesKonto); 115: meineKonten.addElement(neuesKonto._duplicate()); 116: return neuesKonto; 117: } 118: 119: public void loescheKonto(Konto Konto) throws 120: UngueltigesKontoException { 121: 122: if (!CORBAAlgorithms.contains(meineKonten, Konto)) { 123: 124: // Ungültiges Konto; Exception auslösen. 125: System.out.println("BankImpl: Versuch ungültiges " + 126: "Konto zu löschen."); 127: throw new UngueltigesKontoException(); 128: } 129: 130: System.out.println("BankImpl: Lösche Konto \"" + 131: Konto.KontoNummer() + "\"."); 132: 133: // Angegebenes Konto löschen. 134: CORBAAlgorithms.removeElement(meineKonten, Konto); 135: Konto._release(); 136: } 137: 138: public Konto[] getKonten() { 139: 140: Konto[] list = new Konto[meineKonten.size()]; 141: meineKonten.copyInto(list); 142: 143: Enumeration e = meineKonten.elements(); 144: while (e.hasMoreElements()) { 145: ((Konto)e.nextElement())._duplicate(); 146: } 147: 148: return list; 149: } 150: 151: public BankKarte ausgebenBankKarte(short pin, Konto Konto) throws 152: UngueltigesKontoException { 153: 154: // Überprüfen, ob das Konto bei dieser Bank geführt wird. 155: if (!CORBAAlgorithms.contains(meineKonten, Konto)) { 156: 157: // Ungültiges Konto; Exception auslösen.
158: System.out.println("BankImpl: Versuch ungültiges Konto " + 159: "zu löschen."); 160: throw new UngueltigesKontoException(); 161: } 162: 163: // Falls wir bis hierher angelangt sind, muß das Konto bei dieser Bank 164: // existieren, so daß wir fortfahren können. 165: BankKarte neueKarte = new BankKarteImpl(pin, Konto); 166: BankHaupt.meinBOA.obj_is_ready(neueKarte); 167: 168: System.out.println("BankImpl: Issuing new BankKarte on " + 169: "Konto " + Konto.KontoNummer()); 170: neueKarte._duplicate(); 171: return neueKarte; 172: } 173: 174: public void anfordernAktualisierungsDienst(Konto Konto) throws 175: UngueltigesKontoException { 176: 177: // Überprüfen, ob das Konto bei dieser Bank geführt wird. 178: if (!CORBAAlgorithms.contains(meineKonten, Konto)) { 179: 180: // Ungültiges Konto; Exception auslösen. 181: System.out.println("BankImpl: Versuch ungültiges Konto " + 182: "zu löschen."); 183: throw new UngueltigesKontoException(); 184: } 185: 186: // Falls wir bis hierher angelangt sind, muß das Konto bei dieser Bank 187: // existieren, so daß wir fortfahren können. 188: meineAktualisiertenKonten.addElement(Konto._duplicate()); 189: } 190: 191: // Nächste verfügbare Kontonummer zurückgeben. 192: protected String getNaechsteKontoNummer() { 193: 194: return "Konto" + meineNaechsteKontoNummer++; 195: } 196: 197: // Das aktuelle Datum im Format "Mmm TT JJJJ" zurückliefern. 198: protected String getCurrentDate() { 199: 200: SimpleDateFormat format = new SimpleDateFormat("MMM dd " + 201: "yyyy"); 202: return format.format(new Date());
203: } 204: } Listing 13.9: UpdateAccountThread.java. 1: // UpdateAccountThread.java 2: 3: import java.util.Enumeration; 4: import java.util.Vector; 5: 6: import idlGlobal.Konto; 7: import idlGlobal.Kunde; 8: 9: public class UpdateAccountThread extends Thread { 10: 11: // Die Konten, die von diesem Thread aktualisiert werden sollen. 12: Vector meineKonten; 13: 14: public UpdateAccountThread(Vector Konten) { 15: 16: meineKonten = Konten; 17: } 18: 19: protected UpdateAccountThread() { 20: 21: } 22: 23: public void run() { 24: 25: // Endlosschleife (oder bis der Thread entfernt wird). 26: while (true) { 27: 28: // Eine Minute zwischen den Aktualisierungen warten. 29: try { 30: sleep(60000); 31: } catch (InterruptedException ex) { 32: } 33: 34: // Durch jedes Konto gehen... 35: Enumeration e = meineKonten.elements(); 36: while (e.hasMoreElements()) { 37: Konto Konto = (Konto)e.nextElement(); 38: 39: // Jeden Kunden, der mit dem Konto 40: // verknüpft ist durchlaufen... 41: Kunde[] Kunden = Konto.getKunden(); 42: for (int i = 0; i < Kunden.length; i++) {
43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
try { // Eine Aktualisierungsmeldung an jeden Kunde senden. Kunden[i].aktualisiereKontostand(Konto, Konto.Kontostand()); } catch (Exception ex) { // Alle auftretenden Exceptions ignorieren. } } } } } }
Listing 13.10: BankHaupt.java. 1: // BankHaupt.java 2: 3: import org.omg.CORBA.BOA; 4: import org.omg.CORBA.ORB; 5: 6: import idlGlobal.BankServer; 7: import idlGlobal.BankServerHelper; 8: 9: public class BankHaupt { 10: 11: public static ORB meinORB; 12: public static BOA meinBOA; 13: 14: public static void main(String[] args) { 15: 16: // Anzahl der Parameter überprüfen; es sollte genau eines 17: // vorliegen. 18: if (args.length != 1) { 19: System.out.println("Verwendung: Bank "); 20: return; 21: } 22: 23: // Den Namen der Bank dem ersten Parameter zuweisen. 24: String bankName = args[0]; 25: 26: BankServer bankServer; 27: BankImpl bank; 28: 29: try { 30:
31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: + 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71:
// ORB und BOA initialisieren. meinORB = ORB.init(args, null); meinBOA = meinORB.BOA_init(); // BankImpl-Objekt erzeugen und mit dem ORB // registrieren. bank = new BankImpl(bankName); meinBOA.obj_is_ready(bank); // Bindung an einen BankServer. bankServer = BankServerHelper.bind(meinORB); System.out.println("BankImpl: Bindung an BankServer " + "erfolgreich durchgeführt."); } catch (Exception ex) { // Die Bindung ist mißlungen... System.out.println("BankImpl: Kann Bindung zum BankServer nicht " "herstellen."); return; } try { // Mit BankServer registrieren. bankServer.registriereBank(bank); // Auf CORBA-Ereignisse warten. meinBOA.impl_is_ready(); } catch (Exception ex) { // Die Funktion registriereBank() ist fehlgeschlagen... System.out.println("BankImpl: Kann Bank nicht " + "registrieren:"); System.out.println(ex); return; } // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. return; } }
Jetzt folgen die Besonderheiten aus den verschiedenen Klassen, mit denen die Server-Komponente unserer Bankanwendung implementiert wurde. Beachten Sie zunächst die Implementierung der Methode getKunden() in KontoImpl.java (siehe Zeilen 60-71 in Listing 13.4).
Sie werden feststellen, daß das Kopieren der Objekte aus Vector-Containerklassen in Arrays auf relativ einfache Weise erfolgt. Zunächst wird ein Array mit entsprechender Größe erzeugt (Zeile 62), dann wird der Inhalt des Vector-Elements wird mit Hilfe eines einzelnen Methodenaufrufs (copyInto() in Zeile 63) in dieses Array kopiert. Da es sich hierbei um CORBA-Objekte handelt, die zur Versendung an den Client vorgesehen sind, müssen diese mit Hilfe der Methode _duplicate() dupliziert werden. Dies ist der Grund für die nachfolgenden Iterationen im Vector-Element (Zeilen 65-68). Beachten Sie als nächstes den Konstruktor für BankImpl (Zeilen 34-46 des Listings 13.8). Im Konstruktor wird ein Objekt vom Typ UpdateAccountThread, das wir uns im folgenden etwas genauer ansehen werden, erzeugt und gestartet. Für einfache Threads benötigt Java nicht mehr, als daß ein Thread-Objekt erzeugt und die Methode start() für dieses Objekt aufgerufen wird. Sehen Sie sich nun das eigentliche Objekt UpdateAccountThread etwas genauer an, wobei Sie in Zeile 9 von Listing 13.9 beginnen. Eine Möglichkeit zur Implementierung von Threads in Java besteht darin, eine Klasse zu erzeugen, die vom Typ java.lang.Thread (oder einfach von Thread) abgeleitet wird, wie das bei UpdateAccountThread der Fall ist. Im nächsten Schritt wird die Methode run()implementiert. Die eigentliche Arbeitsweise von UpdateAccountThread.run() ist selbsterklärend. Sehen Sie sich schließlich an, wie in BankHaupt.java eine Anwendung an ein CORBA-Objekt gebunden wird (siehe Listing 13.10). Auch hier wird der ORB initialisiert (Zeilen 31-33). Bald nach der Initialisierung des ORB und des BOA wird in BankHaupt mit Hilfe der bind()-Methode von BankServerHelper die Bindung an ein Objekt vom Typ BankServer durchgeführt, wie in Zeile 41 zu sehen ist. (Hierbei sollte noch einmal daran erinnert werden, daß bind() keine Standardmethode ist und hier nur der Einfachheit halber verwendet wird.) Geldautomat Die Implementierung des Geldautomat-Servers, der aus den Dateien GeldautomatImpl.java und GeldautomatHaupt.java besteht, ist in den Listings 13.11 und 13.12 dargestellt. Dieser Vorgang ist gut nachvollziehbar und enthält keine Überraschungen. Listing 13.11: GeldautomatImpl.java. 1: // GeldautomatImpl.java 2: 3: import idlGlobal.Konto; 4: import idlGlobal.BankKarte; 5: 6: import idlGlobal.AuthentifizierungException; 7: import idlGlobal.UnzureichendeGeldmittelException; 8: import idlGlobal.UngueltigerBetragException; 9: 10: public class GeldautomatImpl extends
idlGlobal._GeldautomatImplBase { 11: 12: private String meinName; 13: 14: public GeldautomatImpl(String name) { 15: 16: meinName = new String(name); 17: } 18: 19: private GeldautomatImpl() { 20: 21: meinName = new String(); 22: } 23: 24: public String name() { 25: 26: return meinName; 27: } 28: 29: public void name(String name) { 30: 31: meinName = new String(name); 32: } 33: 34: public float Abhebung(BankKarte Karte, Konto Konto, short pin, 35: float Betrag) throws AuthentifizierungException, 36: UngueltigerBetragException, UnzureichendeGeldmittelException { 37: 38: System.out.println("Geldautomat: Authorizing Konto for " + 39: "withdrawal."); 40: if (pin != Karte.pin() || !Karte.istAuthentifiziert(Konto)) { 41: 42: // Falsche PIN oder Karte nicht authentifiziert; eine 43: // Exception auslösen. 44: System.out.println(" Authentifizierung fehlgeschlagen."); 45: throw new AuthentifizierungException(); 46: } 47: 48: System.out.println(" Authentifizierung erfolgreich; Abhebung wird an Konto " 49: + "weitergeleitet."); 50: return Konto.Abhebung(Betrag); 51: } 52: 53: public float Einzahlung(BankKarte Karte, Konto Konto, short pin, 54: float Betrag) throws AuthentifizierungException, 55: UngueltigerBetragException {
56: 57: System.out.println("Geldautomat: Authentifiziere Konto für " + 58: "Einzahlung."); 59: if (pin != Karte.pin() || !Karte.istAuthentifiziert(Konto)) { 60: 61: // Falsche PIN oder Karte nicht authentifiziert; eine 62: // Exception auslösen. 63: System.out.println(" Authentifizierung fehlgeschlagen."); 64: throw new AuthentifizierungException(); 65: } 66: 67: System.out.println(" Authentifizierung erfolgreich; Einzahlung " 68: + "wird an Konto weitergeleitet."); 69: return Konto.Einzahlung(Betrag); 70: } 71: 72: public float getKontostand(BankKarte Karte, Konto Konto, short 73: pin) throws AuthentifizierungException { 74: 75: System.out.println("Geldautomat: Authentifiziere Konto für " + 76: "Kontostandabfrage."); 77: if (pin != Karte.pin() || !Karte.istAuthentifiziert(Konto)) { 78: 79: // Falsche PIN oder Karte nicht authentifiziert; eine 80: // Exception auslösen. 81: System.out.println(" Authentifizierung fehlgeschlagen."); 82: throw new AuthentifizierungException(); 83: } 84: 85: System.out.println(" Authentifizierung erfolgreich; Kontostandabfrage wird an Konto" 86: + "wird an Konto weitergeleitet."); 87: return Konto.Kontostand(); 88: } 89: } Listing 13.12: GeldautomatHaupt.java. 1: 2: 3: 4: 5: 6: 7: 8: 9:
// GeldautomatHaupt.java import org.omg.CORBA.BOA; import org.omg.CORBA.ORB; import idlGlobal.BankServer; import idlGlobal.BankServerHelper; public class GeldautomatHaupt {
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 52: 53: 54: 55: 56: 57:
public static ORB meinORB; public static BOA meinBOA; public static void main(String[] args) { // Anzahl der Parameter überprüfen; es sollte genau // einer vorliegen. if (args.length != 1) { System.out.println("Verwendung: Geldautomat "); return; } // Namen des Geldautomat dem ersten Parameter zuweisen. String geldautomatName = args[0]; BankServer bankServer; GeldautomatImpl geldautomat; try { // ORB und BOA initialisieren. meinORB = ORB.init(args, null); meinBOA = meinORB.BOA_init(); // Ein GeldautomatImpl-Objekt erzeugen und mit dem ORB // registrieren. geldautomat = new GeldautomatImpl(geldautomatName); meinBOA.obj_is_ready(geldautomat); // Bindung an einen BankServer. bankServer = BankServerHelper.bind(meinORB); System.out.println("GeldautomatImpl: Erfolgreich an " + "BankServer gedunden."); } catch (Exception ex) { // Die Bindung ist mißlungen... System.out.println("GeldautomatImpl: Kann Bindung an " + "BankServer nicht herstellen."); return; } 51: try { // Mit BankServer registrieren. bankServer.registriereGeldautomat(geldautomat); // Auf CORBA-Ereignisse warten. meinBOA.impl_is_ready();
58: } catch (Exception ex) { 59: 60: // Befehl registriereGeldautomat() mißlungen... 61: System.out.println("GeldautomatImpl: Kann Geldautomat nicht registrieren:"); 62: System.out.println(ex); 63: return; 64: } 65: 66: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 67: return; 68: } 69: } Implementierung der Client-Funktionalität Die Client-Module KundenImpl.java und GeldautomatClientHaupt.java sind annähernd identisch mit ihren C++-Gegenstücken. Die Implementierungen sind in den Listings 13.13 bzw. 13.14 dargestellt. Listing 13.13: KundenImpl.java. 1: // KundenImpl.java 2: 3: import java.util.Enumeration; 4: import java.util.Vector; 5: 6: import idlGlobal.Konto; 7: 8: public class KundenImpl extends idlGlobal._KundenImplBase { 9: 10: // Der Name dieses Kunden. 11: private String meinName; 12: 13: // Die Personalausweisnummer dieses Kunden. 14: private String meinePersonalAusweisNummer; 15: 16: // Die Anschrift des Kunden. 17: private String meineAdresse; 18: 19: // Der Mädchenname der Mutter dieses Kunden. 20: private String meineMuttersMaedchenName; 21: 22: // Kontoliste dieses Kunden. 23: private Vector meineKonten; 24: 25: // Liste der BankKarten dieses Kunden. 26: private Vector meineBankKarten;
27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74:
public KundenImpl(String name, String PersonalAusweisNummer, String adresse, String MuttersMaedchenName) { super(PersonalAusweisNummer); meinName = new String(name); meinePersonalAusweisNummer = new String(PersonalAusweisNummer); meineAdresse = new String(adresse); meineMuttersMaedchenName = new String(MuttersMaedchenName); meineKonten = new Vector(); meineBankKarten = new Vector(); } protected KundenImpl() { meinName = new String(); meinePersonalAusweisNummer = new String(); meineAdresse = new String(); meineMuttersMaedchenName = new String(); meineKonten = new Vector(); meineBankKarten = new Vector(); } public void name(String name) { meinName = new String(name); } public String name() { return meinName; } 62: public String PersonalAusweisNummer() { return meinePersonalAusweisNummer; } public void adresse(String adresse) { meineAdresse = new String(adresse); } public String adresse() { return meineAdresse;
75: } 76: 77: public java.lang.String MuttersMaedchenName() { 78: 79: return meineMuttersMaedchenName; 80: } 81: 82: public Konto[] getKonten() { 83: 84: Konto[] list = new Konto[meineKonten.size()]; 85: meineKonten.copyInto(list); 86: 87: Enumeration e = meineKonten.elements(); 88: while (e.hasMoreElements()) { 89: ((Konto)e.nextElement())._duplicate(); 90: } 91: 92: return list; 93: } 94: 95: public void aktualisiereKontostand(Konto Konto, float 96: Kontostand) { 97: 98: System.out.println("KundenImpl: Kontoaktualisierung " + 99: "empfangen:"); 100: System.out.println(" Neuer Kontostand: DM" + Kontostand); 101: } 102: } Listing 13.14: GeldautomatClientHaupt.java. 1: // GeldautomatClientHaupt.java 2: 3: import org.omg.CORBA.BOA; 4: import org.omg.CORBA.ORB; 5: 6: import idlGlobal.Konto; 7: import idlGlobal.Geldautomat; 8: import idlGlobal.BankKarte; 9: import idlGlobal.Bank; 10: import idlGlobal.BankServer; 11: import idlGlobal.BankServerHelper; 12: 13: import idlGlobal.AuthentifizierungException; 14: import idlGlobal.UnzureichendeGeldmittelException; 15: import idlGlobal.UngueltigesKontoException; 16: import idlGlobal.UngueltigerBetragException;
17: 18: public class GeldautomatClientHaupt { 19: 20: public static ORB meinORB; 21: public static BOA meinBOA; 22: 23: public static void main(String[] args) { 24: 25: // Anzahl der Parameter überprüfen; es sollten genau 26: // fünf vorliegen. 27: if (args.length != 5) { 28: System.out.println("Verwendung: GeldautomatClient " + 29: " <mädchenname der mutter" + 30: " name> "); 31: return; 32: } 33: 34: // Befehlszeilenparamater den Attributen 35: // des Kunden zuweisen. 36: String name = args[0]; 37: String PersonalAusweisNummer = args[1]; 38: String adresse = args[2]; 39: String MuttersMaedchenName = args[3]; 40: short pin = Short.decode(args[4]).shortValue(); 41: 42: // ORB und BOA initialisieren. 43: meinORB = ORB.init(args, null); 44: meinBOA = meinORB.BOA_init(); 45: 46: // Objekt Kunde erzeugen. 47: System.out.println("GeldautomatClient: Erzeuge neuen Kunden:"); 48: System.out.println(" Name: " + name); 49: System.out.println(" Personalausweisnummer: " + 50: PersonalAusweisNummer); 51: System.out.println(" Adresse: " + adresse); 52: System.out.println(" Mädchenname der Mutter: " + 53: MuttersMaedchenName); 54: KundenImpl Kunde = new KundenImpl(name, 55: PersonalAusweisNummer, adresse, MuttersMaedchenName); 56: 57: // BOA informieren, daß das KundenImpl-Objekt bereit ist. 58: meinBOA.obj_is_ready(Kunde); 59: 60: // BankServer-Objekt suchen und versuchen eine Banken- und Geldautomatenliste 61: // abzufragen. 62: BankServer bankServer;
63: try { 64: bankServer = BankServerHelper.bind(meinORB); 65: } catch (Exception ex) { 66: 67: // Die Bindung ist mißlungen... 68: System.out.println("GeldautomatClient: Kann Bindung mit einem " + 69: "BankServer nicht herstellen:"); 70: System.out.println(ex); 71: return; 72: } 73: System.out.println("GeldautomatClient: Erfolgreich mit einem " + 74: "BankServer gebunden."); 75: 76: Bank[] banks; 77: GELDAUTOMAT[] Geldautomaten; 78: 79: try { 80: banks = bankServer.getBanken(); 81: Geldautomaten = bankServer.getGeldautomaten(); 82: } catch (Exception ex) { 83: 84: // Der Versuch ist fehlgeschlagen... 85: System.out.println("GeldautomatClient: Kann Liste der Banken und " 86: + "Geldautomaten nicht abfragen:"); 87: System.out.println(ex); 88: return; 89: } 90: 91: // Ducksches Bankhaus und den ersten Geldautomat aus der Liste 92: // verwenden. 93: if (banks.length == 0) { 94: 95: // Es sind keine Banken verfügbar. 96: System.out.println("GeldautomatClient: Es sind keine Banken verfügbar."); 97: return; 98: } 99: if (Geldautomaten.length == 0) { 100: 101: // Es sind keine Geldautomaten verfügbar. 102: System.out.println("GeldautomatClient: Es sind keine Geldautomaten verfügbar."); 103: return; 104: } 105: Bank bank = banks[0]; 106: Geldautomat geldautomat = Geldautomaten[0];
107: System.out.println("GeldautomatClient: Verwende Bank \"" + bank. 108: name() + "\" and Geldautomat \"" + geldautomat.name() + "\"."); 109: 110: // Einige interessante Dinge ausführen. 111: 112: Konto Konto; 113: BankKarte BankKarte; 114: 115: try { 116: Konto = bank.erstelleKonto(Kunde, "checking", 117: 0.0f); 118: } catch (Exception ex) { 119: 120: // Funktion erstelleKonto() fehlgeschlagen... 121: System.out.println("GeldautomatClient: Kann Konto nicht " + 122: "erzeugen:"); 123: System.out.println(ex); 124: return; 125: } 126: 127: try { 128: 129: // Automatischen Kontoaktualisierungsdienst von der 130: // Bank anfordern. 131: bank.anfordernAktualisierungsDienst(Konto); 132: } catch (Exception ex) { 133: 134: // anfordernAktualisierungsDienst() fehlgeschlagen... 135: System.out.println("GeldautomatClient: Kann Kontoaktualisierungsdienst " 136: + "nicht anfordern:"); 137: System.out.println(ex); 138: return; 139: } 140: 141: try { 142: 143: // Einige Kontostatistiken ausgeben. 144: System.out.println("GeldautomatClient: Neues Konto wurde eröffnet:"); 145: System.out.println(" Kontonummer: " + Konto. 146: KontoNummer()); 147: System.out.println(" Eröffnungsdatum: " + Konto. 148: EroefffnungsDatum()); 149: System.out.println(" Kontostand: " + Konto. 150: Kontostand()); 151: 152: // Eine BankKarte für das neu erzeugte Konto
153: // ausstellen. 154: System.out.println("GeldautomatClient: BankKarte wird von Bank " + 155: "bezogen."); 156: try { 157: BankKarte = bank.ausgebenBankKarte(pin, Konto); 158: } catch (UngueltigesKontoException ex) { 159: 160: // Aus irgendeinem Grund war das Konto ungültig (dies 161: // sollte nicht vorkommen). 162: System.out.println("GeldautomatClient: Exception abgefangen: " 163: + "Ungültiges Konto"); 164: return; 165: } 166: 167: // Einige Kontotransaktionen mit dem Geldautomat 168: // durchführen. 169: System.out.println("GeldautomatClient: Führe " + 170: "Transaktionen aus."); 171: try { 172: System.out.println(" Einzahlung DM 250,00..."); 173: System.out.println(" Neuer Kontostand: DM" + geldautomat. 174: Einzahlung(BankKarte, Konto, pin, 250.00f)); 175: 176: // Dies wird eine Exception auslösen, da wir versuchen zu viel 177: // abzuheben. 178: System.out.println(" Abhebung: DM 500,00..."); 179: System.out.println(" Neuer Kontostand: DM" + geldautomat. 180: Abhebung(BankKarte, Konto, pin, 500.00f)); 181: } catch (AuthentifizierungException ex) { 182: System.out.println("GeldautomatClient: Exception abgefangen:" + 183: " Ungültige PIN oder keine Authentifizierung (wie " + 184: "erwartet)"); 185: } catch (UngueltigerBetragException ex) { 186: System.out.println("GeldautomatClient: Exception abgefangen:" + 187: " Ungültiger Betrag"); 188: } catch (UnzureichendeGeldmittelException ex) { 189: System.out.println("GeldautomatClient: Exception abgefangen:" + 190: " Unzureichende Geldmittel"); 191: } 192: 193: // Einige Kontotransaktionen mit dem Geldautomat 194: // durchführen. 195: System.out.println("GeldautomatClient: Weitere Transaktionen werden " + 196: "durchgeführt."); 197: try {
198: System.out.println(" Einzahlung DM 500,00..."); 199: System.out.println(" Neuer Kontostand: DM" + geldautomat. 200: Einzahlung(BankKarte, Konto, pin, 500.00f)); 201: 202: // Dies wird eine Exception auslösen, da wir die 203: // falsche PIN verwenden. 204: System.out.println(" Abhebung DM 250,00 mit " + 205: "falscher PIN..."); 206: System.out.println(" Neuer Kontostand: DM" + geldautomat. 207: Abhebung(BankKarte, Konto, (short)(pin + 208: 1), 250.00f)); 209: } catch (AuthentifizierungException ex) { 210: System.out.println("GeldautomatClient: Exception abgefangen:" + 211: " Ungültige PIN oder keine Authentifizierung (wie " + 212: "erwartet)"); 213: } catch (UngueltigerBetragException ex) { 214: System.out.println("GeldautomatClient: Exception abgefangen:" + 215: " Ungültiger Betrag"); 216: } catch (UnzureichendeGeldmittelException ex) { 217: System.out.println("GeldautomatClient: Exception abgefangen:" + 218: " Unzureichende Geldmittel"); 219: } 220: 221: // Konto löschen. 222: try { 223: System.out.println(" Konto wird gelöscht."); 224: bank.loescheKonto(Konto); 225: 226: // Versuch das Konto erneut zu löschen, nur zum Spaß. 227: // Dies sollte zur Auslösung einer Exception führen. 228: System.out.println(" Versuch, durch nochmaliges Löschen des Kontos eine " + 229: "Exception auszulösen."); 230: bank.loescheKonto(Konto); 231: } catch (UngueltigesKontoException ex) { 232: 233: // Sicher, daß die Exception ausgelöst wurde. 234: System.out.println("GeldautomatClient: Exception abgefangen:" + 235: " Ungültiges Konto (wie erwartet)"); 236: } 237: } catch (Exception ex) { 238: 239: // Eine Kontofunktion konnte nicht ausgeführt werden... 240: System.out.println("GeldautomatClient: Fehler beim Versuch auf " + 241: "Konto zuzugreifen:"); 242: System.out.println(ex);
243: return; 244: } 245: 246: // Lange genug warten, um eine oder zwei Kontoaktualisierungen 247: // abzufangen. 248: try { 249: Thread.currentThread().sleep(120000); 250: } catch (InterruptedException ex) { 251: } 252: 253: // Wenn diese Stelle erreicht wird, ist die Anwendung beendet. 254: return; 255: } 256: } Ausführen der Anwendung Mit Ausnahme der Portierung nach Java unterscheidet sich die Anwendung in keiner Weise von der vom 9. Tag. Die Ausgabe ist annähernd identisch, abgesehen von geringen Abweichungen in der Formatierung, die zwischen C++ und Java bestehen. Der einzige Unterschied besteht in der Art, in der die Anwendungskomponenten aufgerufen werden (mit der Ausnahme, daß bei Verwendung eines JavaEntwicklungswerkzeugs, das native ausführbare Dateien erzeugt, das Aufrufen der Komponenten ebenfalls auf gleiche Weise erfolgt). Start durch Ausführung der Anwendung BankServer: java BankServerHaupt Auch hier erzeugt BankServer die folgende Ausgabe: BankServer bereit. Nun kann die Anwendung Bank gestartet werden: java BankHaupt "Ducksches Bankhaus" Dies erzeugt folgende Ausgabe: Bank "Ducksches Bankhaus" bereit. In der Zwischenzeit liefert die Anwendung BankServer folgende Ausgabe: BankServerImpl: Bank "Ducksches Bankhaus" wird registriert. Danach wird die Anwendung Geldautomat gestartet.
java GeldautomatHaupt "Ducksches Bankhaus Geldautomat" Die Anwendung Geldautomat liefert folgende Ausgabe: Geldautomat "Geldautomat Ducksches Bankhaus" bereit. Daraufhin setzt die Anwendung BankServer die folgende Nachricht ab: BankServerImpl: Geldautomat "Ducksches Bankhaus Geldautomat" wird registriert. Nun ist alles zur Ausführung der Anwendung GeldautomatClient bereit. Geben Sie hierfür folgendes ein: java GeldautomatClientHaupt "Gustav Gans" 123456789 "Erpelstraße 12, 12345 Entenhausen« Gössel Nun gibt die Anwendung GeldautomatClient folgendes aus: GeldautomatClient: Neue Kundendaten werden erstellt: Name: Gustav Gans Personalausweisnummer: 123456789 Adresse: Erpelstraße 12 Geburtsname der Mutter: Gössel GeldautomatClient: Verbindung mit BankServer hergestellt. GeldautomatClient: Verwendung von Bank "Ducksches Bankhaus" und Geldautomat »Geldautomat Ducksches Bankhaus«. GeldautomatClient: Neues Konto wurde eröffnet: Kontonummer: Account0 Eröffnungsdatum: 20. Okt. 1997 Kontostand: 0.0 GeldautomatClient: BankKarte von Bank wird abgerufen. GeldautomatClient: Transaktionen werden durchgeführt. Einlage 250,00 DM... Neuer Kontostand: 250,00 DM Abhebung 500,00 DM... GeldautomatClient: Exception abgefangen: Kontostand nicht ausreichend GeldautomatClient: Weitere Transaktionen werden durchgeführt. Einlage 500,00 DM... Neuer Kontostand: 750,00 DM Abhebung von 250,00 DM mit ungültiger PIN...
GeldautomatClient: Exception abgefangen: Ungültige PIN oder Keine Berechtigung (wie erwartet) Konto wird gelöscht. Versuch, durch nochmaliges Löschen des Kontos eine Exception auszulösen. GeldautomatClient: Exception abgefangen: Ungültiges Konto (wie erwartet) An dieser Stelle geht die Anwendung GeldautomatClient zwei Minuten lang in Leerlauf, während sie auf Nachrichten von der Anwendung Bank wartet. Gedulden Sie sich ein wenig, GeldautomatClient liefert schließlich die folgende Ausgabe: KundenImpl: Kontoaktualisierung empfangen: Neuer Kontostand: 750,00 DM Alle diese Vorgänge laufen sehr schnell ab. Wenn sie beendet sind, können Sie zu den anderen Anwendungsfenstern wechseln und darin Hinweise darauf entdecken, wie sich diese Abläufe dort ausgewirkt haben. Wenn Sie zunächst einen Blick auf die Anwendung BankServer werfen, werden Sie folgendes sehen (die neuen Ausgabenachrichten sind in Fettschrift dargestellt): BankServer bereit. BankServerImpl: Liste von 1 Bank(en) wird zurückgegeben. BankServerImpl: Liste von 1 Geldautomat(en) wird zurückgegeben. Die Ausgabe der anderen Anwendungen entspricht der vom letzten Mal (abgesehen von der Ausgabe der Anwendung Bank). Wenden Sie sich dem Fenster zu, in dem die Anwendung Bank ausgeführt wird. Sie werden die folgende vertraute Ausgabe sehen: Bank "Ducksches Bankhaus" bereit. BankImpl: Einrichtung eines Girokontos für den Kunden Gustav Gans. KontoImpl: Kontostand für abzuhebenden Betrag zu niedrig. BankImpl: Löschen des Kontos "Account00000000". BankImpl: Versuch, ein ungültiges Konto zu löschen. Bleiben Sie einige Augenblicke bei dieser Anwendung, um folgende Ausgabe zu sehen (wenn es etwa eine Minute gedauert hat, bis das Fenster für die Ausgabe der Anwendung Bank aufgerufen war, steht das folgende möglicherweise bereits schon am Bildschirm): BankImpl: Konten werden aktualisiert. Beachten Sie, daß diese Nachricht unmittelbar vor dem Senden der Aktualisierungsnachrichten an alle
Kontoinhaber durch den zweiten Thread der Bankanwendung ausgegeben wird.
Zusammenfassung Heute erhielten Sie am Anfang eine Einführung in die Programmiersprache/Plattform Java, danach haben Sie die Bankanwendung von C++ nach Java portiert. Diese Portierung lief ziemlich schnörkellos ab. Der Hauptteil des Codes konnte Zeile für Zeile von C++ in Java umgesetzt werden. Wenn Sie zuvor bereits mit Java vertraut waren, wurden Sie hierdurch sicher nicht sehr gefordert. Als Java-Neuling dagegen haben Sie ein wenig über die Sprache erfahren und, was noch wichtiger ist, miterlebt, wie mühelos die Implementierung von CORBA-Anwendungen in Java durchgeführt werden kann. Da die Portierung der Bankanwendung von C++ nach Java im Grunde nichts besonders Neues gebracht hat, war diese Übung möglicherweise nicht so interessant für Sie. Am 14. Tag jedoch wird diese JavaPortierung noch weiter ausgearbeitet, daher sollten Sie dabeibleiben. Der 14. Tag dient dazu, den nächsten logischen Schritt für die Java-Version der Bankanwendung durchzuführen. Da eine der Stärken von Java in der Fähigkeit liegt, den Endbenutzern graphische Anwendungen über WWW-Browser zur Verfügung zu stellen, ist es sinnvoll, eine graphische Benutzeroberfläche für die Bankanwendung zu erstellen, anstatt mit der (etwas langweiligen) zeichenorientierten Oberfläche weiterzuarbeiten. Genau dies werden Sie im nächsten und damit letzten Kapitel dieses Buchs tun. Auf diese Weise werden Sie einen Einblick darüber erhalten, was mit der Verbindung von CORBA und Java alles erreicht werden kann.
Fragen und Antworten Frage: Ich habe immer gedacht, Java wäre nur zum Erstellen netter Animationen auf WWW-Seiten geeignet. Ist das etwa gar nicht so? Antwort: Wenn auch der hohe Beliebtheitsgrad von Java seinen Ursprung in der Eignung dieser Sprache für das World Wide Web hat und die erste Generation von Java-Entwicklungswerkzeugen hauptsächlich auf die Erstellung einfacher WWW-Kleinanwendungen ausgerichtet war, so ist Java doch weitaus mehr als lediglich eine Sprache zum Schreiben netter, aber im Grunde nutzloser Software. Java eignet sich in der Tat, ähnlich wie C++, zur Entwicklung universell einsetzbarer Software. Die Integration von Java mit CORBA spricht Bände über die Tatsache, daß Java nicht nur eine SpielzeugProgrammiersprache ist. Frage: Ein Großteil des Java-Codes in diesem Kapitel sieht dem C++-Code aus den vorhergehenden Kapiteln verdächtig ähnlich. Ist das immer so?
Antwort: Da sich die Syntax von C++ und von Java in weiten Bereichen ähnelt, ist es bei einfachen Anwendungen nicht ungewöhnlich, daß sie ihren in C++ geschriebenen Gegenstücken ähneln. Wenn in einer Anwendung jedoch die Verwendung unterschiedlicher Klassenbibliotheken einsetzt, ist schnell Schluß mit der offensichtlichen Ähnlichkeit.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen (zu diesem Kapitel liegen keine Übungen vor, jedoch im nächsten gibt es wieder welche). Die Antworten zu den Quizfragen finden Sie in Anhang A. Quiz 1. Welches IDL-Konstrukt entspricht dem package in Java? 2. Welchen Vorteil bietet die RMI gegenüber CORBA? Wie sieht es umgekehrt aus? 3. Warum ist es für einen Entwickler sinnvoll, eine CORBA-Anwendung mit Java zu entwickeln?
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Tag 14 Einrichten von WebFähigkeit für das Beispiel Bank mit Java Entwicklung von BankApplet Ausführen des Applets Zusammenfassung F&A Workshop
An Tag 13, »Entwicklung von Internet-Anwendungen mit CORBA und Java« erhielten Sie eine Einführung in die Programmiersprache Java und portierten dann die Anwendung Bank von C++ nach Java. Der Portierungsvorgang war wegen der Ähnlichkeiten zwischen C++ und Java einfach sowie aufgrund des Umstandes, daß Sie der Anwendung keine Funktionen hinzufügten, als Sie diese portierten. Das Ergebnis war eine CORBA-Anwendung, die sich exakt wie ihre Vorgängerin verhielt, ohne besondere Extras. Heute werden Sie die Java-Version des Beispiels Bank auf die nächste logische Stufe ihrer Evolution bringen. Eines der größten Potentiale von Java liegt in der Entwicklung grafischer Benutzeroberflächen (GUIs), und in diesem Kapitel werden Sie die Möglichkeiten von Java auf diesem Gebiet kennenlernen. Natürlich würde das Thema der GUI-Entwicklung in Java selbst ein ganzes Buch füllen, so daß hier viele Details ausgelassen werden. Am Ende des Tages jedoch werden
Sie ein funktionales Java-Applet erstellt haben, das eine GUI-Version der zuvor vom GeldautomatClient-Teil der Anwendung Bank geleisteten Funktionalität zur Verfügung stellt.
Entwicklung von BankApplet Der Entwicklungsprozeß für das Applet unterscheidet sich etwas von den Prozessen früherer Kapitel, hauptsächlich aufgrund der Verwendung einer GUI-Oberfläche anstelle früherer Befehlszeilenschnittstellen. Hier ist der Prozeß, nach dem Sie diesmal vorgehen werden: ■
■
■
Implementieren der Server-Funktionen. Wie sich herausstellt, müssen Sie keine Änderungen an bestehenden Server-Komponenten vornehmen. Implementieren der Client-Funktionen. Sie werden den früheren GeldautomatClient ersetzen durch ein Java-Applet namens, wie originell, BankApplet. Das BankApplet einsetzen. Sie werden BankApplet im appletviewer ausführen (eine selbständige Anwendung, die Java-Applets ausführt) und dann in einem Web-Browser.
Implementieren der Server-Funktionen Wie an Tag 13 müssen keine Änderungen an den Server-Komponenten der Anwendung Bank vorgenommen werden. Wenn es soweit ist, die Beispielanwendung auszuführen, dann werden Sie dieselben Server-Komponenten BankServer, Bank und Geldautomat ausführen wie an Tag 13. Implementieren der Client-Funktionen Bevor Sie sich in die Entwicklung von BankApplet vertiefen, müssen Sie eine geringfügige Änderung am bestehenden KundenImpl vornehmen. Der Grund ist der, daß bis jetzt alle Ausgaben der Client-Anwendung (und in diesem Fall der Server-Anwendungen) an die Systemkonsole (den Stream cout in C++ oder den Stream System.out in Java) gingen. Zuvor druckte die Methode aktualisiereKontostand() von KundenImpl einfach eine Nachricht auf System.out, was im Falle einer Konsolenmodusanwendung wie dem früheren GeldautomatClient in Ordnung ist. Im Falle einer grafischen Client-Anwendung wie BankApplet jedoch möchten Sie Botschaften vermutlich in einer grafischeren Weise verarbeiten. Sie werden sehen, wie BankApplet solche Botschaften handhabt, nehmen Sie jedoch zunächst lediglich zur Kenntnis, daß KundenImpl die Aktualisierungsnachricht an ein anderes Objekt weitergibt - eines, das die Schnittstelle KontoUpdateListener implementiert (was Sie auch in einem Augenblick sehen werden). Kunde Die Änderungen der Klasse KundenImpl sind geringfügig; der Konstruktor KundenImpl erhält nun einen Parameter KontoUpdateListener, der verwendet wird, wenn später Kontoaktualisierungsnachrichten an KundenImpl gesendet werden. Wie außerdem zuvor erwähnt wurde, reicht nun die Methode aktualisiereKontostand() selbst die Aktualisierungsbotschaft
an das registrierte KontoUpdateListener-Objekt weiter, anstatt daß eine Botschaft auf System.out ausgegeben wird. (Sie werden bald feststellen, daß es sich bei dem Objekt KontoUpdateListener in diesem Fall um BankApplet selbst handelt.) Das veränderte KundenImpl.java ist in Listing 14.1 zu sehen. Listing 14.1: KundenImpl.java. 1: // KundenImpl.java 2: 3: import java.util.Enumeration; 4: import java.util.Vector; 5: 6: import idlGlobal.Konto; 7: 8: public class KundenImpl extends idlGlobal._KundenImplBase { 9: 10: // Der Name dieses Kunden. 11: private String meinName; 12: 13: // Die Personalausweisnummer dieses Kunden. 14: private String meinePersonalAusweisNummer; 15: 16: // Die Anschrift des Kunden. 17: private String meineAdresse; 18: 19: // Der Mädchenname der Mutter dieses Kunden. 20: private String meineMuttersMaedchenName; 21: 22: // Kontoliste dieses Kunden. 23: private Vector meineKonten; 24: 25: // Liste der BankKarten dieses Kunden. 26: private Vector meineBankKarten; 27: 28: // Der KontoAktualisierungUeberwacher dieses Kunden(nur einer ist erlaubt, 29: // um den Quelltext überschaubar zu halten). 30: private KontoAktualisierungUeberwacher meinKontoAktualisierungUeberwacher; 31: 32: public KundenImpl(String name, String PersonalAusweisNummer, 33: String adresse, String MuttersMaedchenName, 34: KontoAktualisierungUeberwacher KontoAktualisierungUeberwacher) { 35: 36: super(PersonalAusweisNummer); 37:
38: meinName = new String(name); 39: meinePersonalAusweisNummer = new 40: String(PersonalAusweisNummer); 41: meineAdresse = new String(adresse); 42: meineMuttersMaedchenName = new String(MuttersMaedchenName); 43: meineKonten = new Vector(); 44: meineBankKarten = new Vector(); 45: meinKontoAktualisierungUeberwacher = KontoAktualisierungUeberwacher; 46: } 47: 48: protected KundenImpl() { 49: 50: meinName = new String(); 51: meinePersonalAusweisNummer = new String(); 52: meineAdresse = new String(); 53: meineMuttersMaedchenName = new String(); 54: meineKonten = new Vector(); 55: meineBankKarten = new Vector(); 56: } 57: 58: public void name(String name) { 59: 60: meinName = new String(name); 61: } 62: 63: public String name() { 64: 65: return meinName; 66: } 67: 68: public String PersonalAusweisNummer() { 69: 70: return meinePersonalAusweisNummer; 71: } 72: 73: public void adresse(String adresse) { 74: 75: meineAdresse = new String(adresse); 76: } 77: 78: public String adresse() { 79: 80: return meineAdresse; 81: } 82: 83: public java.lang.String MuttersMaedchenName() {
84: 85: return meineMuttersMaedchenName; 86: } 87: 88: public Konto[] getKonten() { 89: 90: Konto[] list = new Konto[meineKonten.size()]; 91: meineKonten.copyInto(list); 92: 93: Enumeration e = meineKonten.elements(); 94: while (e.hasMoreElements()) { 95: ((Konto)e.nextElement())._duplicate(); 96: } 97: 98: return list; 99: } 100: 101: public void aktualisiereKontostand(Konto Konto, float 102: Kontostand) { 103: 104: meinKontoAktualisierungUeberwacher.update(Konto, Kontostand); 105: } 106: } Die Schnittstelle KontoUpdateListener ist einfach, besteht doch ihr einziger Zweck darin, Aktualisierungsbotschaften für Konten anzunehmen. Folglich reflektiert ihre einzige Methode, update(), die Methode aktualisiereKontostand() in KundenImpl. Beachten Sie, daß KontoUpdateListener als Schnittstelle keine Implementierung dieser Methode beinhaltet; denn die Implementierung liegt in der Verantwortlichkeit derjenigen Klasse(n), welche diese Schnittstelle implementieren. KontoUpdateListener.java ist in Listing 14.2 zu sehen. Listing 14.2: KontoUpdateListener.java. 1: // KontoAktualisierungUeberwacher.java 2: 3: import idlGlobal.Konto; 4: 5: public interface KontoAktualisierungUeberwacher { 6: 7: /** 8: * Update the given Konto with the new Kontostand. 9: */ 10: public void update(Konto Konto, float Kontostand); 11: }
Ein Wort über Java-Entwicklungswerkzeuge Sie sind fast soweit, mit der Arbeit an BankApplet selbst zu beginnen. Falls Sie bereits einen Blick auf BankApplet.java (siehe Listing 14.3) geworfen haben, dann haben Sie gesehen, daß die Datei recht groß ist - viel größer, als alles, was bisher in diesem Buch erschien. Erschrecken Sie nicht. Ein großer Teil des Quelltextes wurde nicht von Hand eingegeben, sondern von einem Entwicklungswerkzeug generiert (ein Entwicklungswerkzeug oder dessen Teil zum Definieren von GUI-Schnittstellen wird oft als GUI-Builder bezeichnet). Visual Café 2.0 von Symantec (verfügbar unter http://cafe.symantec.com/) wurde verwendet, um dieses Applet zu entwickeln, doch kann jeder beliebige GUI-Builder für Java - etwa Java Workshop von Sun (http://www.sun.com/), Visual J++ von Microsoft (http://www.microsoft.com/), JBuilder von Borland (http://www.borland.com/), Visual Age für Java von IBM (http://www.software.ibm.com/) oder viele andere - verwendet werden, um den GUIEntwicklungsteil dieser Aufgabe zu erledigen. Jedes dieser Produkte erzeugt unterschiedlichen Code, abhängig von den gewählten GUI-Komponenten, ergibt jedoch ein ähnliches Endprodukt. Obwohl Sie sicherlich sämtlichen Quelltext für die Benutzerschnittstellen von Hand eingeben können, macht Ihnen die Verwendung eines GUI-Builders für alle außer den einfachsten Benutzerschnittstellen das Leben um einiges leichter. Die Beschreibung selbst eines dieser Werkzeuge geht über den Umfang dieses Buches hinaus, so daß wir hoffen, daß Sie bereits einige Erfahrungen mit diesen gemacht haben. Zahlreiche GUI-Builder versetzen Sie nicht nur in die Lage, Benutzerschnittstellenkomponenten auf Formularen abzulegen (diese werden auch als Bildschirme oder Dialoge bezeichnet), sondern auch Wechselwirkungen zwischen Komponenten zu definieren, ohne irgendwelchen Quelltext zu schreiben. Je mehr Arbeit der GUI-Builder leistet, desto weniger Quelltext müssen Sie von Hand schreiben, und Sie können stets den vom GUI-Builder generierten Quelltext manuell modifizieren, um genau die gewünschten Ergebnisse zu erhalten. BankApplet BankApplet.java, einschließlich des von Visual Café generierten und des von Hand geschriebenen Quelltexts, ist in Listing 14.3 abgedruckt. Wieder wurde der größte Teil dieses Quelltexts (fast die gesamte Methode init() beispielsweise) von Visual Café generiert. In wenigen Augenblikken werden Sie die von Hand geschriebenen Partien genauer untersuchen. Sie werden sehen, daß das Verhalten des Applets den der in früheren Kapiteln implementierten Anwendungen ähnelt. Die Strukturen unterscheiden sich jedoch erheblich, da sich die Strukturen GUI-basierter Anwendungen oft von denen ihrer konsolenbasierten Gegenstücke unterscheiden. Bevor Sie den Quelltext von BankApplet untersuchen, ist es hilfreich zu sehen, was er produziert. Abbildung 14.1 illustriert das Hauptfenster von BankApplet, wie es unter Verwendung von Visual Café entworfen wurde. Abbildung 14.2 zeigt die zugehörige Hierarchie von Objekten, aus denen sich die Benutzerschnittstelle zusammensetzt.
Abbildung 14.1: BankApplet Hauptfenster
Abbildung 14.2: Beispiel für eine Fensterhierarchie Listing 14.3: BankApplet.java. 1: // BankApplet.java 2: 3: import java.awt.*;
4: import java.awt.event.ItemEvent; 5: import java.applet.*; 6: import java.util.Enumeration; 7: import java.util.Hashtable; 8: 9: import org.omg.CORBA.BOA; 10: import org.omg.CORBA.ORB; 11: 12: import idlGlobal.Konto; 13: import idlGlobal.Geldautomat; 14: import idlGlobal.Bank; 15: import idlGlobal.BankServer; 16: import idlGlobal.BankServerHelper; 17: import idlGlobal.CheckingAccountHelper; 18: import idlGlobal.Kunde; 19: 20: import idlGlobal.UnzureichendeGeldmittelException; 21: import idlGlobal.UngueltigesKontoException; 22: import idlGlobal.UngueltigerBetragException; 23: 24: public class BankApplet extends Applet implements 25: KontoAktualisierungUeberwacher { 26: 27: public static ORB meinORB; 28: public static BOA meinBOA; 29: 30: // Der BankServer dieses BankApplets. 31: BankServer myBankServer; 32: 33: // Die aktuell ausgewählte Bank, Geldautomat, Kunde, 34: // und Konto dieses BankApplets. 35: Bank meineAusgewaehlteBank; 36: Geldautomat meinAusgewaehlterGeldautomat; 37: Kunde meinAusgewaehlterKunde; 38: Konto meinAusgewaehltesKonto; 39: 40: // Die Liste der Banken dieser BankApplet. 41: Hashtable meineBanken = new Hashtable(); 42: 43: // Die Liste der Geldautomaten dieser BankApplet, basierend auf dem Geldautomat-Namen. 44: Hashtable meineGeldautomaten = new Hashtable(); 45: 46: // Die Kundenliste dieser BankApplet, basierend auf Kundenamen. 47: // Beachten Sie, daß dies bedeutet, daß Kundennamen in dieser 48: // Applet eindeutig sein müssen!
49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94:
Hashtable meineKunden = new Hashtable(); // This BankApplet's list of Konten. This Hashtable is keyed // on Kunde objects, which in turn point to Hashtables which // are keyed on the concatenation of Bank names and Konto // numbers and identify Konten. Hashtable meineKonten = new Hashtable(); // This BankApplet's map which links Konten to Banks. Hashtable myAccountBankMap = new Hashtable(); public void init() { //{{INIT_CONTROLS GridBagLayout gridBagLayout; gridBagLayout = new GridBagLayout(); setLayout(gridBagLayout); setSize(404, 327); setBackground(new Color(-4144944)); bankChoice = new java.awt.Choice(); bankChoice.setBounds(112, 3, 322, 21); GridBagConstraints gbc; gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); gbc.ipadx = 100; ((GridBagLayout)getLayout()).setConstraints(bankChoice, gbc); add(bankChoice); geldautomatChoice = new java.awt.Choice(); geldautomatChoice.setBounds(112, 30, 156, 21); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(geldautomatChoice, gbc); add(geldautomatChoice); customerChoice = new java.awt.Choice(); customerChoice.setBounds(112, 57, 156, 21); gbc = new GridBagConstraints();
95: gbc.gridx = 1; 96: gbc.gridy = 2; 97: gbc.weightx = 1.0; 98: gbc.fill = GridBagConstraints.HORIZONTAL; 99: gbc.insets = new Insets(2, 2, 2, 2); 100: ((GridBagLayout)getLayout()).setConstraints(customerChoice, 101: gbc); 102: add(customerChoice); 103: newCustomerButton = new java.awt.Button(); 104: newCustomerButton.setActionCommand("button"); 105: newCustomerButton.setLabel("New..."); 106: newCustomerButton.setBounds(272, 56, 48, 23); 107: gbc = new GridBagConstraints(); 108: gbc.gridx = 2; 109: gbc.gridy = 2; 110: gbc.fill = GridBagConstraints.NONE; 111: gbc.insets = new Insets(2, 2, 2, 2); 112: ((GridBagLayout)getLayout()). 113: setConstraints(newCustomerButton, gbc); 114: add(newCustomerButton); 115: accountChoice = new java.awt.Choice(); 116: accountChoice.setBounds(112, 84, 156, 21); 117: gbc = new GridBagConstraints(); 118: gbc.gridx = 1; 119: gbc.gridy = 3; 120: gbc.weightx = 1.0; 121: gbc.fill = GridBagConstraints.HORIZONTAL; 122: gbc.insets = new Insets(2, 2, 2, 2); 123: ((GridBagLayout)getLayout()).setConstraints(accountChoice, 124: gbc); 125: add(accountChoice); 126: accountChoice.setEnabled(false); 127: newAccountButton = new java.awt.Button(); 128: newAccountButton.setActionCommand("button"); 129: newAccountButton.setLabel("New..."); 130: newAccountButton.setBounds(272, 83, 48, 23); 131: gbc = new GridBagConstraints(); 132: gbc.gridx = 2; 133: gbc.gridy = 3; 134: gbc.fill = GridBagConstraints.NONE; 135: gbc.insets = new Insets(2, 2, 2, 2); 136: ((GridBagLayout)getLayout()). 137: setConstraints(newAccountButton, gbc); 138: add(newAccountButton); 139: newAccountButton.setEnabled(false); 140: autoUpdateButton = new java.awt.Button();
141: autoUpdateButton.setActionCommand("button"); 142: autoUpdateButton.setLabel("AutoUpdate"); 143: autoUpdateButton.setBounds(324, 83, 78, 23); 144: gbc = new GridBagConstraints(); 145: gbc.gridx = 3; 146: gbc.gridy = 3; 147: gbc.fill = GridBagConstraints.NONE; 148: gbc.insets = new Insets(2, 2, 2, 2); 149: ((GridBagLayout)getLayout()). 150: setConstraints(autoUpdateButton, gbc); 151: add(autoUpdateButton); 152: autoUpdateButton.setEnabled(false); 153: selectBankLabel = new java.awt.Label("Select Bank"); 154: selectBankLabel.setBounds(2, 2, 79, 23); 155: gbc = new GridBagConstraints(); 156: gbc.gridx = 0; 157: gbc.gridy = 0; 158: gbc.anchor = GridBagConstraints.WEST; 159: gbc.fill = GridBagConstraints.NONE; 160: gbc.insets = new Insets(2, 2, 2, 2); 161: ((GridBagLayout)getLayout()). 162: setConstraints(selectBankLabel, gbc); 163: add(selectBankLabel); 164: selectGeldautomatLabel = new java.awt.Label("Select Geldautomat"); 165: selectGeldautomatLabel.setBounds(2, 29, 74, 23); 166: gbc = new GridBagConstraints(); 167: gbc.gridx = 0; 168: gbc.gridy = 1; 169: gbc.anchor = GridBagConstraints.WEST; 170: gbc.fill = GridBagConstraints.NONE; 171: gbc.insets = new Insets(2, 2, 2, 2); 172: ((GridBagLayout)getLayout()). 173: setConstraints(selectGeldautomatLabel, gbc); 174: add(selectGeldautomatLabel); 175: selectCustomerLabel = new java.awt. 176: Label("Select Kunde"); 177: selectCustomerLabel.setBounds(2, 56, 106, 23); 178: gbc = new GridBagConstraints(); 179: gbc.gridx = 0; 180: gbc.gridy = 2; 181: gbc.anchor = GridBagConstraints.WEST; 182: gbc.fill = GridBagConstraints.NONE; 183: gbc.insets = new Insets(2, 2, 2, 2); 184: ((GridBagLayout)getLayout()). 185: setConstraints(selectCustomerLabel, gbc);
186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231:
add(selectCustomerLabel); selectAccountLabel = new java.awt.Label("Select Konto"); selectAccountLabel.setBounds(2, 83, 94, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 3; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(selectAccountLabel, gbc); add(selectAccountLabel); accountActionPanel = new java.awt.Panel(); gridBagLayout = new GridBagLayout(); accountActionPanel.setLayout(gridBagLayout); accountActionPanel.setBounds(2, 110, 400, 188); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 4; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(accountActionPanel, gbc); add(accountActionPanel); accountInfoPanel = new java.awt.Panel(); gridBagLayout = new GridBagLayout(); accountInfoPanel.setLayout(gridBagLayout); accountInfoPanel.setBounds(0, 13, 246, 162); gbc = new GridBagConstraints(); gbc.weightx = 0.65; gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 0, 2); ((GridBagLayout)accountActionPanel.getLayout()). setConstraints(accountInfoPanel, gbc); accountActionPanel.add(accountInfoPanel); issuingBankTextField = new java.awt.TextField(); issuingBankTextField.setEditable(false); issuingBankTextField.setBounds(136, 29, 108, 23); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 1; gbc.weightx = 1.0;
232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277:
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(issuingBankTextField, gbc); accountInfoPanel.add(issuingBankTextField); accountNumberTextField = new java.awt.TextField(); accountNumberTextField.setEditable(false); accountNumberTextField.setBounds(136, 56, 108, 23); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 2; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(accountNumberTextField, gbc); accountInfoPanel.add(accountNumberTextField); accountTypeTextField = new java.awt.TextField(); accountTypeTextField.setEditable(false); accountTypeTextField.setBounds(136, 83, 108, 23); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 3; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(accountTypeTextField, gbc); accountInfoPanel.add(accountTypeTextField); creationDateTextField = new java.awt.TextField(); creationDateTextField.setEditable(false); creationDateTextField.setBounds(136, 110, 115, 23); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 4; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(creationDateTextField, gbc); accountInfoPanel.add(creationDateTextField); currentBalanceTextField = new java.awt.TextField(); currentBalanceTextField.setEditable(false); currentBalanceTextField.setBounds(136, 137, 115, 23); gbc = new GridBagConstraints(); gbc.gridx = 2;
278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323:
gbc.gridy = 5; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(currentBalanceTextField, gbc); accountInfoPanel.add(currentBalanceTextField); accountInfoLabel = new java.awt. Label("Konto Information", Label.CENTER); accountInfoLabel.setBounds(62, 2, 122, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 3; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(accountInfoLabel, gbc); accountInfoPanel.add(accountInfoLabel); issuingBankLabel = new java.awt.Label("Issuing Bank"); issuingBankLabel.setBounds(2, 29, 86, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(issuingBankLabel, gbc); accountInfoPanel.add(issuingBankLabel); accountNumberLabel = new java.awt.Label("Kontonummer"); accountNumberLabel.setBounds(2, 56, 105, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(accountNumberLabel, gbc); accountInfoPanel.add(accountNumberLabel); accountTypeLabel = new java.awt.Label("Konto Type"); accountTypeLabel.setBounds(2, 83, 86, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 3;
324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369:
gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(accountTypeLabel, gbc); accountInfoPanel.add(accountTypeLabel); creationDateLabel = new java.awt.Label("Eröffnungsdatum"); creationDateLabel.setBounds(2, 110, 90, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 4; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(creationDateLabel, gbc); accountInfoPanel.add(creationDateLabel); currentBalanceLabel = new java.awt. Label("Current Kontostand"); currentBalanceLabel.setBounds(2, 137, 103, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 5; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(currentBalanceLabel, gbc); accountInfoPanel.add(currentBalanceLabel); dollarLabel1 = new java.awt.Label("$", Label.RIGHT); dollarLabel1.setBounds(111, 137, 21, 23); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 5; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)accountInfoPanel.getLayout()). setConstraints(dollarLabel1, gbc); accountInfoPanel.add(dollarLabel1); transactionsPanel = new java.awt.Panel(); gridBagLayout = new GridBagLayout(); transactionsPanel.setLayout(gridBagLayout); transactionsPanel.setBounds(250, 13, 149, 162); gbc = new GridBagConstraints(); gbc.weightx = 0.35;
370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415:
gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(0, 2, 0, 0); ((GridBagLayout)accountActionPanel.getLayout()). setConstraints(transactionsPanel, gbc); accountActionPanel.add(transactionsPanel); transactionTextField = new java.awt.TextField(); transactionTextField.setBounds(27, 29, 129, 23); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 1.0; gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()). setConstraints(transactionTextField, gbc); transactionsPanel.add(transactionTextField); transactionTextField.setEnabled(false); depositButton = new java.awt.Button(); depositButton.setActionCommand("button"); depositButton.setLabel("Einzahlung"); depositButton.setBounds(2, 56, 154, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()). setConstraints(depositButton, gbc); transactionsPanel.add(depositButton); depositButton.setEnabled(false); withdrawButton = new java.awt.Button(); withdrawButton.setActionCommand("button"); withdrawButton.setLabel("Abhebung"); withdrawButton.setBounds(2, 83, 154, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()).
416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461:
setConstraints(withdrawButton, gbc); transactionsPanel.add(withdrawButton); withdrawButton.setEnabled(false); updateAccountButton = new java.awt.Button(); updateAccountButton.setActionCommand("button"); updateAccountButton.setLabel("Update Konto"); updateAccountButton.setBounds(2, 137, 154, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; gbc.weighty = 1.0; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()). setConstraints(updateAccountButton, gbc); transactionsPanel.add(updateAccountButton); updateAccountButton.setEnabled(false); transactionsLabel = new java.awt.Label("Transactions", Label.CENTER); transactionsLabel.setBounds(31, 2, 86, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()). setConstraints(transactionsLabel, gbc); transactionsPanel.add(transactionsLabel); dollarLabel2 = new java.awt.Label("$", Label.RIGHT); dollarLabel2.setBounds(2, 29, 21, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)transactionsPanel.getLayout()). setConstraints(dollarLabel2, gbc); transactionsPanel.add(dollarLabel2); statusTextField = new java.awt.TextField(); statusTextField.setEditable(false); statusTextField.setText("BankApplet Bereit.");
462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507:
statusTextField.setBounds(2, 302, 400, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 4; gbc.weightx = 1.0; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(statusTextField, gbc); add(statusTextField); //}} //{{REGISTER_LISTENERS SymAction lSymAction = new SymAction(); newCustomerButton.addActionListener(lSymAction); newAccountButton.addActionListener(lSymAction); SymItem lSymItem = new SymItem(); bankChoice.addItemListener(lSymItem); geldautomatChoice.addItemListener(lSymItem); customerChoice.addItemListener(lSymItem); accountChoice.addItemListener(lSymItem); autoUpdateButton.addActionListener(lSymAction); depositButton.addActionListener(lSymAction); withdrawButton.addActionListener(lSymAction); updateAccountButton.addActionListener(lSymAction); //}} // ORB und BOA initialisieren. meinORB = ORB.init(this); meinBOA = meinORB.BOA_init(); // Locate a BankServer object and try to get a list of // Banks and Geldautomaten from it. setStatus("Binding to a BankServer..."); try { myBankServer = BankServerHelper.bind(meinORB); } catch (Exception ex) { // Die Bindung ist mißlungen... setStatus("Bindung mit einem BankServer nicht möglich."); System.out.println(ex); return; } setStatus("Bindung mit einem BankServer erfolgreich
durchgeführt."); 508: 509: // Get lists of Banks and Geldautomaten from the BankServer. 510: GELDAUTOMAT[] geldautomaten; 511: Bank[] banks; 512: setStatus("Getting list of available Banks..."); 513: try { 514: banks = myBankServer.getBanken(); 515: } catch (Exception ex) { 516: 517: // Der Versuch ist fehlgeschlagen... 518: setStatus("Unable to get list of Banks."); 519: return; 520: } 521: setStatus("Getting list of available Geldautomaten..."); 522: try { 523: geldautomaten = myBankServer.getGeldautomaten(); 524: } catch (Exception ex) { 525: 526: // Der Versuch ist fehlgeschlagen... 527: setStatus("Unable to get list of Geldautomaten."); 528: return; 529: } 530: addBanks(banks); 531: hinzufuegenGeldautomaten(geldautomaten); 532: setStatus(null); 533: } 534: 535: //{{DECLARE_CONTROLS 536: java.awt.Choice bankChoice; 537: java.awt.Choice geldautomatChoice; 538: java.awt.Choice customerChoice; 539: java.awt.Button newCustomerButton; 540: java.awt.Choice accountChoice; 541: java.awt.Button newAccountButton; 542: java.awt.Button autoUpdateButton; 543: java.awt.Label selectBankLabel; 544: java.awt.Label selectGeldautomatLabel; 545: java.awt.Label selectCustomerLabel; 546: java.awt.Label selectAccountLabel; 547: java.awt.Panel accountActionPanel; 548: java.awt.Panel accountInfoPanel; 549: java.awt.TextField issuingBankTextField; 550: java.awt.TextField accountNumberTextField; 551: java.awt.TextField accountTypeTextField; 552: java.awt.TextField creationDateTextField;
553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598:
java.awt.TextField currentBalanceTextField; java.awt.Label accountInfoLabel; java.awt.Label issuingBankLabel; java.awt.Label accountNumberLabel; java.awt.Label accountTypeLabel; java.awt.Label creationDateLabel; java.awt.Label currentBalanceLabel; java.awt.Label dollarLabel1; java.awt.Panel transactionsPanel; java.awt.TextField transactionTextField; java.awt.Button depositButton; java.awt.Button withdrawButton; java.awt.Button updateAccountButton; java.awt.Label transactionsLabel; java.awt.Label dollarLabel2; java.awt.TextField statusTextField; //}} /** * Set the status bar message to the given string. If the * string is null, the status message is reset to the default. */ public void setStatus(String status) { if (status != null) { statusTextField.setText(status); } else { statusTextField.setText("BankApplet bereit."); } } /** * Update the given Konto with the new Kontostand. */ public void update(Konto Konto, float Kontostand) { setStatus("Received AutoUpdate for Konto \"" + ((Bank) myAccountBankMap.get(meinAusgewaehltesKonto)).name() + ":" + Konto.KontoNummer() + "\""); // If the updated Konto is currently selected, update the // display immediately. if (Konto._is_equivalent(meinAusgewaehltesKonto)) { currentBalanceTextField.setText(String. valueOf(Kontostand)); }
599: } 600: 601: /** 602: * Add the given array of Banks to the Bank choice box and to 603: * this BankApplet's internal data structures. 604: */ 605: protected void addBanks(Bank[] banks) { 606: 607: for (int i = 0; i < banks.length; i++) { 608: String name = banks[i].name(); 609: meineBanken.put(name, banks[i]); 610: bankChoice.add(name); 611: } 612: if (meineAusgewaehlteBank == null && banks.length > 0) { 613: meineAusgewaehlteBank = banks[0]; 614: } 615: } 616: 617: /** 618: * Add the given array of Geldautomaten to the Geldautomat choice box and to 619: * this BankApplet's internal data structures. 620: */ 621: protected void hinzufuegenGeldautomaten(GELDAUTOMAT[] geldautomaten) { 622: 623: for (int i = 0; i < geldautomaten.length; i++) { 624: String name = geldautomaten[i].name(); 625: meineGeldautomaten.put(name, geldautomaten[i]); 626: geldautomatChoice.add(name); 627: } 628: if (meinAusgewaehlterGeldautomat == null && geldautomaten.length > 0) { 629: meinAusgewaehlterGeldautomat = geldautomaten[0]; 630: } 631: } 632: 633: /** 634: * Create a new Kunde with the given name, Social Security 635: * number, adresse, mother's maiden name, and PIN. 636: */ 637: protected void createCustomer(String name, String 638: PersonalAusweisNummer, String adresse, String 639: MuttersMaedchenName) { 640: 641: // Create a new Kunde and register it with the BOA.
642: setStatus("Creating new Kunde..."); 643: KundenImpl Kunde = new KundenImpl(name, 644: PersonalAusweisNummer, adresse, MuttersMaedchenName, 645: this); 646: meinBOA.obj_is_ready(Kunde); 647: meineKunden.put(name, Kunde); 648: customerChoice.add(name); 649: if (meinAusgewaehlterKunde == null) { 650: meinAusgewaehlterKunde = Kunde; 651: } 652: 653: // Now that there is a Kunde selected, the Konto 654: // operations can be enabled. 655: accountChoice.setEnabled(true); 656: newAccountButton.setEnabled(true); 657: 658: setStatus(null); 659: } 660: 661: /** 662: * Create a new Konto with the given owner, Konto type, and 663: * initial Kontostand. 664: */ 665: protected void erstelleKonto(Kunde owner, String type, float 666: anfangsSaldo) { 667: 668: setStatus("Creating new Konto..."); 669: Konto Konto = meineAusgewaehlteBank.erstelleKonto(owner, type, 670: anfangsSaldo); 671: Hashtable Konten = (Hashtable)meineKonten. 672: get(meinAusgewaehlterKunde); 673: 674: // If there doesn't exist an index of Konten for this 675: // Kunde already, create one. 676: if (Konten == null) { 677: Konten = new Hashtable(); 678: meineKonten.put(meinAusgewaehlterKunde, Konten); 679: } 680: String accountName = meineAusgewaehlteBank.name() + ":" + Konto. 681: KontoNummer(); 682: Konten.put(accountName, Konto); 683: accountChoice.add(accountName); 684: myAccountBankMap.put(Konto, meineAusgewaehlteBank); 685: 686: if (meinAusgewaehltesKonto == null) {
687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732:
meinAusgewaehltesKonto = Konto; } // Now that there is an Konto selected, the Konto // features can be enabled. Also update the Konto info // display. enableAccountFeatures(true); updateAccountInfo(); setStatus(null); } /** * Enable/disable the Konto-related features. */ protected void enableAccountFeatures(boolean enable) { autoUpdateButton.enable(enable); transactionTextField.enable(enable); depositButton.enable(enable); withdrawButton.enable(enable); updateAccountButton.enable(enable); if (!enable) { issuingBankTextField.setText(""); accountNumberTextField.setText(""); accountTypeTextField.setText(""); creationDateTextField.setText(""); currentBalanceTextField.setText(""); transactionTextField.setText(""); } } /** * Update the Konto information to correspond to the * currently selected Konto. */ protected void updateAccountInfo() { if (meinAusgewaehltesKonto == null) { return; } issuingBankTextField.setText(((Bank)myAccountBankMap. get(meinAusgewaehltesKonto)).name()); accountNumberTextField.setText(meinAusgewaehltesKonto. KontoNummer());
733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778:
if (CheckingAccountHelper.narrow(meinAusgewaehltesKonto) != null) { accountTypeTextField.setText("Checking"); } else { // If Konto is not a CheckingAccount, assume it is a // SavingsAccount accountTypeTextField.setText("Savings"); } creationDateTextField.setText(meinAusgewaehltesKonto. EroefffnungsDatum()); currentBalanceTextField.setText(String. valueOf(meinAusgewaehltesKonto.Kontostand())); } class SymAction implements java.awt.event.ActionListener { public void actionPerformed(java.awt.event.ActionEvent event) { Object object = event.getSource(); if (object == newCustomerButton) newCustomerButton_Action(event); else if (object == newAccountButton) newAccountButton_Action(event); else if (object == autoUpdateButton) autoUpdateButton_Action(event); else if (object == depositButton) depositButton_Action(event); else if (object == withdrawButton) withdrawButton_Action(event); else if (object == updateAccountButton) updateAccountButton_Action(event); } } void newCustomerButton_Action(java.awt.event.ActionEvent event) { Container theFrame = this; do { theFrame = theFrame.getParent(); } while ((theFrame != null) && !(theFrame instanceof Frame)); if (theFrame == null) theFrame = new Frame();
779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797: 798: 799: 800: 801: 802: 803: 804: 805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824:
CustomerDialog cd = new CustomerDialog((Frame)theFrame, true); cd.show(); if (cd.myOKPressed) { createCustomer(cd.nameTextField.getText(), cd. PersonalAusweisNummerTextField.getText(), cd. addressTextField.getText(), cd. MuttersMaedchenNameTextField.getText()); } } void newAccountButton_Action(java.awt.event.ActionEvent event) { Container theFrame = this; do { theFrame = theFrame.getParent(); } while ((theFrame != null) && !(theFrame instanceof Frame)); if (theFrame == null) theFrame = new Frame(); AccountDialog ad = new AccountDialog((Frame)theFrame, true); ad.show(); if (ad.myOKPressed) { erstelleKonto(meinAusgewaehlterKunde, ad.checkingRadio. getState() ? "checking" : "savings", new Float(ad.initialBalanceTextField.getText()). floatValue()); } } class SymItem implements java.awt.event.ItemListener { public void itemStateChanged(java.awt.event.ItemEvent event) { Object object = event.getSource(); if (object == bankChoice) bankChoice_ItemStateChanged(event); else if (object == geldautomatChoice) geldautomatChoice_ItemStateChanged(event); else if (object == customerChoice) customerChoice_ItemStateChanged(event); else if (object == accountChoice) accountChoice_ItemStateChanged(event);
825: } 826: } 827: 828: void bankChoice_ItemStateChanged(java.awt.event.ItemEvent 829: event) { 830: 831: if (event.getStateChange() == ItemEvent.SELECTED) { 832: meineAusgewaehlteBank = (Bank)meineBanken.get(bankChoice. 833: getSelectedItem()); 834: } 835: } 836: 837: void geldautomatChoice_ItemStateChanged(java.awt.event.ItemEvent event) 838: { 839: 840: if (event.getStateChange() == ItemEvent.SELECTED) { 841: meinAusgewaehlterGeldautomat = (Geldautomat)meineGeldautomaten.get(geldautomatChoice. 842: getSelectedItem()); 843: } 844: } 845: 846: void customerChoice_ItemStateChanged(java.awt.event.ItemEvent 847: event) { 848: 849: if (event.getStateChange() == ItemEvent.SELECTED) { 850: 851: // Update the Konto list to show the currently 852: // selected Kunde's Konten. 853: meinAusgewaehlterKunde = (Kunde)meineKunden. 854: get(customerChoice.getSelectedItem()); 855: accountChoice.removeAll(); 856: Hashtable Konten = (Hashtable)meineKonten. 857: get(meinAusgewaehlterKunde); 858: if (Konten != null) { 859: Enumeration e = Konten.keys(); 860: while (e.hasMoreElements()) { 861: accountChoice.add((String)e.nextElement()); 862: } 863: } 864: meinAusgewaehltesKonto = null; 865: enableAccountFeatures(false); 866: } 867: } 868:
869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899: 900: 901: 902: 903: 904: 905: 906: 907: 908: 909: 910: 911: 912: 913: 914:
void accountChoice_ItemStateChanged(java.awt.event.ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { Hashtable Konten = (Hashtable)meineKonten. get(meinAusgewaehlterKunde); meinAusgewaehltesKonto = (Konto)Konten. get(accountChoice.getSelectedItem()); enableAccountFeatures(true); updateAccountInfo(); } } void autoUpdateButton_Action(java.awt.event.ActionEvent event) { setStatus("Requesting AutoUpdate service for selected " + "Konto..."); try { ((Bank)myAccountBankMap.get(meinAusgewaehltesKonto)). anfordernAktualisierungsDienst(meinAusgewaehltesKonto); } catch (UngueltigesKontoException ex) { setStatus("Could not request AutoUpdate service for " + "selected Konto: InvalidAcccountException"); } setStatus(null); } void depositButton_Action(java.awt.event.ActionEvent event) { String Betrag = transactionTextField.getText(); setStatus("Depositing $" + Betrag + "..."); float newBalance = 0.0f; try { newBalance = meinAusgewaehltesKonto.Einzahlung(new Float(Betrag).floatValue()); } catch (UngueltigerBetragException ex) { setStatus("Could not perform transaction: " + "UngueltigerBetragException"); return; } catch (NumberFormatException ex) { setStatus("Could not perform transaction: " + "NumberFormatException"); return; } currentBalanceTextField.setText(String.
915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926: 927: 928: 929: 930: 931: 932: 933: 934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952:
valueOf(newBalance)); setStatus("Deposited $" + Betrag + " into selected " + "Konto."); } void withdrawButton_Action(java.awt.event.ActionEvent event) { String Betrag = transactionTextField.getText(); setStatus("Withdrawing $" + Betrag + "..."); float newBalance = 0.0f; try { newBalance = meinAusgewaehltesKonto.Abhebung(new Float(Betrag).floatValue()); } catch (UngueltigerBetragException ex) { setStatus("Could not perform transaction: " + "UngueltigerBetragException"); return; } catch (UnzureichendeGeldmittelException ex) { setStatus("Could not perform transaction: " + "UnzureichendeGeldmittelException"); return; } catch (NumberFormatException ex) { setStatus("Could not perform transaction: " + "NumberFormatException"); return; } currentBalanceTextField.setText(String. valueOf(newBalance)); setStatus("Withdrew $" + Betrag + " from selected " + "Konto."); } void updateAccountButton_Action(java.awt.event.ActionEvent event) { updateAccountInfo(); } }
Werfen Sie nun einen Blick auf ausgewählte Partien von BankApplet.java. Die drei importAnweisungen in den Zeilen 3-5 importieren die Definitionen verschiedener GUI-verwandter JavaKlassen. Die Package java.awt und verwandte Packages enthalten die Klassen und Schnittstellen, aus denen sich das Abstract Windowing Toolkit (AWT) zusammensetzt. Bei dem AWT handelt es sich um eine Sammlung von Benutzerschnittstellenobjekten und dem Ereignisbehandlungsmechanismus, der es diesen Objekten ermöglicht, miteinander zu interagieren. Anwendungen und Bibliotheken können auf den AWT-Kernklassen aufbauen, um komplexere Benutzerschnittstellenobjekte und ganze
Benutzerschnittstellen zu erstellen. Die Package java.util beinhaltet eine Reihe von Dienstprogrammklassen und -schnittstellen. Eine Schnittstelle ist Enumeration (in Zeile 6 importiert), welche die Iteration über Collections (etwa Vector, Dictionary, Hashtable usw.) ermöglicht, ähnlich wie ein C++-Standard Template Library- (STL-) Iterator. Die Klasse Hashtable (importiert in Zeile 7) implementiert, wie ihr Name ahnen läßt, eine Hash-Tabelle (eine Datenstruktur, die eine Objektinstanz auf einer anderen abbildet). Die imports in den Zeilen 9 und 10 sollten Ihnen vertraut vorkommen. Da BankApplet eine CORBA-Anwendung ist, muß es auf die vertrauten ORB und BOA von CORBA zugreifen können. Wie zuvor GeldautomatClient, verwendet BankApplet die früheren CORBA-Objekte und Exceptions. (Erinnern Sie sich daran, daß Sie all diese Definitionen in der Package idlGlobal plaziert haben, als Sie den IDL-Compiler an Tag 13 ausführten.) Die Zeilen 12-22 importieren diese Klassen in die Anwendung. Beachten Sie in den Zeilen 24 und 25, daß die Klasse BankApplet deklariert wurde, um die Schnittstelle KontoUpdateListener zu implementieren. Das bedeutet, daß die Klasse BankApplet in der Lage sein muß, die Methode update() dieser Schnittstelle zu implementieren, und Sie werden später feststellen, daß dies tatsächlich der Fall ist. Es bedeutet außerdem, daß ein BankApplet-Objekt als Parameter für jede Methode verwendet werden kann, die ein KontoUpdateListener als Parameter erfordert. Aus verschiedenen Gründen verfolgt BankApplet die augenblicklich gewählten Bank, Geldautomat, Kunde und Konto. Die Definitionen dieser Elementvariablen sind in den Zeilen 3338 zu sehen. BankApplet muß außerdem verfolgen, welche Bank-Namen welchen Bank-Objekten entsprechen, welche Geldautomat-Namen welchen Geldautomat-Objekten entsprechen usw. Auf diese Weise kann der Benutzer jedes dieser Objekte aus einer Liste von Namen auswählen, und das Applet wird außerdem das tatsächliche Objekt anhand seines Namens feststellen. Die Klasse Hashtable ist für diesen Zweck ideal geeignet; in den meisten Fällen der Zeilen 40-58 bildet es Strings auf den jeweiligen Objekttyp ab. Der größte Teil der Methode init()ist aus dieser Diskussion ausgelassen, da sie automatisch von Visual Café generiert wurde. Es wurde jedoch einiges hinzugefügt, etwa der Quelltext in den Zeilen 491-493. Dies illustriert eine zweite Methode der Initialisierung des ORB. Erinnern Sie sich, daß die erste Methode die Befehlszeilenparameter der Java-Anwendung verwendete. Da ein Java-Applet keine Befehlszeilenparameter hat, wird eine andere Form der Initialisierung verwendet. Diese Form von ORB.init() erhält ein Applet als Parameter. Vom Applet erhält der ORB seine Konfigurationsinformationen.
Denken Sie daran, daß es sich bei der im Beispiel verwendeten Methode ORB.init() um eine Funktion von VisiBroker for Java handelt. Andere Produkte können eine andere Methode zur Initialisierung des ORB verwenden (und das tun sie fast sicher).
Das Stück Quelltext in den Zeilen 495-507 dürfte vertraut erscheinen, mit einem kleinen Dreh jedoch. Anstatt System.out.println() zu verwenden, ruft der Quelltext die Methode setStatus() auf. Sie werden später genau sehen, was diese Methode tut, doch zunächst brauchen Sie nur zu wissen, daß die Methode eine Statusmeldung an den Benutzer zurückgibt. In den Zeilen 509-533 befindet sich der vertraute Quelltextabschnitt, der die verfügbaren Bank- und Geldautomat-Objekte enthält. Wenn diese Listen erhalten werden, werden die Methoden addBanks() und hinzufuegenGeldautomaten() aufgerufen. Wiederum werden Sie sogleich sehen, was diese Methoden tun. Die Methode setStatus() (Zeilen 571-582), auf die früher bereits angespielt wurde, erhält einen String als Parameter. Falls der String gleich null ist, wird eine Standardstatusbotschaft ausgegeben (in diesem Fall "BankApplet bereit."); ansonsten wird der übergebene String angezeigt. Die genauere Untersuchung ergibt, daß die Statusbotschaft im Objekt statusTextField ausgegeben wird, jenem Textfeld, das sich bis zum unteren Rand des BankApplet-Fensters erstreckt. Erinnern Sie sich, daß BankApplet erforderlich war, um die Methode update() als Teil der Implementierung der Schnittstelle KontoUpdateListener zu implementieren. Die Implementierung, die in den Zeilen 584-599 zu sehen ist, gibt eine Statusbotschaft aus (über die Methode setStatus() - was sonst), und falls die Informationen für das aktualisierte Konto gerade angezeigt werden, so wird die Anzeige erneuert. Die Methode addBanks()(Zeilen 601-615), die relativ früh in init() aufgerufen wird, fügt die Namen der übergebenen Bank-Objekte zum Auswahlfeld hinzu, das die Namen enthält. Diese Namen werden, ebenso wie die Banken selbst, zur Hash-Tabelle meineBanken hinzugefügt, so daß die Banken später über ihren Namen angesprochen werden können. hinzufuegenGeldautomaten() ist ähnlich, außer daß es mit Geldautomaten arbeitet. Die Methode createCustomer() in den Zeilen 633-659 stellt ähnliche Funktionen zur Verfügung wie die des vorigen Beispiels GeldautomatClient. Beachten Sie besonders, daß die Methode ein neues KundenImpl-Objekt erstellt und das Objekt beim BOA anmeldet, wofür sie die Methode obj_is_ready(). verwendet. Über das Leisten dieser vertrauten Funktionalität hinaus fügt createCustomer() den Kunde der internen Hash-Tabelle für spätere Abfragen hinzu. Abschließend aktiviert die Methode Teile der Benutzerschnittstelle, die zuvor möglicherweise
deaktiviert waren. Beispielsweise ist es nicht sinnvoll, ein Konto zu erzeugen, bevor ein Kunde angelegt wurde, der mit diesem Konto verknüpft werden kann. Daher sind die Funktionen zum Erstellen eines Kontos deaktiviert, bis ein Kunde gewählt ist. Das Erstellen eines Kontos ist stärker beteiligt, wie die längere Methode erstelleKonto()in den Zeilen 661-697 zeigt. Das Anlegen des Kontos besteht nur aus dem Aufruf der Methode erstelleKonto() für die momentan gewählte Bank, doch ist ein wenig mehr Arbeit mit dem Verknüpfen des neu erstellten Kontos mit dem momentan gewählten Kunden verbunden. Das liegt daran, daß eine zweistufige Hierarchie von Hash-Tabellen (Hashtables) existiert, die Kunden mit ihren Konten in Verbindung bringt. Außerdem basiert die Hashtable von Konten auf der Konkatenation des Namens der ausstellenden Bank mit der Kontonummer, anstatt nur auf der Kontonummer allein (da zwei verschiedene Banken die gleiche Kontonummer ausgeben können, die Schlüssel jedoch eindeutig sein müssen). Abbildung 14.3 illustriert die Beziehung zwischen Kunden und ihren Konten. Wenn schließlich ein Konto gewählt wird, können verschiedene Funktionen aktiviert werden, etwa die Transaktionsfunktionen für Konto. Dies wird von der Methode enableKontoFeatures() bewerkstelligt, die als nächste beschrieben wird.
Abbildung 14.3: Beziehungen zwischen Kunden und Konten Die Methode enableKontoFeatures() (Zeilen 699-717) aktiviert einige Konto-verwandte Benutzerschnittstellenkomponenten, falls ihr Parameter true ist, und deaktiviert sie, falls der Parameter false ist. Zusätzlich werden, falls der Parameter false ist, die Informationen über Konto enthaltenden Textfelder gelöscht. Die Methode updateKontoInfo() (Zeilen 719-746) aktualisiert die Anzeige mit den aktuellen Informationen über das ausgewählte Konto, so durch Ausgabe des Namens der Bank, der Kontonummer, des Typs, des Erstellungsdatums und des aktuellen Kontostandes. Beachten Sie in Zeile 733, daß der Typ von Konto unter Verwendung der Operation narrow() ermittelt wird. In
diesem Fall liefert GiroKontoHelper.narrow() ein GiroKonto, falls es sich bei Konto tatsächlich um ein GiroKonto handelt; ansonsten gibt die Operation null zurück. (Das ist CORBAs Version dessen, was allgemein als Runtime Type Information oder RTTI bezeichnet wird.) Die Methode newCustomerButton_Action(), die in den Zeilen 768-788 zu sehen ist, wird aufgerufen, wenn die Schaltfläche New Kunde betätigt wird. Im wesentlichen erzeugt sie ein neues CustomerDialog-Fenster (das Sie später im Kapitel sehen werden) und ruft createCustomer() mit den von diesem Dialog zurückgegebenen Informationen auf. Die Methode newKontoButton_Action() arbeitet auf sehr ähnliche Weise. Die Methode bankChoice_ItemStateChanged(), die Sie in den Zeilen 828-835 sehen können, wird aufgerufen, wenn ein Element im Auswahlfeld Bank gewählt wird. Es bringt den Namen der Bank in der jeweiligen Hashtable in Erfahrung, um festzustellen, welches Bank-Objekt diesem Namen entspricht, und setzt dann die momentan gewählte Bank auf dieses Objekt. Die Methode geldautomatChoice_ItemStateChanged() verhält sich in ähnlicher Weise. Das tun auch die Methoden customerChoice_ItemStateChanged() und KontoChoice_ ItemStateChanged(), wenn auch letztere beide Methoden zusätzliche Aufgaben ausführen, wenn ein neues Element gewählt wird, wie etwa die Aktualisierung der Anzeige mit neuen Informationen. Im Falle der Auswahl eines Kunden werden die Konten dieses Kunden im Auswahlfeld Konto angezeigt. Im Falle der Auswahl eines Kontos wird die Anzeige von Konto-Informationen mit dem neu gewählten Konto aktualisiert. Die übrigen BankApplet-Methoden sind klar: autoUpdateButton_Action() ruft die Methode anfordernAktualisierungsDienst() der jeweiligen Bank für das gewählte Konto auf, depositButton_Action() ruft Einzahlung() für das gewählte Konto mit dem im Transaktionstextfeld angegebenen Betrag auf, withdrawButton_Action() ruft Abhebung() auf, und updateKontoButton_Action() ruft updateKontoInfo() auf. Dialogfelder von BankApplet Neben dem Hauptfenster von BankApplet existieren zwei weitere Dialogfelder, die vom Applet verwendet werden. Das erste ist CustomerDialog zur Eingabe von Informationen über einen neuen Kunden. Das zweite ist KontoDialog zur Eingabe von Informationen über ein neues Konto. Das Layout von CustomerDialog ist in Abbildung 14.4 gezeigt, die zugehörige Hierarchie von Schnittstellenobjekten ist in Abbildung 14.5 zu sehen.
Abbildung 14.4: Fenster KundenDialog
Abbildung 14.5: Die Fensterhierarchie von KundenDialog Wie BankApplet.java wird der größte Teil von CustomerDialog.java vom GUI-Builder generiert. Listing 14.4 zeigt CustomerDialog.java in ganzer Länge. Dem auf das Listing folgenden Abschnitt können Sie die reale Funktionalität dahinter entnehmen. Listing 14.4: CustomerDialog.java. 1: 2: 3: 4: 5: 6: 7: 8:
// CustomerDialog.java import java.awt.*; public class CustomerDialog extends Dialog { // Set to true if this dialog was completed by pressing OK. public boolean myOKPressed = false;
9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:
public CustomerDialog(Frame parent, boolean modal) { super(parent, modal); //{{INIT_CONTROLS GridBagLayout gridBagLayout; gridBagLayout = new GridBagLayout(); setLayout(gridBagLayout); setVisible(false); setSize(insets().left + insets().right + 435, insets().top + insets().bottom + 110); setBackground(new Color(-4144944)); nameTextField = new java.awt.TextField(); nameTextField.setBounds(insets().left + 148, insets().top + 3, 218, 23); GridBagConstraints gbc; gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(nameTextField, gbc); add(nameTextField); PersonalAusweisNummerTextField = new java.awt.TextField(); PersonalAusweisNummerTextField.setBounds(insets().left + 148, insets().top + 30, 218, 23); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(PersonalAusweisNummerTextField, gbc); add(PersonalAusweisNummerTextField); addressTextField = new java.awt.TextField(); addressTextField.setBounds(insets().left + 148, insets(). top + 57, 218, 23); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 2; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL;
55: gbc.insets = new Insets(2, 2, 2, 2); 56: ((GridBagLayout)getLayout()). 57: setConstraints(addressTextField, gbc); 58: add(addressTextField); 59: MuttersMaedchenNameTextField = new java.awt.TextField(); 60: MuttersMaedchenNameTextField.setBounds(insets().left + 148, 61: insets().top + 84, 218, 23); 62: gbc = new GridBagConstraints(); 63: gbc.gridx = 1; 64: gbc.gridy = 3; 65: gbc.weightx = 1.0; 66: gbc.fill = GridBagConstraints.HORIZONTAL; 67: gbc.insets = new Insets(2, 2, 2, 2); 68: ((GridBagLayout)getLayout()). 69: setConstraints(MuttersMaedchenNameTextField, gbc); 70: add(MuttersMaedchenNameTextField); 71: okButton = new java.awt.Button(); 72: okButton.setActionCommand("button"); 73: okButton.setLabel("OK"); 74: okButton.setBounds(insets().left + 370, insets().top + 3, 75: 63, 23); 76: gbc = new GridBagConstraints(); 77: gbc.gridx = 2; 78: gbc.gridy = 0; 79: gbc.anchor = GridBagConstraints.NORTHEAST; 80: gbc.fill = GridBagConstraints.HORIZONTAL; 81: gbc.insets = new Insets(2, 2, 2, 2); 82: ((GridBagLayout)getLayout()).setConstraints(okButton, gbc); 83: add(okButton); 84: okButton.setEnabled(false); 85: cancelButton = new java.awt.Button(); 86: cancelButton.setActionCommand("button"); 87: cancelButton.setLabel("Cancel"); 88: cancelButton.setBounds(insets().left + 370, insets().top + 89: 30, 63, 23); 90: gbc = new GridBagConstraints(); 91: gbc.gridx = 2; 92: gbc.gridy = 1; 93: gbc.anchor = GridBagConstraints.NORTHEAST; 94: gbc.fill = GridBagConstraints.HORIZONTAL; 95: gbc.insets = new Insets(2, 2, 2, 2); 96: gbc.ipadx = 10; 97: ((GridBagLayout)getLayout()).setConstraints(cancelButton, 98: gbc); 99: add(cancelButton); 100: nameLabel = new java.awt.Label("Name");
101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146:
nameLabel.setBounds(insets().left + 2, insets().top + 3, 48, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(nameLabel, gbc); add(nameLabel); PersonalAusweisNummerLabel = new java.awt. Label("Social Security Number"); PersonalAusweisNummerLabel.setBounds(insets().left + 2, insets().top + 30, 142, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(PersonalAusweisNummerLabel, gbc); add(PersonalAusweisNummerLabel); addressLabel = new java.awt.Label("Adresse"); addressLabel.setBounds(insets().left + 2, insets().top + 57, 60, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(addressLabel, gbc); add(addressLabel); MuttersMaedchenNameLabel = new java.awt. Label("Mother's Maiden Name"); MuttersMaedchenNameLabel.setBounds(insets().left + 2, insets().top + 84, 140, 23); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 3; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2);
147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192:
((GridBagLayout)getLayout()). setConstraints(MuttersMaedchenNameLabel, gbc); add(MuttersMaedchenNameLabel); setTitle("New Kunde Information"); //}} //{{REGISTER_LISTENERS SymWindow aSymWindow = new SymWindow(); this.addWindowListener(aSymWindow); SymAction lSymAction = new SymAction(); okButton.addActionListener(lSymAction); cancelButton.addActionListener(lSymAction); SymText lSymText = new SymText(); nameTextField.addTextListener(lSymText); PersonalAusweisNummerTextField.addTextListener(lSymText); addressTextField.addTextListener(lSymText); MuttersMaedchenNameTextField.addTextListener(lSymText); //}} } public void addNotify() { // Record the size of the window prior to calling parent's // addNotify. Dimension d = getSize(); super.addNotify(); if (fComponentsAdjusted) return; // Adjust components according to the insets setSize(insets().left + insets().right + d.width, insets(). top + insets().bottom + d.height); Component components[] = getComponents(); for (int i = 0; i < components.length; i++) { Point p = components[i].getLocation(); p.translate(insets().left, insets().top); components[i].setLocation(p); } fComponentsAdjusted = true; } // Used for addNotify check. boolean fComponentsAdjusted = false;
193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238:
public CustomerDialog(Frame parent, String title, boolean modal) { this(parent, modal); setTitle(title); } public synchronized void show() { Rectangle bounds = getParent().bounds(); Rectangle abounds = bounds(); move(bounds.x + (bounds.width - abounds.width) / 2, bounds.y + (bounds.height - abounds.height) /2); super.show(); } //{{DECLARE_CONTROLS java.awt.TextField nameTextField; java.awt.TextField PersonalAusweisNummerTextField; java.awt.TextField addressTextField; java.awt.TextField MuttersMaedchenNameTextField; java.awt.Button okButton; java.awt.Button cancelButton; java.awt.Label nameLabel; java.awt.Label PersonalAusweisNummerLabel; java.awt.Label addressLabel; java.awt.Label MuttersMaedchenNameLabel; //}} class SymWindow extends java.awt.event.WindowAdapter { public void windowClosing(java.awt.event.WindowEvent event) { Object object = event.getSource(); if (object == CustomerDialog.this) Dialog1_WindowClosing(event); } } void Dialog1_WindowClosing(java.awt.event.WindowEvent event) { hide(); }
239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284:
class SymAction implements java.awt.event.ActionListener { public void actionPerformed(java.awt.event.ActionEvent event) { Object object = event.getSource(); if (object == okButton) okButton_Action(event); else if (object == cancelButton) cancelButton_Action(event); } } void okButton_Action(java.awt.event.ActionEvent event) { myOKPressed = true; setVisible(false); } void cancelButton_Action(java.awt.event.ActionEvent event) { setVisible(false); } class SymText implements java.awt.event.TextListener { public void textValueChanged(java.awt.event.TextEvent event) { Object object = event.getSource(); if (object == nameTextField) nameTextField_TextValueChanged(event); else if (object == PersonalAusweisNummerTextField) PersonalAusweisNummerTextField_TextValueChanged (event); else if (object == addressTextField) addressTextField_TextValueChanged(event); else if (object == MuttersMaedchenNameTextField) MuttersMaedchenNameTextField_TextValueChanged(event); } } void nameTextField_TextValueChanged(java.awt.event.TextEvent event) {
285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321:
okButton.setEnabled((nameTextField.getText().length() > 0) && (PersonalAusweisNummerTextField.getText(). length() > 0) && (addressTextField.getText(). length() > 0) && (MuttersMaedchenNameTextField. getText().length() > 0)); } void PersonalAusweisNummerTextField_TextValueChanged(java.awt. event.TextEvent event) { okButton.setEnabled((nameTextField.getText().length() > 0) && (PersonalAusweisNummerTextField.getText(). length() > 0) && (addressTextField.getText(). length() > 0) && (MuttersMaedchenNameTextField. getText().length() > 0)); } void addressTextField_TextValueChanged(java.awt.event.TextEvent event) { okButton.setEnabled((nameTextField.getText().length() > 0) && (PersonalAusweisNummerTextField.getText(). length() > 0) && (addressTextField.getText(). length() > 0) && (MuttersMaedchenNameTextField. getText().length() > 0)); } void MuttersMaedchenNameTextField_TextValueChanged(java.awt. event.TextEvent event) { okButton.setEnabled((nameTextField.getText().length() > 0) && (PersonalAusweisNummerTextField.getText(). length() > 0) && (addressTextField.getText(). length() > 0) && (MuttersMaedchenNameTextField. getText().length() > 0)); } }
Lassen Sie uns nun prüfen, was unter der Kappe von CustomerDialog.java geschieht. Wird die Schaltfläche OK betätigt, so wird okButton_Action() (Zeilen 253-257) aufgerufen. Diese Methode blendet CustomerDialog aus und setzt ein Flag, um anzugeben, daß der Dialog durch Betätigen der Schaltfläche OK geschlossen wurde (BankApplet prüft dieses Flag, um festzustellen, ob die Informationen von CustomerDialog verarbeitet werden sollen). cancelButton_Action() (Zeilen 259-262) verhält sich ähnlich, setzt jedoch dieses Flag nicht.
Ändert sich das Textfeld Kunde (mit anderen Worten, wenn der Benutzer eine Taste in diesem Feld betätigt), so wird die Methode nameTextField_TextValueChanged() (Zeilen 282-290) aufgerufen. Die Methode deaktiviert die Schaltfläche OK, sofern nicht ein Wert in allen Textfeldern eingegeben wurde (das heißt, die Länge des Textes in jedem Feld ist größer als Null), in welchem Fall die Schaltfläche OK aktiviert wird. (Die Schaltfläche Cancel ist immer aktiviert und kann daher jederzeit betätigt werden.) Jedes Textfeld hat eine Methode, die sich ähnlich verhält: PersonalAusweisNummerTextField_TextValueChanged() (Zeilen 292-300), adresseTextField_TextValueChanged() (Zeilen 302-310) und MuttersMaedchenName TextField_TextValueChanged() (Zeilen 312-320) bilden alle dieses Verhalten nach. Die nächste Komponente von BankApplet ist KontoDialog, das in Abbildung 14.6 illustriert ist und dessen Fensterhierarchie in Abbildung 14.7 zu sehen ist. KontoDialog.java, das (Sie werden es erraten haben) größtenteils aus generiertem Quelltext besteht, erscheint in Listing 14.5.
Abbildung 14.6: Fenster KontoDialog
Abbildung 14.7: Die Fensterhierarchie von KontoDialog
Listing 14.5: KontoDialog.java. 1: // AccountDialog.java 2: 3: import java.awt.*; 4: 5: public class AccountDialog extends Dialog { 6: 7: // Set to true if this dialog was completed by pressing OK. 8: public boolean myOKPressed = false; 9: 10: public AccountDialog(Frame parent, boolean modal) { 11: 12: super(parent, modal); 13: 14: //{{INIT_CONTROLS 15: GridBagLayout gridBagLayout; 16: gridBagLayout = new GridBagLayout(); 17: setLayout(gridBagLayout); 18: setVisible(false); 19: setSize(insets().left + insets().right + 277, insets().top 20: + insets().bottom + 124); 21: setBackground(new Color(-4144944)); 22: Group1 = new CheckboxGroup(); 23: checkingRadio = new java.awt.Checkbox("Checking", Group1, 24: true); 25: checkingRadio.setBounds(insets().left + 2, insets().top + 26: 29, 82, 23); 27: GridBagConstraints gbc; 28: gbc = new GridBagConstraints(); 29: gbc.gridx = 0; 30: gbc.gridy = 1; 31: gbc.anchor = GridBagConstraints.WEST; 32: gbc.fill = GridBagConstraints.NONE; 33: gbc.insets = new Insets(2, 2, 2, 2); 34: ((GridBagLayout)getLayout()).setConstraints(checkingRadio, 35: gbc); 36: add(checkingRadio); 37: savingsRadio = new java.awt.Checkbox("Savings", Group1, 38: false); 39: savingsRadio.setBounds(insets().left + 2, insets().top + 40: 56, 74, 23); 41: gbc = new GridBagConstraints(); 42: gbc.gridx = 0; 43: gbc.gridy = 2; 44: gbc.anchor = GridBagConstraints.WEST;
45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:
gbc.fill = GridBagConstraints.NONE; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(savingsRadio, gbc); add(savingsRadio); initialBalanceTextField = new java.awt.TextField(); initialBalanceTextField.setBounds(insets().left + 122, insets().top + 99, 86, 23); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 3; gbc.weightx = 1.0; gbc.anchor = GridBagConstraints.SOUTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()). setConstraints(initialBalanceTextField, gbc); add(initialBalanceTextField); okButton = new java.awt.Button(); okButton.setActionCommand("button"); okButton.setLabel("OK"); okButton.setBounds(insets().left + 212, insets().top + 2, 63, 23); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 0; gbc.anchor = GridBagConstraints.NORTHEAST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); ((GridBagLayout)getLayout()).setConstraints(okButton, gbc); add(okButton); cancelButton = new java.awt.Button(); cancelButton.setActionCommand("button"); cancelButton.setLabel("Cancel"); cancelButton.setBounds(insets().left + 212, insets().top + 29, 63, 23); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 1; gbc.anchor = GridBagConstraints.NORTHEAST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(2, 2, 2, 2); gbc.ipadx = 10; ((GridBagLayout)getLayout()).setConstraints(cancelButton, gbc); add(cancelButton);
91: accountTypeLabel = new java.awt.Label("Konto Type"); 92: accountTypeLabel.setBounds(insets().left + 2, insets().top 93: + 2, 86, 23); 94: gbc = new GridBagConstraints(); 95: gbc.gridx = 0; 96: gbc.gridy = 0; 97: gbc.anchor = GridBagConstraints.WEST; 98: gbc.fill = GridBagConstraints.NONE; 99: gbc.insets = new Insets(2, 2, 2, 2); 100: ((GridBagLayout)getLayout()). 101: setConstraints(accountTypeLabel, gbc); 102: add(accountTypeLabel); 103: initialBalanceLabel = new java.awt.Label("Initial Kontostand"); 104: initialBalanceLabel.setBounds(insets().left + 2, insets(). 105: top + 99, 91, 23); 106: gbc = new GridBagConstraints(); 107: gbc.gridx = 0; 108: gbc.gridy = 3; 109: gbc.weighty = 1.0; 110: gbc.anchor = GridBagConstraints.SOUTHWEST; 111: gbc.fill = GridBagConstraints.NONE; 112: gbc.insets = new Insets(2, 2, 2, 2); 113: ((GridBagLayout)getLayout()). 114: setConstraints(initialBalanceLabel, gbc); 115: add(initialBalanceLabel); 116: dollarLabel = new java.awt.Label("$", Label.RIGHT); 117: dollarLabel.setBounds(insets().left + 97, insets().top + 118: 99, 21, 23); 119: gbc = new GridBagConstraints(); 120: gbc.gridx = 1; 121: gbc.gridy = 3; 122: gbc.anchor = GridBagConstraints.SOUTHEAST; 123: gbc.fill = GridBagConstraints.NONE; 124: gbc.insets = new Insets(2, 2, 2, 2); 125: ((GridBagLayout)getLayout()).setConstraints(dollarLabel, 126: gbc); 127: add(dollarLabel); 128: setTitle("New Konto Information"); 129: //}} 130: 131: //{{REGISTER_LISTENERS 132: SymWindow aSymWindow = new SymWindow(); 133: this.addWindowListener(aSymWindow); 134: SymAction lSymAction = new SymAction(); 135: okButton.addActionListener(lSymAction); 136: cancelButton.addActionListener(lSymAction);
137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182:
//}} } public void addNotify() { // Record the size of the window prior to calling parent's // addNotify. Dimension d = getSize(); super.addNotify(); if (fComponentsAdjusted) return; // Adjust components according to the insets setSize(insets().left + insets().right + d.width, insets(). top + insets().bottom + d.height); Component components[] = getComponents(); for (int i = 0; i < components.length; i++) { Point p = components[i].getLocation(); p.translate(insets().left, insets().top); components[i].setLocation(p); } fComponentsAdjusted = true; } // Used for addNotify check. boolean fComponentsAdjusted = false; public AccountDialog(Frame parent, String title, boolean modal) { this(parent, modal); setTitle(title); } public synchronized void show() { Rectangle bounds = getParent().bounds(); Rectangle abounds = bounds(); move(bounds.x + (bounds.width - abounds.width) / 2, bounds.y + (bounds.height - abounds.height) / 2); super.show(); }
183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228:
//{{DECLARE_CONTROLS java.awt.Checkbox checkingRadio; CheckboxGroup Group1; java.awt.Checkbox savingsRadio; java.awt.TextField initialBalanceTextField; java.awt.Button okButton; java.awt.Button cancelButton; java.awt.Label accountTypeLabel; java.awt.Label initialBalanceLabel; java.awt.Label dollarLabel; //}} class SymWindow extends java.awt.event.WindowAdapter { public void windowClosing(java.awt.event.WindowEvent event) { Object object = event.getSource(); if (object == AccountDialog.this) Dialog1_WindowClosing(event); } } void Dialog1_WindowClosing(java.awt.event.WindowEvent event) { hide(); } class SymAction implements java.awt.event.ActionListener { public void actionPerformed(java.awt.event.ActionEvent event) { Object object = event.getSource(); if (object == okButton) okButton_Action(event); else if (object == cancelButton) cancelButton_Action(event); } } void okButton_Action(java.awt.event.ActionEvent event) { myOKPressed = true; setVisible(false);
229: 230: 231: 232: 233: 234: 235:
} void cancelButton_Action(java.awt.event.ActionEvent event) { setVisible(false); } }
Da KontoDialog noch einfacher ist als CustomerDialog, erfordert seine Betrachtung überhaupt keine Zeit. Die Methoden okButton_Action() (Zeilen 225-229) und cancelButton_Action() (Zeilen 231-234) verhalten sich genau wie ihre Gegenstücke in CustomerDialog; sämtlicher weiterer Quelltext wird vom GUI-Builder erzeugt. Die endgültige Komponente von BankApplet (oder eines jeden Applets in diesem Zusammenhang) ist eine HTML-Datei, die ein <APPLET>-Tag enthält. Die HTML-Datei sollte sich im selben Verzeichnis befinden wie die Datei BankApplet.class, die vom Java-Compiler ausgegeben wird. Im Falle von BankApplet kann dies eine sehr einfache HTML-Datei sein wie in Listing 14.6. Listing 14.6: BankApplet.html. 1: 2: 3: <TITLE>BankApplet, a sample CORBA applet 4: 5: 6: <APPLET CODE="BankApplet.class" WIDTH=404 HEIGHT=327> 7: 9: 10: 11: Beachten Sie besonders drei Aspekte des <APPLET>-Tags in der Datei BankApplet.html. Beachten Sie als erstes den Parameter CODE="BankApplet.class" in Zeile 6. Damit wird die Datei angegeben, in welcher der Hauptteil des Applets residiert (mit anderen Worten, die von java.awt.applet.Applet abgeleitete Klasse). Beachten Sie als nächstes die Parameter WIDTH und HEIGHT, ebenfalls in Zeile 6. Diese geben die Anfangsgröße des Applet-Fensters an. Beachten Sie schließlich den Parameter in den Zeilen 7 und 8. Dieser Parameter, der für VisiBroker für Java 3.0 von Visigenic gedacht ist, teilt dem Browser mit, daß er die zur Verfügung gestellten VisiBroker-Klassen anstelle der eingebauten Klassen verwenden soll (für Browser, die wie der Communicator VisiBroker enthalten).
Ausführen des Applets Wie zuvor erläutert, wurden die mit BankApplet verwendeten Server-Komponenten gegenüber den mit der Java-Version von GeldautomatClient verwendeten nicht verändert. Sie können bei Tag 13 nachsehen, um auf den Prozeß zum Starten von BankServer, Bank und Geldautomat zurückzublicken. Um die Dinge interessant zu machen, möchten Sie wahrscheinlich mehr als eine Bank und einen Geldautomaten starten, so daß Sie sehen können, wie die Auswahlfenster funktionieren, wenn Sie BankApplet ausführen. Wenn die Server-Komponenten am Laufen sind, müssen Sie möglicherweise auch andere Komponenten ausführen, abhängig davon, welches CORBA-Produkt Sie verwenden. Um es Applets zu ermöglichen, Verbindungen zu anderen Hosts herzustellen, enthält beispielsweise VisiBroker von Visigenic den GateKeeper, der als Gateway zwischen Applets und CORBA-Servern fungiert. Falls Sie VisiBroker verwenden, ist das Starten von GateKeeper äußerst einfach: GateKeeper GateKeeper antwortet mit einer Ausgabe ähnlich der folgenden: VisiBroker Developer for Java [03.00.00.C3.05] (SEP 08 1997 16:55:51) IIOP GateKeeper started: Mon Nov 03 01:50:51 GMT+00:00 1997 Java: Version 1.1.4 from Sun Microsystems Inc. OS: Windows 95 version 4.0; CPU: x86 Adding search path: . Adding search path: C:\Bin\Devel\Java\VCafe\BIN\COMPONENTS\ SYMBEANS.JAR Adding search path: C:\Bin\Devel\Java\VCafe\JAVA\LIB Adding search path: D:\Bin\Devel\VisiBroker\lib\vbj30.jar Adding search path: . Adding search path: D:\Bin\Devel\TeachYourselfCORBA\BankExample\ ch14 Adding search path: D:\Bin\Devel\TeachYourselfCORBA\BankExample\ ch14\Kunde Writing IOR to D:\Bin\Devel\TeachYourselfCORBA\BankExample\ch14\ BankApplet\gatekeeper.ior Die tatsächliche Ausgabe hängt von Ihrer Einstellung für CLASSPATH ab, doch wenn Sie etwas erhalten, das dem Obigen ähnelt, dann sind Sie bereit, BankApplet auszuführen. Sie haben zwei Optionen zur Ausführung von BankApplet: Verwendung der im Java Developer's Kit enthaltenen Anwendung appletviewer oder die Verwendung eines Java-fähigen WebBrowsers wie Netscape Navigator, Netscape Communicator, Microsoft Internet Explorer usw. Nun werden Sie erfahren, wie das Applet in beiden Umgebungen ausgeführt wird.
Verwenden von appletviewer Wie zuvor erwähnt, handelt es sich bei appletviewer um ein mit dem Java Development Kit (JDK) von Sun geliefertes Dienstprogramm, das in den Java-Entwicklungsprodukten enthalten ist. Der Zweck von appletviewer besteht, wie der Name andeutet, im Ausführen von Applets. Um BankApplet in appletviewer auszuführen, stellen Sie zunächst sicher, daß sich appletviewer in Ihrem Pfad PATH befindet. (Falls Sie das JDK oder ein anderes JavaEntwicklungswerkzeug zuvor installiert haben, stehen die Chancen gut, daß Ihr PATH bereits richtig konfiguriert ist.) Als nächstes möchten Sie wie immer sicher sein, daß Ihr CLASSPATH korrekt konfiguriert ist. Gehen Sie schließlich in das Verzeichnis, das BankApplet.html, BankApplet.class, usw. enthält, und starten Sie appletviewer: appletviewer BankApplet.html Falls Sie VisiBroker verwenden, werden Sie nach wenigen Sekunden etwas Ähnliches wie das Folgende sehen: VisiBroker Developer for Java [03.00.00.C3.05] (SEP 08 1997 16:55:51) started Mon Nov 03 12:55:19 GMT+00:00 1997 Visigenic Software: http://www.visigenic.com/ Locator: 153.36.240.254:-1 Local Environment: Java: Version 1.1.4 from Sun Microsystems Inc. OS: Windows 95 version 4.0; CPU: x86 Remote Environment: Java: Version 1.1.4 from Sun Microsystems Inc. OS: Windows 95 version 4.0; CPU: x86 Nach einigen weiteren Sekunden öffnet sich das Fenster von appletviewer. Nach einigen weiteren Sekunden initialisiert es BankApplet, was zu einer Ausgabe ähnlich der in Abbildung 14.8 führt.
Abbildung 14.8: BankApplet während der Ausführung Wenn Sie den Namen einer Bank und eines Geldautomaten im jeweiligen Auswahlfeld sehen: so weit, so gut. Falls nicht, stellen Sie sicher, daß mindestens ein Bank- und ein GeldautomatServer gestartet wurden, und starten Sie dann appletviewer erneut. Betätigen Sie nun die Schaltfläche New neben dem Auswahlfeld Kunde. Es erscheint ein Dialogfeld ähnlich dem in Abbildung 14.9. Geben Sie einige Informationen im Dialogfeld ein wie in Abbildung 14.10. Wenn Sie in jedem Textfeld etwas eingegeben haben, werden Sie sehen, daß die Schaltfläche OK aktiviert ist. Wenn Sie mit Ihrer Eingabe zufrieden sind, betätigen Sie OK.
Abbildung 14.9: Die anfängliche Anzeige des Dialogfeldes »KundeDialog«
Abbildung 14.10: Das ausgefüllte Dialogfeld »KundeDialog« Wenn Sie in CustomerDialog OK betätigen, kehren Sie zum Hauptbildschirm von BankApplet zurück, wobei die Botschaft Creating new Kunde... in der Statusanzeige erscheint. Nach einem Moment wird der Status wieder zu BankApplet bereit, und der Name Ihres neuen Kunden erscheint im Auswahlfeld Kunde. Legen Sie nun ein neues Konto für Kunde an, indem Sie die Schaltfläche New neben dem Auswahlfeld Konto betätigen. Das Fenster KontoDialog erscheint, wie in Abbildung 14.11 zu sehen.
Abbildung 14.11: Die anfängliche Anzeige des Dialogfeldes »KontoDialog» Lassen Sie in KontoDialog das Feld Checking für den Konto-Typ markiert. Geben Sie im jeweiligen Textfeld einen Anfangskontostand ein wie in Abbildung 14.12 gezeigt. Betätigen Sie dann OK. (Das Applet akzeptiert ein leeres Feld als Anfangskontostand; in diesem Fall erhält Konto einen Anfangskontostand von Null.)
Abbildung 14.12: Das ausgefüllte Dialogfeld »KontoDialog« Nun erscheint das Hauptfenster von BankApplet wieder, das einen Moment lang die Meldung Creating new Konto... im Statusbereich anzeigt. Der Status wird dann wieder zu BankApplet bereit, woraufhin das Fenster die Kontoinformation ausgibt wie in Abbildung 14.13 gezeigt.
Abbildung 14.13: Anzeige der Kontoinformation in »BankApplet« Versuchen Sie einige Transaktionen. Geben Sie einen Betrag im Textfeld Transactions ein, und betätigen Sie eine der Schaltflächen Einzahlung oder Abhebung. Sehen Sie, was geschieht, wenn Sie versuchen, zuviel abzuheben. Wenn Sie überzeugt sind, daß die Transaktionen korrekt funktionieren, betätigen Sie die Schaltfläche AutoUpdate. Nach höchstens einer Minute werden Sie sehen, daß der Kontostand von Konto um 5 Prozent zugenommen hat (würden Sie es nicht gerne sehen, wenn Ihr eigener Kontostand so schnell steigen würde?) Probieren Sie ruhig etwas mit dem Applet herum. Legen Sie eine Reihe von Kunden und verschiedene Konten für diese bei verschiedenen Banken an. Schalten Sie zwischen Kunden und Konten hinund her, und beobachten Sie, wie die Informationen auf dem Bildschirm entsprechend aktualisiert werden. Verwenden eines Web-Browsers Sie können das Applet auch in einem Java-fähigen Web-Browser wie Netscape Communicator ausführen, wenn auch die Einrichtung des Applets schwieriger ist. Zunächst benötigen Sie Zugriff auf einen Web-Server (auch als HTTP-Server oder HTTP-Dämon bezeichnet). Sie müssen dann die Datei BankApplet.html und die .class-Dateien für BankApplet in einem Verzeichnis plazieren, das für den Web-Server sichtbar ist (sehen Sie in der Dokumentation des Web-Servers nach, falls Sie
nicht sicher sind, wie dies zu geschehen hat). Starten Sie die Server-Komponenten (und GateKeeper, falls erforderlich), wie Sie es im vorigen Schritt getan haben. Wenn alle Server laufen, starten Sie Ihren Web-Browser, und geben Sie den passenden URL für die das BankApplet enthaltende Seite an. (Der korrekte URL hängt von der Konfiguration Ihres WebServers und dem Standort der von Ihnen erstellten Dateien ab.) Möglicherweise benötigt das Applet eine kurze Zeit zum Starten, doch wenn es startet, wird Ihr Browser-Fenster dem von Abbildung 14.14 ähneln.
Abbildung 14.14: Ausführung von »BankApplet« in einem Web-Browser Glückwunsch! Sie haben erfolgreich eine Web-basierte CORBA-Anwendung eingesetzt. (Sie sollten zur Kenntnis nehmen, daß dies eine viel bedeutendere Leistung ist als das Schreiben eines lustigen kleinen Applets.)
Zusammenfassung Heute, beim großen Finale der Beispielanwendung Bank, haben Sie ein Java-Applet als Ersatz für GeldautomatClient von Tag 13 mit grafischer Benutzeroberfläche erstellt. Aus Platzmangel wurden heute zahlreiche Details der Entwicklung von Applets in Java nicht erläutert, dennoch konnten Sie das Applet durch Verwendung sowohl des Dienstprogramms appletviewer aus dem JDK als auch eines Java-fähigen Web-Browsers ausführen. Sie haben nun das Potential erkannt, das mit der Möglichkeit des Einsatzes von CORBA-Anwendungen - selbst der Endbenutzerschnittstelle solcher Anwendungen - im WWW verbunden ist. In vielen Fällen bietet die Möglichkeit der Ausführung einer unternehmensweiten Anwendung in einem Web-Browser handfeste Vorteile: Die Portabilität von Java ermöglicht es der Anwendung, auf zahlreichen Plattformen verbreitet zu werden. Auch der Auslieferungsmechanismus ist gegenüber früheren Methoden vereinfacht, wodurch es zu einer einigermaßen trivialen Angelegenheit wird, Aktualisierungen an Benutzer zu verteilen, die geographisch über ein Unternehmen verstreut sind.
F&A Frage: Warum sind die Listings in diesem Kapitel, insbesondere BankApplet.java, so viel länger als in früheren Kapiteln? Antwort: Weil das Beispiel in diesem Kapitel eine Anwendung mit Grafischer Benutzeroberfläche (Graphical User Interface, GUI) implementiert, ist es verständlicherweise komplexer als sein einfaches, konsolenbasiertes Gegenstück. Das Erstellen der verschiedenen Benutzerschnittstellenkomponenten und die Behandlung der von ihnen generierten Ereignisse bringt ein erhebliches Maß an zusätzlicher Komplexität für die Anwendung. Glücklicherweise wird der größte Teil des Quelltexts, der erforderlich ist, um die GUI-Schnittstellen zu implementieren, von Entwicklungswerkzeugen generiert. Frage: Ich habe Visual Café nicht; kann ich trotzdem das Beispiel-Applet aus diesem Kapitel ausführen? Antwort: Wenn auch Visual Café verwendet wurde, um das Beispiel-Applet in diesem Kapitel zu produzieren, benötigen Sie es nicht - ebensowenig wie jedes andere Java-Entwicklungswerkzeug in diesem Zusammenhang, abgesehen vom frei erhältlichen JDK -, um das Beispiel-Applet auszuführen. Der Besitz von Visual Café ermöglicht eine einfachere Änderung des Applets, doch sollten Sie in der Lage sein, mit praktisch jedem Java-Entwicklungswerkzeug zu arbeiten.
Workshop Der folgende Abschnitt soll Ihnen dabei helfen, Ihr Verständnis des heute vorgestellten Stoffs zu prüfen und das Gelernte in die Praxis umzusetzen. Die Antworten auf die Quizfragen und die Auflösung der Übungen finden Sie in Anhang A. Quiz 1. Warum könnte es vorteilhaft sein, einen CORBA-Client als Java-Applet einzusetzen? 2. Warum ist es nützlich, wenn Browser CORBA-verwandte Klassen beinhalten, die eingebaut sind (beispielsweise enthält der Communicator von Netscape VisiBroker for Java runtime von Visigenic)? 3. Was ist ein möglicher Nachteil des in Frage 2 beschriebenen Bündelungsschemas? Übung Erweitern Sie BankApplet so, daß es noch mehr tolle Sachen macht. Sie könnten registerblattartige Bedienfelder einbauen, um die zu einem bestimmten Kunden gehörigen Konten anzuzeigen. Oder Sie könnten das Applet so erweitern, daß mehrere Kunden mit einem Konto verknüpft werden können, und die Konto-Information entsprechend ändern, so daß alle mit einem gegebenen Konto verknüpften Kunden gezeigt werden. (Da dies eine Übung mit offenem Ende ist, wird keine Antwort angegeben.)
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Anhang A Antworten auf die Quizfragen und Lösungen zu den Übungen Tag 1: CORBA kennenlernen Tag 2: Die Architektur von CORBA Tag 3: Beherrschen der Sprache Interface Definition Language (IDL) Tag 4: Entwicklung einer CORBA-Anwendung Tag 5: Entwerfen des Systems: Ein Schnellkurs in objektorientierter Analyse und Entwurfstechnik Tag 6: Implementierung grundlegender Anwendungsfunktionen Tag 7: Verwendung von Exceptions zur Fehlerprüfung Tag 8: Hinzufügen von Geldautomat-Fähigkeit Tag 9: Verwendung von Callbacks zum Hinzufügen von Push-Fähigkeit Tag 10: Lernen über CORBA-Entwurfsfragen Tag 11: Verwenden der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII) Tag 12: CORBAservices und CORBAfacilities
Tag 13: Entwicklung von Internet-Anwendungen mit CORBA und Java Tag 14: Einrichten von Web-Fähigkeit für das Beispiel Bank mit Java
Tag 1: CORBA kennenlernen Quiz Wofür steht IIOP und was bedeutet es? Antwort: IIOP (Internet Inter-ORB Protocol) ist ein Protokoll, das es den ORBs verschiedener CORBA-Anbieter ermöglicht, miteinander zu kommunizieren, wobei das Protokoll TCP/IP verwendet wird. Die Implementierung von IIOP ist Voraussetzung dafür, daß ein Produkt CORBA-2-kompatibel ist. Welche Beziehungen bestehen zwischen CORBA, OMA und OMG? Antwort: Die OMG (Object Management Group) ist die Organisation, die für die OMA- und CORBA-Standards zuständig ist. Die OMA (Object Management Architecture) besteht aus der ORB-Funktionalität (Object Request Broker), den CORBAservices und CORBAfacilities, den Domänenschnittstellen und den Anwendungsobjekten. CORBA (Common Object Request Broker Architecture) schließlich ist die Standard-Implementierung für die ORB-Funktionalität der OMA. Was versteht man unter einem Client-Stub? Antwort: Ein Client-Stub ist ein Code-Abschnitt, der in der Regel von einem IDL-Compiler generiert wird und der es der Client-Anwendung erlaubt, eine Schnittstelle zu CORBA-Server-Objekten herzustellen. Die vom Client-Stub gelieferte Schnittstelle entspricht der auf dem Server. Somit hat der Client den (falschen) Eindruck, daß die Methodenaufrufe lokal erfolgen. Was ist eine Objektreferenz? Und was ist eine IOR? Antwort: Eine Objektreferenz ist ein Zeiger (Verweis) auf ein CORBA-Objekt. Ein Client nimmt alle Methodenaufrufe für ein CORBA-Objekt über eine Referenz auf dieses Objekt vor. Eine IOR (interoperable Objektreferenz) bezeichnet in CORBA/IIOP eine Objektreferenz.
Tag 2: Die Architektur von CORBA Quiz Was versteht man unter Formatübertragung (Marshaling)? Antwort: Bei der Formatübertragung werden die für eine Methode benötigten Parameter in ein Format übertragen, das über das Netzwerk übertragen werden kann (bei der umgekehrten Formatübertragung geschieht der umgekehrte Vorgang, d.h., die Daten werden aus dem Übertragungsformat wieder in das benötigte Format umgesetzt). Welche Funktionen hat ein ORB? Antwort: Wenn eine Objektreferenz vorhanden ist, sucht der ORB die Implementierung des betreffenden Objekts und bereitet einen Server darauf vor, Anforderungen zu empfangen. Ferner führt er die Formatübertragung (Marshaling) und die umgekehrte Formatübertragung der Parameter (Unmarshaling) in einem Methodenaufruf durch. Wie entstehen Server-Skeletons und Client-Stubs? Antwort: Server-Skeletons und Client-Stubs werden vom IDL-Compiler generiert. Mit welcher Server-Aktivierungsstrategie könnte man einen Server beschreiben, der manuell gestartet und gestoppt wird? Antwort: Dies ist die Strategie des permanenten Servers. Inwiefern wird durch die Verwendung der IDL die Sprachunabhängigkeit von CORBA-Objekten verstärkt? Antwort: Die IDL stellt einen sprachenunabhängigen Mechanismus zur Beschreibung der Schnittstellen von CORBA-Objekten zur Verfügung. Die sprachenunabhängigen IDL-Konstrukte können dann mit Hilfe eines IDL-Compilers in die jeweiligen sprachenspezifischen Konstrukte für die betreffende Sprache umgesetzt werden.
Tag 3: Beherrschen der Sprache Interface Definition Language (IDL) Quiz Definieren Sie einen Typ (unter Verwendung von typedef) mit der Bezeichnung temperaturSequenz, der eine Sequenz in Form von sequence of floats darstellt. (Ja, dies ist erlaubt.) Antwort: Die IDL-Definition würde folgendermaßen aussehen: typedef sequence<sequence> temperatureSequence; Warum könnte ein Typ, wie in der vorherigen Frage beschrieben, sinnvoll sein? Antwort: Stellen Sie sich ein Temperaturmeßsystem vor, das periodisch die aktuelle Temperatur mißt. Die an einem einzigen Tag erfaßten Temperaturwerte könnten im Datentyp sequence gespeichert werden. Stellen Sie sich weiter vor, daß am Ende jedes Tages die Temperaturdaten für den betreffenden Tag in einem anderen Datentyp sequence gespeichert würden. Bei diesem Typ sequence würde es sich um sequence<sequence> handeln, womit Sie die Antwort auf Frage 1 haben. Wozu sind Exceptions hilfreich? Antwort: Exceptions sind aus mindestens zwei Gründen hilfreich. Betrachten wir zunächst einmal eine Methode, die ein Ergebnis vom Typ boolean liefert. Wenn keine Exceptions verfügbar wären, müßte die Methode bestimmte Rückgabewerte dazu reservieren, Fehlerbedingungen zu signalisieren. Natürlich wird damit ein möglicher gültiger Rückgabewert ausgeschaltet. Bei einem Rückgabewert vom Typ boolean gibt es nur zwei mögliche Rückgabewerte, so daß die Verwendung eines davon zum Signalisieren einer Fehlerbedingung unsinnig wäre. Zweitens können Exceptions in Sprachen, die dieses Merkmal unterstützen, die Fehlerbehandlung stark vereinfachen. Der Entwickler braucht nicht mehr jeden Rückgabewert eines Methodenaufrufs auf eine Fehlerbedingung hin zu überprüfen, sondern kann allgemeinere Fehlerbehandlungsmechanismen erstellen, so daß der Quelltext übersichtlicher wird. Außerdem kann eine Hierarchie von Exception-Behandlungsroutinen definiert werden, weil nicht behandelte Exceptions im Aufrufstapel nach oben weitergegeben werden. Wozu ist das Konstrukt module hilfreich? Antwort: Das Konstrukt module ist hilfreich, weil es die Partitionierung eines Systems erleichtert. Ein gut unterteiltes System läßt sich leichter definieren und implementieren als ein monolithisches, weil durch die Strategie des »teile und herrsche« unabhängige oder halb unabhängige Komponenten in
handhabbarer Größe entstehen. Im Vergleich dazu sind die Systemkomponenten bei einem monolithischen Entwurf kaum handhabbar. Benennen Sie einige Einsatzmöglichkeiten für den Datentypen octet. Antwort: Der Datentyp octet wird für alle Datentypen verwendet, die keiner Art von Umwandlung unterzogen werden sollen. Wenn beispielsweise ein als Bitmap vorliegendes Bild über ein Netzwerk geschickt werden soll, muß es in genau dem Format ankommen, in dem es gesendet wurde. Hierzu kann das Bild als Array von Daten des Typs octet gesendet werden, so daß sichergestellt ist, daß die Daten auf dem Weg von der Quelle bis zum Ziel nicht umgewandelt werden. Andere Datentypen, die auf diese Weise übertragen werden können, sind ausführbare Dateien, Java-Klassendateien und die meisten Multimedia-Dateien. (Bei allen übrigen IDL-Datentypen dagegen kann während der Übertragung zwischen verschiedenen Hardware-Plattformen, Betriebssystemen und/oder Programmiersprachen eine Formatumwandlung erfolgen.) Definieren Sie einen Aufzählungstypen, der die Monate eines Jahres enthält. Antwort: Die Typdefinition würde folgendermaßen aussehen (Sie können natürlich einen beliebigen anderen Namen wählen): enum MonateEinesJahres { Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember }; Warum könnte eine nicht-blockierende Methode im Vergleich zu einer blockierenden Methode von Vorteil sein? Antwort: Stellen Sie sich einen Fall vor, in dem es sich bei einem CORBA-Client um eine interaktive Anwendung handelt. Während der Client Aufrufe an CORBA-Server absetzt, möchte der Benutzer vielleicht mit der Anwendung interagieren. Wenn in diesem Fall Remote-Methodenaufrufe, die unter
Umständen lange dauern, blockiert werden müßten, würden hierdurch die Antwortzeiten der ClientAnwendung stark beeinträchtigt. Mit nicht-blokkierenden Methoden kann dieses Problem gelöst werden, weil sie dem Client während eines Methodenaufrufs eine weitere Verarbeitung ermöglichen, so daß der Client die Benutzereingaben zeitgerecht behandeln kann. (Auch hier ist die Verwendung von Multithreading im Client fast immer die beste Lösung, weil das Aufrufen von oneway-Methoden unzuverlässig ist. Am 10. Tag werden diese Frage und weitere Themen ausführlicher behandelt. Stellen Sie sich einen zusammengesetzten Datentypen mit einer großen Anzahl von Datenelementen vor. Dieser Datentyp wird häufig von der Client-Anwendung verwendet werden, die im allgemeinen auf alle Datenelemente zugreifen muß. Wäre es effizienter, diesen Datentypen in einem struct oder in einer Schnittstelle zu verkapseln? Begründen Sie Ihre Antwort. Antwort: Wenn in diesem Datentyp eine große Anzahl von Datenelementen vorhanden ist und die ClientAnwendung auf jedes einzelne zugreifen muß, wäre es effektiver, die Daten als struct zum Client zu schicken. Der Grund hierfür liegt darin, daß beim Zugriff auf alle Datenelemente einer Schnittstelle (bekanntlich erfolgt die Weitergabe des Implementierungsobjekts durch Referenz) für jedes Datenelement eine eigene Methode erforderlich ist, wodurch ein großer Systemaufwand entsteht. Ein struct wird hingegen über den Wert gesendet, so daß der Client, wenn er das struct zurückerhalten hat, auf seine eigene lokale Kopie der Daten in den Datenelementen zugreifen kann. Eine IDL-Methode kann einen Wert zurückgeben. Was ist jedoch der Zweck der Parametertypen in und inout? Antwort: Wenn eine Methode gleichzeitig mehrere Werte unterschiedlicher Typen zurückgeben muß, können die Parameter out und inout zusätzlich zum (oder anstelle) des Rückgabewerts verwendet werden. Dies entspricht der in C++ praktizierten Weitergabe von Parametern durch Referenz. Auf diese Weise können über einen einzigen Methodenaufruf mehrere Werte zurückgegeben werden. Warum ist eine oneway-Methode nicht in der Lage, einen Wert an die aufrufende Stelle zurückzuliefern? Können Sie sich einen Mechanismus vorstellen, der oneway-Aufrufe verwendet, um der aufrufenden Stelle ein Ergebnis zu übergeben? Antwort: Da es sich bei oneway um eine nicht-blockierende Methode handelt, wartet der aufrufende Client nicht auf ein Ergebnis. Aus diesem Grund gibt es für einen oneway-Methodenaufruf auch keine Möglichkeit, ein Ergebnis an den Client zurückzugeben. (Aus demselben Grund können auch keine Exceptions ausgelöst werden.) Ein System von oneway-Aufrufen könnte allerdings wie folgt eingerichtet werden: Der Client richtet einen oneway-Aufruf an den Server und setzt seine Bearbeitung fort. Nachdem der Server die Methode ausgeführt hat, führt er einen oneway-Aufruf zurück zum Client durch (Sie bekommen einen Sonderpunkt, wenn Sie dies als Rückruf identifiziert haben), wobei das Ergebnis des vorherigen Aufrufs zurückgegeben wird.
Übungen Nehmen Sie folgende Klassen an: Leitungsrohr, Wasserhahn, Sicherungskasten, Steckdose, Rohr, Wassererhitzer, Wasserpumpe, Leitung. Wie würden Sie diese Klassen partitionieren? Welche Verbindung, falls überhaupt, liegt zwischen den von Ihnen erzeugten Partitionen vor? Antwort: Oft gibt es beim Partitionieren eines Systems keine eindeutige Antwort auf die Frage, welche Klassen in welche Teilbereiche gehören. Bei vielen ist es offensichtlich, aber es gibt immer welche, die in mehrere Teilbereiche fallen. In solchen Fällen gibt es keine richtige oder falsche Antwort. Auch hat unter Umständen die Art der Verwendung der Klassen durch das System Einfluß auf die Partitionierung, und es kann vorkommen, daß eine für ein System sinnvolle und richtige Partitionierung für ein anderes System gänzlich ungeeignet ist. Antwort: Von den Klassen in dieser Übung gehören Leitungsrohr, Sicherungskasten, Steckdose und Leitung eindeutig zu einem Bereich (den wir Elektrik nennen könnten). Wasserhahn und Rohr gehören genauso eindeutig zu einem anderen Bereich (den wir Installation nennen könnten). Wassererhitzer und Wasserpumpe jedoch könnten in beide Bereiche gehören. Sie sind zwar Teile der Installation, aber es handelt sich andererseits um elektrische Geräte, so daß sie auch als Teil der Elektrik betrachtet werden könnten. In diesem Fall gibt es keine absolute richtige oder falsche Antwort. Je nach Systemanforderung ist die Einteilung auf die eine oder andere Art sinnvoll. Antwort: Gehen wir davon aus, daß Wassererhitzer und Wasserpumpe in den Bereich Installation gehören sollen; in diesem Fall gäbe es sicherlich eine Beziehung zwischen Wassererhitzer und Steckdose (oder zwischen Wassererhitzer und Sicherungskasten, wenn diese direkt miteinander verbunden sind). Eine ähnliche Beziehung wäre auch für Wasserpumpe vorhanden. Da zwischen den beiden Teilbereichen nur wenige Beziehungen vorhanden sind und die Beziehungen innerhalb der Teilbereiche recht eng sind, wäre dieses Schema sinnvoll. Erzeugen Sie eine Schnittstelle, die einen Radiowecker beschreibt (mit dem man die Stunden, Minuten, Weckzeit und so weiter setzen kann). Antwort: Für diese Übung gibt es sicher viele mögliche Lösungen. Ihre Schnittstelle könnte beispielsweise wie folgt aussehen: // Schnittstelle für einen Radiowecker. Mit dieser Schnittstelle wird // die Schnittstelle vieler Bedienelemente von Uhr/Radio emuliert, z.B.ein // "Knopf" zum Einstellen der Stunden, ein weiterer "Knopf" für die // Minuten und so weiter.
interface ClockRadio { // Die aktuelle Uhrzeit ermitteln. Das Ergebnis wird in den // Ausgabeparametern Stunde, Minute und Sekunden zurückgegeben. void getTime(out short hour, out short minute, out short second); // Die aktuelle Stunde um Eins vorstellen; auf Null zurücksetzen, // wenn der Maximalwert (23) für die Stunde überschritten wird. // Mit dieser Methode kann die Uhrzeit eingestellt werden. // Sie gibt den neuen Stundenwert zurück. short IncrementHour(); // Die aktuelle Minute um Eins vorstellen; auf Null zurücksetzen, // wenn der Maximalwert (59) überschritten wird. Mit dieser Methode // kann die Uhrzeit eingestellt werden. Sie gibt den neuen Minutenwert zurück. short IncrementMinute(); // Die Weckzeit-Stunde um Eins vorstellen; auf Null zurücksetzen, // wenn der Maximalwert (23) überschritten wird. Mit dieser Methode // kann die Weckzeit eingestellt werden. Sie gibt den neuen Stundenwert zurück. short IncrementAlarmHour(); // Die Weckzeit-Minute um Eins vorstellen; auf Null zurücksetzen, // wenn der Maximalwert (59) überschritten wird. Mit dieser Methode // kann die Weckzeit eingestellt werden. Sie gibt den neuen Minutenwert zurück. short IncrementAlarmMinute(); // Den Alarm aktivieren. void activateAlarm(); // Den Alarm deaktivieren. void deActivateAlarm(); };
Tag 4: Entwicklung einer CORBA-Anwendung Quiz Welchen Zweck erfüllen die Server-Skeletons und die Client-Stubs? Antwort: Server-Skeletons stellen die Gerüstklassen dar, welche die Gerüstmethoden liefern, für die der Entwickler Implementierungen (entweder über Vererbung oder Delegation) zur Verfügung stellt. Client-Stubs stellen den Clients eine Schnittstelle zu den Server-Methoden zur Verfügung. Warum muß der Server das Implementierungsobjekt beim CORBA-Bezeichnungsdienst registrieren? Antwort: Der Server muß sich beim CORBA-Bezeichnungsdienst registrieren lassen, weil es sonst keine
Möglichkeit für die Clients gibt, den Server zu finden. Diese Sichtbarkeit kann auch auf andere Weise erreicht werden, so beispielsweise durch das Schreiben von Objektreferenzen in Zeichenkettenform auf bekannte und erreichbare Orte wie eine Datei auf einer Festplatte (bei einem Netzwerkdateisystem), einen WWW-Server oder einen FTP-Server. Der Bezeichnungsdienst stellt die Standardmethode zur Veröffentlichung von Objektreferenzen dar. Warum müssen Client und Server Exceptions abfangen, vor allem dann, wenn die von Ihnen definierten IDL-Operationen keine auslösen? Antwort: Jede Remote-Methode kann potentiell Exceptions auslösen, die abgefangen und behandelt werden müssen. Auch wenn in der IDL-Definition für eine Methode keine Exceptions definiert sind, kann diese Methode dennoch eine CORBA-System-Exception auslösen (so beispielsweise bei einem Netzwerkfehler). Übungen Im Beispiel BoersenMarkt wurde herausgestellt, daß es sinnvoll wäre, in der Methode getAktienWert() eine Exception auszulösen, wenn ein ungültiger AktienSymbol-Wert übergeben wird. Ändern Sie die Datei BoersenMarkt.idl so ab, daß die Methode eine Exception vom Typ UngueltigesAktienSymbolException auslösen kann. (Sie müssen auch eine Definition für diese Exception hinzufügen.) Antwort: Die entstehende Datei BoersenMarkt.idl könnte wie folgt aussehen (unter Umständen müssen Sie weitere Datenelemente zur Definition von UngueltigesAktienSymbolException hinzufügen): 1: // BoersenMarkt.idl 2: 3: // Das Modul BoersenMarkt besteht aus Definitionen 4: // die für eine Börsenanwendung hilfreich sind. 5: module BoersenMarkt { 6: 7: // Der Typ AktienSymbol wird für Symbole (Namen) 8: // verwendet, die für Aktien stehen. 9: typedef string AktienSymbol; 10: 11: // Eine AktienSymbolListe ist einfach eine Sequenz 12: // von AktienSymbolen. 13: typedef sequence AktienSymbolListe; 14: 15: // Das Interface BoersenServer ist die Schnittstelle 16: // für einen Server, die Börseninfos bereitstellt. 17: // (Informationen zu den einzelnen Methoden finden 18: // Sie in den jeweiligen Kommentaren.)
19: interface BoersenServer { 20: 21: // getAktienWert() liefert den aktuellen Wert eines 22: // AktienSymbols. Ist das angegebene 23: // AktienSymbol unbekannt, ist das Ergebnis undefiniert (dies wäre eine 24: // gute Stelle um eine Exception auszulösen). 25: float getAktienWert(in AktienSymbol symbol); 26: 27: // getAktienSymbole() gibt eine Sequenz mit allen 28: // AktienSymbolen zurück, die diesem BoersenServer bekannt sind. 29: AktienSymbolListe getAktienSymbole(); 30: }; 31: }; // BoersenMarkt.idl // Das Modul BoersenMarkt besteht aus Definitionen, die zum Erstellen // börsenorientierter Anwendungen geeignet sind. module BoersenMarkt { // Die Exception UngueltigesAktienSymbolException wird ausgelöst, // wenn ein ungültiges Aktiensymbol an eine Methode übergeben wird. exception UngueltigesAktienSymbolException { }; // Der Typ AktienSymbol wird für Symbole (Namen) // von Aktien verwendet. typedef string AktienSymbol; // Die Schnittstelle BoersenServer ist die Schnittstelle für einen Server, // der Börseninformationen zur Verfügung stellt. // (Weitere Informationen hierzu finden Sie in den Kommentaren // zu den einzelnen Methoden.) interface BoersenServer { // getAktienWert() gibt den aktuellen Wert für das // betreffende AktienSymbol zurück. Wenn das AktienSymbol // unbekannt ist, ist das Ergebnis nicht definiert (dies // wäre eine gute Stelle zum Auslösen einer Exception). float getAktienWert(in AktienSymbol symbol) raises (UngueltigesAktienSymbolException); // getAktienSymbole() gibt eine Sequenz aller diesem // BoersenServer bekannten AktienSymbole zurück. sequence getAktienSymbole(); }; }; Im BoersenMarkt-Beispiel wurde eine Implementierung nach dem Delegationsmechanismus
vorgenommen. Implementieren Sie den Server BoersenServer unter Verwendung des Vererbungsmechanismus. (Für besonders Ehrgeizige: Integrieren Sie auch den Exception-auslösenden Mechanismus aus der ersten Übung.) Antwort: Folgende Änderungen sind erforderlich: Sie müssen den Server so ändern, daß er tatsächlich die Exception UngueltigesAktienSymbolException auslöst, wenn ein ungültiges Aktiensymbol gefunden wird. Gleichfalls muß der Client so geändert werden, daß er diese Exception abfängt und behandelt. Für den Server ist hier die neue Implementierung der Methode getAktienWert() gezeigt (der übrige Quelltext bleibt unverändert): // Gibt den aktuellen Wert für das betreffende AktienSymbol zurück. public float getAktienWert(String symbol) throws UngueltigesAktienSymbolException { // Versuch, das angegebene Symbol zu finden. int AktienIndex = myAktienSymbole.indexOf(symbol); if (AktienIndex != -1) { // Wenn das Symbol gefunden wurde, seinen Wert zurückgeben. return ((Float)meineAktienWerte.elementAt(AktienIndex)). floatValue(); } else { // Symbol wurde nicht gefunden. throw new UngueltigesAktienSymbolException(); } } Die Änderungen am Client sind auf die Methode fuehreEtwasAus() beschränkt, mit der nun die Exception abgefangen und eine Warnmeldung ausgegeben wird: // Tolle Sachen mit dem BoersenServer anstellen. protected void fuehreEtwasAus() { try { // Die gültigen Aktiensymbole von BoersenServer abrufen. String[] AktienSymbole = meinBoersenServer.getAktienSymbole(); // Die Aktiensymbole und ihre Werte anzeigen. for (int i = 0; i < AktienSymbole.length; i++) { try { System.out.println(AktienSymbole[i] + " " + meinBoersenServer.getAktienWert(AktienSymbole [i])); } catch (UngueltigesAktienSymbolException ex) { System.out.println("Ungültiges Aktiensymbol.«); }
} } catch (org.omg.CORBA.SystemException ex) { System.err.println("Schwerwiegender Fehler: " + ex); } }
Tag 5: Entwerfen des Systems: Ein Schnellkurs in objektorientierter Analyse und Entwurfstechnik Quiz Finden Sie die möglichen Objekte im folgenden System: Ein Bestellungssystem ermöglicht es Kunden, Produkte bei einer bestimmten Firma zu bestellen. Jede Bestellung besteht aus einem oder mehreren Einträgen (Zeilen), in denen jeweils eine Anzahl und ein bestimmtes Produkt angegeben ist. Jedem Produkt wiederum ist ein Preis zugeordnet. Antwort: Mögliche Objekte können über die in der Beschreibung enthaltenen Substantive gefunden werden: Bestellung, Eintrag, Anzahl, Produkt und Preis. (Bei einer weitergehenden Analyse würde man herausfinden, daß einige davon, insbesondere Anzahl und Preis, keine gut geeigneten Objekte wären, aber dies hängt von der zu entwerfenden Anwendung ab.) Was versteht man unter der UML und wozu dient sie? Antwort: Die UML (Unified Modeling Language, vereinheitlichte Modelliersprache) erleichtert die objektorientierte Analyse und den Entwurfsprozeß. Die UML kann zum Modellieren einer Reihe von Systemaspekten verwendet werden, angefangen vom statischen Entwurf (Verwendung von Klassendiagrammen) bis hin zum dynamischen Entwurf (Verwendung von Use-Cases und Szenarios). Beim Systementwurf für ein Bestellungsverarbeitungssystem ist eine der Anforderungen »es muß schnell sein«. Ist diese Anforderung gut formuliert, oder könnte man das besser ausdrücken? Wenn ja, wie würden Sie es formulieren? Antwort: Da »schnell« an sich kein leicht quantifizierbarer Begriff ist, und sich diese Eigenschaft nicht einfach testen läßt, ist »es muß schnell sein« wohl keine gute Formulierung einer Anforderung. Besser wäre es, eine mittlere Antwortzeit für das System vorzugeben, beispielsweise eine Sekunde oder weniger. Übung Ändern Sie den Systementwurf so ab, daß die Bank aus Zweigstellen besteht, von denen jede einige Kundenkonten hat. Erstellen Sie das Klassendiagramm für den geänderten Entwurf.
Das geänderte Klassendiagramm müßte wie in Abbildung A.1 gezeigt aussehen. Beachten Sie, daß nun nicht mehr die Bank für das Erstellen des Kontos verantwortlich ist, sondern die Zweigstelle, obwohl sowohl die Bank als auch die einzelnen Zweigstellen die Fähigkeit zum Auflisten und Schließen von Konten haben.
Abbildung A.1: Das geänderte Klassendiagramm für die Bankanwendung
Tag 6: Implementierung grundlegender Anwendungsfunktionen Quiz
In diesem Kapitel wurde erwähnt, daß bei Verwendung von _is_equivalent() nicht garantiert der Wert TRUE zurückgegeben wird, wenn zwei Objektreferenzen auf dasselbe Objekt verweisen. Können Sie sich einen Mechanismus vorstellen, mit dem man zuverlässiger feststellen könnte, ob zwei Referenzen auf dasselbe Objekt verweisen? (Der Einfachheit halber können Sie davon ausgehen, daß es sich um Objekte desselben Typs handelt.) Antwort: Der Mechanismus könnte Objekten GUIDs (global unique identifiers, globale eindeutige Identifikatoren) oder UUIDs (universal unique identifiers, universelle eindeutige Identifikatoren) zuweisen und den Client-Anwendungen diese Identifikatoren (über eine Datenzugriffsmethode wie getGUID()) zur Verfügung stellen. Mit einem solchen Mechanismus könnte man die Identität von Objekten durch Vergleich der Identifikatoren zuverlässiger ermitteln. Wenn zweimal dieselbe GUID zurückgegeben wird, muß es sich in beiden Fällen um dasselbe Objekt handeln. Was würde passieren, wenn für ein Objekt, das zuvor mit _duplicate() dupliziert wurde, _release() nicht aufgerufen wird? Antwort: Wenn _release() für ein Objekt nicht aufgerufen wird, würde das Objekt ewig fortbestehen, und der Referenzzähler (in einer Implementierung zur Zählung der Referenzen) würde nie Null erreichen. Warum ist in NeuerKundeHauptmodul.cpp der Block try ... catch (const CORBA::Exception& ex) enthalten? Antwort: Bei CORBA::Exception handelt es sich um eine generische Exception, die von jeder Remote-Methode ausgelöst werden kann. Da in NeuerKundeHauptmodul.cpp eine Reihe Remote-Methoden aufgerufen wird, erfolgt dies innerhalb des Blocks try ... catch. Übung Ändern Sie die Client-Anwendung so ab, daß sie die Namen der Kunden ausgibt, die dem eröffneten Konto zugeordnet sind (es müssen diejenigen Kundendaten ausgegeben werden, die über die Befehlszeile eingegeben wurden). Fügen Sie nach Zeile 85 von NeuerKundeHauptmodul.cpp (siehe Listing 6.21) den folgenden Quelltext ein. Er ruft mit Hilfe der Methode getKunden() die Kontoinhaber ab und geht dann die Folge der von dieser Methode gelieferten Kunden durch und gibt den Namen jedes Kunden (wie er von name() zurückgegeben wird) aus. // Liste der Kunden ausgeben, die ein Konto haben. cout << " Liste der Kontoinhaber wird ausgegeben:" << endl; KundenListe* Kunden = Konto->getKunden();
for (CORBA::ULong i = 0; i < Kunden->length(); i++) { cout << " Inhaber " << i + 1 << ": " << ((*Kunden)[i])-> name(); }
Tag 7: Verwendung von Exceptions zur Fehlerprüfung Quiz Was versteht man unter »eine Exception auslösen"? Antwort: Beim Auslösen einer Exception wird eine neue Exception erstellt und an das Objekt weitergegeben, das die Methode aufgerufen hat, für die die Exception ausgelöst wurde. Die aufrufende Methode kann dann die Exception behandeln oder an die Methode weitergeben, durch die sie wiederum aufgerufen wurde. Was bedeutet »eine Exception abfangen"? Antwort: Unter dem Abfangen einer Exception versteht man die Behandlung einer Exception, die von einer aufgerufenen Methode ausgelöst wurde. Beim Abfangen einer Exception wird unter Umständen eine Fehlermeldung an den Benutzer ausgegeben; es kann auch ein Versuch erfolgen, die Bedingung aufzulösen, welche die Exception ausgelöst hat, oder es geschieht gar nichts. Wozu dienen Exceptions? Antwort: Exceptions sind hilfreich, weil sie eine strukturierte Fehlerbehandlung ermöglichen. Ohne Verwendung von Exceptions müßten bei einer Methode bestimmte Rückgabewerte immer so definiert werden, daß sie eine Fehlerbedingung signalisieren, und die aufrufende Methode müßte immer nach diesen besonderen Rückgabecodes suchen. Bei Verwendung von Exceptions fallen viele komplexe Definitionen weg, die sich sonst aus Fehlerbehandlungsmechanismen ergeben würden. Übungen Ändern Sie die folgende Schnittstellendefinition so ab, daß in den gewünschten Situationen die geeigneten Exceptions ausgelöst werden. exception UngueltigeZahlException { }; exception KeinEingehenderAnrufException { }; exception HoererNichtAbgehobenException { };
interface Telefon { void HoererAbgehoben(); void HoererNichtAbgehoben(); void waehleNummer(in string TelefonNummer); void AnrufEntgegennehmen(); }; Eine mögliche Lösung sähe wie folgt aus: exception UngueltigeZahlException { }; exception KeinEingehenderAnrufException { }; exception HoererNichtAbgehobenException { }; interface Telefon { void HoererAbgehoben(); void HoererNichtAbgehoben(); void waehleNummer(in string TelefonNummer) raises (UngueltigeZahlException, HoererNichtAbgehobenException); void AnrufEntgegennehmen() raises (KeinEingehenderAnrufException); }; Antwort: Bei dieser Lösung wird davon ausgegangen, daß es erst zulässig ist, eine Nummer zu wählen, wenn das Telefon abgehoben wurde. Es wäre gleichfalls sinnvoll, daß waehleNummer() die Methode HoererAbgehoben() aufruft, wenn das Telefon noch nicht abgehoben wurde. Implementieren Sie die Schnittstelle aus Übung 1, und lösen Sie in den betreffenden Situationen die geeigneten Exceptions aus. (Bei den meisten Methoden passiert wahrscheinlich nichts, außer bei waehleNummer(), mit der wahrscheinlich die Gültigkeit der angegebenen Telefonnummer geprüft wird). Antwort: Hier soll keine Komplettlösung angegeben werden. Die Implementierung für Übung 1 würde aber vermutlich folgendermaßen aussehen (der folgende Quelltext ist Pseudocode): void waehleNummer(string TelefonNummer) { if (Telefon ist aufgelegt) { raise HoererNichtAbgehobenException; } if (Telefonnummer ist ungültig) {
raise UngueltigeZahlException; } Nummer wählen... } void AnrufEntgegennehmen() { if (kein ankommender Ruf vorhanden ist) { raise KeinEingehenderAnrufException; } Anruf entgegennehmen... }
Tag 8: Hinzufügen von Geldautomat-Fähigkeit Quiz Frage: Was sind die vier Schritte, nach denen Sie üblicherweise vorgehen, um Erweiterungen an einer CORBAAnwendung vorzunehmen? Zunächst müssen Sie die zusätzlichen Anforderungen definieren, dann den Systementwurf ändern, anschließend die IDL-Definitionen so anpassen, daß darin der neue Entwurf berücksichtigt ist, und zum Schluß die neue Funktionalität implementieren. Übung Fügen Sie der Schnittstelle Geldautomaten eine Operation hinzu, die es ermöglicht, Beträge zwischen Konten zu verschieben. Achten Sie darauf, auch geeignete Exceptions bereitzustellen. Der IDL-Quelltext müßte etwa wie folgt aussehen: float Ueberweisung(in BankKarte Karte, in Konto fromKonto, in Konto toKonto, in short pin in float Betrag) raises (AuthentifizierungException, UngueltigerBetragException, UnzureichendeGeldmittelException); Beachten Sie, daß bei der Implementierung dieser Methode wahrscheinlich die BankKarte zur Prüfung der Berechtigung sowohl für den Parameter fromKonto als auch für toKonto verwendet werden müßte. (Es wäre in der Tat sinnvoll, Überweisungen auf jedes beliebige Konto zuzulassen, aber reale Bankkarten erlauben nur Überweisungen zwischen Konten, für die diese Karten berechtigt sind.)
Tag 9: Verwendung von Callbacks zum Hinzufügen von Push-
Fähigkeit Quiz Warum wird das Thema Thread-Sicherheit bei der in diesem Kapitel entwickelten Beispielanwendung bedeutsam? Antwort: Die Thread-Sicherheit ist wichtig, weil es vorkommen könnte, daß mehrere Threads versuchen, gleichzeitig auf dieselben Daten zuzugreifen. Damit die mögliche Zerstörung bzw. Verfälschung der Daten verhindert werden kann, muß der Zugriff auf diese Thread-sicher erfolgen. Können Sie sich anstelle der Verwendung von oneway-Methoden zur Benachrichtigung von Clients über Aktualisierungen einen anderen Weg des effizienten Sendens von Aktualisierungsbotschaften an Clients denken? (Hinweis: Multithreading könnte hierbei von Nutzen sein.) Antwort: Wenn Sie zum Senden jeder Client-Aktualisierungsnachricht einen neuen Thread starten, so können Sie damit die Effizienz einer nicht über oneway-Aufrufe definierten Nachrichtenübermittlung verbessern. Dies liegt daran, daß in diesem Fall nicht ein einzelner Thread darauf zu warten braucht, daß der Client antwortet, bevor er die nächste Nachricht übermitteln kann, sondern jeder Thread blockiert, während er auf eine Antwort des betreffenden Clients wartet. Übungen Früher im Kapitel wurde angemerkt, daß zur Zeit keine Möglichkeit besteht, den automatischen Kontoaktualisierungsdienst abzubestellen. Erstellen Sie eine IDL-Methodensignatur für eine solche Operation. Vergessen Sie nicht, passende Exceptions vorzusehen, falls erforderlich. Antwort: Die Methodensignatur, die zur Schnittstelle Bank hinzugefügt werden müßte, sieht in etwa so aus: void cancelUpdateService(in Konto Konto) raises (UngueltigesKontoException); Antwort: Die Exception UngueltigesKontoException wird ausgelöst, wenn das Konto nicht zu der Bank gehört, für die die Operation aufgerufen wurde. So können Sie entweder die Exception auslösen oder die Operation ignorieren, wenn sie für ein Konto aufgerufen wird, das schon bei diesem Dienst angemeldet war. Implementieren Sie die Abbestellmethode für den Kontoaktualisierungsdienst von Übung 1.
Antwort: Hier wird nicht die vollständige Implementierung angegeben, sondern der folgende Pseudocode, der den allgemeinen Algorithmus zur Verfügung stellt: void cancelUpdateService(Konto Konto) { if (Konto gehört nicht zu dieser Bank) { throw UngueltigesKontoException; } if (Konto ist bereits beim automatischen Aktualisierungsdienst angemeldet) { throw UngueltigesKontoException; } (sonst) Konto aus der Liste der für den automatischen Aktualisierungsdienst angemeldeten Konten löschen }
Tag 10: Lernen über CORBA-Entwurfsfragen Quiz Was ist das mit dem Mischen von Client- und Server-Funktionen in einer Einfach-Thread-CORBAAnwendung verknüpfte Hauptproblem? Antwort: Je nachdem, um was für eine Anwendung es sich handelt, kann das Mischen von Client- und ServerFunktionalität in einer aus einem einzigen Thread bestehenden Anwendung dazu führen, daß möglicherweise eine gegenseitige Sperrung auftritt. Wie kann die Verwendung von Referenzzählung in einer CORBA-Anwendung zu Problemen führen? Antwort: Wenn eine Anwendungskomponente abstürzt, wird keiner der Referenzzähler für die zugehörigen Objekte zurückgesetzt. Daher werden die betreffenden Objekte nicht aus dem Speicher gelöscht, obwohl dies der Fall sein müßte. Welche Version von X11 (dem System X Window) wäre notwendig, um X-basierte MultithreadAnwendungen sicher auszuführen?
Antwort: Zum sicheren Ausführen von Multithread-Anwendungen wird X11R6.1 oder höher benötigt. Die früheren Versionen verfügen nicht über die Thread-sicheren Bibliotheken. Warum ist die Möglichkeit der Übergabe von Objekten als Wert manchmal nützlich? Antwort: Wenn eine Anwendungskomponente eine Reihe von Operationen an einem Objekt ausführen möchte, ist es oft effektiver, eine lokale Kopie dieses Objekts zu verwenden, anstatt zahlreiche Methodenaufrufe für ein Remote-Objekt durchzuführen. Warum ist es üblicherweise nicht ratsam, das früher in diesem Kapitel eingeführte Entwurfsmuster Ausschließlicher oneway-Aufruf zu verwenden? Antwort: Da oneway-Methoden unzuverlässig sind, ist es schwierig, das Entwurfsmuster für exklusive onewayAufrufe für Konstellationen zu implementieren, in denen eine zuverlässige Nachrichtenübermittlung garantiert sein muß.
Tag 11: Verwenden der dynamischen Aufrufschnittstelle (Dynamic Invocation Interface, DII) Quiz Würden Sie erwarten, daß DII für die meisten CORBA-Anwendungsentwickler von Nutzen ist? Warum oder warum nicht? Antwort: Wie mehrfach in diesem Kapitel betont, ist die DII für die meisten CORBA-Anwendungsentwickler vermutlich nicht hilfreich. Dies liegt in erster Linie daran, daß die Schnittstellen für die meisten Anwendungen zum Zeitpunkt der Kompilierung sowieso schon bekannt sind. Auch führt die Verwendung der DII zu einer erhöhten Komplexität, die der Entwickler tunlichst vermeiden sollte. Was sind die Vorteile von DII gegenüber statischen Methodenaufrufen? Antwort: Die DII weist gegenüber dem statischen Methodenaufruf zwei Vorteile auf: Erstens kann der Client flexibel Operationen auf Schnittstellen aufrufen, die zum Zeitpunkt der Client-Kompilierung unbekannt waren, und zweitens kann eine von zahlreichen Optionen zur Beschaffung des Rückgabeergebnisses bei einem Remote-Methodenaufruf verwendet werden.
Was sind die Nachteile von DII, verglichen mit statischen Methodenaufrufen? Antwort: Nachteile der DII sind ihre Komplexität, das Fehlen von Funktionen zum Prüfen statischer Methodenaufrufe, der zusätzliche Systemaufwand, der mit dem Aufrufmechanismus verbunden ist, und der für die Ermittlung der Schnittstellen erforderliche Systemaufwand.
Tag 12: CORBAservices und CORBAfacilities Quiz Wer erstellt die Spezifikationen für die CORBAservices und die CORBAfacilities? Antwort: Die Spezifikationen für die CORBAservices und CORBAfacilities werden von der OMG (Object Management Group) definiert. Wer stellt die Implementierungen für die CORBAservices und die CORBAfacilities zur Verfügung? Antwort: Die Implementierungen für die CORBAservices und CORBAfacilities werden von den Anbietern selbst zur Verfügung gestellt. Die OMG liefert nur die Spezifikationen, nicht jedoch die Implementierungen. Welche CORBAservices und/oder CORBAfacilities muß ein Hersteller, wenn überhaupt, mit einem ORBProdukt zur Verfügung stellen, damit eine Anwendung als mit CORBA 2.0 kompatibel gilt? Antwort: Damit ein Anbieter sein Produkt als CORBA-2-kompatibel bezeichnen kann, brauchen darin keine Implementierungen von CORBAservices oder CORBAfacilities vorhanden zu sein. Ausschlaggebend für die Kompatibilität sind einzig und allein die ORB-Funktionen des Produkts. Warum sind Einrichtungen für vertikale Märkte hilfreich? Antwort: Einrichtungen für vertikale Märkte sind hilfreich, weil sie die Interoperabilität zwischen Anwendungen innerhalb einer bestimmten Branche verbessern können. Ferner können sie die gemeinsame Verwendung von Daten durch mehrere Unternehmen innerhalb einer Branche erleichtern. Übungen
Geben Sie an, wie in der Beispielbankanwendung der BankServer durch den Objektvermittlungsdienst ersetzt werden könnte. Antwort: In der Beispielbankanwendung ist die Komponente BankServer nur vorhanden, um anderen Anwendungskomponenten das Auffinden der Banken und Geldautomaten zu ermöglichen. Wie sich herausgestellt hat, stellt der Vermittlungsdienst (Trader-Service) genau diese Funktion zur Verfügung. Anstatt bei einem BankServer zu suchen und sich bei diesem registrieren zu lassen, könnten sich die Banken und Geldautomaten beim Vermittlungsdienst registrieren lassen. Diese Komponenten stünden anschließend anderen Anwendungskomponenten zur Verfügung, insbesondere könnte die Komponente Kunden die Komponenten über denselben Vermittlungsdienst suchen. Auf diese Weise übernähme der Vermittlungsdienst effektiv die Funktionalität der Komponente BankServer. Beschreiben Sie, wie der Ereignisdienst innerhalb der Bankanwendung verwendet werden könnte (Hinweis: denken Sie an die am 9. Tag hinzugefügte Funktion zur automatischen Kontoaktualisierung). Was wäre der Vorteil eines solchen Ansatzes? Antwort: Derzeit muß die Bank zur automatischen Kontoaktualisierung die beim Aktualisierungsdienst angemeldeten Konten durchgehen. Die Bank ruft eine Rückrufmethode (Callback) für alle Kunden auf, die mit den betreffenden Konten assoziiert sind. Mit dem Ereignisdienst könnte die Bank Herausgeber von Ereignissen für die Kontostandsaktualisierung werden, für die sich die Kunden anmelden könnten. Der Vorteil dieses Ansatzes liegt darin, daß damit die komplexen Vorgänge um die Versendung von Aktualisierungsnachrichten an die Kunden in der Bankanwendung wegfallen und die Einzelheiten der Nachrichtenübermittlung auf den Ereignisdienst übertragen werden. (Für besonders Ehrgeizige:) Wenn Ihnen Produkte zur Verfügung stehen, die einen oder mehrere CORBAservices implementieren, versuchen Sie, die Funktionalität eines Diensts Ihrer Wahl in die Beispielbankanwendung zu integrieren. (Im Abschnitt »Welche CORBAservices sollen verwendet werden?« haben wir überlegt, welche Dienste für unsere Beispielanwendung gut geeignet wären.) Da es für diese Übung zahlreiche Lösungsmöglichkeiten gibt, haben wir auf die Darstellung einer Lösung verzichtet.
Tag 13: Entwicklung von Internet-Anwendungen mit CORBA und Java Quiz Welches IDL-Konstrukt entspricht dem package in Java? Antwort: Das in der IDL definierte module ist dem in Java definierten package sehr ähnlich.
Welchen Vorteil bietet die RMI gegenüber CORBA? Wie sieht es umgekehrt aus? Antwort: Ein Vorteil von RMI gegenüber CORBA besteht darin, daß RMI die Weitergabe von Objekten durch Werte erlaubt. Vorteile von CORBA gegenüber RMI sind die Sprachenunabhängigkeit und die Robustheit. Warum ist es für einen Entwickler sinnvoll, eine CORBA-Anwendung mit Java zu entwickeln? Antwort: Die Portierbarkeit von Java macht diese Sprache besonders attraktiv für die Entwicklung von CORBAClient-Anwendungen, die auf unterschiedlichen Plattformen laufen müssen.
Tag 14: Einrichten von Web-Fähigkeit für das Beispiel Bank mit Java Quiz Warum könnte es vorteilhaft sein, einen CORBA-Client als Java-Applet einzusetzen? Antwort: Zwei mögliche Argumente für die Verwendung von Java-Applets für CORBA-Clients liegen in der Portierbarkeit von Java und in den einfachen Verteilungsmechanismen für Applets über WWWBrowser. Warum ist es nützlich, wenn Browser CORBA-verwandte Klassen beinhalten, die eingebaut sind (beispielsweise enthält der Communicator von Netscape VisiBroker for Java runtime von Visigenic)? Antwort: Da die für die CORBA-Konnektivität benötigten Klassen im Browser integriert sind, brauchen sie nicht jedes Mal zusammen mit dem Applet heruntergeladen zu werden. Dies verkürzt die zum Herunterladen benötigte Zeit. Was ist ein möglicher Nachteil des in Frage 2 beschriebenen Bündelungsschemas? Antwort: Ein möglicher Nachteil liegt in der Tatsache, daß die zusammen mit dem Browser zur Verfügung gestellten Klassen eines Tages veraltet sein werden. In diesem Fall müssen neue Versionen der betreffenden Klassen auf jeden Fall zusammen mit dem Applet heruntergeladen werden. Übung
Erweitern Sie BankApplet so, daß es noch mehr tolle Sachen macht. Sie könnten registerblattartige Bedienfelder einbauen, um die zu einem bestimmten Kunden gehörigen Konten anzuzeigen. Oder Sie könnten das Applet so erweitern, daß mehrere Kunden mit einem Konto verknüpft werden können, und die Konto-Information entsprechend ändern, so daß alle mit einem gegebenen Konto verknüpften Kunden gezeigt werden. (Da dies eine Übung mit offenem Ende ist, wird keine Antwort angegeben.)
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Anhang B CORBA-Werkzeuge und Dienstprogramme Welche CORBA-ORB-Produkte gibt es? Welche CORBA-relevanten Entwicklungswerkzeuge gibt es?
Dieser Anhang bietet eine kurze Übersicht über einige der momentan erhältlichen CORBA-Produkte. Obwohl ich mich nach allen Kräften bemüht habe, diese Informationen so aktuell wie möglich zu gestalten, gibt es immer Produkte, die einem »durchrutschen«. Außerdem werden aufgrund des wachsenden Interesses für CORBA ständig neue Produkte angekündigt. Hier also die Übersicht über alle CORBA-bezogenen Produkte, von denen ich Kenntnis hatte, als ich dieses Buch schrieb.
Welche CORBA-ORB-Produkte gibt es? Die in diesem Abschnitt beschriebenen Produkte enthalten ORBs (Object Request Brokers) und werden zur Entwicklung und Verteilung von CORBA-Anwendungen verwendet. In der Regel bieten diese Produkte keine vollständige Entwicklungsumgebung, sondern nur den ORB selbst, einen IDLCompiler und diverse weitere Werkzeuge, die bei der Entwicklung von CORBA-Anwendungen hilfreich sein können. Corbus von BBN Corbus ist ein mit CORBA 2.0 kompatibler ORB, der für die amerikanische Regierung und zur nicht kommerziellen Nutzung kostenlos zur Verfügung gestellt wird (unter bestimmten Bedingungen, Einzelheiten hierzu finden Sie auf der WWW-Seite von BBN). Corbus steht für Solaris, SunOS und HP-UX zur Verfügung. Informationen zu Corbus und herunterladbare Programmkopien erhalten Sie auf der WWW-Seite von
BBN unter http://www.bbn.com/products/dpom/corbus.htm. ObjectBroker von BEA Systems BEA ist vielleicht am besten durch seinen Transaktionsverarbeitungsmonitor Tuxedo bekannt. Das Unternehmen bietet einen CORBA-ORB (den es ungefähr im März 1997 von Digital übernommen hat). ObjectBroker unterstützt 20 verschiedene Plattformen (auf der WWW-Seite von BEA ist leider nicht angegeben, welche dies sind) und bietet OLE-Integration auf Windows-Plattformen. BEA behauptet, daß ObjectBroker der am weitesten ausgereifte ORB ist, denn die erste Version stammt von 1991 (in der Tat ist dies, in CORBA-Jahren gerechnet, schon eine Ewigkeit her). Informationen zu ObjectBroker finden Sie auf der WWW-Seite von BEA unter http://www.beasys.com/products/obb/index.htm. CHORUS/COOL ORB von Chorus Systems Chorus, das kürzlich (September 1997) von Sun Microsystems übernommen wurde, bietet mit dem Produkt CHORUS/COOL ORB einen CORBA-2.0-kompatiblen ORB. CHORUS/COOL ORB ist für eine beeindruckende Menge von Betriebssystemen verfügbar, zu denen AIX, Echtzeitbetriebssysteme und eingebettete Betriebssysteme von CHORUS, HP-UX, Linux, OpenDesktop und OpenServer von SCO, SunOS, Solaris, Windows 95 und Windows NT gehören. Informationen zu CHORUS/COOL ORB sowie eine herunterladbare Testkopie finden Sie auf der WWW-Seite von Chorus unter http://www.chorus.com/Products/Cool/index.html. SmalltalkBroker von DNS Technologies SmalltalkBroker von DNS Technologies gehört zur seltenen Rasse der CORBA-2.0-kompatiblen ORBAnwendungen für Smalltalk. SmalltalkBroker stellt auch eine Reihe von CORBAservicesImplementierungen zur Verfügung: den CORBA-Bezeichnungsdienst, den CORBALebenszyklusdienst, den CORBA-Ereignisdienst und den CORBA-Transaktionsdienst. Informationen zu SmalltalkBroker stehen auf der WWW-Seite von DNS Technologies unter http://www.dnstech.com/stbprod.htm zur Verfügung. CORBAplus-Produkte von Expersoft Von der CORBAplus-Produktfamilie von Expersoft gibt es diverse Varianten: ■
■
CORBAplus für C++, ein CORBA-2.0-kompatibler ORB, unterstützt Windows 95, Windows NT, Solaris, HP-UX und AIX. Er bietet ferner Implementierungen für den CORBABezeichnungsdienst, den CORBA-Ereignisdienst und den CORBA-Beziehungsdienst. CORBAplus Java Edition ist eine 100 % reine Java-ORB-Implementierung, mit der JDK 1.0.2 und JDK 1.1 unterstützt werden.
■
■
CORBAplus ActiveX Bridge bietet Interoperabilität zwischen ActiveX- und CORBAObjekten, wodurch unter anderem die Entwicklung von CORBA-Anwendungen mit Visual Basic möglich ist. CORBAplus Enterprise Edition fügt Funktionalität zum asynchronen Messaging hinzu, ferner liefert sie Unterstützung von Multithreading-Anwendungen, einen dynamischen TypenManager (der zusammen mit der DDI verwendet wird) und eine Funktion zur Adressierung von URL-Objekten.
Informationen zur CORBAplus-Produktfamilie sowie herunterladbare Testkopien finden Sie auf der WWW-Seite von Expersoft unter http://www.expersoft.com/Products/CORBAplus/corbaplus.htm. ORB Plus von Hewlett-Packard Mit ORB Plus hat auch Hewlett-Packard den CORBA-Markt betreten. Das Produkt unterstützt (natürlich) HP-UX, Solaris und Windows NT. ORB Plus ist nicht nur ein CORBA-2.0-kompatibler ORB, sondern bietet auch Implementierungen für den CORBA-Lebenszyklusdienst, den CORBABezeichnungsdienst und den CORBA-Ereignisdienst. Ein einzigartiges Merkmal von ORB Plus ist die Unterstützung des DCE-CIOP (im wesentlichen entspricht dies dem IIOP für DCE), zumindest unter HP-UX. Hewlett-Packard hat bei der Entwicklung von Vorschlägen zum Erstellen von Brücken zwischen CORBA und den Microsoft-Technologien wie DCOM und ActiveX eine wesentliche Rolle gespielt, so daß Entwickler, die beide Welten miteinander verbinden möchten, die Produkte von Hewlett-Packard im Auge behalten sollten. Weitere Informationen zu ORB Plus erhalten Sie auf der WWW-Seite von Hewlett-Packard unter http://www.hp.com/gsy/orbplus.html. SOMobjects von IBM SOMobjects ist der Beitrag von IBM zur CORBA-Welt und steht für AIX, OS/2 und Windows NT zur Verfügung. SOMobjects bietet Implementierungen für eine Reihe von CORBAservices, so z.B. für den CORBA-Ereignisdienst, den CORBA-Lebenszyklusdienst, den CORBA-Objektpersistenzdienst, den CORBA-Concurrency-Dienst und den CORBA-Transaktionsdienst. Informationen zu SOMobjects sowie eine herunterladbare Programmkopiefinden Sie auf der WWWSeite von IBM unter http://www.software. ibm.com/ad/somobjects/. Orbix, OrbixWeb und weitere Produkte von Products von IONA Technologies IONA und Orbix sind wohl zwei der am besten bekannten und am meisten geschätzten Namen, wenn es um CORBA geht. IONA bietet eine Reihe von CORBA-Produkten für ein weites Anwendungsspektrum an:
■
■
■
■
Orbix ist wohl der am weitesten verbreitete CORBA-ORB. Das Produkt ist für eine Fülle von Betriebssystemen verfügbar, wozu auch viele Unix-Systeme (Solaris, HP-UX, IRIX, AIX und Digital Unix) gehören, für Windows NT, Windows 95, OS/2, VxWorks, QNX und MVS erhältlich. Orbix unterstützt Sprachabbildungen für Smalltalk, Ada95 und C++. IONA geht mit Orbix auch noch einen Schritt weiter: Unter Windows unterstützt Orbix die Integration von OLE und ActiveX; unter MVS bietet das Produkt eine vollständige Unterstützung für Cobol, CICS, DB/2 und IMS. OrbixWeb für Java überträgt die Leistungsfähigkeit von Orbix in die Welt der Java-Applets und -Anwendungen. Da OrbixWeb zu 100 % in Java programmiert wurde, funktioniert es auf jedem Java-fähigen Browser. OrbixWeb ist der erste ORB, der JDK 1.1 unterstützt, und auch JDK 1.0.2 wird voll unterstützt. Orbix Wonderwall ist ein Begleitprodukt zu OrbixWeb und bietet Sicherheit in Form eines IIOP-Firewall (wie erwähnt, ist das IIOP, das Internet-Protokoll für die Kommunikation zwischen ORBs, der Standardmechanismus, über den die ORBs verschiedener Anbieter miteinander kommunizieren können). IONA stellt dem Entwickler auch Implementierungen vieler CORBAservices-Spezifikationen zur Verfügung, beispielsweise in Form von OrbixEvents (CORBA-Ereignisdienst), OrbixManager (CORBA-Anwendungsüberwachungs- und Verwaltungswerkzeuge), OrbixNames (CORBA-Bezeichnungsdienst), OrbixOTS (CORBA-Transaktionsdienst), OrbixSecurity (CORBA-Sicherheitsdienst), OrbixTalk (eine Multicasting-Implementierung des CORBA-Ereignisdienstes) und OrbixTrader (CORBA-Vermittlungsdienst).
Offensichtlich ist IONA inzwischen zu einem wahren Universalanbieter mit einer breiten Palette von CORBA-Lösungen geworden. Informationen zu Orbix, OrbixWeb und den übrigen Produkten von IONA, von denen viele als Testversionen heruntergeladen werden können, finden Sie auf der WWWSeite von IONA unter http://www.iona.com/. Navigator und Enterprise Server von Netscape Netscape nutzt eine Lizenz für VisiBroker für Java von Visigenic Software (darauf komme ich später noch ausführlich zu sprechen) und integriert die ORB-Technologie in den beliebten Web-Browser Navigator 4.0 (und auch in den Communicator). Dies ist ein Segen für Intranet-Entwickler (und in geringerem Maße auch für Internet-Entwickler), weil die Client-Seite einer CORBA-Anwendung (also der in Java geschriebene Teil, der im Browser ausgeführt wird) kleiner gehalten werden kann (somit braucht nicht so viel Code auf den Client heruntergeladen zu werden), da der ORB in den Browser integriert ist. Da VisiBroker zu 100 % in Java programmiert wurde, funktioniert eine solche Anwendung auch mit anderen Browsern als Netscape, obwohl der VisiBroker-Code in diesem Fall auf den betreffenden Browser heruntergeladen werden muß. (Natürlich steht es anderen Anbietern frei, die ORB-Technologie ebenfalls in ihre Browser zu integrieren.) Netscape hat die ORB-Technologie von Visigenic auch mit Enterprise Server 3.0 gekoppelt, so daß die CORBA-Funktionalität auch auf der Server-Seite zur Verfügung steht. Informationen zum Navigator sowie eine herunterladbare Programmkopie stehen auf der WWW-Seite von Netscape unter http://www.netscape.com/ zur Verfügung. Entwicklungsrelevante
Informationen für Netscape-Produkte finden Sie unter http://developer.netscape.com/. OmniBroker von Object-Oriented Concepts OmniBroker von Object-Oriented Concepts ist ein weiterer CORBA-2.0-kompatibler ORB, der zur nichtkommerziellen Verwendung kostenlos erhältlich ist. Als Dreingabe wird mit OmniBroker auch der komplette Quelltext zur Verfügung gestellt. OmniBroker unterstützt die IDL-Sprachabbildungen für C++ und Java in vollem Maß. Da der Quelltext verfügbar ist, kann das Produkt auf quasi jeder Plattform eingesetzt werden. OmniBroker für C++ wurde unter IRIX, Solaris, HP-UX, AIX, Linux, Windows 95 und Windows NT getestet; OmniBroker für Java kann mit JDK 1.0.2 oder 1.1.3 von Sun sowie mit Visual J++ 1.1 von Microsoft eingesetzt werden. Informationen zu OmniBroker sowie eine herunterladbare Programmkopie stehen auf der WWW-Seite von Object-Oriented Concepts unter http://www.ooc.com/ob.html zur Verfügung. DOME von Object-Oriented Technologies Auch DOME ist ein kostenlos (obwohl die Bestimmungen für die Weiterverteilung nicht klar sind) erhältlicher CORBA-ORB, der C++ und C unterstützt. DOME steht für eine Vielzahl von Plattformen einschließlich Solaris, SunOS, AIX, HP-UX, Digital Unix, Windows 3.1, Windows 95, Windows NT, VMS, OS/2, OS-9, pSOS und Linux zur Verfügung. Weitere Informationen zu DOME finden Sie auf der WWW-Seite von Object-Oriented Technologies unter http://www.oot.co.uk/. Voyager von Objectspace Voyager ist zwar noch nicht CORBA-kompatibel (Objectspace plant die CORBA-Interoperabilität für das nächste Release), ist aber für Java-Entwickler von großem Interesse. Ein Blick auf die Liste der Funktionen macht klar, warum: Zusätzlich zu den ORB-ähnlichen Funktionen, die man erwarten würde (wie Remote-Methodenaufruf, Bezeichnungsdienst usw.), stellt Voyager noch viel, viel mehr zur Verfügung. So kann beispielsweise jede Java-Klasse ohne Eindringen fern aktiviert werden (selbst wenn der Quelltext nicht verfügbar ist). Voyager unterstützt auch eine Vielzahl von MessagingDiensten sowie mobile Objekte (dies sind Objekte, die förmlich auf jeden Voyager-Server im Netzwerk verschoben werden können) und sogar autonome Agenten (Objekte, die sich selbst auf andere Voyager-Server verschieben können). Voyager fügt auch noch weitere Funktionen hinzu, wie die Unterstützung der Objektpersistenz über verschiedene Datenbanken hin (sowie seinen eigenen integrierten Permanenzmechanismus) und einen angepaßten Sicherheitsmanager. Und zur Krönung des Ganzen stellt Objectspace Voyager auch noch kostenlos zur Verfügung. (Objectspace berechnet allerdings seine Support-Leistungen für Voyager, obwohl der Umfang des kostenlosen Supports durchaus anständig ist.) Es ist erstaunlich, daß Objectspace die Nutzung dieser Technologie so einfach weggibt, besonders wenn man die Funktionalität des Produkts betrachtet. Obwohl Voyager ein rein in Java programmiertes Produkt ist, können Voyager-Objekte nach dem
Hinzufügen der CORBA-Unterstützung mit CORBA-Anwendungen interagieren. Zusätzlich plant Objectspace auch noch die Unterstützung für die Interoperabilität mit DCOM. Java-Entwickler sind es sich selbst schuldig, dieses Produkt auszuprobieren, und der Rest der CORBAGemeinde sollte auf dem laufenden bleiben. Informationen zu Voyager und die kostenlose herunterladbare Programmkopie sind auf der WWW-Seite von Objectspace unter http://www.objectspace.com/voyager/ erhältlich. omniORB2 vom Olivetti und Oracle Research Laboratory omniORB2 stammt von Olivetti und Oracle Research Laboratory (eine interessante Kombination) und wird einigen Entwicklern aufgrund eines herausragenden Merkmals besonders attraktiv erscheinen: Das Produkt ist kostenlos erhältlich, sogar bei kommerzieller Nutzung. Ja, Sie haben richtig gelesen! ORL hat omniORB2 über die GNU General Public License und die GNU Library General Public License verfügbar gemacht. Als ich dieses Buch geschrieben habe, konnte man omniORB2 noch nicht als vollständige CORBA-2.0-Implementierung bezeichnen, weil die Unterstützung für Typecode und den Datentyp any noch fehlte. Ferner wurden die DII (Dynamic Invocation Interface) sowie die DSI (Dynamic Skeleton Interface) noch nicht unterstützt. Trotzdem kann sich omniORB2 für sehr viele Entwicklungsprojekte als nützlich erweisen. Die GNU General Public License Einzelheiten zur GNU General Public License (GPL) stehen auf der WWW-Seite der Free Software Foundation (FSF) unter http://www.fsf.org/copyleft/gpl.html zur Verfügung. Analog dazu sind Details zur GNU Library General Public License unter http://www.fsf.org/copyleft/lgpl.html erhältlich. Im wesentlichen kann Software, die unter diesen Bedingungen verteilt wird, kostenlos kopiert und verteilt oder sogar geändert und verkauft werden, unter der Voraussetzung, daß der Quelltext mit dem Produkt, das diese Software verwendet, immer mitgeliefert wird. Die GPL ist bei den Benutzern kostenloser Unix-ähnlicher Betriebssysteme wie Linux oder FreeBSD sehr beliebt.
Informationen zu omniORB2 sowie die (kostenlos erhältliche) Verteilung finden Sie auf der WWWSeite von ORL unter http://www.orl.co.uk/omniORB/omniORB.html. NEO und Joe von SunSoft Die CORBA-Produktfamilie von Sun umfaßt vier Hauptprodukte: ■
Solaris NEO ist ein CORBA-2.0-kompatibler ORB, der Implementierungen für den CORBA-
■
■
■
Bezeichnungsdienst, den CORBA-Eigenschaftendienst, den CORBA-Ereignisdienst, den CORBA-Beziehungsdienst und den CORBA-Lebenszyklusdienst bietet. Solaris NEO rühmt sich auch noch anderer Funktionen, wie z.B. SNMP-Verwaltungsfunktionen (Simple Network Management Protocol), Unterstützung für Arbeitsgruppen, gleichzeitig ablaufende Anfragen, permanente Objektspeicherung und viele weitere. Joe ist ein CORBA-ORB, der zu 100 % in Java implementiert wurde. Joe ist im Paket zusammen mit Solaris NEO oder auch einzeln erhältlich. NEO Connectivity for Microsoft Windows ermöglicht Windows 95 und Windows NT, unter Verwendung von ActiveX, OLE und COM-Schnittstellen, die Kommunikation mit CORBAObjekten. Solstice NEO dient zur CORBA-Systemverwaltung und stellt folgende Funktionen zur Verfügung: Verwaltung von ORBs, Anzeige des Status von CORBA-Objekten und Prozessen, Ausgleich der Systembelastung und weitere Verwaltungsfunktionen. Solstice NEO basiert auf Java und ist zusammen mit Solaris NEO oder auch einzeln erhältlich.
Informationen zu NEO und Joe sowie herunterladbare Programmkopien einiger Sun-Produkte finden Sie auf der WWW-Seite von Sun unter http://www.sun.com/solaris/neo/. Jaguar CTS von Sybase Obwohl die aktuelle Version (1.1) von Jaguar CTS nicht CORBA-kompatibel ist, verspricht Sybase für Version 2 CORBA-Interoperabilität. Bis dahin ist Jaguar CTS besonders deshalb interessant, weil das Produkt eine große Reihe verschiedenster Technologien und Produkte unterstützt. Jaguar CTS kann mit Java, ActiveX, PowerBuilder und C/C++ verwendet werden. Es unterstützt die Konnektivität über HTTP, TDS und (in Version 2) IIOP. Ferner wird über JDBC, ODBC oder CTlib auch die Datenbank-Konnektivität zu Datenbanken wie Sybase (kein Wunder), Oracle, Informix, SQL Server oder Großrechnerdatenbanken unterstützt. Informationen zu Jaguar CTS stehen auf der WWW-Seite von Sybase unter http://www1.sybase.com/products/jaguar/ zur Verfügung. TIB/ObjectBus von TIBCO TIBCOs starke Seite ist das Messaging, und es festigt diese Position mit dem Produkt TIB/ObjectBus, einem CORBA-2.0-kompatiblen ORB, von dem TIBCO behauptet, daß dies »der einzige ORB ist, der sowohl Broadcasting- als auch zuverlässige Multicasting-Kommunikation verwendet, um ORBAnforderungen einzuleiten und ORB-Ereignisse auf eine unbegrenzte Anzahl von Servern und Benutzern zu verteilen«. Zusätzlich zur Unterstützung des CORBA-Bezeichnungsdienstes und des CORBA-Ereignisdienstes bietet TIB/ObjectBus CORBA-kompatible Mechanismen für die Kommunikationsmodelle »publish-subscribe« und »request-reply«. TIB/ObjectBus unterstützt eine Vielzahl von Plattformen wie Solaris, HP-UX, VMS, Digital Unix, OS/2, AIX und Windows NT. Ferner plant TIBCO die Unterstützung von Java, des CORBA-Transaktionsdienstes sowie anderer Funktionen. Weitere Informationen zu TIB/ObjectBus steht auf der WWW-Seite von TIBCO unter
http://www.tibco.com/objctbus/tib_object_bus.html zur Verfügung. VisiBroker und weitere Produkte von Visigenic Software Visigenic ist dabei, sich im Bereich CORBA schnell einen Namen zu machen. Das Unternehmen hat Lizenzen seiner ORB-Technologie an eine Anzahl namhafter Anbieter vergeben, zu denen Borland, Netscape, Novell, Oracle, SGI und Sybase gehören. Zusätzlich zur Lizenzierung der eigenen Technologie bietet Visigenic noch die folgenden Produkte: ■
■
■
VisiBroker für C++ ist ein vollständiger CORBA-ORB zur Entwicklung von CORBAAnwendungen in C++. VisiBroker steht für eine Vielzahl von Plattformen (Solaris, HP-UX, AIX, IRIX, Digital Unix, Windows 95 und Windows NT) zur Verfügung. VisiBroker für Java ist eine 100 % in Java geschriebene Implementierung eines CORBAORBs. Visigenic zeichnet sich dadurch aus, daß das Unternehmen den ersten mit CORBA 2.0 kompatiblen ORB auf den Markt gebracht hat, der 100 % in Java geschrieben wurde. Visigenic stellt ferner Implementierungen einer Reihe von CORBAservices zur Verfügung: VisiBroker Naming Service (CORBA-Bezeichnungsdienst), VisiBroker Event Service (CORBA-Ereignisdienst) und TPBroker (CORBA-Transaktionsdienst).
Visigenic hat kürzlich Visigenic/DAP (Distributed Application Platform Architecture) angekündigt, eine Architektur, mit der eine CORBA-Entwicklungsplattform entstehen soll. Zusätzlich zu den derzeit angebotenen CORBAservices plant Visigenic, noch weitere Dienste zur Verfügung zu stellen, wie den CORBA-Vermittlungsdienst, integrierte Transaktionsfunktionalität und asynchrones Messaging. Informationen zu VisiBroker und zu den übrigen Visigenic-Produkten, von denen viele zum Ausprobieren heruntergeladen werden können, finden Sie auf der WWW-Seite von Visigenic unter http://www.visigenic.com/. ILU von Xerox PARC Genaugenommen ist ILU (Inter-Language Unification, Vereinheitlichung zwischen Sprachen) kein CORBA-2.0-kompatibler ORB, obwohl er das IIOP sowie die CORBA-Sprachabbildungen für C, C++ und Java unterstützt. Die von ILU zur Verfügung gestellten Funktionen sind eine Obermenge zu CORBA. So gehören zu den von ILU unterstützen Sprachen außer den bereits erwähnten (C, C++ und Java) noch Python, Common LISP und Modula-3. Ferner enthält ILU, was wichtig ist, eine HTTPImplementierung (Hypertext Transport Protocol, das im Internet verwendete Transportprotokoll). Auf der WWW-Seite von ILU ist angegeben, daß eines der Implementierungsziele darin besteht, die Kompatibilität mit den vorhandenen offenen Standards möglichst zu erweitern. Dieses Ziel spiegelt sich in der Liste der von ILU unterstützten Plattformen wider: SunOS, Solaris, HP-UX, AIX, OSF, IRIX, FreeBSD, Linux, LynxOS, SCO, Windows 3.1, Windows 95 und Windows NT. Zusätzlich unterstützt ILU eine Reihe von Modellen für Threading und Ereignisschleifen. Es sei noch einmal gesagt, daß ILU strenggenommen nicht CORBA-2.0-kompatibel ist, das Produkt ist aber ohne Einschränkungen kostenlos erhältlich (wenn man davon absieht, daß Kopien der Software mit einem Copyright-Vermerk versehen werden müssen).
Informationen zu ILU und die kostenlos herunterladbare Progammkopie erhalten Sie auf der WWWSeite von PARC (eigentlich ist dies die FTP-Site) unter ftp://ftp.parc.xerox.com/pub/ilu/ilu.html.
Welche CORBA-relevanten Entwicklungswerkzeuge gibt es? In diesem Abschnitt beschreibe ich die »CORBA-relevanten« Entwicklungswerkzeuge, worunter hier verstanden sein soll, daß diese Produkte die CORBA-Methodik für die Entwicklung von Anwendungen zumindest in gewissem Rahmen berücksichtigen. Ein solches Werkzeug kann z.B. IDLDefinitionen generieren und prüfen, oder es versetzt einen Entwickler in die Lage, graphisch eine Reihe von IDL-Schnittstellen zu erstellen, über welche die Funktionen einer CORBA-Anwendung definiert werden. Eine Reihe von Entwicklungsprodukten enthält möglicherweise ORBs (wie bereits erwähnt, hat Visigenic Software seine ORB-Technologie an andere Software-Anbieter lizenziert). Entwicklungsprodukte, die zwar ORBs enthalten, aber keine CORBA-spezifischen Entwicklungswerkzeuge zur Verfügung stellen, sind hier nicht berücksichtigt. Software through Pictures von Aonix Software through Pictures ist eine Programmfamilie, die unter anderem die objektorientierte Analyse und das objektorientierte Entwerfen von Anwendungen unterstützt. Software through Pictures unterstützt eine Vielzahl von methodischen Vorgehensweisen für OO-Technologien, wozu UML (Unified Modeling Language), OMT (Object Modeling Technique) und Booch gehören. Software through Pictures ermöglicht die graphische Bearbeitung von Geschäftsanforderungen und unterstützt Use-Cases, Objektmodelle, dynamische Modelle, Funktionsmodelle, Diagramme zur Objektinteraktion und Klassentabellen. Software through Pictures kann zusammen mit einer Vielzahl von Sprachen und Werkzeugen eingesetzt werden, zu denen Smalltalk, C++, Java und die IDL gehören (damit ist das Programm ein nützliches CORBA-Entwicklungswerkzeug). Zum automatischen Generieren der Dokumentation werden FrameMaker und Interleaf unterstützt. Software through Pictures unterstützt SunOS, Solaris, HP-UX, AIX, Digital Unix und Windows NT. Weitere Informationen zur Produktfamilie Software through Pictures erhalten Sie auf der WWW-Seite von Aonix unter http://www.aonix.com/Products/StP/stp.html. CORBA-Entwicklungswerkzeuge von Black & White Software Black & White Software bietet eine Fülle von CORBA-Entwicklungswerkzeugen an: ■
OrbixBuilder ist eine Produktfamilie, die nahtlos zusammen mit verschiedenen Entwicklungswerkzeugen eingesetzt werden kann. OrbixBuilder stellt graphische Dienstprogramme sowie die Code-Generierung für CORBA-Clients und -Server zur Verfügung und wird im Paket zusammen mit Orbix oder OrbixWeb (je nachdem, ob der OrbixBuilder für C++ oder Java benötigt wird) von IONA Technologies ausgeliefert. Zusätzlich zur Unterstützung der Produkte UIM/Orbix und Web/Enable von Black & White Software
■
■
■
■
■
unterstützt OrbixBuilder Visual Café von Symantec. Object/Observer bietet Diagnose- und Verfolgungssysteme für CORBA-Anwendungen und ermöglicht dabei die Überwachung der Server-Aktivität, der Netzwerkauslastung sowie weiterer kommunikationsrelevanter Details. UIM/Orbix ist ein graphisches Anwendungserstellungsprogramm, das für die Entwicklung von CORBA-Anwendungen in C++ gedacht ist. Zusätzlich kann UIM/Orbix durch andere von Black & White angebotene Produkte erweitert werden. Orb/Enable ist eine Reihe von Werkzeugen, welche die Entwicklung von CORBAAnwendungen erleichtern. Das Produkt ermöglicht das Erstellen und Importieren von IDLDateien sowie das visuelle Durchsuchen der CORBA-Server-Schnittstellen, die in einem Interface Repository (Schnittstellen-Objektablage) verfügbar sind. Web/Enable ist ein Add-on zu UIM/Orbix, das die Entwicklung von CORBA-Anwendungen in Java erleichtert, wobei die C++-Entwicklungswerkzeuge von UIM/Orbix ergänzt werden. Web/Enable enthält auch OrbixWeb von IONA Technologies. Object/LM stellt Funktionen für die Zugriffskontrolle sowie die Messung der Auslastung von CORBA-Anwendungen zur Verfügung. Hierzu gesellen sich noch eine Lizenzverwaltung und ein Sicherheitsmechanismus.
Weitere Informationen zu den CORBA-Entwicklungsprodukten von Black & White finden Sie auf der WWW-Seite von Black & White unter http://www.blackwhite.com/products/corbadev.html. Distributed Smalltalk von ParcPlace Distributed Smalltalk ist ein interaktives Werkzeug, das die Entwicklung von CORBA-Anwendungen in - raten Sie mal - Smalltalk unterstützt. Distributed Smalltalk stellt nicht nur graphische Entwurfsund Entwicklungswerkzeuge zur Verfügung, sondern enthält auch einen CORBA-2.0-kompatiblen ORB sowie Implementierungen für den CORBA-Bezeichnungsdienst, den CORBA-Ereignisdienst, den CORBA-Transaktionsdienst und den CORBA-Concurrency-Dienst. Distributed Smalltalk weist auch noch andere CORBA-Funktionen auf, wie eine Durchsuchungsfunktion für die SchnittstellenObjektablage und die Fähigkeit, ausgehend von vorhandenen Smalltalk-Klassen IDL-Quelltext zu generieren. Distributed Smalltalk unterstützt Windows 3.1, Windows 95, Windows NT, MacOS, HPUX, AIX, Solaris und SunOS. Weitere Informationen zu Distributed Smalltalk finden Sie auf der WWW-Seite von ParcPlace unter http://www.parcplace.com/products/dst/info/dst.htm. Rose von Rational Software Rose von Rational Software ist das beliebteste visuelle Entwurfswerkzeug, das derzeit auf dem Markt ist. Das Werkzeug unterstützt Booch, OMT (Object Modeling Technique) sowie UML-Notationen (Unified Modeling Language) und bietet Werkzeuge für die UseCase-Analyse, Klassen- und Objektmodellierung, Komponentenmodellierung usw. Rose unterstützt ferner eine Reihe von Sprachen: Visual Basic, PowerBuilder, C++, Forté, Java, Smalltalk und SQLWindows. Zusätzlich können mit Rose logische Modelle für relationale Datenbanken erstellt werden, beispielsweise für
Oracle7, Sybase, SQL Server, Watcom-SQL und ANSI-SQL. Besonderen Nutzen für CORBAAnwendungsentwickler bietet die Fähigkeit von Rose, ausgehend von graphischen Objektmodellen IDL-Quelltext zu generieren. Rose ist für eine Vielzahl von Plattformen wie Windows 95, Windows NT, IRIX, Solaris, AIX, Digital Unix und HP-UX erhältlich. Weitere Informationen zu Rose und eine herunterladbare Testkopie finden Sie auf der WWW-Seite von Rational unter http://www.rational.com/products/rose/index.html. SNiFF+ von TakeFive Software SNiFF+ ist ein Entwicklungswerkzeug (eigentlich umfaßt es eine ganze Familie von Werkzeugen), das eine Vielzahl von Sprachen wie C, C++, FORTRAN, Java, IDL und andere unterstützt. SNiFF+ ist für CORBA-Entwickler deshalb von Nutzen, weil das Produkt das Generieren und Prüfen von IDLDateien ermöglicht. Natürlich bietet SNiFF+ eine große Bandbreite von graphischen Werkzeugen, wie ein Klassen-Suchprogramm, einen Debugger usw. Weitere Informationen über die SNiFF+-Produktfamilie sowie herunterladbare Test-Software finden Sie auf der WWW-Seite von TakeFive unter http://www.takefive.com/products.htm. Universal Network Architecture Services (UNAS) von TRW Universal Network Architecture Services (UNAS) von TRW ist ein Entwicklungswerkzeug, das mit einem CORBA-ORB kombiniert ist und einige über CORBA hinausgehende Funktionen unterstützt, wie Objektüberwachung und -steuerung, Systemleistungsbeurteilung und -optimierung sowie Fehlertoleranz. UNAS unterstützt ferner den CORBA-Bezeichnungsdienst und die SNMP-Verwaltung (Simple Network Management Protocol). Ferner werden die Sprachen C++ und Ada unterstützt sowie die Plattformen SunOS, Solaris, HP-UX, AIX, IRIX, Digital Unix, OpenVMS, DEC CMW, ULTRIX, Rational Apex, Rational R1000, SCO Unix und Windows NT. Weitere Informationen über UNAS finden Sie auf der WWW-Seite von TRW unter http://www.trw.com/unas/.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9
Anhang C Was steht bevor? Die Zukunft von CORBA Ein kurzer Blick in den »Rückspiegel« Für die Gegenwart und die nähere Zukunft: CORBA 2.1 Bereits am Horizont erkennbar: CORBA 3.0 Ein Blick nach vorn Zusammenfassung
Dieses Buch behandelte schwerpunktmäßig die Möglichkeiten, die CORBA heute bietet. Aber wie wird es in sechs Monaten, in einem Jahr oder noch weiter in der Zukunft aussehen? Für die Planung der Systeme von morgen sollten Sie eine Vorstellung darüber haben, in welche Richtung sich CORBA bis dann entwickelt haben wird. Dieser Anhang beleuchtet einige Ergänzungsvorschläge für CORBA und untersucht die grundsätzliche Richtung, die CORBA in der Zukunft nehmen wird.
Ein kurzer Blick in den »Rückspiegel« Damit man voraussagen kann, wohin sich CORBA entwickeln wird, ist es hilfreich, sich ein wenig mit der Geschichte dieser Architektur zu befassen. Am 1. Tag haben Sie erfahren, daß CORBA im Jahr 1990, kurz nach Gründung der Object Management Group, die für CORBA verantwortlich zeichnet, ins Leben gerufen wurde. CORBA 1.0 und der kurz darauf veröffentlichte Nachfolger, CORBA 1.1, bereiteten den Boden für die Kommunikation zwischen verteilten Objekten. Im Jahr 1994 setzte die OMG die Version 2.0 der CORBA-Spezifikation in Kraft, wobei das hauptsächliche Ziel in der Definition eines Standards für die Interoperabilität von ORBs der verschiedenen Anbieter bestand. (Sie werden sich erinnern, daß der Object Request Broker, für den das Akronym ORB steht, diejenige
Komponente von CORBA ist, welche die Kommunikation zwischen Objekten erleichtert.) CORBA 2.0 stellte einen bedeutenden Schritt in Richtung auf die Interoperabilität zwischen verschiedenen Produkten dar, ließ aber immer noch einige Leistungsmerkmale vermissen. Hierbei ist vor allem die mangelnde Fähigkeit zu nennen, Objekte durch Werte weiterzugeben (wie Sie am 10. Tag gelernt haben). Gleichzeitig entwickelte die OMG Spezifikationen für zusätzliche Dienste und Einrichtungen, die unter dem Namen CORBAservices und CORBAfacilities bekannt sind.
Für die Gegenwart und die nähere Zukunft: CORBA 2.1 CORBA 2.1 wurde im September 1997 in Kraft gesetzt und umfaßt einige Änderungen in Detailbereichen der CORBA-Spezifikation: ■ ■
■
Überarbeitete Interoperabilitätsspezifikation Erweiterungen der IDL-Sprache, vor allem durch den 64-Bit-Ganzzahltyp long long und den Zeichentyp wchar Integration der Standard-Sprachabbildungen für Cobol und Ada
Keine dieser Änderungen ist revolutionär, aber vor allem die IDL-Erweiterungen bringen CORBA hinsichtlich der Sprachen und Betriebssysteme, die 64-Bit-Ganzzahlen und Zeichensätze mit mehrere Byte langen Zeichen unterstützen, auf den neuesten Stand.
Bereits am Horizont erkennbar: CORBA 3.0 Am Horizont zeichnet sich bereits der CORBA-3.0-Standard ab, der den nächsten wichtigen Schritt in der Entwicklung von CORBA darstellen wird. Während ich dieses Buch schreibe, hat die OMG noch keine Ankündigungen über neue Funktionen und Verbesserungen in CORBA 3.0 verlauten lassen, aber wenn man sich die RFPs (Request For Proposal, Aufforderung zur Einreichung von Vorschlägen) und die RFIs (Request For Information, Aufforderungen zur Lieferung von Informationen) genauer anschaut, kann man mit einiger Sicherheit erraten, was mit dieser neuen Hauptstufe in der Entwicklung der CORBA-Spezifikation zu erwarten ist. Task Forces der OMG Wenn man voraussagen möchte, was in CORBA 3.0 enthalten sein wird, ist es auch hilfreich zu wissen, wie die OMG arbeitet. Diese Organisation unterhält zwei Technologieausschüsse (TC, Technology Committee), die gesonderte Task Forces (Spezialeinheiten) mit der Lösung besonderer Probleme beauftragt. Zu diesem Zweck geben diese Spezialeinheiten Aufforderungen zur Einreichung von Vorschlägen (RFPs) und Aufforderungen zur Lieferung von Informationen (RFIs) heraus, mit denen mögliche Lösungen aus dem Umfeld der gesamten Industrie eingeholt werden können. Derzeit wurden vom Domain Technology Committee (Ausschuß für Bereichstechnologie) und vom Platform Technology Committee (Ausschuß für Plattformtechnologie) einige Task Forces beauftragt, die entsprechende RFPs und RFIs herausgegeben haben. In diesem Abschnitt werden diese Anforderungen kurz vorgestellt, denn der Inhalt von CORBA 3.0 wird, wenn dieser Standard einmal in Kraft gesetzt sein wird, in hohem Maße von diesen Anforderungen beeinflußt sein.
Task Forces des Platform Technology Committee Die Arbeit der Task Forces (Spezialeinheiten) des Platform Technology Committee (Ausschuß für Plattformtechnologie) ist bis zu einem bestimmten Punkt in der CORBA-Spezifikation selbst enthalten. Es gibt beispielsweise Spezialeinheiten zur Lösung der Problematik hinsichtlich der Interoperabilität zwischen COM und CORBA (zur Erinnerung: COM ist das Komponentenobjektmodell (Component Object Model) von Microsoft), wobei IDL-Abbildungen für verschiedene Programmiersprachen erstellt und zusätzliche Funktionen für CORBA vorgeschlagen werden sollen. Derzeit unterhält das Platform Technology Committee die Object Analysis and Design Task Force (Spezialeinheit für Objektanalyse und -entwurf, OA&D TF) und die ORB/Object Services Task Force (Spezialeinheit für ORB-/Objektdienste). Die meisten der hier vorgestellten Beschreibungen stammen direkt aus der WWW-Seite von OMG. Einzelheiten zu den einzelnen Task Forces können unter http://www.omg.org/omg00/task.htm abgerufen werden, Informationen über die aktuelle Arbeit der Technologieausschüsse sowie Beschreibungen der betreffenden RFPs und RFIs unter http://www.omg.org/library/schedule.htm. Die ORB and Object Services Platform Task Force Gemäß ihres Auftrags hat die ORB and Object Services Platform Task Force (Plattform-Spezialeinheit für ORB- und Objektdienste) die Aufgabe, Spezifikationen anzufordern, auszuwerten und auszuwählen, die als Empfehlungen an das Platform Technology Committee herausgegeben werden, damit sie von der OMG für die Bereiche der ORB-Technologie (die unter die CORBA-Spezifikation fällt) oder der allgemeinen Objektdienste (die unter die CORBAservices fallen) in Kraft gesetzt werden. Ferner gibt der Auftrag vor, daß solche Spezifikationen die Grundlage zur Entwicklung nutzbringender CORBA-gestützter Anwendungen aus verteilten Objekten bilden, eine universelle Basis für die Interoperabilität von Anwendungen darstellen oder Einrichtungen und Gerüste auf hoher Ebene unterstützen sollen. Derzeit sind die folgenden RFIs und RFPs der Object Services Platform Task Force aktuell: ■
■
ORB and Object Services RFI 1 (Internet). Eine RFI über Internet-gestützte Dienste und Schnittstellen, die die Integration und Einbindung von OMG- und CORBA-Objekten in das Internet fördern sollen. Das globale Ziel dieser RFI liegt darin, Informationen aus allen Bereichen der Gesellschaft zu sammeln, um die IPSIG der OMG und die OMG selbst bei der Inkraftsetzung von Spezifikationen zu unterstützen, welche die von der OMG festgelegte OMA für das Internet aufbereiten und eine weitere Einbindung von bzw. Ausrichtung an InternetStandards, -Protokollen, -Werkzeugen und -Dienstprogrammen fördern sollen. Diese RFI fordert maßgebliche Informationen aus den folgenden Bereichen an: Anforderungen, Architekturen, Entwürfe, Projekte, Produkte, Protokolle und Standards. ORB and Object Services RFI 2 (Echtzeit-Thematik). Diese RFI fordert Informationen über die Notwendigkeit und die Verfügbarkeit von Technologien für die folgenden Themen an: EchtzeitBetriebsumgebungen, die dazu geeignet sind, Echtzeit-ORBs zu unterstützen, Echtzeit-ORBs,
■
■
■
■
■
■
■
■
■
Objektdienste, gemeinsame Einrichtungen und Erweiterungen für den Echtzeitbetrieb sowie allgemeine Echtzeit-Funktionalität, die sich im Kontext der Objekttechnologie auf die oben genannten Bereiche anwenden läßt. Multiple Interfaces and Composition RFP. Diese RFP befaßt sich mit der Auflösung von Konflikten zwischen Mehrfach-IDL-Schnittstellen für ein und dasselbe Objekt. Die Zusammensetzungseinrichtung stellt die Mittel für Objekte zur Verfügung, die mit Hilfe von Mehrfachschnittstellendefinitionen aus logisch voneinander getrennten Diensten zusammengesetzt werden sollen. Diese Einrichtung wurde bereits als Grundlage für zahlreiche Systemanforderungen vorgeschlagen. Messaging Service RFP (ORBOS RFP1). Hierbei handelt es sich um eine RFP, die Vorschläge für Dienste und ORB-Verbesserungen anfordert, welche für die Verwaltung asynchroner Meldungen in verteilten Objektsystemen, einschließlich Ordnung und Anforderungsdienstqualität vorgesehen sind. Objects-by-Value RFP (ORBOS RFP2). Diese RFP erwartet Vorschläge für Schnittstellen, mit denen in CORBA-Objektoperationen die Übergabe von CORBA-Objekten durch Werte (anstatt durch Objektreferenzen) als Parameter ermöglicht wird. Die Übergabe von Objekten durch Werte ist in vielen Fällen effektiver und einfacher. Java to IDL RFP (ORBOS RFP5). Diese RFP fordert Vorschläge zur Verbesserung der JavaSprachabbildung in CORBA über eine Java/IDL-Abbildung. Wenn eine Java/IDL-Abbildung zur Verfügung steht, haben die Entwickler die Möglichkeit, verteilte Anwendungen direkt in Java zu erstellen, wobei die Kommunikation über das IIOP erfolgt. Wenn aus Java-Quelltext IDL-Quelltext generiert werden kann, können zahlreiche Sprachen auf diese in Java geschriebenen Komponenten zugreifen. DCE/CORBA Interworking RFP (ORBOS RFP6). Diese RFP fordert Vorschläge zu folgenden Themen an: Application Level Interworking (Interaktion auf Anwendungsebene), Interaktion zwischen CORBA-Clients und DCE-Servern sowie zwischen DCE-Clients und CORBAServern, Bereitstellung bestimmter CORBAservices und CORBAfacilities (z.B. Sicherheits-, Bezeichnungs- und Zeitmodule) mit vorhandenen DCE-Komponenten (z.B. Sicherheitsdiensten, Verzeichnisdiensten und verteilte Zeitfunktion). Persistent State Service, Version 2.0 RFP (ORBOS RFP7). Diese RFP dient zur Anforderung von Vorschlägen, die eine kohärente und pragmatische Spezifikation für die CORBAPermanenz liefern. Der Schwerpunkt der RFP liegt auf einer Einrichtung zur Erzielung und Beibehaltung von Permanenz, die vom Objektimplementierungsmodul verwendet werden soll. Hierbei ist es wichtig, darauf einzugehen, ob und ggf. wie diese neue Spezifikation (PSS) mit anderen Spezifikationen von OMG, z.B. POA, dem Concurrency-Dienst, dem Transaktionsdienst und dem Dienst für die Objektübergabe durch Werte in Interaktion treten kann. CORBA Component Model RFP (ORBOS RFP8). Diese RFP fordert Vorschläge für ein verteiltes Komponentenmodell an, das auf der OMA basiert und in der Lage ist, Interoperabilität mit anderen kommenden Komponententechnologien, wie z.B. dem JavaBeansKomponentenmodell, herzustellen. CORBA Scripting Language RFP (ORBOS RFP9). Diese RFP soll zu einer koordinierten Strategie zur Einführung eines Komponentenmodells in die OMA beitragen. Es werden Vorschläge für eine Skript-Sprache angefordert, mit der Skripte für CORBA-Komponenten geschrieben werden können. Minimum CORBA RFP (ORBOS RFP10). Diese RFP fordert Vorschläge zu folgenden
■
■
■
■
■
Themen an: Definition einer reduzierten CORBA-Kernspezifikation, die eine grundlegende Client-Server-Funktionalität implementieren und die Konfiguration von Untermengen der vollständigen IDL-Definition von OMG ermöglichen soll; ferner die Auswirkungen, die das Herausnehmen der Konfigurationen von CORBA-Funktionen auf den vorhandenen CORBAStandard und gemeinsame Objektdienste hat. Dieses Minimal-Corba soll bei der Erstellung von eingebetteten Systemen helfen. Realtime CORBA 1.0 RFP. Diese RFP soll eine Vereinheitlichung der CORBAEchtzeiterweiterungen bewirken und die Verwendung der OMA im Echtzeitbereich fördern. COOL von Chorus ist beispielsweise ein kommerzielles Beispiel für einen CORBA-ORB mit Echtzeitfunktionen. Diese und andere nicht standardisierte CORBA-Echtzeitvarianten werden bereits in bestimmten Anwendungen, wie z.B. der Software für die luftgestützte Radarzielverfolgung an Bord von AWACS-Flugzeugen, verwendet. Data Interchange Facility and Mobile Agent Facility RFP. Bei der Data Interchange Facility (Datenaustauscheinrichtung) handelt es sich um einen Dienst, der die Interoperabilität zwischen einzelnen Objekten unterstützt. Die wichtigsten Elemente dieses Dienstes sind die Schnittstellen zum Datenaustausch, der Datenobjekttyp, der Lebenszyklus von Datenobjekten und die Schnittstellen für die Datenumsetzung. Einen weiteren wichtigen Bereich stellt die Verwendung von Metadaten dar. Zur Implementierung mobiler Agenten müssen die drei folgenden Schlüsselfunktionen vom ORB unterstützt werden: Starten und Laden von Agenten durch die ORB-Komponente, die herkömmlicherweise als Client-Seite bezeichnet wird, zeitlich asynchroner Betrieb sowie Benachrichtigung der Absender und Empfänger über die Ankunft der für den Empfänger vorgesehenen Pakete. Diese Änderungen werden von der Mobile Agent Facility vorgeschlagen. Input Methode Manager Facility RFP. Diese RFP fordert Vorschläge für Spezifikationen zu den gemeinsamen Merkmalen der Input Methode Manager Facility an, die eine Verwaltung der Eingabemethoden u.a. für asiatische, über mehrere Byte definierte Zeichen auf CORBAPlattformen ermöglichen. Firewall RFP. Diese RFP fordert Vorschläge für das folgende Thema an: Spezifikation der Verwendung des IIOP in Netzwerk-Firewalls zur Beschränkung der Verwendung der CORBAgestützten Anwendungen eines Unternehmens über das Internet und das Intranet. Zusätzlich sollen ähnliche Spezifikationen hinsichtlich anderer Protokolle für die Kommunikation zwischen ORBs erstellt werden. Printing Facility RFP. Hierbei handelt es sich um eine RFP zur Anforderung von Vorschlägen für die Objekte der Druckeinrichtung. Diese Einrichtung führt die Verwaltung (Einplanung, Spool-Betrieb und Auffinden) von Druck-Servern sowie die Weiterleitung von Druckaufträgen durch. Die Druckeinrichtung sollte in der Lage sein, eine Reihe von Druckanforderungen, von der Verarbeitung einfacher Dokumente bis hin zum Druck großer Mengen für Produktionszwecke, zu erfüllen.
Die Object Analysis and Design Task Force Im Auftrag der Object Analysis and Design Task Force (Spezialeinheit für Objektanalyse und entwurf) ist festgehalten, daß es ihre Aufgabe ist, den Entwicklern ein besseres Verständnis für die Entwicklung von Anwendungen mit Hilfe der objektorientierten Technologie (OT) zu ermöglichen und somit deren Marktanteil zu erhöhen; weiterhin eine Technologie zur Inkraftsetzung zu empfehlen, welche die Interoperabilität während des Lebenszyklus ermöglicht, und die Wiederverwendung von
Entwürfen/Produkten zu ermöglichen, die mit Hilfe von Werkzeugen für Objektanalyse und -entwurf (OA&D) entwickelt wurden; ferner soll sie eine Technologie zur Inkraftsetzung einer gemeinsamen Semantik, von Metamodellen und einer abstrakten Syntax für die methodischen Vorgehensweisen im OA&D-Bereich empfehlen, vorhandene OMG-Spezifikationen fördern, Fortschritte bei den methodischen Vorgehensweisen im OA&D-Bereich erleichtern und schließlich eine verbundmäßige Zusammenarbeit mit anderen geeigneten Organisationen empfehlen. Dies ist sicherlich eine gewaltige Aufgabe, die damit von der neuesten Task Force der OMG übernommen wird. Sie hat auch bereits zwei RFPs hervorgebracht, die ein Gerüst für Analyse- und Entwurfswerkzeuge sowie eine MetaobjektEinrichtung zur weiteren Förderung der Interoperabilität zwischen den CORBA-Werkzeugen und Anwendungen zum Gegenstand haben. Derzeit sind die folgenden RFPs der Object Analysis and Design Task Force aktuell: ■
■
Analysis and Design Task Force RFP1. Der Schwerpunkt dieser RFP liegt auf der Entwicklung eines Gerüsts für die Interoperabilität der Analyse- und Entwurfswerkzeuge auf semantischer Ebene mit Hilfe einer Analyse- und Entwurfseinrichtung (Analysis and Design Facility). Diese Einrichtung soll die Schnittstellen und die Semantik enthalten, die zur Unterstützung der Erstellung und Bearbeitung von OA&D-Modellen benötigt werden, welche das Verhalten von Objektanwendungen im Rahmen der OMA definieren. Hierzu gehört eine Reihe von Notationssystemen, mit denen diese Modelle konsistent beschrieben werden können. Diese RFP wurde mit der Inkraftsetzung der von Rational, IBM, Objectime und anderen Organisationen vorgeschlagenen OMB UML 1.0 bereits erfolgreich abgeschlossen. Die Veröffentlichung der formellen Spezifikation ist für das Frühjahr 1998 vorgesehen.
Task Forces des Domain Technology Committee Die Arbeit des Domain Technology Committee (Ausschuß für Bereichstechnologie) trägt seine Früchte in Form der CORBAfacilities, vor allem bei den vertikalen Einrichtungen, wie z.B. in den Bereichen Gesundheits- und Fertigungswesen sowie Telekommunikation. Derzeit hat das Domain Technology Committee die folgenden Task Forces im Einsatz: ■ ■ ■ ■ ■ ■ ■
Business Object Task Force Electronic Commerce Domain Task Force Financial Domain Task Force (CORBAfinancials) Manufacturing Domain Task Force (CORBAmanufacturing) CORBAmed Task Force Telecommunications Domain Task Force (CORBAtel) Transportation Domain Task Force (CORBAtransport)
(Hierbei ist zu beachten, daß viele der Task Forces, die das Domain Technology Committee bilden, in vertikalen Bereichen organisiert sind.) Die Business Object Domain Task Force
Laut den Informationen, welche die WWW-Seite von OMG bietet, besteht die Aufgabe der Business Object Domain Task Force (Spezialeinheit für Geschäftsobjekte) in der Definition der OMGGeschäftsobjekte. Sie soll die Verwendung der OMG-Technologie für verteilte Objekte für Geschäftssysteme sowie die Einheitlichkeit der Task Force-Standards innerhalb der vertikalen Bereiche erleichtern und fördern; ferner soll diese Spezialeinheit Einfachheit in der Erstellung, Verwendung und der Einsetzung von Geschäftsobjekten für Anwendungsentwickler, Interoperabilität zwischen unabhängig voneinander entwickelten Geschäftsobjekten sowie die Inkraftsetzung und Verwendung von Standards für gemeinsame Geschäftsobjekte und Anwendungskomponentenstandards erzielen; schließlich hat sie die Aufgabe, RFIs und RFPs auszugeben und die Auswertung der darauf eingegangenen Antworten und Vorschläge zur Übernahme in die OMG-Spezifikationen für Objekte, Gerüste, Dienste und Architekturen, die für eine breite Palette von Geschäftszweigen in Frage kommen, vorzunehmen. Die Business Object Domain Task Force übt mit Sicherheit eine ganz wichtige Funktion aus. Da die meisten verteilten Anwendungen auf Unternehmensebene die Kommunikation zwischen den einzelnen Geschäftsobjekten erleichtern sollen, haben die Ziele dieser Task Force viel mit denen der Entwickler von verteilten Anwendungen gemein: Es geht darum, die Entwicklung und den Einsatz robuster, interoperabler Geschäftsobjekte zu ermöglichen. Derzeit sind folgende RFPs und RFIs der Business Object Domain Task Force aktuell: ■
■
■
Common Business Object and Business Object Facility RFP. Diese RFP fordert Vorschläge für gemeinsam genutzte Geschäftsobjekte (Common Business Objects) an. Bei diesen Objekten handelt es sich um eine Geschäftssemantik, die für die meisten Geschäftszweige als gemeinsam anzusehen ist, und eine Einrichtung für Geschäftsobjekte (Business Object Facility), welche die Infrastruktur zur Unterstützung von Geschäftsobjekten darstellt, die in einer verteilten Objektumgebung als kooperative Anwendungskomponenten agieren. Workflow Management Facility RFP. Die Workflow Management Facility (Einrichtung für die Arbeitsablaufverwaltung) definiert Schnittstellen und deren Semantik, die zur Manipulation und Ausführung interoperabler Arbeitsablaufobjekte und ihrer Metadaten erforderlich sind. Diese Einrichtung dient als Integrationsplattform auf hoher Ebene für die Entwicklung flexibler Anwendungen zur Verwaltung von Arbeitsabläufen, die Objekte und vorhandene Anwendungen mit einbeziehen. Diese RFP fordert Vorschläge für die oben genannte Einrichtung an. Common Business Objects RFI (CBO RFI). Die Common Business Object Working Group (Arbeitsgruppe für gemeinsame Geschäftsobjekte) versucht, eine Referenzarchitektur zu definieren, die ein präzises und effektives Gerüst zur Verfügung stellt, innerhalb dessen die Abhängigkeiten zwischen einzelnen Bereichen verstanden und miteinander in Einklang gebracht werden können. Die Arbeitsgruppe fordert Einzelpersonen und Organisationen, die sich in diesem Thema gut auskennen oder über Informationen dazu verfügen, auf, ihre Kenntnisse als Basis für die Arbeit in diesem Bereich einzubringen.
Die Manufacturing Domain Task Force Die Manufacturing Domain Task Force (Spezialeinheit für das Fertigungswesen, CORBAmanufacturing) ist Nachfolgerin der Manufacturing Special Interest Group (Gruppe für
spezielle Belange im Fertigungswesen) und hat die Aufgabe, die Entstehung von kostengünstigen, rechtzeitig auf dem Markt verfügbaren und interoperablen Softwarekomponenten für das Fertigungswesen über CORBA-Technologie zu fördern, eine Technologie zur Inkraftsetzung zu empfehlen, welche die Interoperabilität und Modularität von CORBA-gestützten Softwarekomponenten für das Fertigungswesen ermöglicht, die Entwicklung und Verwendung von CORBA-gestützten Softwarekomponenten für das Fertigungswesen zu fördern und dabei den Markt für die Objekttechnologie zu vergrößern; ferner soll diese Task Force eine verbundmäßige Zusammenarbeit mit geeigneten Organisationen zur Unterstützung der oben genannten Ziele empfehlen. Derzeit sind folgende RFPs und RFIs der Manufacturing Domain Task Force aktuell: ■
■
■
Manufacturing High-Level Requirements RFI (MFG RFI1). Diese Aufforderung zur Lieferung von Informationen soll Anregungen zur Klarstellung und Überarbeitung des Objektmodells für die Fertigung (Manufacturing Object Model) einholen, eine Aufgliederungsmöglichkeit für dieses Modell auf hoher Ebene ermitteln und die Liste der infrastrukturellen Anforderungen verbessern. Die ermittelte Aufgliederung soll die Grundlage für das weitere Vorgehen sowie für die zukünftigen RFPs dieser Task Force bilden. Manufacturing DTF RFI-2. Diese RFI fordert von ERP-Benutzern und -Anbietern Anregungen zu Empfehlungen über die Anzahl von RFPs sowie über die Fachgebiete an, die von den RFPs abgedeckt werden sollen; ferner soll ermittelt werden, welche ERP-Systeme von der Task Force herausgegeben werden sollen. ERP enthält Systeme für die Planung von Materialanforderungen (MRP) und die Planung von Fertigungsressourcen (MRP-II). ERP spielt vor allem beim Geschäftsprozeß zur Produktionsplanung eine Rolle. Product Data Management Enablers RFP (MFG RFP1). Über diese RFP sollen Standardschnittstellen für die von dem System zur Produktdatenverwaltung (PDM-Systeme) zur Verfügung gestellten Dienste festgelegt werden. Diese Schnittstellen, die über ORBs zur Verfügung gestellt werden, sollen den Standard zur Verfügung stellen, der zur Unterstützung einer verteilten Produktverwaltungsumgebung erforderlich ist; außerdem sollen sie Standardschnittstellen zu unterschiedlichen PDM-Systemen zur Verfügung stellen.
Die Electronic Commerce Domain Task Force Der Handel auf elektronischem Wege ist zu einem brisanten Bereich in der Computerwelt geworden. Er hat vor allem durch die explosionsartige Entwicklung des World-Wide-Web-Phänomens reichlich Nahrung bekommen. Viele Unternehmen streben heutzutage danach, Möglichkeiten zum Verkauf ihrer Produkte über elektronische Absatzkanäle zu finden und - was genauso wichtig ist - ihre urheberrechtlich geschützten Produkte und ihr geistiges Eigentum zu schützen. Die Electronic Commerce Domain Task Force (Spezialeinheit für das elektronische Handelswesen) wurde beauftragt, sich mit den Problemen zu befassen, die sich im Zusammenhang mit dem Handel auf elektronischem Wege ergeben. Die Ziele dieser Task Force liegen darin, im Rahmen der OMG Unterstützung und Engagement für die Technologie zu sammeln, die Konsultierung von Experten fortzusetzen und RFPs herauszugeben. Sie hat die Aufgabe, breit gefächerte Reaktionen von der Industrie und den einzelnen Bereichen auf ihre RFPs (wo zutreffend) einzuholen; außerdem soll sie den Arbeitsumfang für die Domain Task Force
herausarbeiten, was speziell auch das Definieren einer Verwaltung für Inhalte sowie das Definieren von Rechten und Lizenzgebühren, von elektronischem Zahlungsverkehr und von Online-Einzelhandel (elektronischer Handel) beinhaltet. Derzeit sind folgende RFPs und RFIs der Electronic Commerce Domain Task Force aktuell: ■
■
■
■
■
Electronic Payment Facility (EC RFP1). Diese RFP fordert Vorschläge für ein Objektgerüst an, die die Implementierung von branchenweit einheitlichen Protokollen für den elektronischen Zahlungsverkehr in einem OMA-kompatiblen System sowie auf dieses Gerüst zurückgreifende Spezifikationen für Zahlungsverkehrsprotokolle in einer oder mehreren Branchen unterstützen. Negotiation Facility RFP (EC RFP2). Diese RFP fordert Vorschläge für einen oder mehrere Geschäftsprozesse an, die in IDL geschrieben sind und Verhandlungen mehrerer Teilnehmer ermöglichen. Ferner sollen Vorschläge zu einem Objektgerüst eingeholt werden, welche das dynamische Ersetzen von Verhandlungsregeln, Regelprüfungen und Schnittstellen unterstützt, über die mit Hilfe einer Bereichsstrategie die Offenlegung von Informationen eingeschränkt und die im Verlauf einer Verhandlung getroffenen Entscheidungen kontrolliert werden können. Electronic Healthcare Claims Facility RFP (EC RFP3). Diese RFP fordert Vorschläge für die Schnittstelle einer Anspruchseinrichtung für das Gesundheitswesen, so daß der Austausch vereinheitlichter Erstattungsansprüche im Gesundheitswesen unter Verwendung festgelegter Datenformate für diesen Bereich verwaltet werden kann. Asset and Content Management RFI (EC RFI1). Der technische Umfang dieser RFI besteht in der Zusammenstellung von Technologien und Schnittstellen, die für die Bereiche Asset und Content Management (Verwaltung von Wirtschaftsgütern und Inhalten) notwendig sind, z.B. die Erstellung und Erfassung einer digitalen Suche nach Wirtschaftsgütern und Inhalten sowie der Zugriff auf digitale Wirtschaftsgüter und Inhalte. Die Antworten sollten die in Verbindung mit dieser RFI maßgeblichen Standards aufzeigen. Dabei sollten De-facto-Industriestandards genauso enthalten sein wie De-jure-Standards, die von anerkannten Norminstituten erarbeitet wurden. Enabling Technologies and Services for EC (EC RFI2). Diese RFI zielt speziell auf die Technologie ab, die zur Erleichterung der Interaktionen zwischen Konsumenten, Anbietern und interessierten Dritten in einem Kommunikationsnetzwerk notwendig ist.
Die Telecommunications Domain Task Force Die Aufgabe der Telecommunications Domain Task Force (Spezialeinheit für das Telekommunikationswesen, CORBAtel) ist, RFIs und RFPs für eine CORBA-gestützte Technologie herauszugeben, die für die Telekommunikationsindustrie relevant ist. Ferner soll sie die Antworten auf RFIs und RFPs sowie die RFCs (Request for Comments, Aufforderung zur Abgabe von Kommentaren) für Empfehlungen zur Inkraftsetzung durch das Domain Technology Committee auswerten. Als weitere Aufgabe obliegt es dieser Task Force, die Anforderungen der Telekommunikationsindustrie an das Architecture Board (Architekturgremium), das Platform Technology Committee (Ausschuß für Plattformtechnologie) und bei Bedarf an andere Untergruppierungen der OMG weiterzuleiten. Außerdem soll sie das Liaison Subcommittee (Unterausschuß für verbundmäßige Zusammenarbeit) hinsichtlich dessen Beziehung mit Norminstituten und -konsortien im Bereich Telekommunikation unterstützen und beraten. Schließlich hat sie noch die Aufgabe, die Verwendung von OMG-Technologien als Lösungen für die Bedürfnisse
der Telekommunikationsindustrie zu fördern. Derzeit sind folgende RFPs und RFIs der Telecommunications Domain Task Force aktuell: ■
■
■
■
Topology RFP (Telecom RFP2). Sinn und Zweck dieser RFP ist es, Vorschläge für einen Topologiedienst (Topology Service) anzufordern, der Informationen über Beziehungen verwaltet, die eine Integration von Systemen und Anwendungen ermöglicht. Notification Service RFP (Telecom RFP3). Diese RFP fordert Vorschläge für einen Dienst an, der die Funktionen des Ereignisdienstes (Event Service) von der OMG so erweitert, daß er eine Filterfunktion unterstützt; gleichzeitig soll der Dienst die Skalierbarkeitsanforderungen von ereignisgestützten Anwendungen erfüllen, die in großen und heterogenen verteilten Netzwerken laufen und den Ereignisverwaltungsanforderungen für verteilte Systeme, Netzwerke und Anwendungen zur Telekommunikationsverwaltung entsprechen. Die RFP fordert weiterhin eine Spezifikation hinsichtlich der Benachrichtigungstypen und Inhalte, die auf bestimmte vertikale Bereiche zutreffen. CORBA/TMN Interworking RFP. Die Zielsetzung dieser RFP liegt in der Definition einer Gruppe von Interoperabilitätsabbildungen und CORBA-Schnittstellen für den Telekommunikationsbereich (Telecom Domain Interfaces), die die Entwicklung von CORBAgestützten TMN-Systemen ermöglichen, die mit nicht auf CORBA basierenden TMN-Systemen zusammenarbeiten können, welche Schnittstellen auf der Grundlage von Standardmodellen für die Objektverwaltung, wie dem OSI Management oder dem Internet Management und StandardVerwaltungsprotokollen, wie CMIP oder SNMP, exportieren. Ferner soll die Entwicklung von CORBA-gestützten Telekommunikationssystemen definiert werden, die mit dem OSI Management Reference Model oder dem Internet Management Model kompatibel sind und weder CMIP noch SNMP verwenden. Issues for Intelligent Networking with CORBA (Telecom RFI). Diese RFI soll Informationen zu folgenden fünf Fragen einholen: Welche Probleme bestehen für die Bereitstellung von Interoperabilität zwischen vorhandenen Systemen unter Verwendung der Protokollreihe SS7 als umgebungsspezifischem Protokoll? Welche Probleme bestehen bei der Verwendung der Protokollreihe SS7 als umgebungsspezifischem Protokoll für die Kommunikation zwischen den ORBs CORBA-gestützter Implementierungen? Welche zusätzlichen Anforderungen werden für eine Verbesserung der vorhandenen CORBA-Spezifikationen benötigt? Welche aktuellen oder vorhersehbaren Standardisierungsaktivitäten gibt es in diesem Bereich? Welche CORBAObjekte werden für IN-CORBA-Systeme benötigt, in die die Funktionalität für die einzelnen INFunktionseinheiten verkapselt ist?
Die Financial Domain Task Force Der Auftrag der Financial Domain Task Force (Spezialeinheit für das Finanzwesen, CORBAfinancials) besteht darin, die Verwendung von Finanzdiensten und Buchführungssoftware zu fördern, die OMG-Standards entsprechen. Ferner soll sie ein international anerkanntes Forum für die Industrie bereitstellen, dessen Schwerpunkt auf Finanzdiensten und Buchführungseinrichtungen liegt, die für diesen Bereich maßgeblichen Standards, Geschäftsarchitekturen, Forschungs- und Technologiebereiche festlegen, die Anforderungen der Endbenutzer im Bereich Finanzwesen über eine verbundmäßige Zusammenarbeit mit der End User SIG (End User Special Interest Group, Endbenutzergruppe für spezielle Belange) koordinieren, Fortschritte hinsichtlich des aktuellen Standes
bei den methodischen Vorgehensweisen im Bereich OA&D möglich machen, die potentiellen zukünftigen Spezifikationsaktivitäten mit der Common Facilities Task Force (Spezialeinheit für gemeinsame Einrichtungen) koordinieren, alle interessierten Mitglieder der OMG in die Task Force selbst mit einbinden, sowie eine Architektur für CORBAfinancials und einen Plan für das weitere weltweite Vorgehen der Finanzdienstleistungsbranche erstellen. Außerdem hat sie noch die Aufgabe, RFIs, RFPs und RFCs für die in der Finanzdienstleistungsbranche maßgebliche CORBA-gestützte Technologie herauszugeben, die Antworten auf die RFIs und RFPs auszuwerten und dem Domain Technology Committee die betreffende Technologie zur Inkraftsetzung zu empfehlen. Schließlich geht es noch darum, den Liaison Subcommittee (Unterausschuß für verbundmäßige Zusammenarbeit) hinsichtlich dessen Beziehung mit entsprechenden Norminstituten und -konsortien zu unterstützen und zu beraten. (Dieser Aufgabenumfang ist wahrlich nicht gerade klein bemessen!) Derzeit sind folgende RFPs und RFIs der Financial Domain Task Force (CORBAfinancials) aktuell: ■
■
■
Currency RFP (Finance RFP1). Die Zielsetzung dieser RFP liegt in der Anforderung von Schnittstellen, welche die Definition und die Verwaltung von Währungen unterstützt. Hierbei geht es um die Währungen selbst und nicht um das Geld, welches jeweils einen bestimmten Betrag in einer oder in mehreren Währungen darstellt. Diese RFP fordert Vorschläge für die Darstellung von und die Gültigkeitsprüfung für Währungen sowie für Finanzalgebra an. Finance/Insurance Party Management Facility RFP (Finance RFP2). Diese RFP fordert Vorschläge für Spezifikationen hinsichtlich der gemeinsamen Merkmale einer Party Management Facility (Parteienverwaltungseinrichtung) für die Finanzdienstleistungsbranche an. Diese Einrichtungen sind Bestandteile eines Systems, das allgemein als Client/Kunde Information System (Kundeninformationssystem) bekannt ist. Financial DTF Insurance RFI. Die Ziele dieser RFI liegen in der Verbesserung der Kundendienstqualität und in der Verringerung der Kosten durch Verwendung von CORBATechnologien, die Interoperabilität innerhalb des weltweiten Versicherungswesens herstellen sollen, sowie in der Vereinheitlichung der Schnittstellen für Versicherungsobjekte.
Die CORBAmed Task Force Die CORBAmed Task Force (Spezialeinheit für das Gesundheitswesen) hat folgenden Auftrag: Verbesserung der Qualität der Gesundheitsleistungen und Reduzierung der Kosten durch die Verwendung von CORBA-Technologien innerhalb des weltweiten Gesundheitswesens, Anwendung des Inkraftsetzungsprozesses für OMG-Technologien zur Kommunikation bei der Vereinheitlichung der Schnittstellen für Objekte im Gesundheitswesen, Weitergabe der Anforderungen im Gesundheitswesen an das Platform Technology Committee (Ausschuß für Plattformtechnologie) sowie Unterstützung und Beratung des Liaison Subcommitee (Unterausschuß für verbundmäßige Zusammenarbeit) hinsichtlich dessen Beziehung mit entsprechenden Norminstituten und -konsortien. Des weiteren soll CORBAmed sowohl die Systementwickler als auch die Endbenutzer im Gesundheitswesen auf der Grundlage von CORBA-Technologien weiterbilden, für das Gesundheitswesen auf CORBA-Technologien basierende RFIs und RFPs herausgeben und schließlich die RFIs, RFPs und RFCs auswerten und die Ergebnisse dem Domain Technology Committee zur Inkraftsetzung empfehlen. Derzeit sind folgende RFPs und RFIs der CORBAmed Task Force aktuell:
■
■
■
■
■
■
Patient Identification Services RFP (CORBAmed RFP1). Diese RFP fordert Vorschläge für Spezifikationen hinsichtlich der gemeinsamen Merkmale von Patientenidentifikationssystemen an, welche die Interoperabilität zwischen mehreren verschiedenen solcher Systeme ermöglicht. Healthcare Lexicon Service RFP (CORBAmed RFP2). Diese RFP fordert Vorschläge für Spezifikationen hinsichtlich von IDL-Schnittstellen für die gemeinsamen Merkmale einer Gruppe von Lexikonabfragediensten an. Sie beschreibt die Anforderungen für Dienste, die Lexika in einem verteilten, mit der OMA kompatiblen Objektsystem unterstützen sollen. Clinical Observations RFI (CORBAmed RFI2). Diese RFI fordert Informationen über Anforderungen an, die der OMG-CORBAmed Task Force Richtlinien bei der Entwicklung von Spezifikationen für Informationssysteme im Gesundheitswesen an die Hand geben, die mit Patientenüberwachungsdaten zu tun haben. Die übergreifende Zielsetzung liegt in der Inkraftsetzung anbieterneutraler gemeinsamer Schnittstellen, die zur Interoperabilität zwischen Systemen, Anwendungen und Instrumenten dienen, mit denen medizinische Daten im Zusammenhang mit der Überwachung des gesundheitlichen Zustands eines bestimmten Patienten erkannt, übertragen, gespeichert und angezeigt werden. Clinical Decision Support RFI (CORBAmed RFI3). Diese RFI fordert Informationen über Anforderungen an, die der OMG-CORBAmed Task Force Richtlinien bei der Entwicklung von Spezifikationen für klinische Entscheidungshilfesysteme (Decision Support Systems, DSS) an die Hand geben. Die übergreifende Zielsetzung liegt in der Inkraftsetzung anbieterneutraler gemeinsamer Schnittstellen, welche die Qualität der Gesundheitsleistungen verbessern und die Kosten reduzieren, indem sie CORBA-Technologien für die Interoperabilität zwischen Systemen, Anwendungen und Instrumenten einsetzen, mit denen in klinischen Entscheidungshilfesystemen verwendete medizinische Informationen entdeckt, übertragen, gespeichert und angezeigt werden. Lifescience RFI. Diese RFI soll Informationen einholen, welche die Life Sciences Research Domain Special Interest Group (Gruppe für spezielle Belange im Bereich der Forschung für das Leben, LSR-DSIG) dabei unterstützen soll, nützliche und effektive Entscheidungen im Prozeß der Inkraftsetzung von Technologien in der Forschung für das Leben zu treffen. Als ersten Schritt in diesem Prozeß wird die LSR-DSIG eine Architekturbeschreibung, einen Zeitplan für die Herausgabe zusätzlicher RFIs und RFPs (einen Technologie-Verfahrensplan) und eine oder mehrere RFPs entwickeln, mit denen OMG-IDL-Schnittstellen und entsprechende semantische Beschreibungen angefordert werden. Daher fordert diese RFI Informationen über Architekturen, Interoperabilität, Objekt- und Datenmodelle, Schnittstellen, vorhandene Systeme, Standards, rechtliche Fragen und deren Prioritäten an. HL7 RFI. Diese RFI fordert Informationen zu Anforderungen an, die der OMG-CORBAmed Task Force im Bereich CORBA-gestützter HL7-Implementierungsansätze Richtlinien an die Hand geben soll. Die übergreifende Zielsetzung liegt in der Inkraftsetzung anbieterneutraler gemeinsamer Schnittstellen, welche die Qualität der Gesundheitsleistungen verbessern und die Kosten reduzieren können. Die Spezialeinheit CORBAmed wird zur Vereinheitlichung der Schnittstellen im Bereich des Gesundheitswesens den Inkraftsetzungsprozeß der OMG für offene Technologien einsetzen.
Die Transportation Domain Task Force Aufgabe der Transportation Domain Task Force (Spezialeinheit für das Transportwesen,
CORBAtransport) ist es, die Entwicklung und Verwendung von transportbezogenen Systemen zu fördern, die OMG-Spezifikationen und -technologien beinhalten, maßgebliche Standards, Geschäftsobjekte, Komponenten und Technologien im Bereich Transportwesen zu erkennen und diese Informationen in der OMG zu verbreiten. Ferner soll sie innerhalb der OMG-Ausschüsse und Spezialeinheiten dafür sorgen, daß die Spezifikationen für CORBA, für die CORBAservices und CORBAfacilities, für das Geschäftsobjekt und für die einzelnen Bereiche die Bedürfnisse der Transportindustrie berücksichtigen. Weitere Aufgaben bestehen darin, aus der Reihe der Unternehmen, die mit der Entwicklung von Transportsystemen befaßt sind, neue Mitglieder für die entsprechende Bereichsgruppe für spezielle Belange (DSIG) zu gewinnen und ein globales Forum für den freien Austausch von Entwicklungsideen für verteilte Objektsysteme unter den einzelnen Mitgliedern der Transportindustrie und ihren Partnern zu schaffen. Derzeit ist die folgende RFI der Transportation Domain Task Force (diese Task Force ist eine der neuesten Spezialeinheiten in der OMG) aktuell: ■
CORBAtransport RFI. Diese RFI fordert Informationen über Anforderungen, Projekte und Produkte an, die Richtlinien für die Interoperabilität von transportbezogenen Objektsystemen zur Verfügung stellen. Die übergreifende Zielsetzung besteht in der Inkraftsetzung von anbieterneutralen gemeinsamen Schnittstellen. Mit Hilfe der Antworten auf diese RFI werden eine oder mehrere RFPs definiert werden, die OMG-IDL-Schnittstellen und weiteres entsprechendes Material, wie z.B. semantische Beschreibungen, Sequentialisierung und TimingConstraints anfordern sollen. CORBAtransport versucht, Spezifikationsgruppen in mindestens vier vertikalen Hauptbereichen (Transport per Luft, See, Straße und Schiene) sowie eine gemeinsame horizontale Spezifikation zu entwickeln, die für das gesamte Transportwesen gültig sein soll.
Sinn und Zweck dieser ganzen Aktivitäten Wie Sie gesehen haben, ist die OMG wirklich mit vollem Einsatz dabei, die aktuellen Spezifikationen zu verbessern und neue zu entwickeln. Aber, so werden Sie sich fragen, was ist der Sinn und Zweck dieser ganzen Aktivitäten? Konkret gesagt, wer soll von den Ergebnissen der Arbeit dieser verschiedenen Technologieausschüsse und Spezialeinheiten profitieren? ■
■
Jeder profitiert davon. Dies umfaßt alle Entwickler von CORBA-Anwendungen, die aus vielen der an CORBA vorzunehmenden Verbesserungen Nutzen ziehen werden. Die Task Forces des Platform Technology Committee, d.h. die ORB and Object Services Platform Task Force und die Object Analysis and Design Task Force, arbeiten an Spezifikationen, von denen praktisch jeder CORBA-Entwickler profitieren wird. Leistungsmerkmale, wie z.B. das CORBAKomponentenmodell, die Fähigkeit, Objekte durch Werte sowie über Referenzen weiterzugeben, eine Skript-Sprache für CORBA-Objekte und eine Druckfunktionalität sind für eine Vielzahl von Entwicklern von Nutzen. Entwickler, die andere Technologien verwenden. Entwickler, die mit dem Objektmodell COM von Microsoft arbeiten, werden die verbesserte Interoperabilität zwischen COM- und CORBAObjekten zu schätzen wissen. Auch die Entwickler, die das DCE-Modell für verteilte Rechnerumgebungen verwenden, werden ähnliche Vorteile feststellen. Natürlich wird auch für Java eine verbesserte Interoperabilität mit CORBA zur Verfügung stehen, vor allem was die
■
■
Fähigkeit angeht, Java-Schnittstellen in IDL-Schnittstellen zu konvertieren. Die Anwender von elektronischen Handelsanwendungen. Wie weiter oben bereits erwähnt, weist der elektronische Handel das Potential auf, praktisch der bedeutendste Anwendungsbereich im Internet zu werden, und CORBA wird »Gewehr bei Fuß stehen«. Daher wird jeder, der Funktionen für das elektronische Handelswesen nutzen möchte, von der Arbeit profitieren, die von der Electronic Commerce Domain Task Force in diesem Bereich geleistet wird. Entwickler vertikaler Anwendungen. Es wird viel Arbeit in die Unterstützung einer Vielzahl vertikaler Industrieanwendungen investiert. Die dabei zu entwickelnden Standards für diese Märkte werden die Interoperabilität zwischen solchen Anwendungen fördern.
An dieser Stelle sollte noch einmal erwähnt werden, daß die OMG zum Zeitpunkt, an dem dieses Buch geschrieben wird, weder den Inhalt noch ein Veröffentlichungsdatum für CORBA 3.0 festgelegt hat. Daher kann man nur raten, welche der oben beschriebenen Spezifikationen in der nächsten Hauptversion von CORBA enthalten sein wird. Wenn diese Spezifikationen jedoch dann verfügbar sind, können alle CORBA-Entwickler davon profitieren.
Ein Blick nach vorn Die aktuellen Anforderungen zur Einreichung von Vorschlägen sowie die Anforderungen zur Lieferung von Informationen, die von den einzelnen Task Forces innerhalb der OMG herausgegeben werden, vermitteln einen guten Eindruck von der Richtung, die CORBA in der näheren Zukunft nehmen wird. Doch was kommt danach? Wie wird CORBA auf die Entwicklung verteilter Anwendungen in der ferneren Zukunft Einfluß nehmen? Mit welchen Herausforderungen wird CORBA konfrontiert werden? Obwohl es schwer ist, im Bereich der Entwicklungstechnologie für Software irgendwelche Voraussagen zu treffen, werde ich mit den folgenden Ausführungen zu diesem Thema Stellung nehmen. Wie sieht die zukünftige Entwicklung von CORBA aus? Trotz einiger jetzt noch vorhandener Unzulänglichkeiten ist CORBA bereits heute ein sehr leistungsfähiges Entwicklungswerkzeug. Es gibt jedoch vor allem einen Bereich, der noch ziemlich verbesserungswürdig ist: Bisher hat noch nie jemand behauptet, daß das Entwickeln von Anwendungen mit CORBA zu einfach sei. Man muß klar erkennen, daß die ehrfurchtgebietende Leistungsfähigkeit, zusammen mit den unerreichten Merkmalen, plattform- und sprachenübergreifend zu funktionieren, auch ihren Preis hat. Wenn Sie mit CORBA Anwendungen entwickeln wollen, so bedeutet dies, daß Sie diese Arbeit für eine weitere Plattform lernen müssen, ganz ohne Frage. Der Nutzen, der sich beim Erlernen und Anwenden der mit CORBA verbundenen Fertigkeiten ergibt, ist immens, aber im Zeitalter der schnellen Anwendungsentwicklung wird häufig mehr Wert auf das Tempo gelegt, mit dem eine Anwendung in Einsatz gebracht werden kann, und weniger auf die Robustheit eines Entwurfs. Wenn CORBA weiterhin als Entwicklungsplattform erfolgreich sein soll, muß dieser Standard »lernen«, sich in dieser Welt der blitzschnellen Softwareentwicklung zurechtzufinden.
Bedeutet dies, daß die Leistungsfähigkeit von CORBA, die Fähigkeit zur plattformübergreifenden Anwendungsentwicklung oder die Robustheit der Anwenderfreundlichkeit geopfert werden sollen? Natürlich nicht, obwohl die Werkzeuge zur Anwendungsentwicklung ein gutes Stück weiterhelfen können, um die Anwendungsentwicklung mit CORBA für den Entwickler einfacher zu machen. Solche Werkzeuge werden allmählich verfügbar, und Initiativen wie die Java to IDL-RFP der ORB and Object Services Task Force zeigen, daß weitere Anstrengungen in Richtung auf eine nahtlose Verbindung von CORBA und Anwendungsentwicklung unternommen werden. Das Konzept eines nahtlosen Übergangs wird am Ende vielleicht sogar der Schlüssel für den Erfolg von CORBA sein. Obwohl die Verwendung einiger CORBA-Dienste, -Einrichtungen und dergleichen immer ein gewisses Maß an Kenntnissen seitens des Entwicklers erfordern wird, sollte dieser die Infrastruktur einer CORBA-Anwendung so weit wie möglich ignorieren können. Details, wie z.B. die Speicherverwaltung, die Zählung von Referenzen und vielleicht sogar die IDL, sollten von den Entwicklungswerkzeugen selbst behandelt werden und für die meisten Entwickler unsichtbar bleiben. (Natürlich muß gewährleistet sein, daß ein interessierter Anwender sich, wenn gewünscht, bis zu dieser Detailebene durcharbeiten kann; ein gutes Entwicklungswerkzeug wird diese Stufe der Interaktion immer unterstützen.) Der Entwickler kann sich dann mehr auf den Entwurf der Entwicklung selbst konzentrieren, da er sich um die Einzelheiten der Implementierung nicht zu kümmern braucht. Kurz gesagt, befindet sich CORBA bereits an einem Punkt, an dem äußerst beeindruckende Ergebnisse mit dieser Architektur erzielt werden können. Zusätzliche Merkmale werden die Nützlichkeit von CORBA weiter erhöhen, aber zur Sicherung des Erfolgs von CORBA müssen die Anbieter von Entwicklungswerkzeugen Produkte auf den Markt bringen, welche die Anwendungsentwicklung einfacher gestalten als jemals zuvor. (In der Zwischenzeit freuen sich die Verlage, Ihnen Bücher wie das vorliegende SAMS-Buch CORBA in 14 Tagen präsentieren zu können!) Herausforderungen für CORBA Trotz der Leistungsfähigkeit und der Funktionalität der CORBA-Architektur und der sie umgebenden Object Management Architecture muß CORBA noch eine Reihe von Herausforderungen meistern, die sich auf dem Weg zum Erfolg in den Weg stellen. Diese Herausforderungen treten in Form von konkurrierenden Technologien oder im eigentlichen Prozeß der Inkraftsetzung von CORBA und der zugehörigen Spezifikationen auf den Plan. Die folgenden Ausführungen beschreiben kurz einige der Herausforderungen, denen CORBA sich heutzutage stellen muß. DCOM von Microsoft Es überrascht nicht, daß CORBA nicht konkurrenzlos auf dem Markt ist. Obwohl CORBA von fast der gesamten Industrie, einschließlich einiger wahrer Giganten, unterstützt wird, steht der universellen Akzeptanz zumindest ein großes Hindernis im Weg. Diese Herausforderung besteht in Form eines anderen Modells für verteilte Objekte von einem Anbieter, der in der Industrie eine sehr dominante Position einnimmt. Hierbei ist natürlich von der Microsoft Corporation und ihrem Distributed Component Object Model (DCOM) die Rede, denn das Unternehmen versucht, dieses Modell für verteilte Komponentenobjekte als De-facto-Standard im Bereich der verteilten Computerplattformen »durchzusetzen«.
Microsoft ist sich klar darüber, welche Position man gegenüber CORBA, dem offenen Standard, der von fast allen anderen wichtigen »Mitspielern« in der Industrie unterstützt wird, einnehmen will. Diese Position drückt sich vielleicht am besten in den Worten des COM-Produktmanagers Cornelius Willis aus, der von InfoWorld Electric am 18. August 1997 folgendermaßen zitiert wurde: »Natürlich wollen wir, daß COM3 - derzeit als COM+ bekannt - CORBA bedeutungslos macht.« (Die Originalfassung des Artikels kann auf der WWW-Seite von InfoWorld Electric unter http://www.infoworld.com/cgibin/displayArchives.pl?970818.wsoft.htm. abgerufen werden.) Offensichtlich ist Microsoft darauf aus, seine ansonsten schon äußerst expansive Stellung in der Industrie auf die Standards für das verteilte Computerwesen auszudehnen, anstatt einen in weitem Umfang akzeptierten Industriestandard zu unterstützen. Zu Microsofts Gunsten spricht, daß man die Technologien für COM, DCOM und ActiveX unter die Schirmherrschaft der Open Group, einem unabhängigen Standardkonsortium, gestellt hat und eine Partnerschaft mit der Software AG eingegangen ist, die DCOM-Implementierungen für andere Betriebssysteme als Windows zur Verfügung stellen soll. (Weitere Einzelheiten hierzu sind unter http://www.activex.org/ und http://www.softwareag.com/corporat/dcom/default.htm verfügbar.) Natürlich ist Microsoft als Mitbewerber in diesem Bereich willkommen. Konkurrenz ist am besten dazu geeignet, die OMG und die CORBA-Produktanbieter in Atem zu halten, so daß sie alles aus sich herausholen, um möglichst gute Spezifikationen, Implementierungen und Interoperabilitätsmerkmale zur Verfügung zu stellen. Wenn man jedoch die Allgegenwart der Betriebssysteme von Microsoft und in weiterer Konsequenz - von DCOM betrachtet, erkennt man, daß den CORBA-Verfechtern im Kampf gegen Microsoft der Wind kräftig ins Gesicht bläst, denn Microsoft wird eine seiner Plattformen zur Erstellung einer anderen in Einsatz bringen. Es gibt jedoch einen Hoffnungsschimmer: Derzeit weist CORBA gegenüber DCOM einige Vorteile auf, was in einem Artikel zu lesen ist, den Sie auf der WWW-Seite von OMG unter http://www.omg.org/news/activex.htm finden. Ein weiterer Artikel der OMG, der eine Reaktion auf einen Bericht darstellt, welcher von der in Großbritannien beheimateten Analystengruppe Ovum veröffentlicht wurde, bietet ebenfalls einige relevante Informationen zum Vergleich zwischen CORBA und DCOM; der Artikel kann unter http://www.omg.org/news/pr97/ovumpr.htm eingesehen werden. Weitere Herausforderungen Natürlich gibt es noch andere Herausforderungen, denen CORBA sich stellen muß. Hier ist beispielsweise die Problematik zu nennen, eine Organisation zu verwalten, die mehr als 750 Mitglieder zählt. Es ist klar, daß nicht jedes Mitglied über jeden Vorschlag mitentscheidet, aber der mit den Entscheidungen verbundene Aufwand ist nicht unerheblich. Eine der größten Stärken der OMG - ihr großer Rückhalt in der Industrie - kann sich gleichzeitig als eine ihrer größten Schwächen herausstellen, weil die entstehende Bürokratie die Entwicklung und Akzeptanz neuer Spezifikationen verlangsamt. Die OMG hat ihre Aufgabe bisher, vor allem unter Berücksichtigung ihrer Größe, außerordentlich gut gemacht. Im Angesicht der Konkurrenz durch DCOM von Microsoft wird die OMG große Sorgfalt darauf verwenden müssen, sicherzustellen, daß nicht die gesamte Organisation unter ihrem eigenen Gewicht zusammenbricht und möglicherweise CORBA unter sich begräbt.
Zusammenfassung CORBA hat bereits einen weiten Weg zurückgelegt. Mit einer derzeitigen Mitgliederzahl von 750, die noch weiter steigt, hat die OMG endgültig eine bedeutende Stellung auf dem Markt erreicht. CORBA wächst und reift als Standard bereits seit mehr als sieben Jahren, was im Zeitalter des Computers eine wahre Ewigkeit ist. CORBA-Produkte sind für eine Vielzahl von Plattformen und Betriebssystemen verfügbar, die von Dutzenden von Anbietern stammen. (Zu den von CORBA unterstützten Plattformen gehören AS400, HP-UX, MacOS, MS-DOS, MVS, OpenVMS, OS/2, SunOS und Solaris, Windows 3.x, Windows 95, Windows NT sowie viele Unix-Varianten, die noch nicht genannt wurden.) Es ist nicht zu übersehen, daß sich CORBA aus bescheidenen Anfängen zu einer etablierten, reifen Plattform für die Entwicklung verteilter Anwendungen gemausert hat. Der Wachstumsprozeß von CORBA ist noch nicht abgeschlossen, neue Leistungsmerkmale und Verbesserungen werden ständig hinzugefügt. Heute haben Sie einige der potentiellen Verbesserungen kennengelernt, die aktuell vorgeschlagen sind und die von horizontalen Merkmalen, wie dem CORBAObjektmodell, der Unterstützung für verschiedene Sprachen, und der Unterstützung für die Weitergabe von Objekten durch Werte, bis zu Spezifikationen für vertikale Einrichtungen, wie für das Gesundheitswesen, die Telekommunikations- und die Fertigungsindustrie reichen. Die OMG hat große Pläne für CORBA, und mit der Unterstützung einer Fülle von Anbietern, die CORBA-Produkte herstellen, werden diese Pläne auch Wirklichkeit werden. CORBA ist bereits heute eine leistungsfähige, robuste Plattform für die Entwicklung verteilter Unternehmensanwendungen und auf einem guten Wege, noch weit nutzbringender zu werden, als es heute bereits der Fall ist. In der Zukunft wird CORBA nicht nur noch leistungsfähiger und robuster werden, sondern auch eine nahtlose Integration in die Anwendungsentwicklung aufweisen. Die Zukunft hält ganz sicherlich einige aufregende Dinge für CORBA und die CORBA-Entwickler bereit.
Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH. Elektronische Fassung des Titels: Corba in 14 Tagen, ISBN: 3-8272-2031-9