Verteilte Systeme Ein Tutorial über die durchgängige Anwendung verschiedener Java-Technologien bei einer gemeinsamen Ausgangsthematik Dipl.-Inf. (FH) Florian Lutz Version 3.3, April 2012 Hochschule Regensburg Fakultät Informatik und Mathematik Inhaltsverzeichnis Inhaltsverzeichnis 1. Einführung ............................................................................................................ 1 1.1. Motivation ............................................................................................................................... 1 1.2. Aufbau des Tutorials .............................................................................................................. 1 1.3. Verwendete Software-Versionen ........................................................................................... 2 1.3.1. Plattform / System..................................................................................................... 2 1.3.2. Java........................................................................................................................... 2 1.3.3. Eclipse....................................................................................................................... 2 1.3.4. Frameworks / APIs.................................................................................................... 2 2. Ausgangsthematik Buchhaltung......................................................................... 3 2.1. Übersicht ................................................................................................................................ 3 2.1.1. Beschreibung ............................................................................................................ 3 2.1.2. Objektmodell ............................................................................................................. 4 2.2. Schnittstellen-Modell .............................................................................................................. 5 2.2.1. Konto-Schnittstelle .................................................................................................... 6 2.2.2. Kontotyp-Enumeration .............................................................................................. 7 2.2.3. Buchungs-Schnittstelle ............................................................................................. 7 2.2.4. Service-Schnittstelle ................................................................................................. 8 2.2.5. Proxy-Schnittstelle .................................................................................................... 9 2.2.6. Erweiterte Service-Schnittstelle ................................................................................ 9 2.3. Ausnahmen .......................................................................................................................... 10 2.3.1. Buchhaltungsfehler ................................................................................................. 10 2.3.2. Ein/Ausgabe-Fehler ................................................................................................ 10 2.3.3. Nicht implementierte Methoden .............................................................................. 11 2.3.4. Andere Ausnahmen ................................................................................................ 11 2.4. Modulares Konzept der Programmteile ............................................................................... 12 2.4.1. Client ....................................................................................................................... 12 2.4.1.1. Proxy-Instanzierung über Reflection ......................................................... 12 2.4.1.2. Laden einer Service-Klasse....................................................................... 13 2.4.2. Datenhaltung / Geschäftslogik................................................................................ 13 2.4.2.1. Daten und Persistenz ................................................................................ 13 2.4.2.2. Geschäftslogik ........................................................................................... 14 2.4.3. Transport / Middleware ........................................................................................... 14 2.4.3.1. Client.......................................................................................................... 14 2.4.3.2. Server ........................................................................................................ 14 2.4.4. Verkettung............................................................................................................... 15 -I- Inhaltsverzeichnis 3. Benutzeroberfläche ............................................................................................ 17 3.1. Übersicht .............................................................................................................................. 17 3.2. Zugriff über eine Proxy-Klasse............................................................................................. 18 3.2.1. Laden und Verbinden eines Buchhaltungssystems................................................ 18 3.2.2. Schließen der Verbindung ...................................................................................... 20 3.3. Aufbau der Oberfläche ......................................................................................................... 21 3.3.1. Kontenliste .............................................................................................................. 21 3.3.1.1. Laden der Kontenobjekte .......................................................................... 21 3.3.1.2. Anlegen eines neuen Kontos..................................................................... 21 3.3.1.3. Ändern der Daten eines Kontos ................................................................ 22 3.3.1.4. Löschen eines Kontos ............................................................................... 22 3.3.2. Buchungsliste.......................................................................................................... 23 3.3.2.1. Laden der Buchungsobjekte...................................................................... 23 3.3.2.2. Erfassen einer neuen Buchung ................................................................. 23 3.3.2.3. Ändern der Daten einer Buchung .............................................................. 24 3.3.2.4. Löschen einer Buchung............................................................................. 24 3.3.3. Soll- und Haben-Buchungen je Konto .................................................................... 25 3.3.4. Saldo eines Kontos ................................................................................................. 25 3.4. Behandlung von Ausnahmen ............................................................................................... 26 3.4.1. Nachrichtenfenster.................................................................................................. 26 3.4.2. Statusleiste ............................................................................................................. 26 3.4.3. Direkte Einblendung................................................................................................ 27 3.5. Kapselung aller Aufrufe........................................................................................................ 28 3.5.1. Adapterschicht ........................................................................................................ 28 3.5.1.1. Swing-MVC-Pattern ................................................................................... 28 3.5.1.2. Zwischenspeicherung der Daten ............................................................... 29 3.5.2. Aufteilung in separate Vorgänge ............................................................................ 29 3.5.2.1. Vorgänge zum Zugriff auf die Buchhaltung ............................................... 30 3.5.2.2. Vorgänge zur Aktualisierung der Benutzeroberfläche............................... 31 3.5.3. Kontrolle der Vorgänge ........................................................................................... 32 3.5.3.1. Status-Benachrichtigung über Listener-Interface ...................................... 32 3.5.3.2. Auflistung der laufenden Vorgänge ........................................................... 32 3.5.3.3. Abbruch eines Vorgangs ........................................................................... 33 3.6. Threading ............................................................................................................................. 34 3.6.1. Zentraler Threading-Service ................................................................................... 34 3.6.1.1. AWT-Event-Dispatching-Thread................................................................ 34 3.6.1.2. Worker-Threads......................................................................................... 35 3.6.2. Threading-Modelle .................................................................................................. 35 3.6.2.1. Singlethreaded........................................................................................... 36 3.6.2.2. Synchron einreihen.................................................................................... 37 3.6.2.3. Asynchron mit zusätzlichem Thread.......................................................... 38 3.6.2.4. Multithreaded ............................................................................................. 38 3.6.3. Abbruch eines laufenden Vorgangs........................................................................ 39 3.7. Weitere Funktionen .............................................................................................................. 41 3.7.1. Testfunktionen ........................................................................................................ 41 3.7.1.1. Daten erzeugen und aufräumen................................................................ 41 3.7.1.2. Test der Funktionalität ............................................................................... 41 3.7.1.3. Test der Geschwindigkeit .......................................................................... 42 3.7.1.4. Test auf Threadsicherheit.......................................................................... 42 3.7.2. Logging ................................................................................................................... 43 3.7.2.1. Logger-Klasse............................................................................................ 43 3.7.2.2. Auswertung des Stacktrace....................................................................... 44 3.7.2.3. Hierarchische Methodenaufrufe ................................................................ 44 3.7.2.4. Unterscheidung nach Thread .................................................................... 45 3.7.3. Look & Feel ............................................................................................................. 45 3.7.4. Hilfe ......................................................................................................................... 47 3.8. Suchen und Laden der Proxy-Klasse .................................................................................. 48 3.8.1. Suchverzeichnis...................................................................................................... 48 3.8.2. Klassenlader für den Suchvorgang......................................................................... 48 3.8.3. Listener-Schnittstelle für den Klassenlader ............................................................ 49 - II - Inhaltsverzeichnis 3.8.4. Eigenschaften gefundener Klassen ........................................................................ 50 3.8.5. Laden gefundener Klassen ..................................................................................... 51 3.8.6. Anmerkung.............................................................................................................. 51 3.9. Verwendung der Buchhaltung in Eclipse ............................................................................. 52 3.9.1. Neues Projekt anlegen............................................................................................ 52 3.9.2. Importieren des Package mit der Buchhaltungsoberfläche .................................... 54 3.9.3. Start der Anwendung .............................................................................................. 58 3.9.4. Erstellung eines eigenen Moduls für die Buchhaltung............................................ 59 - III - Inhaltsverzeichnis 4. Datenhaltung....................................................................................................... 63 4.1. Übersicht .............................................................................................................................. 63 4.1.1. Datenspeicherung................................................................................................... 63 4.1.2. Geschäftslogik ........................................................................................................ 63 4.2. Einfache lokale Datenhaltung .............................................................................................. 64 4.2.1. Allgemeines ............................................................................................................ 64 4.2.2. Daten in HashMaps ................................................................................................ 64 4.2.2.1. Konten und Buchungen ............................................................................. 64 4.2.2.2. Soll- und Haben-Buchungen ..................................................................... 64 4.2.2.3. Bidirektionaler Bezug................................................................................. 64 4.2.3. Implementierung der Schnittstellen ........................................................................ 65 4.2.3.1. Service-Klasse........................................................................................... 65 4.2.3.2. Konto-Klasse ............................................................................................. 67 4.2.3.3. Buchungs-Klasse....................................................................................... 69 4.2.3.4. Proxy-Klasse.............................................................................................. 70 4.2.4. Fazit ........................................................................................................................ 70 4.3. Java-Serialisierung............................................................................................................... 71 4.3.1. Allgemeines ............................................................................................................ 71 4.3.2. Serialisierbare Klassen ........................................................................................... 71 4.3.2.1. Konto-Klasse ............................................................................................. 71 4.3.2.2. Buchungs-Klasse....................................................................................... 71 4.3.3. De-/Serialisierungs-Vorgang................................................................................... 72 4.3.3.1. Service-Klasse........................................................................................... 72 4.3.3.2. Proxy-Klasse.............................................................................................. 73 4.3.4. Fazit ........................................................................................................................ 74 4.4. XML-Serialisierung über JAXB............................................................................................. 75 4.4.1. Allgemeines ............................................................................................................ 75 4.4.2. Annotations ............................................................................................................. 75 4.4.2.1. Package-Annotations ................................................................................ 75 4.4.2.2. Klassen-Annotations.................................................................................. 76 4.4.2.3. Enum-Annotations ..................................................................................... 77 4.4.2.4. Field-Annotations....................................................................................... 77 4.4.2.5. Objekt-Fabrik-Annotations......................................................................... 79 4.4.2.6. Adapter-Annotations .................................................................................. 79 4.4.3. Anwendung der Annotations bei den Buchhaltungs-Klassen................................. 79 4.4.3.1. Service-Klasse........................................................................................... 79 4.4.3.2. Buchungs-Klasse....................................................................................... 80 4.4.3.3. Callback-Methoden.................................................................................... 81 4.4.3.4. Konto-Klasse ............................................................................................. 82 4.4.4. Erzeugung des XSD-Schemas aus den annotierten Klassen ................................ 82 4.4.4.1. Generierung durch einen Ant-Vorgang ..................................................... 82 4.4.4.2. Das Schema zu den HashMaps ................................................................ 83 4.4.4.3. Resultierende XML-Datei .......................................................................... 84 4.4.4.4. Probleme ................................................................................................... 84 4.4.5. Implementierung der Klassen nach einem vorgegebenen Schema ....................... 85 4.4.5.1. Service-Klasse........................................................................................... 85 4.4.5.2. Konto-Klasse ............................................................................................. 86 4.4.5.3. Buchungs-Klasse....................................................................................... 86 4.4.5.4. Schema und Daten .................................................................................... 87 4.4.6. Proxy- und Utility-Klasse......................................................................................... 89 4.4.6.1. Proxy-Klasse.............................................................................................. 89 4.4.6.2. Utility-Klasse .............................................................................................. 90 4.4.6.3. Speichern der Objekte............................................................................... 90 4.4.6.4. Laden der Objekte ..................................................................................... 91 4.4.7. Fazit ........................................................................................................................ 91 4.5. Direkter SQL-Datenbankzugriff mit JDBC............................................................................ 92 4.5.1. Allgemeines ............................................................................................................ 92 4.5.2. JDBC-Datenbankanbindung ................................................................................... 92 4.5.2.1. Verbindung aufbauen ................................................................................ 92 4.5.2.2. Einzelne Werte .......................................................................................... 93 - IV - Inhaltsverzeichnis 4.5.2.3. Auflistungen ............................................................................................... 94 4.5.2.4. Anweisungen ............................................................................................. 94 4.5.2.5. PreparedStatements.................................................................................. 95 4.5.3. SQL-Anweisungen .................................................................................................. 95 4.5.3.1. Datenbank-Schema ................................................................................... 95 4.5.3.2. Service-Klasse........................................................................................... 96 4.5.3.3. Konto-Klasse ............................................................................................. 97 4.5.3.4. Buchungs-Klasse....................................................................................... 97 4.5.4. Fazit ........................................................................................................................ 98 4.6. Hibernate.............................................................................................................................. 99 4.6.1. Allgemeines ............................................................................................................ 99 4.6.1.1. Persistenz von Objekten.......................................................................... 100 4.6.1.2. Arbeitsweise mit POJOs.......................................................................... 100 4.6.1.3. OR-Mapping ............................................................................................ 101 4.6.2. Annotations ........................................................................................................... 101 4.6.2.1. Entitäten................................................................................................... 101 4.6.2.2. Identifikatoren - Schlüssel ....................................................................... 101 4.6.2.3. Einfache Daten ........................................................................................ 102 4.6.2.4. Beziehungen............................................................................................ 103 4.6.2.5. Kollektionen ............................................................................................. 104 4.6.2.6. Kaskadierung........................................................................................... 106 4.6.3. Installation von Hibernate ..................................................................................... 106 4.6.3.1. Hibernate-Einstellungen .......................................................................... 106 4.6.3.2. Datenbankanbindung .............................................................................. 107 4.6.3.3. Weitere Einstellungen.............................................................................. 107 4.6.4. Zugriff auf Hibernate ............................................................................................. 108 4.6.4.1. Erzeugen der Konfiguration..................................................................... 108 4.6.4.2. Sitzungen................................................................................................. 108 4.6.4.3. Transaktionen .......................................................................................... 109 4.6.4.4. Fehlerbehandlung.................................................................................... 110 4.6.4.5. Transaktions-Zeitlimit .............................................................................. 110 4.6.4.6. Versionskontrolle ..................................................................................... 111 4.6.4.7. Sperren .................................................................................................... 112 4.6.5. Verwendung der Objekte ...................................................................................... 112 4.6.5.1. Zustände.................................................................................................. 112 4.6.5.2. Automatische Erkennung des Objektzustandes...................................... 113 4.6.5.3. Laden der Objekte ................................................................................... 114 4.6.5.4. Abfragen .................................................................................................. 115 4.6.5.5. Modifikationen an Objekten ..................................................................... 115 4.6.5.6. Löschen persistenter Objekte.................................................................. 115 4.6.6. Buchhaltung mit einer Singleton-Service-Klasse.................................................. 116 4.6.6.1. Service-Klasse......................................................................................... 116 4.6.6.2. Konto-Klasse ........................................................................................... 118 4.6.6.3. Buchungs-Klasse..................................................................................... 119 4.6.6.4. Proxy-Klasse............................................................................................ 119 4.6.7. Buchhaltung mit einer Entity-Service-Klasse........................................................ 120 4.6.7.1. Service-Klasse......................................................................................... 120 4.6.7.2. Konto-Klasse ........................................................................................... 121 4.6.7.3. Buchungs-Klasse..................................................................................... 121 4.6.7.4. Proxy-Klasse............................................................................................ 122 4.6.7.5. Fazit ......................................................................................................... 123 -V- Inhaltsverzeichnis 5. Transport............................................................................................................124 5.1. Übersicht ............................................................................................................................ 124 5.1.1. Prinzipieller Ablauf ................................................................................................ 124 5.1.2. Erstellung mit Eclipse............................................................................................ 125 5.2. Eigener Transport über Sockets ........................................................................................ 126 5.2.1. Allgemeines .......................................................................................................... 126 5.2.2. Anbindung an die Datenhaltung ........................................................................... 126 5.2.2.1. SocketServer ........................................................................................... 126 5.2.2.2. Parallele Server-Instanzen ...................................................................... 127 5.2.3. Aufbau und Nutzung der Verbindung ................................................................... 127 5.2.3.1. Socket-Proxy-Klasse ............................................................................... 127 5.2.3.2. Verarbeitung auf dem Server .................................................................. 128 5.2.4. Definition eines eigenes Protokolls....................................................................... 129 5.2.4.1. Anfrage .................................................................................................... 130 5.2.4.2. Anfrage-Klassen ...................................................................................... 130 5.2.4.3. Antwort..................................................................................................... 132 5.2.4.4. Antwort-Klassen....................................................................................... 133 5.2.5. Un-/Marshalling der Objekte ................................................................................. 135 5.2.5.1. Die Klasse StringUnMarshaller................................................................ 135 5.2.5.2. Verarbeitung definierter Superklassen .................................................... 135 5.2.5.3. Aufruf der Un-/Marshal-Methoden ........................................................... 136 5.2.5.4. Einfache Objekte ..................................................................................... 136 5.2.5.5. Kollektionen ............................................................................................. 137 5.2.5.6. Ausnahmen.............................................................................................. 137 5.2.6. Entfernte Objekte .................................................................................................. 138 5.2.6.1. Aufruf der Methoden über Reflection ...................................................... 138 5.2.6.2. Übertragung der entfernten Objekte........................................................ 138 5.2.6.3. Lokale Stellvertreter................................................................................. 140 5.2.6.4. Anfrage-Methoden ................................................................................... 140 5.2.7. Fazit ...................................................................................................................... 142 5.3. Remote Method Invocation (RMI) ...................................................................................... 143 5.3.1. Allgemeines .......................................................................................................... 143 5.3.2. Entfernte Objekte .................................................................................................. 144 5.3.2.1. Remote-Interface ..................................................................................... 144 5.3.2.2. Remote-Object......................................................................................... 144 5.3.3. Anbindung an die Datenhaltung ........................................................................... 144 5.3.3.1. Proxy-Instanzierung................................................................................. 144 5.3.3.2. Weitergabe der Aufrufe ........................................................................... 145 5.3.4. Server - RMI-Registry ........................................................................................... 145 5.3.4.1. Registry starten........................................................................................ 145 5.3.4.2. Registry einbinden ................................................................................... 145 5.3.4.3. Objekt registrieren ................................................................................... 146 5.3.4.4. Der vollständige Server ........................................................................... 146 5.3.5. Entfernte Buchhaltungsobjekte............................................................................. 147 5.3.5.1. Service-Klasse......................................................................................... 147 5.3.5.2. Konto-Klasse ........................................................................................... 148 5.3.5.3. Buchungs-Klasse..................................................................................... 149 5.3.6. Aufruf der entfernten Buchhaltung........................................................................ 149 5.3.6.1. Anfrage an den Namensdienst ................................................................ 150 5.3.6.2. RMI-Proxy-Klasse.................................................................................... 150 5.3.7. Codebase.............................................................................................................. 151 5.3.8. Fazit ...................................................................................................................... 151 5.4. CORBA............................................................................................................................... 152 5.4.1. Allgemeines .......................................................................................................... 152 5.4.1.1. Object Request Broker (ORB) ................................................................. 152 5.4.1.2. Interoperable Object Reference (IOR)..................................................... 152 5.4.1.3. Portable Object Adapter (POA) ............................................................... 152 5.4.1.4. Tie-Mechanismus .................................................................................... 153 5.4.1.5. CORBA-Dienste....................................................................................... 153 5.4.1.6. Implementierungen .................................................................................. 153 - VI - Inhaltsverzeichnis 5.4.2. Interface Definition Language............................................................................... 154 5.4.2.1. Buchhaltungs-IDL .................................................................................... 154 5.4.2.2. Zuordnung der IDL-Elemente zu Java-Typen ......................................... 156 5.4.2.3. Java-Artefakte generieren ....................................................................... 156 5.4.2.4. Erzeugte Klassen und Schnittstellen ....................................................... 157 5.4.3. Verwendung der erzeugten Klassen auf der Serverseite ..................................... 158 5.4.3.1. CORBA-Server ........................................................................................ 158 5.4.3.2. Konto-Klasse ........................................................................................... 161 5.4.3.3. Buchungs-Klasse..................................................................................... 162 5.4.3.4. Der ORB-Daemon ................................................................................... 162 5.4.4. Verwendung der erzeugten Klassen auf der Clientseite ...................................... 163 5.4.4.1. Service-Klasse......................................................................................... 163 5.4.4.2. Konto-Klasse ........................................................................................... 164 5.4.4.3. Buchungs-Klasse..................................................................................... 165 5.4.4.4. Proxy-Klasse............................................................................................ 166 5.4.5. JACORB................................................................................................................ 167 5.4.5.1. Features................................................................................................... 167 5.4.5.2. Installation................................................................................................ 167 5.4.5.3. Verwendung............................................................................................. 168 5.4.5.4. Einstellungen ........................................................................................... 169 5.4.6. Fazit ...................................................................................................................... 169 - VII - Inhaltsverzeichnis 6. Java-Enterprise..................................................................................................170 6.1. Übersicht ............................................................................................................................ 170 6.1.1. Komponenten........................................................................................................ 171 6.1.1.1. EJB-Container ......................................................................................... 171 6.1.1.2. Application-Client-Container.................................................................... 171 6.1.1.3. Web-Container......................................................................................... 171 6.1.1.4. Applet-Container...................................................................................... 171 6.1.1.5. Datenbank ............................................................................................... 171 6.1.2. Dienste .................................................................................................................. 172 6.1.2.1. JMS.......................................................................................................... 172 6.1.2.2. JMX.......................................................................................................... 172 6.1.2.3. JAAS........................................................................................................ 172 6.1.2.4. JTA .......................................................................................................... 172 6.1.2.5. JavaMail................................................................................................... 172 6.1.2.6. JAF .......................................................................................................... 172 6.1.2.7. JCA .......................................................................................................... 173 6.1.2.8. JAXR........................................................................................................ 173 6.1.2.9. JAX-RPC ................................................................................................. 173 6.1.3. Verfügbare Implementierungen ............................................................................ 173 6.1.3.1. Open Source Server ................................................................................ 173 6.1.3.2. Proprietäre Server ................................................................................... 173 6.1.3.3. Web-Container......................................................................................... 173 6.2. Der JBoss-Application-Server ............................................................................................ 174 6.2.1. Allgemeines .......................................................................................................... 174 6.2.2. Installation ............................................................................................................. 174 6.2.2.1. Aus dem beiliegenden Paket................................................................... 175 6.2.2.2. Originalinstallation von jboss.org............................................................. 175 6.2.2.3. Verzeichnisstruktur .................................................................................. 176 6.2.3. Datenbankanbindung............................................................................................ 176 6.2.3.1. Hypersonic............................................................................................... 177 6.2.3.2. MySQL..................................................................................................... 178 6.2.3.3. PostgreSQL ............................................................................................. 179 6.2.3.4. Oracle ...................................................................................................... 179 6.2.4. Verwendung.......................................................................................................... 180 6.2.4.1. Einfacher Start ......................................................................................... 181 6.2.4.2. JBoss als Windows-Systemdienst........................................................... 182 6.2.4.3. Deployment von Anwendungen............................................................... 183 6.2.5. Verwaltung über die JMX-Konsole ....................................................................... 183 6.2.5.1. System-Eigenschaften............................................................................. 184 6.2.5.2. JNDI-Übersicht ........................................................................................ 184 6.2.5.3. Hypersonic-Manager ............................................................................... 185 6.2.6. Produktiver Betrieb ............................................................................................... 186 6.2.7. Integration in die IDE ............................................................................................ 186 6.2.7.1. Eclipse-Projekt anlegen........................................................................... 186 6.2.7.2. JBoss-Konfiguration anlegen................................................................... 188 6.2.7.3. Den Server steuern.................................................................................. 189 6.2.7.4. Deployment eigener Anwendungen ........................................................ 190 6.3. Enterprise JavaBeans ........................................................................................................ 191 6.3.1. Allgemeines .......................................................................................................... 191 6.3.1.1. Annotations.............................................................................................. 191 6.3.1.2. Home-Interface ........................................................................................ 191 6.3.1.3. Business-Interface ................................................................................... 191 6.3.1.4. Life-Cycle und Callback-Methoden.......................................................... 192 6.3.2. Session-Beans...................................................................................................... 193 6.3.2.1. Zustandslose Session-Beans .................................................................. 193 6.3.2.2. Zustandsbehaftete Session-Beans.......................................................... 193 6.3.2.3. Dependency-Injection.............................................................................. 194 6.3.2.4. Interceptoren (AOP)................................................................................. 194 6.3.2.5. EntityManager.......................................................................................... 195 6.3.2.6. Service-Klasse......................................................................................... 195 - VIII - Inhaltsverzeichnis 6.3.3. Entity-Beans.......................................................................................................... 198 6.3.3.1. Bean-Managed-Persistence .................................................................... 198 6.3.3.2. Container-Managed-Persistence............................................................. 198 6.3.3.3. FetchType................................................................................................ 199 6.3.3.4. Entity-Bean-Prinzip .................................................................................. 199 6.3.3.5. Konto-Klasse ........................................................................................... 200 6.3.3.6. Buchungs-Klasse..................................................................................... 202 6.3.4. Weitere Bean-Arten .............................................................................................. 203 6.3.4.1. JMS - Message-Driven-Beans................................................................. 203 6.3.4.2. JMX - Management-Beans...................................................................... 206 6.3.4.3. Service-Beans ......................................................................................... 207 6.3.4.4. TimerService............................................................................................ 207 6.3.4.5. Management-Service-Bean mit Timer-Service ....................................... 208 6.3.5. Deployment........................................................................................................... 209 6.3.5.1. Enterprise-Archiv ..................................................................................... 209 6.3.5.2. Archiv für Session-Beans ........................................................................ 210 6.3.5.3. Archiv für Entitiy-Beans ........................................................................... 210 6.3.5.4. Archiv für weitere Klassen ....................................................................... 211 6.3.5.5. Gemeinsame Ant-Build-Datei .................................................................. 212 6.3.5.6. Einstellungen ........................................................................................... 213 6.3.6. Client-Anwendung................................................................................................. 214 6.3.6.1. Einbinden der nötigen Bibliotheken ......................................................... 214 6.3.6.2. Aufruf der Anwendung auf dem Server ................................................... 214 6.3.6.3. Senden einer Nachricht an die MessageBean ........................................ 215 6.3.6.4. Allgemeine Client-Einstellungen.............................................................. 216 6.4. Web-Services ..................................................................................................................... 218 6.4.1. Allgemeines .......................................................................................................... 218 6.4.1.1. RPC-Style vs. Document-Style................................................................ 219 6.4.1.2. literal vs. encoded.................................................................................... 219 6.4.2. Die Buchhaltung als Web-Service-RPC-Endpunkt............................................... 220 6.4.2.1. Service-Schnittstelle ................................................................................ 220 6.4.2.2. Konto-Schnittstelle................................................................................... 221 6.4.2.3. Buchungs-Schnittstelle ............................................................................ 221 6.4.2.4. Implementierung ...................................................................................... 222 6.4.2.5. Deployment.............................................................................................. 223 6.4.3. JBoss-Web-Service-Tools .................................................................................... 225 6.4.3.1. Verwendung mit Ant ................................................................................ 225 6.4.3.2. WSDL-Datei............................................................................................. 226 6.4.3.3. Client-Artefakte ........................................................................................ 227 6.4.3.4. Erfahrungen mit den JBoss-Web-Service-Tools ..................................... 227 6.4.4. Web-Services mit AXIS......................................................................................... 228 6.4.4.1. Installation................................................................................................ 228 6.4.4.2. Ant-Vorgang............................................................................................. 228 6.4.4.3. Client-Artefakte ........................................................................................ 229 6.4.5. Client-Implementierung......................................................................................... 229 6.4.5.1. Proxy-Klasse............................................................................................ 229 6.4.5.2. Service-Klasse......................................................................................... 230 6.4.5.3. Konto-Klasse ........................................................................................... 231 6.4.5.4. Buchungs-Klasse..................................................................................... 233 6.4.6. Fazit ...................................................................................................................... 233 - IX - Inhaltsverzeichnis 7. Buchhaltung Mobil ............................................................................................234 7.1. Übersicht ............................................................................................................................ 234 7.1.1. Pocket-PC ............................................................................................................. 234 7.1.2. Windows-Mobile.................................................................................................... 234 7.1.3. Java Micro Edition................................................................................................. 235 7.1.4. Mysaifu JVM ......................................................................................................... 235 7.2. Installation .......................................................................................................................... 236 7.2.1. Laufzeitumgebung ................................................................................................ 236 7.2.2. Aufruf der JVM ...................................................................................................... 236 7.2.3. Entwicklungsumgebung ........................................................................................ 236 7.3. Buchhaltungsoberfläche..................................................................................................... 237 7.3.1. Verfügbare Proxy-Module ..................................................................................... 237 7.3.2. Aufbau der Oberfläche.......................................................................................... 238 7.3.2.1. Konto und Buchungsliste......................................................................... 238 7.3.2.2. Konto anlegen oder ändern ..................................................................... 239 7.3.2.3. Buchung anlegen oder ändern ................................................................ 239 7.3.2.4. Konto oder Buchung löschen .................................................................. 240 7.3.2.5. Konto-Details ........................................................................................... 240 7.3.3. Ausnahmen........................................................................................................... 241 7.3.4. Menü ..................................................................................................................... 241 7.3.4.1. Schnittstellen-Menü ................................................................................. 241 7.3.4.2. Hilfe-Menü ............................................................................................... 242 7.4. Anbindung an die vorhandenen Systeme .......................................................................... 243 7.4.1. Lokale und Dummy-Klassen ................................................................................. 243 7.4.2. An das Socket-Modul............................................................................................ 243 7.4.3. Über RMI............................................................................................................... 243 7.4.4. Andere Technologien............................................................................................ 243 7.5. Erfahrungen und Probleme ................................................................................................ 243 -X- Inhaltsverzeichnis Schlußwort .............................................................................................................244 Verzeichnisse.........................................................................................................245 Abkürzungsverzeichnis ............................................................................................................. 245 Abbildungsverzeichnis .............................................................................................................. 248 Codeverzeichnis........................................................................................................................ 251 Resourcenverzeichnis ............................................................................................................... 254 Tabellenverzeichnis................................................................................................................... 255 Quellenverzeichnis .................................................................................................................... 256 Anhänge .................................................................................................................261 Anhang A: System-Eigenschaften des JBoss........................................................................... 261 Anhang B: Erweiterte Darstellung des asynchronen Threading ............................................... 263 Anhang C: Notwendige Bibliotheken......................................................................................... 264 Java Architecture for XML Binding (JAXB) ..................................................................... 264 Hibernate-Core................................................................................................................ 264 Hibernate-Annotations .................................................................................................... 264 Web-Services.................................................................................................................. 264 JBoss-Web-Services-Tools............................................................................................. 264 AXIS ................................................................................................................................ 265 JBoss-Client .................................................................................................................... 265 JacORB........................................................................................................................... 265 Anhang D: Performance-Messung............................................................................................ 266 Datenhaltung................................................................................................................... 266 Transport......................................................................................................................... 266 Enterprise JavaBeans..................................................................................................... 266 Anhang E: WSDL-Datei für den Buchhaltungsservice.............................................................. 267 Anhang F: Changelog der Anwendung ..................................................................................... 275 - XI - Einführung - Motivation 1. Einführung 1.1. Motivation Der Ausgangspunkt für dieses Tutorial ist die Vorlesung „Verteilte Systeme“. Dabei war ein Thema die Implementierung eines verteilten Systems anhand von vorgegebenen Schnittstellen. Diese Schnittstellen wurden an die entsprechenden Vorlesung „Verteilte Systeme“ der Fachhochschule Aargau https://web.imvs.technik.fhnw.ch/_zope/ds angelehnt und spezifizieren eine einfache Bankanwendung mit einem Bankobjekt und mehreren Kontenobjekten. Im Verlauf des Semesters führten diese Vorgaben eigentlich eher unbeabsichtigt zu mehreren modularen Implementierungen der Bankanwendung, die jeweils auf unterschiedliche Weise (Sockets, RMI, CORBA) ein verteiltes System realisieren und aufeinander aufbauen und miteinander verkettet sind. Daraus entstand die Idee, gezielt ein eigenständiges und etwas umfangreiches Beispiel für die unterschiedlichen Möglichkeiten verteilter Systeme mit Java zu erstellen. 1.2. Aufbau des Tutorials Dieses Tutorial soll anhand einer durchgehenden thematischen Grundlage mehrere Vorgehensweisen und Technologien darstellen, die im Zusammenhang mit verteilten Systemen unter Java (zum Teil aber auch systemübergreifend) anwendbar sind. Zuerst wird die Ausgangsthematik, eine Finanzbuchhaltungsanwendung, näher erläutert und analog zur Bankanwendung die nötigen Schnittstellen definiert. Danach wird die Clientanwendung vorgestellt, eine Swing-Benutzeroberfläche, die den Zugriff auf die spezifizierten Buchhaltungsschnittstellen durchführt, und die Resultate auswertet und darstellt. Als nächstes folgen verschiedene Möglichkeiten der Datenhaltung, um lokal oder auf der Serverseite die Daten zu speichern. Dann werden übliche netzwerkbasierte Übertragungsarten betrachtet, bei denen die Anwendung nun in Client- und Serverseite aufgeteilt wird Schließlich folgt die Betrachtung und Implementierung der Buchhaltung mit Enterprise Java Beans und als Web-Service. Auch die Implementierung einer Web-Oberfläche mit JavaServer-Faces als zusätzliche Client-Anwendung zur Swing-GUI wäre eine Möglichkeit, die aber nicht näher ausgeführt wird. Vom Umfang der einzelnen Teilthemen her liegt das Hauptaugenmerk des Tutorials beim Hibernate-Persistenz-Framework (siehe 4.6) und dem JBoss-Application-Server (siehe 6.2). -1- Einführung - Verwendete Software-Versionen 1.3. Verwendete Software-Versionen 1.3.1. Plattform / System Entwickelt wird die Buchhaltungsanwendung unter dem Betriebssystem Windows XP, und um zusätzlichen Ballast zu vermeiden, wird jeweils nicht explizit auf andere Plattformen eingegangen. Allerdings sollte es durch die prinzipielle plattformunabhängigkeit von Java leicht möglich sein, alle angesprochenen Vorgehensweisen auch auf andere Systeme zu übertragen. 1.3.2. Java Bei den zugehörigen Java-Programmen kommt der JDK (Java Development Kit) in der Version 6 (1.6.0) Update 11 [01] zum Einsatz. Alle implementierten Beispiele sind auf den Features dieser Java-Version aufgebaut, aber auch die Verwendung der Version 5 (1.5.0) von Java sollte keine Probleme bereiten. Lediglich die Web-Services (6.4) funktionieren nicht ohne besondere Modifikationen, weil die Web-Services-Klassen im JDK nicht mit den Web-Services-Klassen, die der JBossApplication-Server (6.2) mitbringt und benötigt, kompatibel sind. 1.3.3. Eclipse Als Entwicklungsumgebung wird die "Eclipse IDE for Java EE Developers" [38] verwendet. Eine Variante der Version 3.4.1 (Ganymede) [30] des Eclipse SDK, die unter http://www.eclipse.org/downloads/download.php?file=/technology/epp/downl oads/release/ganymede/SR2/eclipse-jee-ganymede-SR2-win32.zip heruntergeldaden werden kann und Plugins enthält, die speziell die Entwicklung von Java Enterprise Anwendungen unterstützen. Auch die ältere Eclipse-Version 3.3.0 (Europa) [82] kann verwendet werden, wobei ebenfalls die Variante für "Java EE Developer" zu empfehlen ist. 1.3.4. Frameworks / APIs Dieses Tutorial hält sich an die folgenden, zum Zeitpunkt der Erstellung (April 2009) verfügbaren, Versionen der verwendeten APIs. Zu einem späteren Zeitpunkt können sich darin noch Änderungen ergeben, die dann unter Umständen von den aufgeführten Inhalten und Vorgehensweisen abweichen. Hibernate-Core 3.3.1.GA [27] Hibernate-Annotations 3.4.0.GA [27] Hibernate-EntityManager 3.4.0.GA [27] JWSDP 2.0 - Java Web-Services Development Pack [28] JBoss-Application-Server 4.2.2.GA [02] Apache AXIS 1.4 [32] -2- Ausgangsthematik Buchhaltung - Übersicht 2. Ausgangsthematik Buchhaltung 2.1. Übersicht 2.1.1. Beschreibung Als Rahmen und Ausgangsbeispiel für die verschiedenen Konzepte und Technologien, die im Folgenden vorgestellt werden, soll eine stark vereinfachte Finanzbuchhaltung [12] verwendet werden. Dies dient dabei aber nur als durchgängige Thematik, das Hauptaugenmerk liegt auf den Java-Technologien. Die Buchhaltung wird durch folgende Annahmen definiert: jeder Buchungssatz besitzt genau eine Soll- und eine Haben-Buchung. da nur eine Soll- und eine Haben-Buchung existieren besitzt ein Buchungssatz nur genau einen Betrag. das Buchungsdatum wird nicht erhoben Umsatzsteuer wird grundsätzlich nicht berücksichtigt Konten werden lediglich in fünf definierte Kontenarten unterschieden: o o o o o Aktiv Passiv Aufwand Ertrag Ergebnisrechnung Es gibt folglich keine automatische Aggregation von Hilfskonten in übergeordnete Konten Es gibt keine automatisierte Generierung von Ergebnisrechnungen Ansonsten verhält sich das Buchhaltungssystem passend zu den im Fach Rechnungswesen vermittelten Buchhaltungsmethoden [13] nach HGB unter Verwendung z.B. des IndustrieKontenrahmen -3- Ausgangsthematik Buchhaltung - Übersicht 2.1.2. Objektmodell Diese Annahmen führen dann zu einem vereinfachten Objektmodell, das die relevanten Daten einer Finanzbuchhaltung abdeckt. Abbildung 1: Objektmodell der Buchhaltung Das Modell erlaubt es Buchungssätze in der folgenden gewohnten Form anzugeben: Soll-Konto 6000 an Haben-Konto 4400 Betrag 1000,- € Text Kauf von Rohstoffen Tabelle 1: Beispiel-Buchungsatz Um später Rechnungen auf einem entfernten System durchzuführen, soll der Buchhaltungsdienst eine Saldierfunktion bieten, die in verteilten Szenarien jeweils auf dem Server den Saldo zu einer gegebenen Kontonummer ermittelt, auch wenn diese Funktionalität eigentlich eher einem Konto direkt zuzuordnen wäre. -4- Ausgangsthematik Buchhaltung - Schnittstellen-Modell 2.2. Schnittstellen-Modell Im Hinblick auf eine serviceorientierte Programmierung und die spätere Implementierung der verschiedenen Technologien wird das Model des Buchhaltungssystems auf Schnittstellen mit durchgängigen englischen Bezeichnern abgebildet. AccountType.ACTIVE AccountType.PASSIVE AccountType.EXPENSE AccountType.PROCEEDS AccountType.AMOUNT Account AccountingEntry AccountingEntry.DebitAccount AccountingEntry.CreditAccount AccountingEntry.Amount Account.Balance Aktiv-Konto Passiv-Konto Aufwand-Konto Ertrag-Konto Ergebnisrechnung (IKR Klasse 8) Konto Buchungssatz Soll-Konto Haben-Konto Betrag der Buchung Saldo des Kontos Tabelle 2: Verwendete Begriffe Abbildung 2: Schnittstellenmodell der Buchhaltung -5- Ausgangsthematik Buchhaltung - Schnittstellen-Modell Dieses Modell erlaubt später eine flexible modulare Implementierung verschiedener Ausprägungen des Systems. Einige Methoden stehen unter Umständen nicht im Einklang mit bekannten realen Buchhaltungsverfahren (z.B. Buchung löschen vs. Storno-Buchung), sind aber im Rahmen des Gesamtkonzepts der später angesprochenen Technologien sinnvoll, die hier im Vordergrund vor einer korrekten Finanzbuchhaltung stehen. Dies ist auch der Grund weshalb alle im Folgenden angesprochenen Schnittstellen bereits von Remote abgeleitet wurden. So können die Schnittstellen direkt für RMI (siehe 5.3) und EJB (siehe 6.3) verwendet werden. 2.2.1. Konto-Schnittstelle Konto-Klassen implementieren die Konto-Schnittstelle zur Verwaltung der minimal notwendigen Daten für ein Buchungs-Konto. Die Schnittstelle liefert und ändert die Daten eines Kontos (Nummer, Typ, Beschreibung) über Getter- und Setter-Methoden. Lediglich die Nummer eines Kontos soll einmal festgelegt und nicht nachträglich veränderbar sein. public interface IAccount extends Remote { Set<Integer> getCreditEntries() throws IOException; Set<Integer> getDebitEntries() throws IOException; String getDescription() throws IOException; Integer getNumber() throws IOException; AccountType getType() throws IOException; void setDescription(String description) throws IOException; void setType(AccountType type) throws IOException; } Code 1: Interface IAccount existieren noch zwei weitere Methoden getCreditAccounts() und getDebitAccounts(), die die Haben- und Soll-Buchungen, die dem Konto zugeordnet sind, Dazu zurückgeben sollen. Diese beiden Methoden werden später aufgerufen, sobald ein Konto in der Kontentabelle der Benutzeroberfläche ausgewählt wird. Die Resultate der Methoden werden in Tabellen entsprechend angezeigt. -6- Ausgangsthematik Buchhaltung - Schnittstellen-Modell 2.2.2. Kontotyp-Enumeration Für die Buchhaltung werden fünf mögliche Kontotypen in der Enumeration AccountType festgelegt, die vom Rechnungswesen her bekannt sind. package de.fhr.vs_iw.model; public enum AccountType implements Serializable { ACTIVE (1, "Aktiv"), AMOUNT (5, "Ergebnis"), EXPENSE (3, "Aufwand"), PASSIVE (2, "Passiv"), PROCEEDS(4, "Ertrag"); public int getId(); public String toString(); public static AccountType getAccountTypeById(int id); public static List<AccountType> getAccountTypes(); } Code 2: Enumeration AccountType Jeder Kontotyp erhält eine ID unabhängig von seiner Einordnung in der Enumeration. 2.2.3. Buchungs-Schnittstelle Die Buchungs-Schnittstelle bietet über Getter- und Setter-Methoden entsprechenden Zugriff auf die Daten einer Buchung (ID, Haben-Konto, Soll-Konto, Betrag, Text). package de.fhr.vs_iw.model; public interface IAccountingEntry extends Remote { Double getAmount() throws IOException; Integer getCreditAccount() throws IOException; Integer getDebitAccount() throws IOException; Integer getId() throws IOException; String getText() throws IOException; void setAmount(Double amount) throws IOException; void setCreditAccount(Integer number) throws IOException, AccountingException; void setDebitAccount(Integer number) throws IOException, AccountingException; void setText(String text) throws IOException; } Code 3: Interface IAccountingEntry Die ID muß dabei von der Implementierung des Buchhaltungssystems für jede Buchung eindeutig vergeben werden und darf nicht nachträglich geändert werden, weshalb dafür in der Schnittstelle auch keine Setter-Methode vorgesehen ist. -7- Ausgangsthematik Buchhaltung - Schnittstellen-Modell 2.2.4. Service-Schnittstelle Über die Service-Schnittstelle wird dann auf die Geschäftslogik der Buchhaltung, sowie die Buchungen und Konten, zugegriffen. package de.fhr.vs_iw.model; public interface IAccountingService extends Remote { Double calculateBalance(Integer number) throws IOException, AccountingException; void createAccount(Integer number, AccountType type, String description) throws IOException, AccountingException; Integer createAccountingEntry(Integer debit, Integer credit, Double amount, String text) throws IOException, AccountingException; IAccount getAccountByNumber(Integer number) throws IOException, AccountingException; Set<Integer> getAccountingEntries() throws IOException; IAccountingEntry getAccountingEntryById(Integer id) throws IOException, AccountingException; Set<Integer> getAccounts() throws IOException; void removeAccount(Integer number) throws IOException, AccountingException; void removeAccountingEntry(Integer id) throws IOException, AccountingException; } Code 4: Interface IAccountingService Die wichtigsten Methoden, sind im ersten Ansatz getAccounts() und getAccountignEntries() um alle vorhandenen Buchungen und Konten zu erhalten. Die zurückgegebenen Werte (Kontonummer bzw. Buchungs-ID) stellen für die Geschäftslogik der Buchhaltung Primärschlüssel dar und müssen im jeweiligen System eindeutig sein. Danach kann für jeden erhaltenen Wert eine der Factory-Methoden getAccountByNumber(Integer number) für die Konten bzw. getAccountingEntryById(Integer id) für die Buchungen aufgerufen werden, um die entsprechenden Objekte zu erhalten. Die weiteren Methoden zum Anlegen und Löschen von Buchungen vervollständigen das Buchhaltungssystem, damit es in seiner einfachen Form verwendbar wird. Die Methode createAccount() erzeugt ein neues Konto, muß aber eine Buchhaltungsausnahme werfen, wenn bereits ein Konto mit der angegebenen Nummer existiert oder die angegebene Kontonummer ungültig (z.B. negativ) ist. Beim Erfassen einer neuen Buchung durch createAccountingEntry() muß eine Buchhaltungsausnahme geworfen werden, wenn das angegebene Haben- und Soll-Konto identisch sind oder der angegebene Betrag ungültig ist. Für alle entsprechenden Methoden, die einen Schlüsselwert (Nummer bzw. ID) als Parameter erfordern, gilt, daß diese eine Buchhaltungsausnahme werfen müssen, wenn kein Objekt zum angegebenen Schlüsselwert existiert. -8- Ausgangsthematik Buchhaltung - Schnittstellen-Modell 2.2.5. Proxy-Schnittstelle Die Proxy-Schnittstelle dient als Einstiegspunkt, um überhaupt Zugriff auf eine Implementierung eines Buchhaltungssystems zu erlangen. Sie hat nichts mit der Geschäftslogik der Buchhaltung zu tun, wird aber benötigt, damit die von der Benutzeroberfläche verwendeten Implementierungen des Systems austauschbar sind. package de.fhr.vs_iw.model; public interface IAccountingProxy { void close() throws IOException; IAccountingService connect(String[] args) throws IOException; } Code 5: Interface IAccountingProxy Durch den Aufruf von connect(String[] args) wird die Instanz einer Service-Klasse geladen. Es ist dabei der Implementierung der Proxy-Klasse überlassen, auf welche Weise diese Instanz der Service-Klasse erzeugt wird und welche Argumente dafür erforderlich sind. Da eine Proxy-Klasse über Reflection instanziert wird, sollte eine Implementierung der ProxyKlasse einen öffentlichen Standard-Konstruktor haben. Die Schnittstelle ist deshalb auch nicht von Remote abgeleitet, weil sie als Einstiegspunkt auf jeden Fall lokal vorhanden und direkt aufrufbar sein muß. Um den Zugriff auf eine Implementierung des Buchhaltungssystems sauber abschließen zu können gibt es schließlich noch die Methode close(). Sie wird aufgerufen, damit das System die Möglichkeit hat Daten zu speichern oder offene Verbindungen zu schließen. 2.2.6. Erweiterte Service-Schnittstelle Bei der Entwicklung der Beispiele hat sich gezeigt, daß einige Funktionalitäten benötigt werden, die über den Umfang der Service-Schnittstelle hinausgehen, z.B. bei der Verwendung von Entity-Beans (siehe 6.3). package de.fhr.vs_iw.model; public interface IExtendedAccountingService extends IAccountingService { IAccount accountChange(IAccount account) throws AccountingException, IOException; IAccountingEntry accountingEntryChange(IAccountingEntry entry) throws AccountingException, IOException; } Code 6: Interface IExtendedAccountingService Durch die beiden Methoden der erweiterten Service-Schnittstelle müssen geänderte Kontenund Buchungsobjekte an das Buchhaltungssystem zurückgegeben werden, damit die geänderten Daten verarbeitet und gespeichert werden können. Eine Implementierung der Methoden muß die Daten der zurückgegebenen Objekte auf ihre Gültigkeit hinsichtilich der Geschäftslogik überprüfen und im Fehlerfall eine Buchhaltungssausnahme werfen (siehe 2.3.1). -9- Ausgangsthematik Buchhaltung - Ausnahmen 2.3. Ausnahmen Für mögliche auftretende Fehler werden entsprechende Ausnahmen definiert, die von den Implementierungen des Systems geworfen werden können bzw. müssen. 2.3.1. Buchhaltungsfehler Für Buchhaltungsfehler, also für Fehler die auftreten, weil Daten nicht der Geschäftslogik der Buchhaltung entsprechen, wird die Klasse AccountingException definiert. package de.fhr.vs_iw.model; public class AccountingException extends Exception { public AccountingException(); public AccountingException(String message) {} public AccountingException(String message, Throwable cause); public AccountingException(Throwable cause); } Code 7: Class AccountingException Ein Buchhaltungsfehler sollte in folgenden Situationen auftreten: Anlegen eines bereits vorhandenen Kontos Angabe einer negativen Kontonummer Erfassen einer Buchung mit identischem Haben- und Soll-Konto Holen eines nicht vorhandenen Kontenobjekts Holen eines nicht vorhandenen Buchungsobjekts Berechnen des Saldos eines nicht vorhandenen Kontos Angabe eines nicht vorhandenen Haben-Kontos bei einer Buchung Angabe eines nicht vorhandenen Soll-Kontos bei einer Buchung Löschen eines nicht vorhandenen Kontos Löschen einer nicht vorhandenen Buchung Löschen eines Kontos, das noch für Buchungen verwendet wird 2.3.2. Ein/Ausgabe-Fehler Alle Methoden der Buchhaltungs-Schnittstellen sind in ihrer Signatur mit einer IOException versehen. Dies geschah im Hinblick auf die direkte Verwendung der Schnittstellen bei RMI, wo definiert ist, daß alle Methoden, die entfernt aufrufbar sein sollen mit einer RemoteException versehen sein müssen, welche eine Unterklasse der IOException ist. Auch im Hinblick auf andere Technologien zur Datenübertragung ist die Methodendefiniton mit einer IOException sinnvoll. Andere geprüfte Ausnahmen können bzw. müssen unter Zuhilfenahme einer IOException geworfen werden. - 10 - Ausgangsthematik Buchhaltung - Ausnahmen try { ... } catch (Exception e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } Code 8: "Verpacken" einer beliebigen Exception als IOException Dies mag zwar vielleicht etwas unsauber erscheinen, aber eine IOException unterstützt die direkte Verkettung von Ausnahmen nicht über einen Konstruktor, was nützlich wäre und auch schon zur Aufnahme in die Spezifikation der Java-IO-Klassen vorgeschlagen wurde. 2.3.3. Nicht implementierte Methoden Für den Fall, daß eine in den Schnittstellen definierte Methode nicht vollständig implementiert werden soll, d.h. daß sie nicht mit der Funktionalität, die von der Geschäftslogik her vorgesehen ist, ausgestattet wird, gibt es eine NotImplemented-Ausnahme. package de.fhr.vs_iw.model; public class NotImplemented extends RuntimeException { public NotImplemented(); } Code 9: Class NotImplemented (Exception) Die Ausnahme ist von RuntimeException abgeleitet, damit sie ungeprüft geworfen werden kann, weil es sich dabei nicht um einen Fehler bezüglich der Geschäftslogik oder der Datenübertragung handelt. 2.3.4. Andere Ausnahmen Weitere Ausnahmen werden nicht spezifiziert. Jedoch muß beim Zugriff auf eine Implementierung des Buchhaltungssystems damit gerechnet werden, daß weitere Ausnahmen geworfen werden können. - 11 - Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile 2.4. Modulares Konzept der Programmteile Die Idee besteht darin, nicht nur verschiedenartige Implementierungen des Buchhaltungssystems zu erstellen, sondern diese auch modular zu verbinden, damit sich die Eigenschaften der jeweils verwendeten Technologien ergänzen. Tabelle 3: Modulares Konzept der Programmteile Die Module sollen unabhängig voneinander implementiert und verwendet werden können. 2.4.1. Client Die Client-Seite ruft die Methoden eines Buchhaltungssystems auf, greift auf die Konten und Buchungen zu und verarbeitet die zurückgelieferten Daten (siehe 3). 2.4.1.1. Proxy-Instanzierung über Reflection Als Einstiegspunkt wird eine Klasse, die die Proxy-Schnittstelle implementiert, über Reflection instanziert. Eine solche Klasse sollte deshalb einen öffentlichen Standard-Konstruktor ohne Argumente besitzen. Für diesen Vorgang gibt es in der Utilities-Klasse die statische Methode loadProxy(String className) bzw. loadProxy(Class clas). public static IAccountingProxy loadProxy(String className) throws NoInterfaceException, ClassNotFoundException, InvocationTargetException { return loadProxy(Class.forName(className)); } public static IAccountingProxy loadProxy(Class clas) throws NoInterfaceException, InvocationTargetException { Object object = null; try { object = clas.newInstance(new Object[0]); } catch (Exception e) { throw new InvocationTargetException(e); } if (!(object instanceof IAccountingProxy)) { throw new NoInterfaceException(clas.getName(), INTERFACE_NAME); } return (IAccountingProxy)object; } Code 10: Methoden loadProxy(…) der Utilities-Klasse - 12 - Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile Für diese Methode wird auch die Ausnahme NoInterfaceException eingeführt, die geworfen wird, wenn die angegebene Klasse zwar geladen werden kann, aber die benötigte ProxySchnittstelle nicht implementiert. package de.fh.vs_iw.util; public class NoInterfaceException extends Exception { public NoInterfaceException(); public NoInterfaceException(String className, String interfaceName); } Code 11: Class NoInterfaceException Normalerweise sollte die Methode aber ein verwendbares IAccountingProxy-Objekt zur weiteren Verwendung zurückliefern, sofern nicht absichtlich der Name einer inkompatiblen Klasse angegeben wird. Dies stellt die einfachste Möglichkeit dar, um benannte Klassen zur Laufzeit nachzuladen. Die Benutzeroberfläche verwendet darüber hinaus weitere Mechanismen, um ein flexibles Plugin-Konzept zu realisieren (siehe 3.2 und 3.8) 2.4.1.2. Laden einer Service-Klasse Von dem instanzierten Proxy-Objekt wird dann die connect()-Methode aufgerufen, die modulspezifische Argumente in einem String[]-Array übernimmt. Dieses Objekt ist nun dafür zuständig eine Instanz einer IAccountingService-Klasse zu erzeugen und zurückzugeben. Auf welche Weise diese Instanz erstellt wird ist jeweils der konkreten Implementierung einer Proxy-Klasse überlassen. Dies ist auch der Grund, warum die Proxy-Schnittstelle spezifiziert wurde, damit immer ein einheitlicher lokaler Einstiegspunkt für jede Implementierung vorhanden ist, unabhängig von der konkreten Funktion eines Moduls 2.4.2. Datenhaltung / Geschäftslogik Bei der ersten Gruppe der vorgestellten Technologien geht es um die Implementierung einer Datenhaltungsschicht zur konsistenten Verwaltung und persistenten Speicherung der Daten des Buchhaltungssystems. Diese Module stellen die Grundlage dar, um später damit verteilte Systeme zu erstellen (siehe 4). 2.4.2.1. Daten und Persistenz Der zentrale Punkt der Datenhaltungsschicht ist die Speicherung der Daten des Systems in Java-Objekten, die Verwaltung dieser Objekte und der Zugriff auf die Daten über die Konto(siehe 2.2.1) bzw. Buchungsschnittstelle (siehe 2.2.3). Hinzu kommt die Anforderung der Persistenz, der dauerhaften Speicherung der Daten auch über das Anwendungsende hinaus und das Laden vorhandener Daten beim Start der Anwendung. - 13 - Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile 2.4.2.2. Geschäftslogik Damit die Konsistenz und Integrität der gespeicherten Daten sichergestellt werden kann, muß vor deren Speicherung die Gültigkeit bezüglich der vorgegebenen Geschäftslogik (siehe 2.1.1 und 2.3.1) überprüft werden. Dazu kann ein Modul der Datenhaltung die Geschäftslogik direkt implementieren, so daß sich ingesamt ein System mit drei Schichten ergibt (3-TierArchitecture). Die Geschäftslogik könnte aber auch in einem separaten Modul ausgelagert werden, das der Datenhaltungsschicht als zusätzliche Schicht vorgeschoben wird (4-TierArchitecture) und über eine weitere definierte Schnittstelle von mehreren Datenhaltungsmodulen gemeinsam verwendet werden könnte. 2.4.3. Transport / Middleware Die weiteren angesprochenen Technologien bauen auf der Datenhaltungsschicht auf und dienen dann der Datenübertragung. Sie stellen den Kernpunkt der Thematik „Verteilte Systeme“ dar (siehe 5) und werden als austauschbare Schicht zwischen der Benutzeroberfläche und der Datenhaltung eingebunden. Ein Modul der Transportschicht besteht immer aus zwei Teilen, die untereinander eine durch ein Protokoll definierte Kommunikation aufbauen und die Daten des Systems übertragen. 2.4.3.1. Client Der Client-Teil eines Transport-Moduls stellt eine Implementierung der Proxy-Schnittstelle zur Verfügung, die den Zugriff duch die Benutzeroberfläche gestattet, und gibt alle Daten unverändert an den zugehörigen Server weiter. Die Antworten des Servers werden wiederum direkt an die Benutzeroberfläche zurückgegeben. 2.4.3.2. Server Der Server-Teil läuft als eigenständiger Prozess und wartet auf Verbindungen von den Clients. Er übernimmt dann die Daten von den Client zur weiteren Verarbeitung und schickt die Antworten zurück. Geschäftslogik und Datenhaltung könnten dierekt im Server implementiert werden, was aber der gewünschten Modularität des Systems widersprechen würde. Ein Server sollte so ausgelegt sein, daß er mit mehreren Clients parallel umgehen kann. - 14 - Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile 2.4.4. Verkettung Über die jeweiligen Proxy-Klassen werden die verschiedenen Module bzw. Implementierungen untereinander verkettet. So kann die Benutzeroberfläche oder ein anderer Client im ersten Ansatz direkt auf die Datenhaltung zugreifen, oder aber der Client benutzt eine Implementierung der Transportschicht, um sich mit einem entfernten System zu verbinden, das wiederum auf die Datenhaltung zugreift. Der Server-Teil eines Transport-Moduls wird dabei seinerseits wieder zum Client des nächsten Moduls der Kette. Abbildung 3: Vekettung der Module des Buchhaltungssystems - 15 - Ausgangsthematik Buchhaltung - Modulares Konzept der Programmteile Durch die einheitliche Proxy-Schnittstelle können auch mehrere Module der Transportschicht hintereinandergeschaltet werden, und es können sich verschiedene Szenarien ergeben, wie die Module des System miteinander interagieren und letztendlich das verteilte Buchhaltungssystem realisieren. Abbildung 4: Mögliche Szenarien der Verkettung von Implementierungen - 16 - Benutzeroberfläche - Übersicht 3. Benutzeroberfläche 3.1. Übersicht Der Ausgangspunkt für das Buchhaltungssystem ist die Swing-Benutzeroberfläche, welche die Methoden des Schnittstellen-Modells aufruft und so die Buchhaltungsdaten anzeigt. Abbildung 5: Hauptansicht der Buchhaltungsoberfläche Das Buchhaltungssystem kann damit vollständig angesprochen werden. Somit dient die Benutzeroberfläche als Client für alle konkreten Implementierungen des Systems mit verschiedenen Technologien. Die Oberfläche nicht unbedingt auf eine praktische Nutzung zur Buchhaltung ausgelegt, sondern eher technisch auf den Aufruf der einzelnen Methoden der Schnittstellen des Buchhaltungsmodell, und die direkte und detaillierte Verarbeitung und Anzeige der Rückgabewerte bzw. Ausnahmen. - 17 - Benutzeroberfläche - Zugriff über eine Proxy-Klasse 3.2. Zugriff über eine Proxy-Klasse Gestartet wird das System über die Client-Klasse, die das Hauptfenster der Benutzeroberfläche anzeigt: java de.fhr.vs_iw.Client [[proxyclass] arguments ...] oder aus dem Buchhaltungs-Package (siehe 3.9.2): java -jar vs_iw_fibu_main_3.0.jar [[proxyclass] arguments ...] Auf der Kommandozeile kann der Name einer Proxy-Klasse und weitere Argumente zum Laden der Service-Klasse angegeben werden. Das Buchhaltungssystem wird durch die angegebene Proxy-Klasse sofort geladen, und die angegebenen Argumente werden als StringArray unverändert an die connect()-Methode übergeben. Bei diesem direkten Aufruf wird der Java-System-Klassenlader verwendet, und darum muß sich die angegebene Proxy-Klasse im aktuellen CLASSPATH befinden. Besser ist es jedoch, die Buchhaltungsanwendung nicht direkt von der Kommandozeile, sondern zusammen mit der Eclipse-Entwicklungsumgebung zu verwenden (siehe 3.9). 3.2.1. Laden und Verbinden eines Buchhaltungssystems Wenn auf der Kommandozeile keine Proxy-Klasse angegeben wurde, kann das Buchhaltungssystem im Menü unter Schnittstelle->Service-Klasse laden... geladen werden. Abbildung 6: Schnittstellen-Menü Dazu sucht die Benutzeroberfläche ausgehend vom aktuellen Anwendungs-Verzeichnis (System.getProperty("user.dir")) in allen Unterverzeichnissen nach class-Dateien, und auch in jar- und zip-Archiven nach Klassen, die die Proxy-Schnittstelle implementieren (siehe auch 3.8). Das Suchverzeichnis kann über die Schaltfläche "Verzeichnis..." auch manuell eingestellt werden. Jede gefundene Klasse, die als Proxy-Klasse in Frage kommt, wird direkt aus dem Dateisystem geladen und in der aktuellen VM verfügbar gemacht. Dabei kommt ein eigener Klassenlader (de.fh.vs_iw.util.ClassFileLoader) zum Einsatz, der auch unabhängig vom eingestellten CLASSPATH, zusammen mit den Informationen über die beim Suchvorgang gefundenen Klassen, eine Klasse mit allen referenzierten Klassen laden kann (siehe 3.8). - 18 - Benutzeroberfläche - Zugriff über eine Proxy-Klasse Abbildung 7: Service-Klasse laden Die gefundenen Proxy-Klassen werden aufgelistet, und in der Statuszeile wird eine Statistik über den Suchvorgang angezeigt. Wie auch auf der Kommandozeile können Argumente zum Laden eingegeben werden. Durch die Schaltfläche "Datei-Argument…" kann eine Datei als Argument ausgewählt und angegeben werden. Wenn die ausgewählte Klasse einen Hinweis auf ihre benötigten Argumente enthält, wird dieser in der Statuszeile des Fenters und als Tooltip in der Klassenliste angezeigt. Abbildung 8: Hinweisanzeige bei der Auswahl der Proxy-Klasse Dazu wird über Reflection, die Klassenvariable ARGS ausgelesen, sofern diese vorhanden ist, und der darin enthaltene String angezeigt. package de.fhr.vs_iw.jaxb.v1; public final class JAXBProxy1 implements IAccountingProxy { ... private static final String ARGS = "Datei [Schema]"; - 19 - Benutzeroberfläche - Zugriff über eine Proxy-Klasse } Code 12: Klassenvariable ARGS Mit "Ok" wird anschließend die ausgewählte Proxy-Klasse geladen und instanziert. Von der Instanz der Proxy-Klasse wird dann die Methode connect(String[] args) aufgerufen, die eine Instanz der Service-Klasse zurückliefern muß. Hier werden die angegebenen Argumente übergeben, damit die Service-Klasse entsprechend geladen werden kann (Socket-Verbindung aufbauen, RMI-Remote-Objekt holen, usw.). Welche Argumente angegeben werden müssen ist dabei der Implementierung der Proxy-Klasse überlassen, sie werden von der Benutzeroberfläche lediglich in ein String-Array umgewandelt und dann direkt weitergegeben. Der Name der aktuell geladenen Proxy-Klasse wird im Titelbalken des Hauptfensters angezeigt. 3.2.2. Schließen der Verbindung Wenn die Anwendung beendet wird, oder aus dem Menü "Schnittstelle" explizit der Punkt "Schnittstelle schließen" gewählt wird, so wird die Methode close() der Proxy-Schnittstelle aufgerufen. Auch wenn direkt eine neue Proxy-Klasse geladen werden soll, so wird vorher von der aktuellen Proxy-Instanz die close()-Methode aufgerufen und deren korrekte Ausführung abgewartet. Durch die Methode wird der Zugriff auf eine Implementierung des Buchhaltungssystems beendet, und es können noch Daten gespeichert oder offene Netzwerkverbindungen geschlossen werden. Die genaue Funktionalität der Methode bleibt der konkreten Implementierung überlassen. - 20 - Benutzeroberfläche - Aufbau der Oberfläche 3.3. Aufbau der Oberfläche 3.3.1. Kontenliste Abbildung 9: Kontenliste Die Kontenliste gibt die einzelnen Daten (Nummer, Typ, Beschreibung) aller in einem Buchhaltungssystem vorhandenen Konten als Tabelle aus. In der Liste kann ein Konto zur Bearbeitung ausgewählt werden. 3.3.1.1. Laden der Kontenobjekte Zum Aufbau der Kontenliste werden alle vorhandenen Kontonummern über die Methode getAccounts() ermittelt und für jede Nummer mit getAccountByNumber() das zugehörige Kontenobjekt geholt. Diese werden dann der Tabelle hinzugefügt. Von jedem Kontenobjekt werden die Methoden getNumber(), getType() und getDescription() zur Anzeige der Kontendaten aufgerufen. 3.3.1.2. Anlegen eines neuen Kontos Über die entsprechende Schaltfläche unterhalb der Kontenliste wird ein Dialogfenster zum Anlegen eines neuen Kontos geöffnet. Dort werden die nötigen Daten eingegeben, wobei das Textfeldes für die Kontonummer auf einen numerischen Inhalt hin überprüft wird. Abbildung 10: Neues Konto anlegen - 21 - Benutzeroberfläche - Aufbau der Oberfläche Mit den eingegeben Daten wird dann direkt die Methode createAccount() aufgerufen, um das neue Konto im System anzulegen. 3.3.1.3. Ändern der Daten eines Kontos Das gleiche Dialogfenster wie zum Anlegen eines Kontos wird auch zum Ändern der Daten eines Kontos verwendet, nur daß die Kontonummer unveränderbar ist. Abbildung 11: Konto bearbeiten Mit den neuen Daten werden die Methoden setType() und setDescription() des ausgewählten Kontenobjekts aufgerufen. Wenn die geladene Instanz einer Service-Klasse die erweiterte Service-Schnittstelle implementiert wird mit dem geänderten Kontenobjekt die Methode changeAccount() aufgerufen. 3.3.1.4. Löschen eines Kontos zum Löschen eines Kontos wird vorher in einem Dialogfenster noch die Bestätigung des Benutzers eingeholt. Abbildung 12: Konto löschen Bei einer positiven Bestätigung wird die Methode removeAccount() des Buchhaltungssystems mit der Nummer des ausgewählten Kontos aufgerufen und das Konto entfernt. - 22 - Benutzeroberfläche - Aufbau der Oberfläche 3.3.2. Buchungsliste Abbildung 13: Buchungsliste Analog zur Kontenliste werden in der Buchungsliste die Daten (ID, Haben-Konto, SollKonto, Betrag, Text) aller vorhandenen Buchungen tabellarisch angezeigt. Auch hier kann eine Buchung ausgewählt und bearbeitet werden. 3.3.2.1. Laden der Buchungsobjekte Zuerst werden die IDs aller vorhandenen Buchungen über die Methode getAccountingEntries() ermittelt und anschließend für jede einzelne der IDs durch getAccountingEntryById() das zugehörige Buchungsobjekt geholt. Für die Anzeige der Daten werden die Methoden getID(), getDebitAccount(), getCreditAccount(), getAmount() und getText() von jedem der Kontenobjekte aufgerufen. 3.3.2.2. Erfassen einer neuen Buchung Mit der zugehörigen Schaltfläche über der Buchungsliste kann eine neue Buchung erfaßt werden. In dem dafür vorgesehenen Dialogfenster wird das Soll- und Haben-Konto der neuen Buchung aus einer Liste der vorhandenen Konten ausgewählt und dazu ein Betrag und eine Beschreibung eingegeben. Der eingegebene Wert für den Betrag der Buchung wird auf seine numerische Gültigkeit geprüft. Abbildung 14: Neue Buchung erfassen - 23 - Benutzeroberfläche - Aufbau der Oberfläche Die eingegebenen Daten werden direkt dem Aufruf der Methode createAccountingEntry() zugeführt, womit die neue Buchung vom System erfaßt wird. Die ID einer Buchung wird allerdings nicht vom Benutzer angegeben, sondern von der Methode zurückgeliefert. 3.3.2.3. Ändern der Daten einer Buchung Auch die Daten einer Buchung können nach Auswahl der Buchung und der Schaltfläche "Buchung bearbeiten" in dem Dialogfenster geändert. Abbildung 15: Buchung bearbeiten ausgewählte Kontenobjekt werden die Methoden setDebitAccount(), setCreditAccount(), getAmount() und getText() entsprechend mit den neuen Daten aufgerufen. Wenn die geladene Instanz einer Service-Klasse die erweiterte Service-Schnittstelle implementiert wird mit dem geänderten Buchungsobjekt die Methode changeAccountingEntry () aufgerufen. Für das 3.3.2.4. Löschen einer Buchung Auch vor dem Löschen einer ausgewählten Buchung über die Schaltfläche wird eine Bestätigung eingeholt. Abbildung 16: Buchung löschen Zum Löschen wird dann die Methode removeAccountingEntry() des Systems aufgerufen und die Buchung entfernt. - 24 - Benutzeroberfläche - Aufbau der Oberfläche 3.3.3. Soll- und Haben-Buchungen je Konto Sobald man in der Kontenliste ein Konto auswählt, werden die zugehörigen Soll- und HabenBuchungen für dieses Konto geladen und in Tabellen angezeigt. Abbildung 17: Liste der Haben- und Soll-Buchungen eines Kontos Der Aufbau erfolgt ähnlich wie bei der Haupt-Buchungsliste. Über die Methoden getDebitAccounts() und getCreditAccounts() des ausgewählten Kontos werden die entsprechenden IDs der Buchungen ermittelt, anschließend wieder die Buchungsobjekte geholt und die Daten von jedem einzelnen Buchungsobjekt geladen. Wenn in einer dieser beiden Buchungslisten eine Buchung ausgewählt wird, so wird dieselbe Buchung auch in der Haupt-Buchungsliste ausgewählt, und auch umgekehrt, damit die Verwendung der Schaltflächen für Buchungen konsistent und logisch ist. 3.3.4. Saldo eines Kontos Der Saldo eines Kontos wird bei Auswahl in der Kontenliste ebenfalls ermittelt. Dazu wird die Methode calculateBalance() des Buchhaltungssystems mit der Nummer des Kontos aufgerufen. Der Saldo wird unterhalb der Kontenliste angezeigt. Abbildung 18: Der Saldo eines Kontos - 25 - Benutzeroberfläche - Behandlung von Ausnahmen 3.4. Behandlung von Ausnahmen Im Allgemeinen werden von der Benutzeroberfläche alle Ausnahmen, auch Laufzeitfehler (abgeleitet von RuntimeException) und kritische Fehler (abgeleitet von Error), gefangen und angezeigt, und zum Teil auch speziell ausgewertet. 3.4.1. Nachrichtenfenster Abbildung 19: Nachrichtenfenster bei Ausnahmen Wenn dann bei den Aktionen und Methodenaufrufen Ausnahmen auftreten, werden diese in einem Nachrichtenfenster angezeigt. Wie auf der Konsole wird im Textbereich des Fensters der Stacktrace der Ausnahme ausgegeben. StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); exception.printStackTrace(pw); pw.close(); textArea.setText(sw.toString()); Code 13: Ausgabe des Stacktrace mit Hilfe von StringWriter und PrintWriter 3.4.2. Statusleiste Auf der Statusleiste werden kurze Informationen über alle Vorgänge, welche die Oberfläche ausführt, und deren Ergebnis angezeigt. Bei eher sekundären, rein informativen Meldungen bleibt die Farbe der Leiste grau. Abbildung 20: Statusleiste grau Bei Aufrufen von Methoden des Buchhaltungssystems und deren erfolgreicher Beendigung wird die Leiste grün hinterlegt. - 26 - Benutzeroberfläche - Behandlung von Ausnahmen Abbildung 21: Statusleiste grün Falls eine der Buchhaltungsmethoden nicht implementiert wurde und die entsprechende Ausnahme wirft, wird die Statusleiste gelb. Abbildung 22: Statusleiste gelb Bei einem fehlerhaften Methodenaufruf wird die Leiste rot hinterlegt. Abbildung 23: Statusleiste rot Außerdem wird am linken Ende der Statusleiste angezeigt in einem Fortschrittsbalken angezeigt, ob gerade asynchrone Vorgänge im Hintergrund laufen, sowie deren Anzahl. 3.4.3. Direkte Einblendung Wenn eine Methode bei einer konkreten Implementierung des Buchhaltungssystems gezielt nicht implementiert wurde, und dementsprechend eine NotImplemented-Ausnahme wirft, so wird diese speziell behandelt und in dem Anzeigebereich, der dem Ergebnis der Methode normal zugeordnet ist, eingeblendet. Abbildung 24: Einblendung bei nicht implementierten Methoden (einzelne Werte) Abbildung 25: Einblendung bei nicht implementierten Methoden (ganze Auflistung) Dies dient dazu, daß der Programmablauf nicht durch ein Nachrichtenfenster unterbrochen wird, weil eine solche Ausnahme eben gezielt geworfen wird, und keinen Fehler an sich darstellt. - 27 - Benutzeroberfläche - Kapselung aller Aufrufe 3.5. Kapselung aller Aufrufe Die Methoden eines Buchhaltungssystems werden durch Aktionen des Benutzers auf der Oberfläche nicht direkt aufgerufen, um die korrekte Funktion und Verwendbarkeit der Oberfläche nicht von der konkreten (unter Umständen unbekannten) Implementierung der Buchhaltungsmethoden und dabei möglicherweise auftretenden Fehlern abhängig zu machen. 3.5.1. Adapterschicht Deshalb wird zwischen Benutzeroberfläche und Buchhaltungsimplementierung eine Adapterschicht (Package de.fhr.vs_iw.adapter) eingezogen. Wie das Buchhaltungsmodell besteht die Schicht aus drei Kern-Komponenten jeweils für die Schnittstellen/Objekte des Modells: IAccountingService → IAccount → IAccountingEntry → ServiceAdapter AccountAdapter AccountingEntryAdapter Jede Adapter-Instanz kapselt genau ein Objekt der Buchhaltung, und die Adapter-Objekte, sowie die Original-Objekte, werden entsprechend in HashMaps verwaltet, damit immer die richtige Zuordnung besteht. Die Klassen der Adapterschicht sind generisch aufgebaut und dann an die Erfordernisse der jeweiligen Objekte (Konten und Buchungen) angepaßt. Außerdem sind hier zusätzliche Vorgangsklassen vorhanden, um den Zugriff auf die Objekte der Buchhaltung in separaten Threads ausführen zu können (siehe 3.6). 3.5.1.1. Swing-MVC-Pattern Die Swing-Komponenten zur Anzeige von Daten in einer Benutzeroberfläche sind nach dem Model-View-Controller-Prinzip (MVC) aufgebaut. Für jede der vier vorhandenen Tabellen der Klasse JTable wird ein eigenes Tabellen-Modell verwendet, das von der Swing-Klasse AbstractTableModel abgeleitet ist. Zusammen mit der generischen Schnittstelle RowTableModel ergibt sich die Klasse AbstractRowTableModel zur zeilenweisen Verwaltung von Objekten eines beliebigen Typs T. package de.fhr.vs_iw.util; public abstract class AbstractRowTableModel<T> extends AbstractTableModel implements RowTableModel<T> { public final void addDistinctRows(List<T> rows); public final void addRow(T row); public final void addRows(List<T> rows); public final boolean contains(T row); public final Class<?> getColumnClass(int column); public abstract int getColumnCount(); public final T getRow(int row); public final int getRowCount(); public final int getRowIndex(T object); public abstract String getToolTipAt(int row, int column, boolean ellip); public abstract Object getValueAt(int row, int column); public final void removeAllRows(); } Code 14: Class AbstractRowTableModel<T> - 28 - Benutzeroberfläche - Kapselung aller Aufrufe Damit werden die Adapter-Objekte direkt den Tabellen-Modellen als Zeilen hinzugefügt und verwaltet. Die konkreten Modell-Klassen sind dann von dieser abstrakten Klasse abgeleitet und geben die Spaltendaten entsprechend der zugehörigen Tabelle aus. package de.fhr.vs_iw.gui; public final class AccountTableModel extends AbstractRowTableModel<AccountAdapter> { public int getColumnCount(); public String getColumnName(int column); public String getToolTipAt(int row, int column, boolean ellip); public Object getValueAt(int row, int column); } Code 15: Class AccountTableModel Da die Basis-Klasse AbstractRowTableModel entsprechend generisch ist, kann sie für Konten (AccountAdapter) und für Buchungen (AccountingEntryAdapter) gleichermaßen verwendet werden kann. package de.fhr.vs_iw.gui; public final class AccountingEntryTableModel extends AbstractRowTableModel<AccountingEntryAdapter> { public int getColumnCount(); public String getColumnName(int column); public String getToolTipAt(int row, int column, boolean ellip); public Object getValueAt(int row, int column); } Code 16: Class AccountingEntryTableModel Die beiden weiteren Tabellen-Modelle für die Soll- und Haben-Buchungen eines Kontos verhalten sich analog zum diesem Buchungs-Tabellen-Modell. 3.5.1.2. Zwischenspeicherung der Daten Weil bei jeder Aktualisierung der Anzeige ein Zugriff auf die Daten des Tabellen-Modells, und damit auch der Adapterschicht, erfolgt, werden die Daten der Buchungen und Konten in der Adapterschicht zwischengespeichert, sonst müßte bei jeder Aktualisierung der Anzeige auf das Buchhaltungssystem zugegriffen werden, was ineffizient und nicht unbedingt notwendig ist. Die Klassen AccountAdapter und AccountingEntryAdapter beinhalten ein Statusflag für das gekapselte Objekt, mit dem der Stand der zwischengespeicherten Daten beschrieben wird. So können die Daten bei Bedarf und bei Änderungen erneut aus dem gekapselten Objekt geladen werden. 3.5.2. Aufteilung in separate Vorgänge Aber auch über die Adapterschicht erfolgt nicht einfach ein Aufruf der Methoden des Buchhaltungssystems. Jeder Methodenaufruf wird in einer eigenen Vorgangsklasse gekapselt, und dann entsprechend einer dieser Vorgänge angestoßen. Der Grund für diese Aufteilung und den Aufbau der Vorgänge wird später unter Punkt 3.6 näher erläutert. - 29 - Benutzeroberfläche - Kapselung aller Aufrufe 3.5.2.1. Vorgänge zum Zugriff auf die Buchhaltung Jede der drei Adapter-Klassen (ServiceAdatper, AccountingEntryAdapter, AccountAdapter) ist an entsprechende Vorgänge für den Zugriff auf die zugehörigen Methoden der Buchhaltungsschnittstellen gebunden. Diese Vorgangsklassen sind von der gemeinsamen Basis-Klasse AbstractServiceTask abgeleitet. package de.fhr.vs_iw.threading; public abstract class AbstractServiceTask implements RunnableServiceTask { public final String getDuration(); public final String getName(); public final boolean isCanceled(); public final void run(); public final void start(final RunnableSwingTask n); public final void throwNow(); protected abstract void task(); } Code 17: Class RunnableServiceTask Die Klasse implementiert die Schnittstelle RunnableServiceTask, und definitert die abstrakte Methode task(), die von einer Unterklasse implementiert werden muß, um separat ausgeführt werden zu können. Ein Vorgang wird mit der Methode start() gestartet. Die run()Methode der Klasse ruft dann die task()-Methode der Unterklasse auf. Da die run()Methode der Schnittstelle Runnable keine Ausnahmen werfen darf, werden Ausnahmen gefangen und gespeichert, und können durch den Aufruf von throwNow() zu einem beliebigen Zeitpunkt erneut geworfen werden. Die generischen Vorgänge der Adapterschicht werden von der Klasse abgeleitet. package de.fhr.vs_iw.adapter; final class MethodCallTask<P, R> extends AbstractServiceTask { ... protected void task() throws Throwable { try { Logger.entering(); if (isCanceled()) { return; } Logger.calling(getName(), param); result = caller.callMethod(method, param, this); if (result == null) { Logger.returnedNull(getName()); } } } finally { Logger.leaving(); } } Code 18: Class MethodCallTask (Adapterschicht) - 30 - Benutzeroberfläche - Kapselung aller Aufrufe Die run()-Methode läuft entsprechend dem angegebenen Verhalten ab und ruft die Methode calculateBalance() auf. Das Ergebnis der Methode (Rückgabewert oder Ausnahme) wird in der Adapterschicht zwischengespeichert. Außerdem enthalten die implementierten Vorgangsklassen weitere Methoden um die spezifischen Parameter für den jeweiligen Aufruf des Buchhaltungssystems anzugeben, wie hier die Methode setNumber(). 3.5.2.2. Vorgänge zur Aktualisierung der Benutzeroberfläche Bei Aufruf der Methode start(RunnableSwingTask task) der Klasse AbstraktServiceTask wird ein weiterer Vorgang angegeben, der nach dem Ende des Service-Vorgangs gestartet wird. Dies ist dann ein Vorgang zur Aktualisierung der Anzeige der Daten in der Benutzeroberfläche, der von der Basis -Klasse AbstractSwingTask abgeleitet ist und die Schnittstelle RunnableSwingTask implementiert.. package de.fhr.vs_iw.threading; public abstract class AbstractSwingTask implements RunnableSwingTask { public final void addTaskListener(SwingTaskListener listener) { public final void cancel(); public final String getName(); public final boolean isCanceled(); public final boolean isStarted(); public final void notifyServiceTaskRunning(); public final void removeTaskListener(SwingTaskListener listener); public final void run(); public final void setThread(Thread thread); public final void start(); protected final void setName(String name); protected abstract void task(); } Code 19: Class AbstractSwingTask Auch hier muß von einer Unterklasse die task()-Methode implementiert werden, die dann durch den Vorgang aufgerufen wird. private final class Caller<T> extends AbstractSwingTask { protected void task() throws Throwable { try { Logger.entering(); if (serviceAdapter == null) { Logger.println(STR_SERVICE_NULL); return; } T result = serviceAdapter.resultMethod(method); } afterMethodSuccess(method, result); } finally { afterMethodAlways(method); Logger.leaving(); } } Code 20: Class Caller (GUI-Vorgang) - 31 - Benutzeroberfläche - Kapselung aller Aufrufe Ein solcher Vorgang wird als Nachfolgevorgang eines Service-Vorgangs gestartet. Er holt das zwischengespeicherte Ergebnis des Methodenaufrufs aus der Adapterschicht und zeigt es in der Benutzeroberfläche an. 3.5.3. Kontrolle der Vorgänge Die Vorgänge sollen zwar grundsätzlich eigenständig ablaufen (siehe 3.6.2), aber dennoch sollte die Oberfläche den Status der Vorgänge anzeigen, und einen Vorgang gegebenenfalls abbrechen können. 3.5.3.1. Status-Benachrichtigung über Listener-Interface Deshalb kann jeder GUI-Vorgang entsprechende Empfänger (die Benutzerpberfläche) über seinen Status informieren. Der Empfänger muß dafür die SwingTaskListener-Schnittstelle implementieren. package de.fhr.vs_iw.threading; public void void void void } interface SwingTaskListener { notifyError(RunnableSwingTask task, Throwable e); notifyFinish(RunnableSwingTask task); notifyServiceTaskRunning(RunnableSwingTask task); notifyStart(RunnableSwingTask task); Code 21: Interface SwingTaskListener Ein Empfänger, der die Schnittstelle implementiert, wird dann bei einem zugehörigen Vorgang über die Methode addTaskListener() registriert, und der Vorgang ruft die Methoden der Schnittstelle am entsprechenden Punkt seines Ablaufs auf: wenn der Vorgang durch seine start()-Methode gestartet wird notifyServiceTaskRunning() wenn der zugehörige Service-Vorgang abläuft notifyFinish() am Ende der run()-Methode notifyError() bei einer Ausnahme durch den Methodenaufruf notifyStart() 3.5.3.2. Auflistung der laufenden Vorgänge Die Klasse des Hauptfensters der Benutzeroberfläche (de.fhr.vs_iw.gui.MainWindow) implementiert die SwingTaskListener-Schnittstelle und registriert sich als Empfänger bei den Vorgängen, welche die Benutzeroberfläche aktualisieren. Durch die daraus resultierenden Informationen über den Beginn und das Ende der einzelnen Vorgänge wird eine Liste der jeweils laufenden Vorgänge vom Typ RunnableSwingTask, die einem Service-Vorgang zugeordnet sind, erstellt. - 32 - Benutzeroberfläche - Kapselung aller Aufrufe Abbildung 26: Liste der laufenden Vorgänge Die Vorgänge werden entsprechend ihrem Ablauf der Liste hinzugefügt oder entfernt. Wenn der zugeordnete Service-Vorgang gerade abläuft, wird die Anzeige gelb hinterlegt. Alle anderen aufgelisteten Vorgänge befinden sich in der Warteschlange. Die Anzahl laufender Vorgänge in der Statusleiste stimmt nicht unbedingt mit der Anzahl Vorgänge in der Liste überein, da es zusätzliche Vorgänge gibt, die nicht auf die Buchhaltungsschnittstellen bzw. die Adapterschicht zugreifen, sondern nur mit den Komponenten der Oberfläche arbeiten, aber auch durch das Threading-System ausgeführt werden. Abbildung 27: Ansicht-Menü Über das Ansicht-Menü kann eingestellt werden, wie die Liste der laufenden Vorgänge angezeigt werden soll: Vorgangsliste automatisch: Vorgangsliste anzeigen. Vorgangsliste ausblenden: Das Fenster wird ein- und ausgeblendet, sobald Vorgänge laufen und länger als eine Sekunde aktiv bzw. inaktiv sind. Das Fenster wird immer angezeigt. Das Fenster wird niemals angezeigt. 3.5.3.3. Abbruch eines Vorgangs Neben der Bezeichnung des Vorgangs wird eine Schaltfläche angezeigt, mit dem der Benutzer den jeweiligen Vorgang abbrechen kann. Dazu wird die Methode cancel() der Klasse RunnableSwingTask aufgerufen (siehe 3.6.3). - 33 - Benutzeroberfläche - Threading 3.6. Threading Diese Art der Trennung in eigenständige Vorgänge, die alle die Schnittstelle Runnable und eine run()-Methode implementieren, dient schließlich dazu, daß die Vorgänge vollständig entkoppelt und gezielt von separaten Threads ausgeführt werden können. 3.6.1. Zentraler Threading-Service Jeder Vorgang wird über eine Methode (execute(), run(), swing()) der Klasse ThreadingService ausgeführt. package de.fhr.vs_iw.threading; public final class ThreadingService { public static enum Mode { ASYNCHRONOUS, MULTITHREADED, SINGLETHREADED, SYNCHRONOUS } public static void execute(RunnableServiceTask task); public static void execute(RunnableSwingTask task); public static Mode getMode(); public static void run(Runnable run) public static void setMode(Mode mode); public static void shutdown(); public static void swing(Runnable task); } Code 22: Class ThreadingService Die Implementierung der Klasse ThreadingService sorgt dafür, daß jeder Vorgang passend ausgeführt wird (siehe auch 3.6.2). Die im Menü einstellbare Option zur Log-Ausgabe von Debug-Informationen der Threads wird hier verwaltet und angewandt. 3.6.1.1. AWT-Event-Dispatching-Thread Um das Design der Swing-Implementierung einfach zu halten sind die Swing-Komponenten nicht für den parallelen Zugriff durch mehrere Threads ausgelegt. Deshalb gibt es die Single-Thread-Regel [29]: "Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread." Dieser eine Thread ist normalerweise der AWT-Event-Dispatch-Thread, und Anwendungen mit einer Benutzeroberfläche, die trotzdem mehrere Threads benötigen, sollten dafür sorgen, daß Zugriffe auf Elemente der Oberfläche nur durch den diesen Thread erfolgen. Dazu gibt es in der Klasse SwingUtilities die statische Methode SwingUtilities.invokeLater(Runnable doRun); wodurch Runnable-Objekte in der AWT-Event-Queue für den AWT-Thread eingereiht und dann passend durch diesen Thread ausgeführt werden können. Der ThreadingService übergibt grundsätzlich alle Vorgänge der Klasse RunnableSwingTask an diese Methode. - 34 - Benutzeroberfläche - Threading 3.6.1.2. Worker-Threads Für Vorgänge die länger dauern oder sogar blockieren können, sollten eigene Threads benutzt werden, weil sonst die Benutzeroberfläche nicht reagiert, solange der AWT-EventDispatch-Thread mit der Ausführung eines Vorgangs beschäftigt ist. Deshalb gibt es für die Vorgänge, die innerhalb der Adapterschicht ablaufen, separate Threads. Dazu werden im ThreadingService die Möglichkeiten von ExecutorService aus dem Paket java.util.concurrent verwendet. private static final ExecutorService MULTI; private static final ExecutorService SINGLE; static { MULTI = Executors.newFixedThreadPool(5); SINGLE = Executors.newSingleThreadExecutor(); } Code 23: Verwendung von ExecutorService im ThreadingService Über die Hilfs-Klasse Executors werden zwei Instanzen von ExecutorService erzeugt, einmal mit einem festen Pool von 5 parallelen Threads, und dann noch mit nur einem einzelnen Thread und einer Warteschlange zur Ausführung von Vorgängen. 3.6.2. Threading-Modelle Der zentrale ThreadingService sorgt grundsätzlich dafür, daß jeder Vorgang (RunnableSwingTask bzw. RunnableServiceTask) durch den richtigen Thread bzw. ExecutorService ausgeführt wird. Er bietet dafür die Möglichkeit, die Threads auf unterschiedliche Weise den verschiedenen Vorgängen zuzuweisen (execute()) oder aber ganz auf zusätzliche Threads zu verzichten. Abbildung 28: Threading-Menü So gibt es vier unterschiedliche Threading-Modelle, die in der Benutzeroberfläche ausgewählt werden können, und dann beim ThreadingService über die Methode setMode() entsprechend mit einer der folgenden Enum-Konstanten eingestellt werden. ThreadingService.Mode.SINGLETHREADED; ThreadingService.Mode.SYNCHRONOUS; ThreadingService.Mode.ASYNCHRONOUS; ThreadingService.Mode.MULTITHREADED; - 35 - Benutzeroberfläche - Threading 3.6.2.1. Singlethreaded Bei diesem Modell wird ganz auf zusätzliche Threads verzichtet und alles wird durch den AWT-Event-Dispatch-Thread ausgeführt. Dabei reagiert allerdings die Benutzeroberfläche solange nicht, bis die Ausführung abgeschlossen ist. Abbildung 29: Ablauf im Modus Singlethreaded Der ThreadingService ruft die run()-Methoden der beteiligten Vorgänge direkt auf task.run(); so wie jede andere Methode, egal um welche Art von Vorgang es sich handelt. Hierfür wäre es allerdings unnötig gewesen, jede Funktionalität in einem eigenen separaten Vorgang zu kapseln und die Vorgänge auch noch in zwei Arten zu unterscheiden, deshalb folgen nun die Modelle, die dieses Prinzip der separierten Vorgänge wirklich ausnutzen. - 36 - Benutzeroberfläche - Threading 3.6.2.2. Synchron einreihen Auch hier werden alle Vorgänge, unabhängig von ihrer Art, vom AWT-Event-DispatchThread ausgeführt, allerdings nicht wie im vorigen Modus, durchgängig in einem Aufruf, sondern über die oben angesprochene Möglichkeit (siehe 3.6.1.1) durch SwingUtilities.invokeLater(Runnable doRun); nacheinander, jeder für Vorgang für sich abgeschlossen, eingereiht in der AWT-Event-Queue. Abbildung 30: Ablauf im Modus Synchron einreihen Dadurch hat der AWT-Event-Dispatch-Thread prinzipiell die Möglichkeit zwischen den Vorgängen des Buchhaltungssystems andere Events auszuführen. In der Praxis bedeutet das zwar, daß die Oberfläche Benutzereingaben nicht wirklich gut annimmt, aber zumindest noch teilweise aktualisiert wird, solange wie eben Vorgänge ablaufen. - 37 - Benutzeroberfläche - Threading 3.6.2.3. Asynchron mit zusätzlichem Thread Hier kommt ein zusätzlicher Worker-Thread zum Einsatz. Wenn der AWT-Event-DispatchThread eine Benutzereingabe verarbeitet, die zum Zugriff auf das Buchhaltungssystem führt, wird ein Service-Vorgang (RunnableServiceTask) durch den ThreadingService in eine separate Queue eingereiht, und dann von einem eigenen Worker-Thread ausgeführt. Abbildung 31: Ablauf im Modus Asynchron und Multithreaded Hier kehrt der Aufruf des Vorgangs gleich zurück und der AWT-Thread ist sofort wieder verfügbar, so daß die Benutzeroberfläche nicht einfriert. Der Worker-Thread führt den Aufruf einer Methode des Buchhaltungssystems aus, speichert das Ergebnis in der Adapterschicht und startet einen Nachfolgevorgang. Dabei handelt es sich um einen Vorgang zur Aktualisierung der Benutzeroberfläche (RunnableSwingTask) der vom ThreadingService mit invokeLater() wieder in der AWT-Event-Queue eingereiht und dann vom AWT-EventDispatch-Thread ausgeführt wird, um die Single-Thread-Regel (siehe 3.6.1.1) einzuhalten. Anhang B stellt den genauen Ablauf in erweiterter Form dar. 3.6.2.4. Multithreaded Der Unterschied zum asynchronen Modus (siehe 3.6.2.3) besteht lediglich darin, daß mehrere Service-Vorgänge nicht in einer Queue eingereiht, und dann von einem Worker-Thread sequentiell, sondern gleichzeitig durch mehrere Worker-Threads parallel ausgeführt werden. - 38 - Benutzeroberfläche - Threading 3.6.3. Abbruch eines laufenden Vorgangs Durch die in den späteren Kapiteln aufgezeigten, verschiedenartigen Möglichkeiten das Buchhaltungssystem zu implementieren, kann es beim Zugriff auf die Methoden der Buchhaltungsschnittstellen technologiebedingt zu Problemen oder Verzögerungen kommen, oder der Methodenaufruf blockiert und kehrt gar nicht zurück. Deshalb kann ein Vorgang prinzipiell durch den Benutzer abgebrochen werden (siehe 3.5.4.3), was aber nur im Asynchronen oder Multithreaded-Modus möglich ist, weil nur dabei der AWT-Thread frei ist, um die Benutzereingabe zu verarbeiten. Abbildung 32: Ablauf beim Abbruch eines Vorgangs - 39 - Benutzeroberfläche - Threading Sobald ein Service-Vorgang (RunnableServiceTask) vom ThreadingService durch einen Thread ausgeführt wird, teilt er dem zugehörigen GUI-Vorgang (RunnableSwingTask) diesen Thread durch den Aufruf von setThread() mit, bevor er die Methode des Buchhaltungssystems aufruft. Wenn nun der Benutzer die Schaltfläche "Abbrechen" im Dialogfenster der laufenden Vorgänge betätigt, wird die Methode cancel() des GUI-Vorgangs aufgerufen. Dieser kennt den (evtl. blockierten) Thread und unterbricht ihn mit interrupt(). Der GUI-Vorgang wird dann sofort für abgeschlossen erklärt (finish()). Wenn der Methodenaufruf durch den Service-Vorgang z.B. wegen Ein-/Ausgabe blockiert hatte, sollte er zurückkehren und der Service-Vorgang zu Ende laufen. Eine Implementierung des Buchhaltungssystems sollte unter Umständen durch Thread.currentThread().isInterrupted(); auch manuell prüfen, ob der ausführende Thread unterbrochen wurde. Wenn der Thread auf etwas wartet, kann an der entsprechenden Stelle eine InterruptedException oder ClosedByInterruptedException geworfen werden, die folgendermaßen weitergegeben (aber dabei nicht erneut geworfen) werden soll [31]. try { ... } catch (InterruptedException e) { Thread.currentThread().interrupt(); } Da der zugehörige GUI-Vorgang durch den Abbruch für abgeschlossen erklärt wurde, wird er nicht wie üblich nach dem Service-Vorgang aufgerufen, und das Ergebnis des Methodenaufrufs wird nicht angezeigt. Außerdem wird noch durch die Klasse javax.Swing.Timer ein Timer gestartet, der eine Aktion (ActionListener, actionPerformed()) verzögert ausführen kann. Timer timer = new Timer(5000, this); timer.start(); Falls ein Thread bis zum Ablauf der Verzögerungszeit nicht auf interrupt() reagiert hat, wird er durch den Timer mit public void actionPerformed(final ActionEvent e) { thread.stop(); } auf die "harte Tour" beendet. Die Verwendung dieser Methode ist zwar problematisch und wird abgelehnt [31], weshalb sie auch nur verzögert als Notlösung ausgeführt wird, und im Menü erst aktiviert werden muß. - 40 - Benutzeroberfläche - Weitere Funktionen 3.7. Weitere Funktionen 3.7.1. Testfunktionen Über das Test-Menü bietet die Benutzeroberfläche die Möglichkeit verschiedene Testfunktionen zur Validierung der Funktionalitäten einer Implementierung des Buchhaltungssystems aufzurufen. Abbildung 33: Test-Menü In Gegensatz zu allen anderen Aufrufen umgehen die Testfunktionen dabei die Adapterschicht und greifen in separaten Threads direkt auf die Methoden der Buchhaltung zu. 3.7.1.1. Daten erzeugen und aufräumen Diese beiden Funktionen sind keine Testfunktionen an sich. Durch den Menüpunkt "Daten erzeugen" wird von der Utilities-Klasse ein Satz Buchhaltungsdaten generiert und mit createAccount() bzw. createAccountingEntry() an das Buchhaltungssystem übergeben, damit ein Benutzer das System zu nicht manuell mit Testdaten füllen muß. Der Menüpunkt "Rückstände aufräumen" ist für die folgenden Tests gedacht und entfernt Konten (removeAccount()) und Buchungen (removeAccountingEntry()), die nach Abschluß einer der Testfunktionen aus irgendeinem Grund übriggeblieben sind. 3.7.1.2. Test der Funktionalität Der Funktionstest prüft die Methoden der Buchhaltung auf eine korrekte Ausführung der im Modell spezifizierten Geschäftslogik. Dazu werden der Reihe nach Konten und Buchungen angelegt, geprüft ob diese danach in den Auflistungen erscheinen, und wieder gelöscht. Außerdem werden die Methoden des Buchhaltungssystems auch gezielt mit ungültigen Werten aufgerufen, um zu prüfen, ob auch die entsprechenden Ausnahmen geworfen werden. Abbildung 34: Funktions-Test-Dialog - 41 - Benutzeroberfläche - Weitere Funktionen Nach Abschluß der Tests wird die erfolgreiche Durchführung angezeigt. Wenn allerdings Fehler auftreten oder ein ungültiges Verhalten des Buchhaltungssystems festgestellt wird, so wird der genaue Testverlauf in einem Nachrichtenfenster angezeigt. 3.7.1.3. Test der Geschwindigkeit Der Geschwindigkeits-Test soll die Dauer von 2000 Methodenaufrufen bei einem Buchhaltungssystem ermitteln. Dazu werden zuerst 1000 Buchungen angelegt, und danach diese 1000 Buchungen wieder gelöscht. Durch die Schaltfläche "Schließen" kann der Testlauf bei zu langer Dauer vorzeitig abgebrochen werden. Abbildung 35: Geschwindigkeits-Test-Dialog Angezeigt werden "Minuten:Sekunden:Millisekunden" der Testdauer. Dieser Test kann zur Bewertung der verschiedenen Technologien, mit denen das Buchhaltungssystem später implementiert, wird dienen. 3.7.1.4. Test auf Threadsicherheit Hierbei wird eine Implementierung des Buchhaltungssystems auf ihre Threadsicherheit getestet. Dazu werden gleichzeitig 10 Threads gestartet, die auf zwei Testkonten parallel Buchungen anlegen und löschen. Am Ende wird der Saldo der Testkonten auf den korrekten Wert geprüft. Abbildung 36: Threadsicherheits-Testdialog Jede Implementierung des Buchhaltungssystems sollte entsprechend threadsicher sein, und diesen Test bestehen. Zum Einen sollte am Ende bei den Testkonten der richtige Saldo berechnet werden, und zum Anderen sollte die Anwendung bei den parallelen Zugriffen auch nicht hängenbleiben (Deadlock). - 42 - Benutzeroberfläche - Weitere Funktionen 3.7.2. Logging Zur Ausgabe des Programmablaufs der Benutzeroberfläche auf der Konsole gibt es eine eigene Logger-Klasse, die über das Logging-Menü gesteuert wird. Abbildung 37: Logging-Menü Das Logging kann im Allgemeinen an- oder ausgeschaltet werden. Weiterhin kann man einzeln nur den Ablauf des AWT-Thread, der Worker-Threads oder von Beiden ausgeben lassen. Für den Asynchronen und Multithreaded-Modus besteht die Möglichkeit die Ausgaben für jeden Thread separat zu puffern und gesammelt auszugeben, damit sich die Ausgaben der verschiedenen Threads nicht beliebig vermischen. 3.7.2.1. Logger-Klasse Die Logger-Klasse verwaltet die Logging-Einstellungen der Benutzeroberfläche aus dem Logging-Menü und erlaubt über ihre Methoden eine gut lesbar formatierte Ausgabe von Debug-Informationen. package de.fhr.vs_iw.util; public final class Logger { public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized public static synchronized } void calling(String method, Object... args); void caught(Throwable e); void enableLogging(boolean log); void entering(Object... args); boolean getAutoSeparator(); boolean getBuffering(); boolean getLogEventThread(); boolean getLogWorkerThreads(), void leaving(); boolean loggingEnabled(); void println(Object object); void println(String text); void registerClass(); void registerClass(Class<?> clas); void returnedNull(String method); void separator(); void setAutoSeparator(boolean auto); void setBuffering(boolean buffering); void setLogEventThread(boolean log); void setLogWorkerThreads(boolean log); Code 24: Class Logger - 43 - Benutzeroberfläche - Weitere Funktionen Eine Klasse registriert sich zuerst durch registerClass() beim Logger, der den Namen der Klasse feststellt, um die Breite der Spalte, die die Klassennamen ausgibt anzupassen. Danach kann die Klasse beliebige Logger-Objekte instanzieren. Es wird gezielt nicht das verbreitete Logging-Framework "Log4j" verwendet, damit die Anwendung einzeln für sich ohne zusätzliche notwendige Bibliotheken verteilt und verwendet werden kann. 3.7.2.2. Auswertung des Stacktrace Um Ordnung und Übersicht in die Ausgaben zu bringen, greift die Logger-Klasse auf den Stacktrace des aufrufenden Thread zurück. Wenn eine Klasse einen Logger instanziert, dann wird ihr Name über den Stacktrace ermittelt. Das vierte Element des Stacktrace stellt dabei die aufrufende Methode dar, die Elemente davor sind die Aufrufe der Logger-Methoden selbst. StackTraceElement[] stack = Thread.currentThread().getStackTrace(); String className = stack[4].getClassName(); String threadName = Thread.currentThread().getName(); Code 25: Ermittlung des Klassennamens und des Threadnamens aus dem Stacktrace So wird bei jeder Ausgabe der Name der aufrufenden Klasse und des aufrufenden Threads in einer formatierten Spalte mit ausgegeben. 11:45:45|AWT-EventQueue-0|MainWindow$AccountsLoader|... 11:45:45|AWT-EventQueue-0|RunnableSwingTask |... 11:45:45|AWT-EventQueue-0|MainWindow |... 11:45:45|AWT-EventQueue-0|RunnableSwingTask |... 11:45:45|worker-0 |ServiceAdapter |... 11:45:45|worker-0 |RunnableServiceTask |... Code 26: In Spalten formatierte Log-Ausgabe Außerdem wird zur Orientierung, vor allem im gepufferten Logging-Modus (siehe 3.8.2.4), noch die Uhrzeit vorangestellt. 3.7.2.3. Hierarchische Methodenaufrufe Der Logger soll die Aufrufe aller relevanten Methoden hierarchisch eingerückt ausgeben. Der Name der Methode wird ebenfalls aus dem Stacktrace ermittelt. StackTraceElement[] stack = Thread.currentThread().getStackTrace(); String methodName = stack[4].getMethodName(); Code 27: Ermittlung des Methodennamens aus dem Stacktrace Jede der Methoden teilt dem Logger über den Aufruf von entering() ihren Beginn mit. Wird die Methode normal (mit einem Rückgabewert) beendet, muß vorher leaving() aufgerufen werden. Die Beendigung einer Methode durch eine Ausnahme wird durch den Aufruf von exception() beim Logger angezeigt. - 44 - Benutzeroberfläche - Weitere Funktionen ...|AccountCreator |+run() ...|RunnableSwingTask | +running() ...|MainWindow | +notifyRunning(<AccountCreator@23756>) ...|MainWindow | -notifyRunning ...|RunnableSwingTask | -running ...|ServiceAdapter | +resultCreateAccount() ...|RunnableServiceTask| +throwException() ...|RunnableServiceTask| -throwException, throw AccountingException ...|ServiceAdapter | caught AccountingException ...|ServiceAdapter | -resultCreateAccount, throw AccountingException ...|RunnableSwingTask | +error() ...|MainWindow | +notifyError(<AccountCreator@23756>) ...|MainWindow | -notifyError ...|RunnableSwingTask | -error ...|RunnableSwingTask | +finish() ...|MainWindow | +notifyFinish(<AccountCreator@23756>) ...|MainWindow | -notifyFinish ...|RunnableSwingTask | -finish ...|AccountCreator |-run Code 28: Logger-Ausgabe der aufgerufenen Methoden Wie hier am Beispiel des Aufrufs der Methoden zum Anlegen eines Kontos (mit ungültiger Kontonummer und dadurch geworfener Ausnahme) erkennbar wird, entsteht eine übersichtliche Auflistung der Methodenaufrufe in den verschiedenen Klassen. 3.7.2.4. Unterscheidung nach Thread Der Logger ermittelt nicht nur den Namen des aufrufenden Threads, auch die Einrückung wird auf den jeweiligen Thread bezogen gespeichert. Je nach Einstellung (siehe Abbildung 31) erfolgt für bestimmte Threads überhaupt keine Ausgabe. Außerdem wird für jeden Thread ein eigener Ausgabepuffer verwendet, damit wie o.g. die Ausgaben nach Thread zusammengefaßt und gesammelt ausgegeben werden können. 3.7.3. Look & Feel Eines der Features von Swing ist die Verfügbarkeit verschiedener Look & Feel Klassen zur Darstellung der Benutzeroberfläche, damit die Oberfläche jeweils zum aktuellen System paßt. Standardmäßig wird auch die zugehörige Look & Feel Klasse des Systems initialisiert und verwendet. try { String systemLF = UIManager.getSystemLookAndFeelClassName(); UIManager.setLookAndFeel(systemLF); } catch (Exception e) { ... } Code 29: Look & Feel Initialisierung Diese API erlaubt aber den einfachen Austausch der Look & Feel Klasse, auch dynamisch zur Laufzeit eines Programms. Durch den Aufruf der Methode UIManager.getInstalledLookAndFeels(); - 45 - Benutzeroberfläche - Weitere Funktionen erhält man eine Auflistung der verfügbaren Look & Feel Klassen. Zu demonstrationszwecken wird die Liste um zusätzliche, nicht standardmäßig mitgelieferte, Varianten erweitert. Abbildung 38: Look & Feel Menü Über das Look & Feel Menü kann nun jederzeit eine Look & Feel Klasse ausgewählt werden, welche die Oberfläche dann entsprechend darstellt. Abbildung 39: Benutzeroberfläche im "Metal" Look & Feel - 46 - Benutzeroberfläche - Weitere Funktionen Der Einstellung der im Menü gewählten Klasse erfolgt durch den Aufruf der setLookAndFeel()-Methode der Utilities-Klasse mit dem Index des gewählten Menüeintrags, welche die gleichnamige Methode des UIManager aufruft. public static void setLookAndFeel(int index) throws Exception { final LookAndFeelInfo[] l = UIManager.getInstalledLookAndFeels(); if (index >= 0 && index < l.length) { UIManager.setLookAndFeel(l[index].getClassName()); } } Code 30: Utilities-Methode setLookAndFeel() Im Anschluß muß aber die Darstellung von bereits angezeigten Swing-Komponenten auf den neu eingestellten Look & Feel aktualisiert werden. Dafür erbt jede Komponente die Methode updateUI() bei deren Aufruf die Darstellung auf Grundlage des aktuellen Look & Feel neu initialisiert wird. Durch Aufruf von SwingUtilities.updateComponentTreeUI(this); im Hauptfenster der Anwendung wird der gesamte Komponenten-Baum des Fensters durch den Aufruf der updateUI()-Methode bei allen Sub-Komponenten aktualisiert. Bei Zusatzfenstern muß die Methode auch für diese Fenster aufgerufen werden, z.B. SwingUtilities.updateComponentTreeUI(taskListDialog); und bei eigenen Komponenten muß unter Umständen die updateUI()-Methode überschrieben und selbst implementiert werden. 3.7.4. Hilfe Abbildung 40: Hilfe-Menü Über das Hilfe-Menü können Informationen zur Buchhaltung direkt aufgerufen werden: Beschreibung: Öffnet die HTML-Kurzbeschreibung zur Buchhaltung in einem externen Web-Browser, sofern diese im Unterverzeichnis "description" vorhanden ist. JavaDoc anzeigen: Sucht nach einer vorhandenen JavaDoc-Beschreibung und zeigt diese im einem Web-Browser an. Info: Öffnet ein Fenster mit einigen Angaben und Kontaktinformationen zur Benutzeroberfläche. - 47 - Benutzeroberfläche - Suchen und Laden der Proxy-Klasse 3.8. Suchen und Laden der Proxy-Klasse Wie bereits in Abschnitt 3.2 beschrieben, kann von der Benutzeroberfläche eine beliebige Proxy-Klasse, welche die vorgegebene Proxy-Schnittstelle implementiert, geladen werden. 3.8.1. Suchverzeichnis Standardmäßig werden die Klassen ausgehend vom aktuellen Verzeichnis gesucht. Man kann aber jederzeit über die Schaltfläche "Verzeichnis..." ein anderes Verzeichnis auswählen. Abbildung 41: Verzeichnis-Auswahl-Dialog Dann wird der Verzeichnisbaum ausgehend vom gausgewählten Verzeichns rekursiv durchlaufen, und jede gefundene class-Datei, auch innerhalb von Archiven, wird verarbeitet. 3.8.2. Klassenlader für den Suchvorgang Der Klassenlader de.fhr.vs_iw.util.FileClassLoader führt zum einen den Suchvorgang durch und ist zum Anderen von der Klasse ClassLoader abgeleitet, damit er die Möglichkeit hat, mit den überschriebenen Methoden findClass() und loadClass() Klassen von der JVM nachladen zu lassen. public final class ClassFileLoader extends ClassLoader { public Class<?> findClass(String name); public Class<?> loadClass(String name); public static void cancel(); public static boolean isSearching(); public static void search(String dir, ClassFileLoaderListener listener); } - 48 - Benutzeroberfläche - Suchen und Laden der Proxy-Klasse Code 31: Class ClassFileLoader Die weiteren Methoden kontrollieren den Suchvorgang. Um Inkonsistenzen bei mehreren Suchvorgängen über mehrere Verzeichnisse zu vermeiden sind diese Methoden statisch und der Klassenlader wird nach dem Singleton-Design-Pattern nur einmal instanziert und gekapselt. Außerdem ist der Suchvorgang threadsicher synchronisiert. 3.8.3. Listener-Schnittstelle für den Klassenlader Die aufrufende Anwendung wird mit Hilfe der ClassFileLoaderListener-Schnittstelle vom Klassenlader über die Ereignisse beim Suchvorgang informiert. package de.fhr.vs_iw.util; import java.io.File; public interface ClassFileLoaderListener { boolean classFound(ClassFileDecoder cfd); void classLoaded(ClassFileEntry entry); boolean classUpdate(ClassFileEntry entry, ClassFileDecoder cfd); boolean errorOccured(File file, Throwable e); void otherPath(File directory); } Code 32. Interface ClassFileLoaderListener Die Anwendung wird dadurch über die folgenden Ereignisse informiert und kann das weitere Vorgehen beim Suchvorgang durch einen entsprechenden Rückgabewert beeinflussen. otherPath classFound classUpdate classLoaded errorOccured wird aufgerufen, wenn das aktuelle Suchverzeichnis gewechselt wurde, oder in innerhalb eines Archivs gesucht wird wird aufgerufen, wenn eine class-Datei gefunden wurde; der Rückgabewert gibt an ob die Klasse in die JVM geladen werden soll wird aufgerufen, wenn eine neu gefundene Klasse den gleichen Namen hat, wie eine bereits zuvor gefundene; der Rückgabewert gibt an, ob nun die neue Klasse verwendet werden soll wird aufgerufen, wenn eine Klasse erfolgreich in die JVM geladen wurde, die Klasse darf aber hier noch nicht verwendet werden, sondern erst nach dem Abschluß des Suchvorgangs wird aufgerufen, wenn bei der Suche eine Ausnahme auftritt; der Rückgabewert gibt an, ob der Vorgang fotgesetzt werden soll Eine gefundene Klasse wird aber nicht unbedingt sofort geladen, auch wenn dies von der Anwendung gewünscht wird. Es kann vorkommen, daß referenzierte Klassen noch nicht gefunden wurden, weshalb das Laden verzögert wird, bis alle Abhängigkeiten aufgelöst werden können. Deshalb sind die gewünschten Klassen gezielt erst nach dem Abschluß des Suchvorgangs verwendbar. Anders als bei üblichen Listener-Schnittstellen kann hier nur der eine Empfänger benachrichtigt werden, der den Suchvorgang gestartet hat, und der Suchvorgang kann auch nur einmal und nicht mehrfach parallel gestartet werden. - 49 - Benutzeroberfläche - Suchen und Laden der Proxy-Klasse 3.8.4. Eigenschaften gefundener Klassen Die Eigenschaften der gefundenen Klassen werden ausgelesen, ohne daß diese von der JVM geladen werden müssen. Die Schnittstelle de.fh.vs_iw.util.ClassFileReflection stellt dann entsprechende Auswertungsmethoden zur Verfügung, um diese Eigenschaften gezielt abzufragen. package de.fhr.vs_iw.util; public interface ClassFileReflection { String[] getFields(); File getFile(); String[] getInterfaces(); String[] getMethods(); String getName(); String getSuperclass(); ZipEntry getZipEntry(); boolean hasField(String field); boolean hasInterface(String iface); boolean hasMethod(String method); boolean isAbstract(); boolean isFinal(); boolean isInterface(); boolean isPublic(); } Code 33: Interface ClassFileReflection Ein Objekt der Klasse ClassFileDecoder dekodiert die Informationen in einer class-Datei und stellt sie über die o.g. Schnittstelle zur Verfügung. Die Datei kann auf eine beliebige Weise eingelesen werden, und wird der Klasse dann als byte[]-Array übergeben. Die ByteDaten der Klasse werden anhand der Format-Spezifikation für class-Dateien [83] dekodiert und strukturiert aufbereitet. package de.fhr.vs_iw.util; public final class ClassFileDecoder implements ClassFileReflection { private final ZipEntry entry; private final File file; ClassFileDecoder(byte[] classbytes); ClassFileDecoder(byte[] classbytes, File file, ZipEntry entry); public public public public public public } String getErrors(); long getMagic(); int getMajorVersion(); int getMinorVersion(); int getSize(); boolean isValid(); <interface methods> Code 34: Class ClassFileDecoder Zusätzlich zu den Eigenschaften der Java-Klasse an sich, wird auch noch die Herkunft der Daten (class-Datei bzw. Teil eines Archivs) festgehalten. - 50 - Benutzeroberfläche - Suchen und Laden der Proxy-Klasse 3.8.5. Laden gefundener Klassen In Listener-Methode classFound() prüft die Anwendung, ob eine gefundene Klasse als Proxy-Klasse in Frage kommt. public boolean classFound(final ClassFileDecoder clas) { if (clas.hasInterface("de.fhr.vs_iw.model.IAccountingProxy")) { return true; } return false; } Code 35: Method classFound() in ServiceSelectDialog Wenn dies durch den Rückgabewert true bestätigt wird, dann wird die gefundene Klasse in die JVM geladen. Der Methode defineClass() von ClassLoader werden die eingelesen Byte-Daten übergeben, damit die JVM daraus eine verwendbare Laufzeit-Java-Klasse (Class-Objekt) erstellt. protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError In einem Objekt von ClassFileEntry wird die geladene Klasse dann zusammen mit den Informationen zu ihrer Herkunft verwaltet, für den Eintrag in der Liste der gefundenen ProxyKlassen und auch intern für den Klassenlader. public final class ClassFileEntry implements ClassFileReflection { private Class<?> clas; private File file; private String name; private ZipEntry zipe; ClassFileEntry(String name, File file, ZipEntry zipe); public Class<?> getTheClass(); void setTheClass(Class< ? > c); <interface methods> } Code 36: Class ClassFileEntry Diese Klasse implementiert auch wieder die Schnittstelle ClassFileReflection zur Abfrage der Eigenschaften der gekapselten Klasse. Hier erfolgt die Abfrage dann aber mit JavaReflection und nicht mehr über die selbst dekodierten Byte-Daten der Klasse. Alle gefundenen und erfolgreich geladenen Proxy-Klassen werden beim Aufruf der ListenerMethode classLoaded() jeweils in die Tabelle im Auswahldialog aufgenommen 3.8.6. Anmerkung Der Klassenladevorgang ist hier eine durchaus komplizierte Angelegenheit, da Java intern nur Klassen kennt, aber keine Dateien im Dateisystem. Das Laden einer Klasse aus einer beliebigen vorliegenden Datei ohne weitere Strukturierung und Pfade war so nicht vorgesehen. - 51 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse 3.9. Verwendung der Buchhaltung in Eclipse Gearbeitet wird mit der Buchhaltungsoberfläche in der freien Java-Entwicklungsumgebung Eclipse [30]. Zu Beginn ist keine spezielle Version davon oder besondere zusätzliche Plugins notwending. Die aktuelle Version 3.3.1.1 (Europa) des Eclipse-SDK kann unter http://www.eclipse.org/downloads/ heruntergeladen werden. Das Paket muß nur in ein beliebiges Verzeichnis entpackt und unter Windows die eclipse.exe ausgeführt werden. Es kann aber zusammen mit dem JBoss-Application-Server eine spezielle Eclipse-Version [38] verwendet werden, die von den JBoss-Entwicklern zur Verfügung gestellt wird und unter http://java.fh-regensburg.de/download/eclipse-SDK-3.4.1-win32.zip heruntergeladen werden kann (siehe auch 6.2.7). Alternativ kann man die JBoss-Plugins später auch bei einem Standard-Eclipse nachträglich hinzufügen. 3.9.1. Neues Projekt anlegen Zuerst muß in Eclipse ein neues Projekt für die Arbeit mit der Buchhaltungsanwendung angelegt werden. Dazu wird unter File -> New -> Project... der Projekt-Assistent aufgerufen. Im ersten Dialog wird ein "Java Project" ausgewählt. Abbildung 42: Eclipse-Dialog "New Project" - 52 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Im nächsten Dialog wird ein Name für das Projekt (z.B. VS_IW_FiBu) vergeben und die Grundeinstellungen vorgenommen. Abbildung 43: Eclipse-Dialog "New Java Project" Die Einstellung "Create separate folders for source and class files" ist sinnvoll, damit das Projekt besser strukturiert wird. Mit der Schaltfläche "Finish" wird der Dialog geschlossen. Abbildung 44: Eclipse Package Explorer - 53 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Das Projekt wird erzeugt und im "Package Explorer" angezeigt. Der Ordner "src" wurde automatisch für die eigenen Packages und Klassen erstellt. 3.9.2. Importieren des Package mit der Buchhaltungsoberfläche Um die Strukturierung des Projekts weiterzuführen, wird nun ein Ordner für eigene Bibliotheken angelegt. Der entsprechende Dialog wird im Menü unter File -> New -> Folder aufgerufen. Der neue Ordner erhält den Namen "lib". Abbildung 45: Eclipse-Dialog "New Folder" Der neue Ordner wird im Package-Explorer angezeigt und auch gleich ausgewählt. Abbildung 46: Eclipse Package Explorer - 54 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Anschließend wird das Package für die Buchhaltung benötigt. Es kann als jar-Datei unter http://java.fh-regensburg.de/download/fibu-3.3/vs_iw_fibu_main_3.3.jar heruntergeladen werden, und enthält die Benutzeroberfläche, die Buchhaltungsschnittstellen, einige Hilfsklassen und Dummy-Proxy-Klassen zu Testzwecken. Über die Import-Funktion von Eclipse, die im Menü unter File -> Import... zu finden ist, wird die Datei importiert. Dazu wird im ersten Dialog unter "General" als Quelle das "File System" ausgewählt. Abbildung 47: Eclipse-Dialog "Import" - Select Im zweiten Dialog wird dann durch die Schaltfläche "Browse…" oder über manuelle Eingabe das Verzeichnis angegeben, in welches die jar-Datei mit der Buchhaltung heruntergeladen wurde. Auf der rechten Seite zeigt Eclipse dann die Dateien in diesem Verzeichnis an. Vor die gewünschte Datei vs_iw_fibu_mein_3.0.jar wird ein Häckchen gesetzt. Als Zielordner ("Into folder:") sollte der neue Ordner für Bibliotheken "VS_IW_FiBu/lib" eingetragen werden. Durch die Schaltfläche "Finish" wird der Import angeschlossen. - 55 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Abbildung 48: Eclipse-Dialog "Import" - File system Im Package Explorer wird die soeben importierte Bibliothek angezeigt, und sie wurde von Eclipse auch dateimäßig ins Projektverzeichnis kopiert. Abbildung 49: Eclipse Package Explorer In den Projekteigenschaften muß nun eingestellt werden, daß die Bibliothek von Eclipse bei der Compilierung von Java-Code einbezogen werden soll. Das entsprechende Dialogfenster wird im Menü unter Project -> Properties aufgerufen. Unter der Rubrik "Java Build Path" und dem Reiter "Libraries" sind die bei der Compilierung verwendeten Bibliotheken aufgelistet. - 56 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Abbildung 50: Eclipse-Dialog "Properties for VS_IW_FiBu" Von der Erstellung des Projektes her ist bereits der Eintrag für die Java-StandardBibliotheken (z.B. "JRE System Library [jre6]") vorhanden. Mit der Schaltfläche "Add JARs…" werden Bibliotheken aus dem aktuellen Workspace hinzugefügt. In einem separaten Fenster kann die jar-Datei der Buchhaltung ausgewählt werden. Abbildung 51: Eclipse-Dialog "JAR Selection" Nach dem Schließen der Dialogfenster wird im Package Explorer die Anzeige aktualisiert. Die jar-Datei wird nicht mehr unter "lib", sondern als Java-Bibliothek unter "References Libraries" angezeigt. Man kann sich nun auch die enthaltenen Packages und Klassen anzeigen lassen. - 57 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Abbildung 52: Eclipse Package Explorer 3.9.3. Start der Anwendung Gestartet wird die Anwendung über die Klasse de.fhr.vs_iw.Client (siehe auch 3.2), die jetzt nach dem Importieren im CLASSPATH von Eclipse verfügbar ist. Über den Menüpunkt Run -> Open Run Dialog... wird das Dialogfenster zum Erstellen einer Startkonfiguration aufgerufen. Abbildung 53: Eclipse-Dialog "Run" - Empty - 58 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Als Typ wird "Java Application" ausgewählt und durch die Schaltfläche "New" wird eine neue Startkonfiguration erzeugt. Für die Konfiguration kann ein Name (z.B. Client) vergeben werden. Unter dem Punkt "Main class" wird die o.g. Klasse de.fhr.vs_iw.Client eingetragen. Abbildung 54: Eclipse-Dialog "Run" - Java Application Auf dem Reiter "Aguments" könnten noch weitere Argumente zum Start der Anwendung angegeben werden, wie auf der Kommandozeile auch. Dies könnte eben der Name einer vorhandenen Proxy-Klasse sein, die sofort beim Aufruf der Benutzeroberfläche geladen werden soll. Durch die Schaltfläche "Run" wird das Dialogfenster geschlossen und die Buchhaltungsanwendung gestartet. 3.9.4. Erstellung eines eigenen Moduls für die Buchhaltung Sobald die Benutzeroberfläche der Buchhaltungsanwendung abblauffähig ist, kann mit der Erstellung eigener Buchhaltungsklassen begonnen werden. Dazu wird günstigerweise zuerst über den Menüpunkt File -> New -> Package eine neues Package (z.B. de.fhr.vs_iw.local) angelegt, um die Übersicht zu behalten. - 59 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Abbildung 55: Eclipse-Dialog "New Java Package" Das neue Package wird im Package Explorer standardmäßig im Ordner "src" angelegt. Abbildung 56: Eclipse Package Explorer Anschließend werden die eigenen Klassen über den entprechenden Dialog im Menü unter File -> New -> Class erstellt. Für jede neue Klasse muß eine Name vergeben werden. Als Package wird über die zweite Schaltfläche "Browse…" die zuvor angelegte Package de.fhr.vs_iw.local ausgewählt. Im Bereich "Interfaces:" wird jeder neuen Klasse mit der Schaltfläche "Add…" eine der Buchhaltungsschnitststellen aus dem FiBu-Paket hinzugefügt (z.B. IAccountingProxy). - 60 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Abbildung 57: Eclipse-Dialog "Implemented Interfaces Selection" Abbildung 58: Eclipse-Dialog "New Java Class" - 61 - Benutzeroberfläche - Verwendung der Buchhaltung in Eclipse Dieser Vorgang wird für alle vier Schnittstellen der Buchhaltung (siehe 2.2) wiederholt, bis für jede Schnittstelle jeweils eine zugehörige implementierende Klasse angelegt wurde. IAccount IAccountingEntry IAccountingService IAccountingProxy -> -> -> -> de.fhr.vs_iw.local.Account de.fhr.vs_iw.local.AccountingEntry de.fhr.vs_iw.local.AccountingService de.fhr.vs_iw.local.LocalProxy Abbildung 59: Eclipse Package Explorer Eclipse erstellt für jede Methode jeder der Schnittstellen eine leere Methode in der zugehörigen Klasse, z.B. in der Klasse de.fhr.vs_iw.IAccountingService: public Double calculateBalance(Integer number) throws IOException, AccountingException { // TODO Auto-generated method stub return null; } Die Methoden müssen nun mit der entsprechenden Funktionalität ausgestattet werden, um die Geschäftslogik bzw. die Datenübertragung des Buchhaltungssystems zu realisieren. Abbildung 60: Eclipse-Gesamtansicht des neu erstellten Projekts - 62 - Datenhaltung - Übersicht 4. Datenhaltung 4.1. Übersicht Der erste Abschnitt über die verschiedenen möglichen Module der Buchhaltung beschäftigt sich mit der Datenhaltungsschicht als Endpunkt des Systems (siehe 2.4). 4.1.1. Datenspeicherung Der Hauptthematik dabei ist die Speicherung der Buchhaltungsdaten. Eine Service-Klasse muß eine Anzahl von Konten und Buchungen verwalten, die ja wiederum ihre einzelnen Daten besitzen. Neu angelegte Konten oder Buchungen müssen später in der Auflistung erscheinen und auch wieder abrufbar sein. 4.1.2. Geschäftslogik Ein weiterer Punkt, der die Datenhaltungsschicht betrifft, ist die korrekte Implementierung der Geschäftslogik, damit bei ungültigen Aufrufen die entsprechenden Ausnahmen geworfen werden. Dies erfordert eine bidirektionale Beziehung zwischen der Service-Klasse, den Konten und den Buchungen. Das bedeutet, daß die Service-Klasse nicht einfach nur alle vorhandenen Konten und Buchungen verwalten muß, sondern jedes Konten- bzw. Buchungsobjekt muß seine zugehörige Service-Klasse kennen. Nur so kann z.B. ein Buchungsobjekt, dessen Haben-Konto neu festgelegt wird, feststellen, ob dieses Konto auch vorhanden und gültig ist, indem es auf die Kontenliste der Service-Klasse zugreift. Beim Laden und Speichern der Objekte muß dieser Bezug hergestellt und vor allem auch wieder aufgelöst werden, damit der Vorgang nicht in eine endlose Rekursion läuft. Dies ist bei allen im Folgenden Technologien ein Punkt der besondere Aufmerksamkeit erfordert. - 63 - Datenhaltung - Einfache lokale Datenhaltung 4.2. Einfache lokale Datenhaltung 4.2.1. Allgemeines Als Einstieg in die Datenhaltung werden die Daten des Buchhaltungssystems in Objekten der laufenden Virtuellen Maschine gespeichert, wobei die Daten aber flüchtig sind, und verloren gehen, wenn die VM terminiert. Die Geschäftslogik wird vollständig implementiert. 4.2.2. Daten in HashMaps Zur Verwaltung werden die Objekte mit den Daten in HashMaps abgelegt, damit effizient darauf zugegriffen werden kann. 4.2.2.1. Konten und Buchungen Die Service-Klasse benötigt zwei HashMaps jeweils für die Buchungen und Konten private Map<Integer, AccountingEntry> accountingEntries = new HashMap<Integer, AccountingEntry>(); private Map<Integer, Account> accounts = new HashMap<Integer, Account>(); Code 37: HashMaps für Konten- und Buchungs-Objekte Ein Objekt wird dabei direkt der ihm vergebenen Nummer bzw. ID zugeordnet, damit es später direkt gefunden zurückgegeben werden kann. 4.2.2.2. Soll- und Haben-Buchungen Die Konto-Klasse verwaltet ihre zugeordneten Soll- und Haben-Buchungen ebenfalls in zwei HashMaps. private Map<Integer, AccountingEntry> creditEntries = new HashMap<Integer, AccountingEntry>(); private Map<Integer, AccountingEntry> debitEntries = new HashMap<Integer, AccountingEntry>(); Code 38: HashMaps für Soll- und Haben-Buchungen Dabei werden natürlich dieselben Objekte referenziert, wie in der entsprechenden HashMap in der Service-Klasse. 4.2.2.3. Bidirektionaler Bezug Die Service-Klasse stellt den bereits angesprochenen bidirektionalen Bezug her, indem sie einen Verweis auf sich selbst (this) an alle neuen Konten- und Buchungs-Objekte übergibt. - 64 - Datenhaltung - Einfache lokale Datenhaltung 4.2.3. Implementierung der Schnittstellen Es werden alle Schnittstellen des Buchhaltungsmodels in Klassen implementiert und mit der nötigen Funktionalität im Sinne der Geschäftlogik versehen. 4.2.3.1. Service-Klasse Die Service-Klasse implementiert die Service-Schnittstelle (siehe 2.2.4) und die HashMaps zur Aufnahme der Konten- und Buchungs-Objekte. Die ID für neue Buchungen wird mit dem Feld nextID einfach hochgezählt. package de.fhr.vs_iw.local; public final class AccountingService implements IAccountingService { private = new private = new Map<Integer, AccountingEntry> accountingEntries HashMap<Integer, AccountingEntry>(); Map<Integer, Account> accounts HashMap<Integer, Account>(); private int nextId = 0; private AccountingService() {} public Double calculateBalance(Integer number) throws AccountingException, IOException { return ((Account)getAccountByNumber(number)).balance(); } public void createAccount(Integer number, AccountType type, String description) throws AccountingException { if (number <= 0) { throw new AccountingException("Die Nummer darf nicht negativ sein"); } synchronized (accounts) { checkAccountNumber(number, false); Account acc = new Account(this, number, description, type); accounts.put(number, acc); } } public Integer createAccountingEntry(Integer debitAccount, Integer creditAccount, Double amount, String text) throws AccountingException { synchronized (accountingEntries) { Integer id = nextId++; AccountingEntry = new AccountingEntry(this, id, debitAccount, creditAccount, amount, text); accountingEntries.put(id, entry); ((Account)getAccountByNumber(debitAccount)).addDebitEntry(entry); ((Account)getAccountByNumber(creditAccount)).addCreditEntry(entry); } return id; } - 65 - Datenhaltung - Einfache lokale Datenhaltung public IAccount getAccountByNumber(Integer number) throws AccountingException { synchronized (accounts) { checkAccountNumber(number, true); return accounts.get(number); } } public Set<Integer> getAccountingEntries() { synchronized (accountingEntries) { return accountingEntries.keySet(); } } public IAccountingEntry getAccountingEntryById(Integer id) throws AccountingException { synchronized (accountingEntries) { IAccountingEntry entry = accountingEntries.get(id); if (entry == null) { throw new AccountingException("Buchung nicht vorhanden"); } return entry; } } public Set<Integer> getAccounts() { synchronized (accounts) { return accounts.keySet(); } } public void removeAccount(Integer accountNumber) throws IOException, AccountingException { synchronized (accountingEntries) { checkAccountNumber(accountNumber, true); for (IAccountingEntry entry : accountingEntries.values()) { if (entry.getCreditAccount().equals(accountNumber) || entry.getDebitAccount().equals(accountNumber)) { throw new AccountingException("Konto wird noch verwendet"); } } synchronized (accounts) { accounts.remove(accountNumber); } } } public void removeAccountingEntry(Integer id) throws IOException, AccountingException { synchronized (accountingEntries) { IAccountingEntry entry = getAccountingEntryById(id); Account credit = (Account)getAccountByNumber(entry.getCreditAccount()); Account debit = (Account)getAccountByNumber(entry.getDebitAccount()); credit.removeCreditEntry(id); debit.removeDebitEntry(id); accountingEntries.remove(id); } } - 66 - Datenhaltung - Einfache lokale Datenhaltung } protected void checkAccountNumber(Integer number, boolean should) throws AccountingException { synchronized (accounts) { if (accounts.containsKey(number)) { if (!should) { throw new AccountingException("Konto bereits vorhanden"); } } else { if (should) { throw new AccountingException("Konto nicht vorhanden"); } } } } Code 39: Class AccountingService (Lokal) Die Klasse implementiert alle spezifizierten Funktionalitäten im Sinne der Geschäftslogik und besitzt dafür die Methode checkAccountNumber(), um den Status eines Kontos zu überprüfen und gegebenenfalls eine Ausnahme zu werfen. Das Erzeugen und Entfernen von Buchungen wird an die dabei involvierten Konten weitergegeben. 4.2.3.2. Konto-Klasse Hier wird die Konto-Schnittstelle (siehe 2.2.1) implementiert und die Buchungen eines Kontos in den angesprochenen HashMaps verwaltet. package de.fhr.vs_iw.local; public final class Account implements IAccount { private String accountDesc = null; private Integer accountNumber = null; private AccountType accountType = null; private = new private = new Map<Integer, AccountingEntry> creditEntries HashMap<Integer, AccountingEntry>(); Map<Integer, AccountingEntry> debitEntries HashMap<Integer, AccountingEntry>(); protected Account(Integer number, String description, AccountType type) { accountNumber = number; accountType = type; accountDesc = description; } public Set<Integer> getCreditEntries() { return new HashSet<Integer>(creditEntries.keySet()); } public Set<Integer> getDebitEntries() { return new HashSet<Integer>(debitEntries.keySet()); } public String getDescription() { return accountDesc; } - 67 - Datenhaltung - Einfache lokale Datenhaltung public Integer getNumber() { return accountNumber; } public AccountType getType() { return accountType; } public void setDescription(String description) { accountDesc = description; } public void setType(AccountType type) { accountType = type; } protected Double balance() throws IOException { double balance = 0.0; for (IAccountingEntry entry : debitEntries.values()) { balance += entry.getAmount().doubleValue(); } for (IAccountingEntry entry : creditEntries.values()) { balance -= entry.getAmount().doubleValue(); } return balance; } protected void addCreditEntry(AccountingEntry entry) { creditEntries.put(entry.getId(), entry); } protected void addDebitEntry(AccountingEntry entry) { debitEntries.put(entry.getId(), entry); } protected void removeCreditEntry(Integer entryId) { creditEntries.remove(entryId); } } protected void removeDebitEntry(Integer entryId) { debitEntries.remove(entryId); } Code 40: Class Account (Lokal) Die Klasse enthält von der Konto-Schnittstelle unabhängige Methoden zur Verwaltung der Objekte in den HashMaps: addCreditEntry() addDebitEntry() removeCreditEntry() removeDebitEntry() - Haben-Buchung hinzufügen - Soll-Buchung hinzufügen - Haben-Buchung entfernen - Soll-Buchung entfernen Dadurch kann die Service-Klasse Informationen über die vorhandenen Buchungen weitergeben. Da die Konto-Klasse dadurch vollständige Informationen über ihre Buchungen besitzt wird auch die Berechnung des Saldos (balance()) hier durchgeführt und von der ServiceKlasse aufgerufen. - 68 - Datenhaltung - Einfache lokale Datenhaltung 4.2.3.3. Buchungs-Klasse Die Buchungs-Klasse nimmt die Daten einer Buchung auf und bietet über die BuchungsSchnittstelle Zugriff darauf. package de.fhr.vs_iw.local; public final class AccountingEntry implements IAccountingEntry { private private private private private Integer creditAccount = null; Integer debitAccount = null; Double entryAmount = null; Integer entryId = null; String entryText = null; private AccountingService serviceClass = null; protected AccountingEntry(AccountingService service, Integer id, Integer debit, Integer credit, Double amount, String text) throws AccountingException { serviceClass = service; setDebitAccount(debit); setCreditAccount(credit); entryAmount = amount; entryId = id; entryText = text; } public Double getAmount() { return entryAmount; } public Integer getCreditAccount() { return creditAccount; } public Integer getDebitAccount() { return debitAccount; } public Integer getId() { return entryId; } public String getText() { return entryText; } public void setAmount(Double amount) { entryAmount = amount; } public void setText(String text) { entryText = text; } - 69 - Datenhaltung - Einfache lokale Datenhaltung public void setCreditAccount(Integer number) throws AccountingException { serviceClass.checkAccountNumber(number, true); if (number.equals(debitAccount)) { throw new AccountingException("Haben-Konto und Soll-Konto identisch") } creditAccount = number; } } public void setDebitAccount(Integer number) throws AccountingException { serviceClass.checkAccountNumber(number, true); if (number.equals(creditAccount)) { throw new AccountingException("Haben-Konto und Soll-Konto identisch") } debitAccount = number; } Code 41: Class AccountingEntry (Lokal) Über den Rückbezug zur Service-Klasse (Feld serviceClass), die im Konstruktor übergeben wird, kann die Funktion checkAccountNumber() aufgerufen werden, um die Gültigkeit der zugeordneten Konten zu überprüfen. 4.2.3.4. Proxy-Klasse Die Proxy-Klasse hat hier noch kaum Funktionalität, sie muß im Prinzip nur vorhanden sein um instanziert werden zu können. package de.fhr.vs_iw.local; public final class LocalProxy implements IAccountingProxy { public LocalProxy() {} public void close() {} public IAccountingService connect(String[] args) throws IOException { return new AccountingService(); } } Code 42: Class LocalProxy Bei Aufruf der connect()-Methode wird lediglich eine neue Instanz der Service-Klasse erzeugt und zurückgegeben. 4.2.4. Fazit Die erfolgte erste Implementierung der Geschäftslogik ist effektiv und einfach, jedoch die Tatsache, daß die Daten des Systems nach dem Ende der Anwendung verloren gehen, paßt nicht unbedingt zu den Erfordernissen der Datenhaltungsschicht. Deshalb geht es im folgenden um die dauerhafte Speicherung der Buchhaltungsdaten. - 70 - Datenhaltung - Java-Serialisierung 4.3. Java-Serialisierung 4.3.1. Allgemeines Eine grundlegende Möglichkeit um Daten mit Java zu speichern, ist das Serialisieren der Objekte in eine Datei. Dazu müssen die vorhandenen Klassen des "lokalen" Moduls lediglich etwas erweitert werden. 4.3.2. Serialisierbare Klassen Im Prinzip muß bei der Konto-Klasse und der Buchungs-Klasse nur die Schnittstelle java.io.Serializable implementiert werden, damit die Konten- und Buchungs-Objekte durch einen ObjectOutputStream gespeichert und durch einen ObjectInputStream eingelesen werden können. 4.3.2.1. Konto-Klasse Die vorhandene Konto-Klasse muß lediglich durch die Implementierung der Serializable-Schnittstelle als serialisierbar deklariert werden. package de.fhr.vs_iw.serialize; public final class Account implements IAccount, Serializable { private static final long serialVersionUID = 1L; ... } <Fields and Methods> Code 43: Class Account (Serialize) Mit der Implementierung der Schnittstelle geht einher, daß die Klasse ein statisches Feld serialVersionUID definieren sollte, das Versionsänderungen bei serialisierten Objekten angibt. So kann die Klasse mit ihren Daten von Java bereits vollständig serialisiert werden. 4.3.2.2. Buchungs-Klasse Auch die vorhandene Buchungs-Klasse wird um die Serializable-Schnittstelle und die zugehörige Serialisierungs-ID erweitert. package de.fhr.vs_iw.serialize; public final class AccountingEntry implements IAccountingEntry, Serializable { private static final long serialVersionUID = 1L; ... <Fields and Methods> ... - 71 - Datenhaltung - Java-Serialisierung private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { entryId=stream.readInt(); entryAmount=stream.readDouble(); entryText=(String)stream.readObject(); debitAccount=stream.readInt(); creditAccount=stream.readInt(); } } private void writeObject(java.io.ObjectOutputStream stream) throws IOException { stream.writeInt(entryId); stream.writeDouble(entryAmount); stream.writeObject(entryText); stream.writeInt(debitAccount); stream.writeInt(creditAccount); } Code 44: Class AccountingEntry (Serialize) Hier besteht allerdings ein Problem mit dem vorhandenen Bezug zur Service-Klasse, die nicht serialisierbar ist, und es auch nicht sein soll, und deshalb einen Fehler hervorrufen würde. Deshalb muß die Serialisierung "manuell" durchgeführt werden, durch die Methoden private void writeObject(java.io.ObjectOutputStream stream) private void readObject(java.io.ObjectInputStream stream) die vom Serialisierungs-Vorgang aufgerufen werden, wenn sie mit genau dieser Signatur vorliegen. Damit werden alle Daten eines Buchungsobjekts, außer dem Bezug zur ServiceKlasse, verarbeitet. 4.3.3. De-/Serialisierungs-Vorgang Nachdem die Konto- und Buchungs-Klasse entsprechend vorbereitet wurden, müssen nun die Objekte dieser Klasse in eine Datei serialisiert und wieder geladen werden. 4.3.3.1. Service-Klasse Dazu wird die Service-Klasse um zwei Methoden zum Laden und Speichern erweitert. protected void loadData(File file) throws FileNotFoundException, IOException, ClassNotFoundException { if (file.exists()) { ObjectInputStream stream = new ObjectInputStream( new FileInputStream(file)); nextId = stream.readInt(); accounts = (Map<Integer, Account>)stream.readObject(); accountingEntries = (Map<Integer, AccountingEntry>)stream.readObject(); stream.close(); } } Code 45: Method loadData() (Serialize) - 72 - Datenhaltung - Java-Serialisierung protected void storeData(File file) throws FileNotFoundException, IOException { ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file)); stream.writeInt(nextId); stream.writeObject(accounts); stream.writeObject(accountingEntries); stream.close(); } Code 46: Method storeData() (Serialize) Die Methode loadData() lädt die serialisierten Objekte aus der angegebenen Datei, die vorher durch storeData() gespeichert wurden. Dabei werden die HashMaps der Konten und Buchungen als Einheit behandelt und auf einmal verarbeitet. Java übernimmt automatisch die Verarbeitung der enthaltenen Objekte. Außerdem wird die ID der nächsten Buchung (nextId) mitgespeichert, damit der Zustand des Systems nach dem Laden wieder konsistent ist. 4.3.3.2. Proxy-Klasse Die Proxy-Klasse muß nun feststellen in welcher Datei die Objekte abgelegt werden sollen und damit die Methoden zum Laden und Speichern der Service-Klasse aufrufen. package de.fhr.vs_iw.serialize; public final class SerializeProxy implements IAccountingProxy { private AccountingService service = null; private File file = null; public void close() throws IOException { service.storeData(file); service = null; file = null; } } public IAccountingService connect(String[] args) throws IOException { if (args.length <= 0) { throw new IllegalArgumentException("Datei muß angegeben werden"); } file = new File(args[0]); service = new AccountingService(); try { service.loadData(file); } catch (ClassNotFoundException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } return service; } Code 47: Class SerializeProxy - 73 - Datenhaltung - Java-Serialisierung 4.3.4. Fazit Die Serialisierung von Java-Objekten stellt zwar eine einfache Möglichkeit dar, Daten zu speichern, zumal auch ohne großartiges Zutun Objekthierarchien verarbeitet werden können. Auch der Zugriff auf die Daten erfolgt sehr schnell, da alle Daten während der Verwendung des Systems im Speicher gehalten werden, und die De-/Serialisierung nur am Anfang bzw. am Ende erfolgt. Für eine dauerhafte Verarbeitung von Daten ist dieses Verfahren jedoch nicht geeignet, da die serialisierten Objekte an die Implementierung der Klassen zum Zeitpunkt der Serialisierung gebunden sind, und schnell eine Situation auftreten kann, in der vorhandene Daten nicht wieder eingelesen werden können, wenn an Klassen funktionale Änderungen durchgeführt worden sind. Das System ist daher zu unflexibel, weil die Implementierung der Geschäftslogik und die letztendliche Datenspeicherung zu sehr ineinander verwoben sind. - 74 - Datenhaltung - XML-Serialisierung über JAXB 4.4. XML-Serialisierung über JAXB 4.4.1. Allgemeines Die Java Architecture für XML Binding (JAXB) [69] ist ein Framework, um die Daten aus einem XML-Schema an Java-Klassen zu binden und sogar aus einer XML-Schema-Instanz heraus zu erzeugen. Dadurch kann mit XML-Daten direkt über Java-Objekte gearbeitet werden, und ein Entwickler muß nicht manuell auf XML-Parsing-Technologien wie DOM (Document Object Model) oder SAX (Simple API for XML) zurückgreifen [70]. Das Framework ist als Teil des Java-Web-Service-Developer-Pack von Sun unter http://java.sun.com/webservices/downloads/webservicespack.html oder separat als Referenz-Implementierung unter https://jaxb.dev.java.net/ erhältlich. Ein Satz von Bibliotheken aus dem jeweiligen Paket muß zum CLASSPATH der Anwendung hinzugefügt werden. jaxb1-impl.jar jaxb-api.jar jaxb-impl.jar jaxb-xjc.jar Die Bibliotheken enthalten die notwendigen Schnittstellen, Annotations und Werkzeuge zur Verwendung von JAXB und der automatischen Erzeugung der notwendigen Artefakte und Dateien. 4.4.2. Annotations Die Verbindung des XML-Schemas zu den Java-Klassen wird durch JSR-250-Annotations [65] aus dem Paket javax.xml.bind.annotations [71] im Quellcode beschrieben. Standardmäßig werden die öffentlichen Eigenschaften einer Klasse, für die entsprechende GetterMethoden vorhanden sind (JavaBean-Style) und einen bekannten Datentyp zurückgeben in das XML-Schema abgebildet, auch wenn diese Eigenschaften nicht explizit durch Annotations ausgezeichnet sind. Ausgehend von einer bestimmten Klasse als Wurzel-Element wird dadurch eine beliebige komplexe Objekthierarchie auf eine XML-Datei abgebildet. Durch die Annotations wird dieser Vorgang gesteuert. 4.4.2.1. Package-Annotations Die erste Gruppe von XML-Annotations hat für eine gesamte Java-Package Gültigkeit und beschreibt allgemeine Einstellung für die Zuordnung zum XML-Schema und die Verarbeitung der Java-Klassen. - 75 - Datenhaltung - XML-Serialisierung über JAXB Annotation @XmlSchema Beschreibung und Standardwerte Ordnet ein Java-Paket einem XML-Namensraum zu. @XmlSchema ( xmlns = {}, namespace = "", elementFormDefault = XmlNsForm.UNSET; attributeFormDefault = XmlNsForm.UNSET, ) Gibt die standardmäßige Verarbeitungsvariante für Felder und Eigenschaften an. @XmlAccessorType @XmlAccessorType ( value = AccessType.PUBLIC_MEMBER ) Stellt die Anordnung der XML-Elemente für Felder und Eigenschaften einer Klasse ein. @XmlAccessorOrder @XmlAccessorOrder ( value = AccessorOrder.UNDEFINED ) Eine eigene Zuordnung zu einem integrierten XML-Schema-Typ. @XmlSchemaType @XmlSchemaTypes @XmlSchemaType ( namespace = "http://www.w3.org/2001/XMLSchema", type = DEFAULT.class ) Faßt mehrere @XmlSchemaType Annotations zusammen. Tabelle 4: JAXB Paket-Annotations 4.4.2.2. Klassen-Annotations Eine Java-Klasse wird auf einen komplexen Typ des XML-Schemas abgebildet, wobei eine Klasse dabei als Wurzel des XML-Baumes bzw. als "Document-Element" beschreiben wird. Annotation @XmlType Beschreibung und Standardwerte Bildet eine Java-Klasse auf einen Schema @XmlType ( name = "##default", propOrder = {""}, namespace = "##default" , factoryClass = DEFAULT.class, factoryMethod = "" ) Definiert eine Klasse als Wurzel-Element des Schemas. @XmlRootElement @XmlRootElement ( name = "##default", namespace = "##default" ) Tabelle 5: JAXB Klassen-Annotations - 76 - Datenhaltung - XML-Serialisierung über JAXB 4.4.2.3. Enum-Annotations Die folgenden Annotations können bei einer Java-Enumeration verwendet werden. Annotation Beschreibung und Standardwerte Ordnet eine Enumeratioen einem einfachen XML-Typ zu. @XmlEnum @XmlEnum ( value = String.class ) Bildet eine einzelne Konstante einer Enumeration in XML ab. @XmlEnumValue @XmlEnumValue ( value = "##default" ) Bildet eine Enumeration durch einen komplexen Typ im XML-Schema ab. @XmlType @XmlType ( name = "##default", propOrder = {""}, namespace = "##default" , factoryClass = DEFAULT.class, factoryMethod = "" ) Gibt die Enumeration als Wurzel-Element an. @XmlRootElement @XmlRootElement ( name = "##default", namespace = "##default" ) Tabelle 6: JAXB Enum-Annotations 4.4.2.4. Field-Annotations Die Eigenschaften einer Klasse (Getter-Methoden) bzw. die Felder werden durch die folgenden Annotations ausgezeichnet. Annotation @XmlElement @XmlElements Beschreibung und Standardwerte Bildet ein Feld als fest benanntes XML-Element ab. @XmlElement ( name = "##default", nillable = false, namespace = "##default", type = DEFAULT.class, defaultValue = "\u0000" ) Faßt mehrere @XmlElement Annotations zusammen. - 77 - Datenhaltung - XML-Serialisierung über JAXB Bildet ein Feld als dynamisch zugeordnetes XML-Element ab. @XmlElementRef @XmlElementRefs @XmlElementRef ( name = "##default", namespace = "##default", type = DEFAULT.class, ) Faßt mehrere @XmlElementRef Annotations zusammen. Wird für eine Liste verwendet, die XML-Elemente aufnimmt, die keinem Feld direkt zugeordnet werden können. @XmlAnyElement @XmlAnyElement ( lax = false, value = W3CDomHandler.class ) Ordnet ein Feld einem XML-Attribut zu. @XmlAttribute @XmlAnyAttribute @XmlTransient @XmlValue @XmlID @XmlIDREF @XmlList @XmlMixed @XmlMimeType @XmlAttachmentRef @XmlInlineBinaryData @XmlElementWrapper @XmlAttribute ( name = ##default, required = false, namespace = "##default" ) Wird für eine Liste verwendet, die XML-Attribute aufnimmt, die keinem Feld direkt zugeordnet werden können. Verhindert daß ein Feld im XML-Schema abgebildet wird. Ordnet ein Feld einem einfachen XML-Typ zu. Verwendet ein Feld als XML-ID. Verwendet ein Feld als Referenz auf eine XML-ID. Bildet die Objekte einer Kollektion nicht als XML-Elemente, sondern als zusammengesetzte Liste ihrer Werte ab. Gibt ein Feld zur Aufnahme von XML-Mixed-Content an. Assoziiert einen MIME-Typ mit einem Feld. Spezifiziert ein Feld als URI zu einem MIME-Anhang. Gibt an, daß binäre Daten in base64-codierter Form direkt im XML-Element stehen sollen, und nicht XOP-codiert werden sollen. Bewirkt die Erstellung eines zusätzlichen Elements um die XML-Darstellung einer Java-Klasse herum. Wird typischerweise bei Kollektionen verwendet. @XmlElementWrapper ( name = "##default", namespace = "##default", nillable = false ) Tabelle 7: JAXB Feld-Annotations - 78 - Datenhaltung - XML-Serialisierung über JAXB 4.4.2.5. Objekt-Fabrik-Annotations Die folgenden Annotations zeichnen eine eigene Objekt-Fabrik aus, die Objekte für bestimmte XML-Elemente mit Daten instanzieren soll. Annotation @XmlRegistry @XmlElementDecl Beschreibung und Standardwerte Markiert eine Klasse als Objekt-Fabrik zur Instanzierung von Objekten für XML-Elemente. Ordnet eine Fabrik-Methode einem XML-Element zu. @XmlElementDecl ( scope = GLOBAL.class, namespace = "##default", substitutionHeadNamespace = "##default", substitutionHeadName = "" ) Tabelle 8: JAXB Objekt-Fabrik-Annotations 4.4.2.6. Adapter-Annotations Mit diesen Annotations werden eigene Adapter-Klassen für die XML-Verarbeitung definiert. Annotation @XmlJavaTypeAdapter @XmlJavaTypeAdapters Beschreibung und Standardwerte Verwendet eine Adapter-Klasse, die die Schnittstelle XmlAdapter implementiert, um eine Java-Klasse manuell auf das XML-Schema abzubilden. @XmlJavaTypeAdapter ( type = DEFAULT.class ) Faßt mehrere @XmlJavaTypeAdapter Annotations zusammen. Tabelle 9: JAXB Adapter-Annotations 4.4.3. Anwendung der Annotations bei den Buchhaltungs-Klassen Die beschrieben Annotations werden nun auf die bereits vorhandenen Klassen aus dem lokalen Modul angewandt. Die Implementierung der Geschäftslogik wird direkt übernommen, und es werden eine nötige Erweiterungen der Klassen durchgeführt. 4.4.3.1. Service-Klasse Die Service-Klasse wird als @XmlRootElement gekennzeichnet. Wichtig ist die zusätzliche Angabe der Ordnung der Elemente. Die Konten müssen vor den Buchungen verarbeitet werden, damit es beim Einlesen nicht zu Ausnahmen kommt. Für die HashMaps werden Getter und Setter-Methoden erzeugt, wodurch sie von JAXB bereits verarbeitet werden können. Außerdem ist noch ein Standard-Konstruktor ohne Argumente nötig. Die ID der nächsten Buchung (nextId) wird als XML-Attribut (@XmlAttribute) gespeichert. - 79 - Datenhaltung - XML-Serialisierung über JAXB package de.fhr.vs_iw.jaxb.v1; @XmlRootElement @XmlType(propOrder = { "accountMap", "accountingEntryMap" }) public final class AccountingService implements IAccountingService { private = new private = new Map<Integer, AccountingEntry> accountingEntryMap HashMap<Integer, AccountingEntry>(); Map<Integer, Account> accountMap HashMap<Integer, Account>(); @XmlAttribute private int nextId = 0; @XmlID @XmlAttribute @SuppressWarnings("unused") private final String xmlid = "service"; public AccountingService() {} public Map<Integer, AccountingEntry> getAccountingEntryMap(); public Map<Integer, Account> getAccountMap(); public void setAccountingEntryMap( Map<Integer, AccountingEntry> accountingEntryMap); public void setAccountMap(Map<Integer, Account> accountMap); } ... Code 48: Class AccountingService (JAXB - Variante 1) Ein zusätzliches Datenfeld muß noch eingeführt werden, und zwar eine XML-ID, die später als Referenz für den notwendigen Rückbezug der Buchungen zu einer Service-Instanz dient. Diese ID muß ein String sein, wird als @XmlID gekennzeichnet und als Attribut gespeichert. 4.4.3.2. Buchungs-Klasse Auch die bereits vorhandene Buchungsklasse wird im Prinzip direkt übernommen und lediglich um einige Punkte erweitert. package de.fhr.vs_iw.jaxb.v1; public final class AccountingEntry implements IAccountingEntry { @XmlIDREF @XmlAttribute private AccountingService service = null; public AccountingEntry() {} public void setId(Integer id); public void afterUnmarshal(Unmarshaller u, Object parent); } ... Code 49: Class AccountingEntry (JAXB - Variante 1) - 80 - Datenhaltung - XML-Serialisierung über JAXB Durch @XmlIDREF wird der Bezug zur Service-Klasse im XML-Schema abgebildet. Ansonsten muß nur noch ein Standard-Konstruktor und eine Setter-Methode für die ID hinzugefügt werden. Da von der Buchungs-Schnittstelle her bereits für alle Felder öffentliche Getter- und Setter-Methoden vorhanden sind, können alle Eigenschaften der Klasse bereits von JAXB verarbeitet werden. Da die Buchungs-Klasse als Wert-Typ in der Buchungs-HashMap der Service-Klasse verwendet wird, wird sie im XML-Schema implizit als XML-Typ (@XmlType) behandelt, auch ohne daß die Annotation explizit vorhanden ist. 4.4.3.3. Callback-Methoden Neben den semantischen Erweiterungen der Buchungs-Klasse ist auch noch eine rein funktionale Erweitung notwendig. Da im lokalen Buchhaltungsmodul eine mehrfache Datenspeicherung, der Buchungen, sowohl in der Service-Klasse, als auch in Soll- und Haben-Buchungen getrennt beim jedem Konto erfolgt, muß auch bei den Konten diese Zuordnung, beim Einlesen der Buchhaltungsdaten aus einer XML-Datei, wiederhergestellt werden. Dazu wird eine Callback-Methode verwendet. public void afterUnmarshal(Unmarshaller u, Object parent) throws AccountingException { ((Account)service.getAccountByNumber(debitAccount)).addDebitEntry(this); ((Account)service.getAccountByNumber(creditAccount)).addCreditEntry(this); } Code 50: Methode afterUnmarshal() Wenn diese Methode in einer Klasse vorhanden ist, so wird sie von JAXB immer nach dem Erzeugen eines neuen Objekts dieser Klasse und dem Setzen aller Eigenschaften aufgerufen. Diese Funktionalität wird in der Buchungs-Klasse genutzt, um die Buchung jeweils beim zugehörigen Soll- und Haben-Konto einzutragen, genauso wie beim Erfassen einer neuen Buchung. Es gibt entsprechend noch weitere Callback-Methoden, die in einer Klasse für die Verarbeitung mit JAXB verwendet werden können, in diesem Fall aber nicht benötigt werden. // Wird direkt nach dem Instanzieren eines "leeren" Objekts aufgerufen public void beforeUnmarshal(Unmarshaller u, Object parent); // Wird vor dem Auslesen der Eigenschaften eines Objekts aufgerufen public boolean beforeMarshal(Marshaller m, Object parent); // Wird nach der Verarbeitung zu einem XML-Element aufgerufen public void afterMmarshal(Marshaller m, Object parent); Das Object parent ist dabei immer eine zugeordnete Objektinstanz, die dem gerade verarbeiteten Element im XML-Baum übergeordnet ist, und deren Verarbeitung je nach Aufbau des Schemas und des aktuellen Vorgangs unter Umständen noch nicht abgeschlossen wurde. - 81 - Datenhaltung - XML-Serialisierung über JAXB 4.4.3.4. Konto-Klasse Die Konto-Klasse wird wie die Buchungs-Klasse nur übernommen und angepaßt. Da die komplexen Funktionalitäten, die bei den Buchhaltungsdaten notwendig sind, bereits vollständig von der Buchungs-Klasse abgedeckt werden, ist hier kaum Aufwand nötig. package de.fhr.vs_iw.jaxb.v1; public final class Account implements IAccount { public Account() {} public void setNumber(Integer number); } ... Code 51: Class Account (JAXB - Variante 1) Die Klasse wird um den fehlenden Standard-Konstruktor und die Setter-Methode für die Kontonummer erweitert, was bereits ausreicht, damit alle Daten eines Kontos in das Schema aufgenommen werden können. 4.4.4. Erzeugung des XSD-Schemas aus den annotierten Klassen Eigentlich könnten Daten mit den so annotierten Klassen bereits in einer XML-Datei abgebildet werden. Günstiger ist es jedoch, zuerst ein XSD-Schema aus den Klassen erzeugen zu lassen. Dadurch wird die Korrektheit der Annotations überprüft, und das erzeugte Schema kann später Verwendet werden, um eine XML-Datei mit Buchhaltungsdaten vor dem Einlesen zu validieren. 4.4.4.1. Generierung durch einen Ant-Vorgang durch einen entsprechenden Ant-Vorgang Erzeugt wird das Schema (com.sun.tools.jxc.SchemaGenTask), der in den JAXB-Bibliotheken enthalten ist. <?xml version="1.0" standalone="yes"?> <project basedir=".." default="generate-schema"> <property file="${basedir}/ant/build.properties" /> <target name="generate-schema"> <path id="classpath">...</path> <taskdef name="schemagen" classname="com.sun.tools.jxc.SchemaGenTask" classpathref="classpath" /> <delete file="${basedir}/generated/de/fhr/vs_iw/jaxb/schema-v1.xsd" /> <schemagen srcdir="${basedir}/src/de/fhr/vs_iw/jaxb/v1/" destdir="${basedir}/generated/de/fhr/vs_iw/jaxb/"> <schema namespace="" file="schema-v1.xsd" /> <classpath refid="classpath" /> <include name="AccountingService.java" /> </schemagen> </target> </project> Resource 1: Ant-Build-Datei jaxb-generate-schema-v1.xml - 82 - Datenhaltung - XML-Serialisierung über JAXB 4.4.4.2. Das Schema zu den HashMaps Das erzeugte Schema bildet die Java-HashMaps jeweils als paarweise XML-Elemente <key> und <value> mit dem entsprechenden XML-Typ ab. <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="accountingService" type="accountingService"/> <xs:complexType name="accountingService" final="extension restriction"> <xs:sequence> <xs:element name="accountMap"> <xs:complexType> <xs:sequence> <xs:element name="entry" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="key" minOccurs="0" type="xs:int"/> <xs:element name="value" minOccurs="0" type="account"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="accountingEntryMap"> <xs:complexType> <xs:sequence> <xs:element name="entry" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="key" minOccurs="0" type="xs:int"/> <xs:element name="value" minOccurs="0" type="accountingEntry"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="xmlid" type="xs:ID"/> </xs:complexType> <xs:complexType name="account" final="extension restriction"> <xs:sequence> <xs:element name="description" type="xs:string" minOccurs="0"/> <xs:element name="number" type="xs:int" minOccurs="0"/> <xs:element name="type" type="accountType" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="accountingEntry" final="extension restriction"> <xs:sequence> <xs:element name="amount" type="xs:double" minOccurs="0"/> <xs:element name="creditAccount" type="xs:int" minOccurs="0"/> <xs:element name="debitAccount" type="xs:int" minOccurs="0"/> <xs:element name="id" type="xs:int" minOccurs="0"/> <xs:element name="text" type="xs:string" minOccurs="0"/> </xs:sequence> <xs:attribute name="service" type="xs:IDREF"/> </xs:complexType> - 83 - Datenhaltung - XML-Serialisierung über JAXB <xs:simpleType name="accountType"> <xs:restriction base="xs:string"> <xs:enumeration value="PROCEEDS"/> <xs:enumeration value="PASSIVE"/> <xs:enumeration value="EXPENSE"/> <xs:enumeration value="AMOUNT"/> <xs:enumeration value="ACTIVE"/> </xs:restriction> </xs:simpleType> </xs:schema> Resource 2: schema-v1.xsd 4.4.4.3. Resultierende XML-Datei Aus diesem Schema ergibt sich vom Aufbau her folgende XML-Datei. <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?> <accountingService xmlid="service"> <accountMap> <entry> <key>2800</key> <value> <description>Bank</description> <number>2800</number> <type>ACTIVE</type> </value> </entry> ... </accountMap> <accountingEntryMap> <entry> <key>2</key> <value service="service"> <amount>100.0</amount> <creditAccount>4400</creditAccount> <debitAccount>6000</debitAccount> <id>2</id> <text>Kauf von Rohstoffen 1</text> </value> </entry> ... </accountingEntryMap> </accountingService> Resource 3: fibu-v1.xml 4.4.4.4. Probleme Die HashMaps sind eine etwas problematische Angelegenheit, da die XML-Struktur unnötig komplex wird. Außerdem hat die aktuelle Version 2 von JAXB einen Fehler, der erst in Version 3 behoben werden soll, durch den HashMaps nur dann richtig verarbeitet werden, wenn sie public (Feld oder Getter/Setter) und nicht mit Annotations versehen sind [72]. - 84 - Datenhaltung - XML-Serialisierung über JAXB 4.4.5. Implementierung der Klassen nach einem vorgegebenen Schema Wegen den Problemen mit den HashMaps werden im zweiten Ansatz die Klassen auf eine andere Weise neu implementiert, die für die Verarbeitung mit JAXB besser geeignet ist und eine einfachere XML-Struktur ergibt. 4.4.5.1. Service-Klasse Der wesentliche Aufbau der Service-Klasse entspricht der ersten Variante. Die Annotations für JAXB (@XmlRootElement, @XmlType(propOrder...), @XmlID, @XmlAttribute) sind gleichermaßen vorhanden, aber die HashMaps werden durch Set bzw. HashSet (möglich wäre auch List) ersetzt und als @XmlElement ausgezeichnet. package de.fhr.vs_iw.jaxb; @XmlRootElement @XmlType(propOrder = { "accountSet", "accountingEntrySet" }) public final class AccountingService implements IAccountingService { @XmlAttribute private int nextId = 0; @XmlID @XmlAttribute private String xmlid = "service"; @XmlElement(name = "accountingEntry") protected Set<AccountingEntry> accountingEntrySet = new HashSet<AccountingEntry>(); @XmlElement(name = "account") protected Set<Account> accountSet = new HashSet<Account>(); public AccountingService() {} ... public IAccount getAccountByNumber(Integer number) throws AccountingException { checkAccountNumber(number, true); for (Account account : accountSet) { if (account.getNumber().equals(number)) { return account; } } return null; } } ... Code 52: Class AccountingService (JAXB - Variante 2) Der Nachteil ist natürlich, daß fast alle Methoden neu implementiert werden müssen, und auf Buchungs- und Kontenobjekte nicht mehr direkt über einen Identifikator zugegriffen werden kann. Die beiden HashSets müssen jeweils manuell nach einem bestimmten Objekt in einer Schleife durchsucht werden. - 85 - Datenhaltung - XML-Serialisierung über JAXB 4.4.5.2. Konto-Klasse Die Konto-Klasse wird im Vergleich zur vorigen Variante etwas stärker abgeändert. Sie soll keine eigenen HashMaps für die Zuordnung der Soll- und Haben-Buchungen mehr enthalten, um die redundante Verwaltung dieser Informationen zu vermeiden. Dafür erhält nun auch diese Klasse einen Rückbezug (@XmlIDREF) zu einer Instanz der Service-Klasse. package de.fhr.vs_iw.jaxb.v2; public final class Account implements IAccount { @XmlIDREF @XmlAttribute private AccountingService service = null; public Account() {} public Set<Integer> getCreditEntries() { Set<Integer> credit = new HashSet<Integer>(); for (AccountingEntry entry : service.accountingEntrySet) { if (entry.getCreditAccount().equals(number)) { credit.add(entry.getId()); } } return credit; } public Set<Integer> getDebitEntries() { Set<Integer> debit = new HashSet<Integer>(); for (AccountingEntry entry : service.accountingEntrySet) { if (entry.getDebitAccount().equals(number)) { debit.add(entry.getId()); } } return debit; } } ... Code 53: Class Account (JAXB - Variante 2) Zum Abruf der Soll- und Haben-Buchungen wird auf den Set der Buchungen in der ServiceKlasse zurückgegriffen und jeweils die entsprechenden Buchungen herausgesucht. Dies bedeutet zwar zur Laufzeit einen etwas höheren Aufwand, vor allem wenn die Anzahl der Buchungen zunimmt, aber wie bereits erwähnt soll das Ziel dieser Implementierung eine einfachere XML-Struktur sein. 4.4.5.3. Buchungs-Klasse Bei der neuen Buchungs-Klasse ändert sich am wenigsten. Hier wird lediglich die CallbackMethode afterUnmarshall() wieder entfernt, da Aufgrund der nun nicht mehr vorhandenen HashMaps in der Konto-Klasse die Buchungen nach dem Laden in den Set der Service-Klasse nicht nochmals zugeordnet werden müssen. - 86 - Datenhaltung - XML-Serialisierung über JAXB package de.fhr.vs_iw.jaxb.v2; public final class AccountingEntry implements IAccountingEntry { @XmlIDREF @XmlAttribute private AccountingService service = null; public AccountingEntry(); public void setId(Integer id); ... } Code 54: Class AccountingEntry (JAXB - Variante 2) Da beim Laden einer Buchung aus der XML-Datei die angegebenen Konten aber immer noch in den Setter-Methoden überprüft werden, ist es weiterhin wichtig daß die Konten vor den Buchungen in der XML-Datei stehen, was bei der Service-Klasse auch angegeben wurde. 4.4.5.4. Schema und Daten Zur Erzeugung des neuen Schemas wird im Prinzip derselbe Ant-Vorgang wie vorher verwendet (siehe 4.4.4.1) nur mit dem anderen Paket als Quelle (de.fhr.vs_iw.jaxb.v2) und einem anderen Namen für die Schema-Datei (schema-v2.xsd). Das erzeugte Schema ist dann durchaus weniger komplex als im vorigen Fall. <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="accountingService" type="accountingService"/> <xs:complexType name="accountingService" final="extension restriction"> <xs:sequence> <xs:element name="account" type="account" maxOccurs="unbounded" minOccurs="0"/> <xs:element name="accountingEntry" type="accountingEntry" maxOccurs="unbounded" minOccurs="0"/> </xs:sequence> <xs:attribute name="xmlid" type="xs:ID"/> <xs:attribute name="nextId" type="xs:int" use="required"/> </xs:complexType> <xs:complexType name="account" final="extension restriction"> <xs:sequence> <xs:element name="description" type="xs:string" minOccurs="0"/> <xs:element name="number" type="xs:int" minOccurs="0"/> <xs:element name="type" type="accountType" minOccurs="0"/> </xs:sequence> <xs:attribute name="service" type="xs:IDREF"/> </xs:complexType> <xs:complexType name="accountingEntry" final="extension restriction"> <xs:sequence> <xs:element name="amount" type="xs:double" minOccurs="0"/> <xs:element name="creditAccount" type="xs:int" minOccurs="0"/> <xs:element name="debitAccount" type="xs:int" minOccurs="0"/> <xs:element name="id" type="xs:int" minOccurs="0"/> <xs:element name="text" type="xs:string" minOccurs="0"/> </xs:sequence> <xs:attribute name="service" type="xs:IDREF"/> </xs:complexType> - 87 - Datenhaltung - XML-Serialisierung über JAXB <xs:simpleType name="accountType"> <xs:restriction base="xs:string"> <xs:enumeration value="PROCEEDS"/> <xs:enumeration value="PASSIVE"/> <xs:enumeration value="EXPENSE"/> <xs:enumeration value="AMOUNT"/> <xs:enumeration value="ACTIVE"/> </xs:restriction> </xs:simpleType> </xs:schema> Resource 4: schema-v2.xsd Es basiert nun nur noch auf den zugehörigen XML-Elementen und XML-Typen zu den drei Klassen der Buchhaltung und kommt ohne zusätzliche oder zum Teil gar überflüssige Elemente aus. Daraus ergibt sich nach dem Speichern von Buchhaltungsdaten eine XML-Datei mit dem folgenden Aufbau. <?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?> <accountingService nextId="12" xmlid="service"> <account service="service"> <description>Bank</description> <number>2800</number> <type>ACTIVE</type> </account> <account service="service"> <description>Schlussbilanzkonto</description> <number>8010</number> <type>AMOUNT</type> </account> ... <accountingEntry service="service"> <amount>10000.0</amount> <creditAccount>2400</creditAccount> <debitAccount>2800</debitAccount> <id>9</id> <text>Bezahlung aus Verkauf 3</text> </accountingEntry> <accountingEntry service="service"> <amount>100.0</amount> <creditAccount>4400</creditAccount> <debitAccount>6000</debitAccount> <id>10</id> <text>Kauf von Rohstoffen 3</text> </accountingEntry> ... </accountingService> Resource 5: fibu-v2.xml Diese XML-Datei enthält nun nur noch die wirklich relevanten Elemente für die Buchhaltungsdaten, was auch einen Kernpunkt bei der Arbeit mit JAXB darstellt. Die Philosophie von JAXB geht eher in die Richtung, eine bestimmte XML-Struktur mit einem günstigen Schema vorzugeben, und die Java-Klassen zur Verarbeitung der XML-Daten an das Schema anzupassen und nicht umgekehrt. JAXB würde auch die Möglichkeit bieten die Klassen aus einem XSD-Schema automatisiert zu generieren. - 88 - Datenhaltung - XML-Serialisierung über JAXB 4.4.6. Proxy- und Utility-Klasse Nachdem nun das XML-Schema und die Zuordnung der XML-Daten zu Java-Klassen und Objekten definiert wurde, müssen die Daten noch eine XML-Datei gespeichert bzw. daraus gelesen werden. 4.4.6.1. Proxy-Klasse Eine angegebene XML-Datei mit Daten wird in der connect()-Methode der JAXB-ProxyKlasse ausgelesen, sofern sie vorhanden ist. Die Proxy-Klasse liefert eine Instanz der ServiceKlasse zurück, mit der dann das Buchhaltungssystem wie gewohnt arbeiten kann. Die Daten werden nur im Speicher der VM gehalten. Erst in der close()-Methode werden die Instanz der Service-Klasse und damit alle Daten in die XML-Datei zurückgeschrieben. package de.fhr.vs_iw.jaxb.v1; public final class JAXBProxy1 implements IAccountingProxy { private String filename = null; private AccountingService service = null; public JAXBProxy1() {} public void close() throws IOException { if (service != null && filename != null) { try { JAXBUtil.writeXML(AccountingService.class, service, filename); } catch (Exception e) { IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } } } } public IAccountingService connect(String[] args) throws IOException { if (args.length <= 0) { throw new IllegalArgumentException("Ein Dateiname muß " + "als erstes Arguemnt angegeben werden"); } filename = args[0]; if (!(new File(filename).exists())) { service = new AccountingService(); } else { try { service = JAXBUtil.readXML(AccountingService.class, filename, "schema-v1.xsd"); } catch (Exception e) { IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } } return service; } Code 55: Class JAXBProxy1 - 89 - Datenhaltung - XML-Serialisierung über JAXB Die Klasse JAXBProxy2 der zweiten Implementierungs-Variante ist grundsätzlich völlig identisch, nur daß sie zu einem anderen Java-Paket gehört und daher die andere Service-Klasse, eben der zweiten Variante verwendet. Jede der beiden Proxy-Klassen verwendet die Methoden der Utility-Klassen zum eigentlichen Zugriff auf die Daten. 4.4.6.2. Utility-Klasse Die notwendigen Aufrufe der JAXB-Klassen zur Laufzeit werden in den Methoden der Klasse JAXBUtil ausgeführt. package de.fhr.vs_iw.jaxb; public final class JAXBUtil { public static <T> T readXML(Class<T> clas, String filename, String schemaname) throws SAXException, JAXBException { public static void writeXML(Class clas, Object object, String filename) throws FileNotFoundException, JAXBException { private static Schema getSchema(String schemaResourceName) throws SAXException { } Code 56: Class JAXBUtil Die Methoden dieser Klasse werden von den Proxy-Klassen zur Verarbeitung der Buchhaltungsdaten in den XML-Dateien aufgerufen. 4.4.6.3. Speichern der Objekte In der Methode writeXML() der Utility-Klasse werden die Daten Objekte der Buchhaltungsklassen in eine XML-Datei gespeichert. public static void writeXML(Class clas, Object object, String filename) throws FileNotFoundException, JAXBException { JAXBContext cont = JAXBContext.newInstance(clas); Marshaller marshaller = cont.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1"); PrintStream ps = new PrintStream(new File(filename)); marshaller.marshal(object, ps); ps.close(); } Code 57: Method writeXML() (JAXBUtil) Zuerst wird der JAXB-Kontext mit der angegebenen Klasse (die Service-Klasse) instanziert. Die angegebene Klasse wird verarbeitet, und Aufgrund ihres Aufbaus und mit den vorhandenen Annotations wird der Kontext konfiguriert. Aus dem Kontext kann ein Marshaller für die Objekte der Klasse erzeugt werden, der die Umwandlung der Daten in das passende XML-Format durchführt. Über einen OutputStream erfolgt die Ausgabe des Objekts in die angegebene Datei. - 90 - Datenhaltung - XML-Serialisierung über JAXB 4.4.6.4. Laden der Objekte Zum Laden von Objekten mit den Daten aus einer XML-Datei enthält die Utility-Klasse die Methode readXML(). public static <T> T readXML(Class<T> clas, String filename, String schemaname) throws SAXException, JAXBException { JAXBContext ctx = JAXBContext.newInstance(clas); Unmarshaller um = ctx.createUnmarshaller(); if (schemaname != null) { um.setSchema(getSchema(schemaname)); } return (T)um.unmarshal(new File(filename)); } Code 58: Method readXML() (JAXBUtil) Auch hier wird zuerst ein JAXB-Kontext für die angegebene Klasse konfiguriert. Über einen Unmarshaller wird ein Objekt der angegebenen Klasse mit den Daten aus der angegebenen XML-Datei erzeugt. Wenn der Name einer Schema-Datei angegeben wird, wird diese mit der Methode getSchema() als Ressource geladen, und verwendet um die XML-Datei beim Einlesen zu validieren. private static Schema getSchema(String schemaResourceName) throws SAXException { SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI); return sf.newSchema(JAXBUtil.class.getResource(schemaResourceName)); } Code 59: Method getSchema() (JAXBUtil) Die Schema-Datei wird als Ressource in der Umgebung der Virtuellen Maschine gesucht und über eine Instanz der Schema-Fabrik als Objekt zur weiteren Verwendung geladen. 4.4.7. Fazit Die direkte Verwendung von Java-Objekten, die durch Annotations ausgezeichnet werden, stellt für den Entwickler eine einfach zu handhabende Möglichkeit zur Datenspeicherung dar. Allerdings kann in diesem Fall die Tatsache, daß die Datenverarbeitung eigentlich ausgehend von einer XML-Datei und den zugehörigen Schema erfolgt, die Flexibilität beim Aufbau der eigenen Java-Klassen einschränken. Außerdem ist die Datenspeicherung im XML-Format zwar sehr flexibel, und die Daten können fast beliebig weiterverarbeitet werden, aber bei großen Datenmengen auch sehr ineffizient, und man muß sich um den Lade- und Speichervorgang, und die Datenintegrität, selbst kümmern. - 91 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC 4.5. Direkter SQL-Datenbankzugriff mit JDBC 4.5.1. Allgemeines Das Ziel jeder Datenhaltung ist heute aber in der Regel ein SQL-Datenbanksystem. Auch die Buchhaltungsdaten sollen in einer Datenbank gespeichert werden. Zum Zugriff auf eine SQLDatenbank unter Java ist der entsprechende JDBC-Treiber, in unserem Beispiel für das MySQL-Datenbanksystem [11], nötig, der unter http://dev.mysql.com/downloads/connector/j/5.0.html heruntergeladen werden kann (siehe auch 6.2.3.2), und in den aktuellen classpath aufgenommen werden muß. Auf den Betrieb des MySQL-Datenbanksystems an sich soll hier aber nicht näher eingegangen werden, da es den Umfang dieses Tutorials übersteigen würde. 4.5.2. JDBC-Datenbankanbindung Der Zugriff auf Datenbank erfolgt bei Java über einheitlich über die JDBC-Schnittstelle (Java Database Connectivity) [37]. Für die jeweils gewünschte Datenbank muß nur der passende JDBC-Treiber vorhanden sein. 4.5.2.1. Verbindung aufbauen Die Proxy-Klasse baut über die Klasse java.sql.Connection eine Verbindung zum MySQL-Datenbanksystem auf. Dazu wird eine URL der Form java:mysql://host[:port]/datenbankname ein Benutzername und ein Paßwort angegeben. Der DriverManager liefert mit diesen Angaben durch Aufruf von getConnection() eine Instanz von Connection. package de.fhr.vs_iw.mysql; public final class MySQLProxy implements IAccountingProxy { private Connection con = null; public MySQLProxy() {} public IAccountingService connect(String[] args) throws IOException { if (args.length < 4) { throw new IllegalArgumentException( "use MySQLProxy host[:port] database user password"); } try { Class.forName("com.mysql.jdbc.Driver"); con = DriverManager.getConnection( "jdbc:mysql://" + args[0] + "/" + args[1], args[2], args[3]); } catch (Exception e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } return new AccountingService(con); } - 92 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC } public void close() throws IOException { try { con.close(); con = null; } catch (SQLException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } Code 60: Class MySQLProxy Durch den Aufruf von Class.forName("com.mysql.jdbc.Driver") wird der MySQLTreiber zuvor geladen, damit er zum Aufbau der Verbindung verfügbar ist. Bei JDBCZugriffen wird bei Fehlern eine SQLException geworfen, die hier in eine IOException verpackt wird, um der Schnittstellen-Spezifikation zu entsprechen (siehe 2.3.2). Die Verbindung (Connection) wird einer neuen Instanz der Service-Klasse übergeben, damit darüber die SQL-Anfragen und Anweisungen ausgeführt werden können. 4.5.2.2. Einzelne Werte Für Anfragen nach einzelnen Werten wird die Service-Klasse um eine parametrische QueryMethode erweitert, die für Anfragen an die Datenbank gedacht ist, die einen einzelnen Wert als Ergebnis zurückliefern (SELECT ... WHERE ...). protected synchronized <T> T query(Class<T> clas, String query, Object... args) throws IOException { try { PreparedStatement stmt = prepare(query, args); ResultSet rs = stmt.executeQuery(); T result = null; if (rs.next()) { result = (T)rs.getObject(1); } rs.close(); stmt.close(); return result; } catch (SQLException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } Code 61: Method query() (MySQL) Die Funktion liefert den ersten Wert der ersten Ergebniszeile einer Abfrage mit dem Typ der angegebenen Klasse (Class<T>) zurück. Beim Aufruf von getObject() eines ResultSet wird ein Java-Objekt, das zum Datentyp der angefragten Spalte einer Tabelle in der Datenbank paßt, zurückgegeben. Wenn die SQL-Anfrage passend formuliert wird, erlaubt dies einen effizienten Zugriff auf einzelne spezifische Werte in der Datenbank. - 93 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC 4.5.2.3. Auflistungen Die Abfrage von einzelnen Werten ist jedoch nicht ganz ausreichend, um die spezifizierten Funktionalitäten der Buchhaltung abzudecken. Auch eine Auflistung mit mehreren Ergebnissen (SELECT ...) muß abgefragt werden können, wozu eine eigene Query-Methode dient, die alle Zeilen eines ResultSet verarbeitet. protected synchronized <T> HashSet<T> querySet(Class<T> clas, String query, Object... args) throws IOException { try { PreparedStatement stmt = prepare(query, args); ResultSet rs = stmt.executeQuery(); HashSet<T> set = new HashSet<T>(); while (rs.next()) { set.add((T)rs.getObject(1)); } rs.close(); stmt.close(); return set; } catch (SQLException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } Code 62: Method querySet() (MySQL) Die Methode liefert einen HashSet<T> mit Elementen des angegebenen Typs zurück. Dazu werden die Werte aus der ersten Spalte aller Ergebniszeilen einer entsprechend formulierten Anfrage verwendet. 4.5.2.4. Anweisungen Schließlich gibt ein noch SQL-Befehle, die kein Ergebnis liefern (INSERT, UPDATE, DELETE). Für diese Art von Anweisungen wird eine andere Methode verwendet. protected synchronized void execute(String query, Object... args) throws IOException { try { PreparedStatement stmt = prepare(query, args); stmt.execute(); stmt.close(); } catch (SQLException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } Code 63: Method execute() (MySQL) - 94 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC 4.5.2.5. PreparedStatements Alle SQL-Befehle werden über die JDBC-Klasse PreparedStatement ausgeführt, wodurch beliebige Java-Objekte als SQL-Parameter verwendet werden. private PreparedStatement prepare(String query, Object... args) throws SQLException { PreparedStatement stmt = con.prepareStatement(query); for (int i = 0; i < args.length; i++) { stmt.setObject(i + 1, args[i]); } return stmt; } Code 64: Method prepare() (MySQL) Der Query-String sollte formuliert werden, daß er Platzhalter ? für Parameter enthält, z.B. UPDATE accountingentry SET text=? WHERE id=? die dann der Reihe nach mit den Werten der angegebenen Objekte gefüllt werden. Die Methode setObject() konvertiert die Java-Objekte nach internen Vorgaben in SQLDatentypen. Eine SQL-Anweisung muß natürlich nicht zwingend Parameter enthalten. 4.5.3. SQL-Anweisungen Da nun die notwendigen Zugriffsfunktionen erstellt wurden, können die Daten des Buchhaltungssystems abgefragt und verändert werden. Die Daten stammen dann immer direkt aus der Datenbank, die Geschäftslogik bleibt in den Klassen implementiert. 4.5.3.1. Datenbank-Schema Zuerst muß ein Datenbank-Schema für die Buchhaltung erstellt werden. Es ist jeweils eine Tabelle für Konten und eine Tabelle für Buchungen nötig, die mit den entsprechenden SQLBefehlen angelegt werden. CREATE TABLE `account` ( `number` int(10) NOT NULL, `accounttype` int(10) default NULL, `description` varchar(255) default NULL, PRIMARY KEY (`number`) ) Type=MyISAM; CREATE TABLE `accountingentry` ( `id` int(10) NOT NULL auto_increment, `text` varchar(255) default NULL, `amount` double default NULL, `creditaccount` int(10) default NULL, `debitaccount` int(10) default NULL, PRIMARY KEY (`id`) ) Type=MyISAM; Resource 6: SQL-Befehle zum Anlegen der Konten- und Buchungs-Tabelle - 95 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC 4.5.3.2. Service-Klasse Jede Methode der Service-Klasse führt SQL-Abfragen aus, und arbeitet immer direkt mit den Daten der Datenbank. calculateBalance() SELECT SUM(amount) FROM accountingentry WHERE debitaccount=? GROUP BY debitaccount SELECT SUM(amount) FROM accountingentry WHERE creditaccount=? GROUP BY creditaccount createAccount() INSERT INTO account SET number=?, accounttype=?, description=? createAccountingEntry() LOCK TABLES accountingentry WRITE INSERT INTO accountingentry SET debitaccount=?, creditaccount=?, amount=?, text=? SELECT LAST_INSERT_ID() FROM accountingentry UNLOCK TABLES getAccountingEntries() SELECT id FROM accountingentry getAccounts() SELECT number FROM account removeAccount() DELETE FROM account WHERE number=? removeAccountingEntry() DELETE FROM accountingentry WHERE id=? Resource 7: SQL-Befehle der Service-Klasse (MySQL) Bei der Methode createAccountingEntry() sind besondere Maßnahmen nötig, da die ID einer neuen Buchung von der Datenbank vergeben wird. Zuerst wird die Tabelle der Buchungen gesperrt, damit keine anderen Befehle dazwischen kommen können. Nach dem Einfügen der neuen Buchung kann mit der MySQL-Funktion LAST_INSERT_ID() die ID der neuen Buchung abgerufen werden. Danach wird die Tabelle wieder entsperrt. Die Konten- und Buchungsobjekte werden wie vorher schon in HashMaps verwaltet und jeweils für eine bestimmte Nummer bzw. ID erzeugt und zurückgegeben. AccountingEntry entry = accountingEntries.get(id); if (entry == null) { entry = new AccountingEntry(this, id); accountingEntries.put(id, entry); } return entry; Account account = accounts.get(number); if (account == null) { account = new Account(this, number); accounts.put(number, account); } return account; Code 65: Verwaltung der Konten- und Buchungsobjekte (MySQL) - 96 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC 4.5.3.3. Konto-Klasse Allerdings speichert ein Objekt der Konto-Klasse nicht wie in den vorherigen Abschnitten seine Daten selbst, sondern lediglich die Kontonummer. Alle Daten werden direkt aus der Kontentabelle der Datenbank geholt und auch wieder dort abgelegt, wobei die Kontonummer als Schlüssel (... WHERE number=?) dient. getCreditEntries() SELECT id FROM accountingentry WHERE creditaccount=? getDebitEntries() SELECT id FROM accountingentry WHERE debitaccount=? getDescription() SELECT description FROM account WHERE number=? getType() SELECT accounttype FROM account WHERE number=? setDescription() UPDATE account SET description=? WHERE number=? setType() UPDATE account SET accounttype=? WHERE number=? Resource 8: SQL-Befehle der Konto-Klasse (MySQL) Neben der Kontonummer enthält jedes Kontenobjekt auch einen Rückbezug zur ServiceKlasse, um die drei Abfrage-Methoden aufrufen zu können. 4.5.3.4. Buchungs-Klasse Auch ein Buchungsobjekt speichert selbst nur die ID und greift damit auf die Datenbank zu. getAmount() SELECT amount FROM accountingentry WHERE id=? getCreditAccount() SELECT creditaccount FROM accountingentry WHERE id=? getDebitAccount() SELECT debitaccount FROM accountingentry WHERE id=? getText() SELECT text FROM accountingentry WHERE id=? setAmount() UPDATE accountingentry SET amount=? WHERE id=? setCreditAccount() UPDATE accountingentry SET creditaccount=? WHERE id=? setDebitAccount() UPDATE accountingentry SET debitaccount=? WHERE id=? - 97 - Datenhaltung - Direkter SQL-Datenbankzugriff mit JDBC setText() UPDATE accountingentry SET text=? WHERE id=? Resource 9: SQL-Befehle der Buchungs-Klasse (MySQL) Die Daten stammen immer direkt aus der Buchungstabelle der Datenbank und werden bei Änderungen auch wieder direkt dort gespeichert. Darüber hinaus ist auch nur wieder der Rückbezug zur Service-Klasse, zum Aufruf der Abfrage-Methoden, als Feld vorhanden. 4.5.4. Fazit Auf dem MySQL-Datenbanksystem und der JDBC-Schnittstelle basierend lassen sich die Daten ordentlich und sicher speichern und abrufen. Auch der gleichzeitige Zugriff durch mehrere Clients auf die Daten ist möglich wodurch sich bereits ein erster Ansatz eines verteilten Systems ergibt, das nicht explizit aufgebaut werden muß. Nachteilig ist allerdings, daß die SQL-Abfragen manuell angegeben und fest im Programmcode enthalten sind. Wenn sich Änderungen am Aufbau der Daten ergeben, müssen die SQLAnweisungen und das Datenbank-Schema manuell angepaßt werden. Und auch die Portabilität ist eingeschränkt, da durch die JDBC-Schnittstelle zwar eine einheitliche Schnittstelle zum Zugriff auf verschiedene Datenbanksysteme bereitgestellt wird, aber die unterstützten SQL-Anweisungen doch unterschiedlich sein können, obwohl SQL eigentlich standardisiert ist.. Deshalb muß an dieser Stelle noch etwas mehr Aufwand betrieben werden, um eine umfassende und einfach verwendbare Datenhaltung zu realisieren, die vom verwendeten Datenbanksystem unabhängig ist oder aber auf die speziellen Eigenheiten der verschiedenen Datenbanksysteme eingehen kann. - 98 - Datenhaltung - Hibernate 4.6. Hibernate Da das Hibernate-Persistenz-Framework [27] auch im JBoss-Application-Server (siehe 6.2) integriert ist, und später bei den Enterprise Java Beans (siehe 6.3) auch wieder verwendet wird, soll hier auf die Funktionalität ausführlicher eingegangen werden, als bei den vorherigen Themen. 4.6.1. Allgemeines Hibernate ist ein Persistenz-Framework, das zwischen einer Anwendung und der darunterliegenden JDBC-Datenbank [37] als zusätzliche AbstraktionsSchicht fungiert und einem Entwickler einen Großteil der Aufgaben bezüglich der Datenhaltung abnimmt. Anwendung Persistente Objekte Hibernate XML-Mapping Annotations hibernate. properties Datenbank Dabei werden auch andere etablierte Java-Standards wie JTA (Java Transaction API) [51] für Transaktionen und JNDI (Java Naming and Directory Interface) [17] zur Verbindung mit einer Datenbank unterstützt. Tabelle 10: Einbindung von Hibernate als zusätzliche Schicht Das Hibernate-Framework ist sehr flexibel, und man kann mehr oder weniger Funktionalitäten der API verwenden, und entsprechend den fehlenden Teil in der eigenen Anwendung implementieren. Bei vollständiger Verwendung von Hibernate wird die eigene Anwendung vollständig von der Datenbank abstrahiert, wobei man jedoch immer noch jederzeit direkt auf die Datenbank zugreifen kann. Transiente Objekte Anwendung Persistente Objekte SessionFactory TransactionFactory JNDI Session ConnectionProvider JDBC Transaction JTA Datenbank Tabelle 11: Schematisher Aufbau der Hibernate-Komponenten Die Funktion und Verwendung der einzelnen Komponenten wird im Folgenden schrittweise näher erläutert. - 99 - Datenhaltung - Hibernate 4.6.1.1. Persistenz von Objekten Die Funktionalität von Hibernate ist auf den objektorientierten Aufbau von Java ausgelegt, so daß man die meiste Zeit nur mit Java-Objekten arbeitet und sich um die genauen Vorgänge zum Zugriff auf die Datenbank, wie Transaktionen und SQL-Anweisungen, nach der einmaligen Konfiguration von Hibernate nicht mehr kümmern muß. Hibernate übernimmt das Speichern der Objekte und stellt diese später wieder zur Verfügung. Auch Sammlungen von Objekten und komplexe Objekthierarchien können von Hibernate verwaltet und gespeichert werden. 4.6.1.2. Arbeitsweise mit POJOs Hibernate arbeitet mit beliebigen Java-Klassen, die wenig spezielle Anforderungen (z.B. Implementierung einer bestimmten Schnittstelle) erfüllen müssen (POJO - Plain Old Java Object), sich aber an folgende Vorgaben halten können bzw. sollten, um ein optimales Verhalten bei der Verwendung mit Hibernate zu erzielen [67]: Einen Standard-Konstruktor ohne Parameter sollte eine persistente Klasse definieren, damit Hibernate ein Objekt über Constructor.newInstance() instanzieren kann. Der Konstruktor muß dafür nicht einmal als public deklariert sein. Außerdem ist es sinnvoll jeder Klasse einen Identifikator mitzugeben, der (unter Umständen unabhängig von der Geschäftslogik) später als Primär-Schlüssel in der Datenbank dient. Ein solcher Identifikator ist aber auf jeden Fall optional, d.h. man kann es auch Hibernate überlassen, die Objekte intern zu identifizieren. Bestimmte Funktionalitäten sind allerdings nur für Klassen verfügbar, die einen Identifikator definieren (Transitive Persistenz, siehe 4.6.2.6, Session.saveOrUpdate(), Session.merge(), siehe 4.6.5.2). In diesem Sinne ist auch das Überschreiben der euqals() und hashCode() Methoden sinnvoll (in speziellen Fällen sogar erforderlich), damit sich die Objekte richtig Verhalten (z.B. in einem Set). Dabei sollte man sich jedoch nicht unbedingt auf die Identifikatoren beziehen, die auch als Datenbank-Schlüssel dienen bzw. von Hibernate vergeben werden, sondern eigene Schlüssel und Vergleichsmöglichkeiten im Sinne der gewünschten Geschäftslogik verwenden. Es bietet sich an, Zugriffmethoden auf die Felder der Klasse (Getter/Setter) im JavaBean-Style (z.B. getBla(), isBla(), setBla()) zu definieren, die dann von Hibernate automatisch verwendet werden. Man kann Hibernate aber auch anweisen, direkt auf die Felder zuzugreifen. Eine Klasse sollte nicht als final deklariert sein, oder aber alle Zugriffsmethoden über eine Schnittstelle implementieren, damit Hibernate zur Laufzeit dynamisch ProxyKlassen dafür (bzw. für die Schnittstelle) erzeugen kann, zur Verwendung von "lazy association fetching" (wird hier nicht näher beschrieben, siehe 6.3.3.3). - 100 - Datenhaltung - Hibernate 4.6.1.3. OR-Mapping Der Kernpunkt von Hibernate ist allerdings das OR-Mapping, die Objekt/relationale Zuordnung der Java-Klassen zu den Tabellen in einer relationalen Datenbank. Diese Zuordnung ist auf Java bezogen und baut auf den persistenten Klassen auf, nicht auf den Tabellen der Datenbank. Dabei werden die Felder von persistenten Klassen, ihre Beziehungen zueinander, und die gewünschte Abbildung in die Datenbank abstrakt beschrieben. Es wird festgelegt wie Beziehungen oder Vererbungshierarchien gespeichert werden, ob bei Sammlungen von Objekten, alle Objekte zusammen automatisch gespeichert werden sollen, oder ob jedes Objekt für sich manuell persistiert werden muß. Die Beschreibung des OR-Mapping kann entweder in einer separaten XML-Datei stehen, oder aber durch Annotations [65] direkt im Quellcode erfolgen. 4.6.2. Annotations Die speziellen Annotations zur Beschreibung von Persistenz werden durch die Enterprise JavaBeans 3.0 Spezifikation (JSR-220 - EJB3) [66] und die Java Persistence API (JPA) im Paket javax.persistence definiert. Hibernate unterstützt dabei alle Aspekte dieser Spezifikation vollständig, und darüber hinaus noch eigene Erweiterungen [68] auf die hier aber nicht weiter eingegangen wird. 4.6.2.1. Entitäten Durch die Annotation @Entity wird eine POJO-Klasse als persistente Klasse (Entitiy-Bean) deklariert. Normal würden die Klassen dann in einer Tabelle der Datenbank mit demselben Namen gespeichert. Dies kann durch die zusätzliche Angabe von @Table angepaßt werden. @Entity @Table(name = "Konten") public class Account { Long number; @Id public Long getNumber() { return number; } } public void setNumber(Long number) { this.number = number; } Code 66: Class Account (Hibernate-Beispiel) 4.6.2.2. Identifikatoren - Schlüssel Die Kontonummer wird im obigen Beispiel durch @Id als Primär-Schlüssel definiert und muß von der eigenen Anwendung vergeben werden. Man kann den Identifikator aber auch von Hibernate erzeugen lassen, durch die Annotation @GeneratedValue. - 101 - Datenhaltung - Hibernate Dafür gibt es mehrere mögliche Strategien zur Erzeugung des Wertes: benutzt einen @TableGenerator für den Identifikator IDENTITY die Datenbank erzeugt eine Identitäts-Spalte SQEUENCE fortlaufende Numerierung AUTO automatische Auswahl von TABLE, IDENTITY oder SEQUENCE ja nach zugrundeliegender Datenbank, z.B: o MySQL IDENTITY mit Spalten-Eigenschaft auto_increment o Resin IDENTITY mit Spalten-Eigenschaft auto_increment o Postgres SEQUENCE o Oracle SEQUENCE TABLE @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } Allerdings ist es am günstigsten den Typ AUTO zu verwenden, damit die Anwendung für verschiedene Datenbanksysteme portabel bleibt. 4.6.2.3. Einfache Daten Von allen als @Entity gekennzeichneten Klassen ließt Hibernate die zu speichernden Felder aus. Standardmäßig werden alle Eigenschaften mit Getter-Methode verarbeitet, wobei man bei jeder dieser Methoden durch Annotations explizit angeben kann, wie das zugehörige Datum behandelt werden soll. @Basic @Transient @Temporal @Lob @Enumerated Keine Annotation das Feld wird einer Spalte in der Datenbank mit dem passenden Datentyp zugeordnet das Feld wird von Hibernate ignoriert Large Object - das Feld wird als CLOB oder serialisiert als BLOB in der Datenbank gespeichert für Datums- und Zeitangaben für Java-Enumerationen wird behandelt wie @Basic @Basic public Integer getNumber(); @Transient public Set<Integer> getDebitAccounts(); Zusätzlich kann bei den obigen Annotations (außer bei @Transient, dafür aber bei @Id und @Version) durch @Column die Spalte manuell genauer angegeben werden. Die Parameter für diese Annotation orientieren sich an gängigen Eigenschaften von Spalten in einer Datenbank. der Name der Spalte ob die Werte Spalte einzigartig sein müssen (Unique-Constraint) name = "columnName"; boolean unique default false; - 102 - Datenhaltung - Hibernate boolean nullable default true; boolean insertable default true; boolean updatable default true; String columnDefinition default ""; String table default ""; int length default 255; int precision default 0; int scale default 0; ob die Spalte keinen Wert (d.h. NULL) enthalten darf ob die Spalte bei INSERTAnweisungen verwendet wird ob die Spalte in UPDATEAnweisungen angegeben wird DDL-Anweisung zum Erzeugen der Spalte Tabelle zu der die Spalte gehört maximale Länge des Inhalts dezimale Genauigkeit dezimaler Maßstab @Id @Column(name = "Buchungs-ID") public Integer getId(); @Basic @Column(unique = true, nullable = false, length = 42) public String getDescription(); Hibernate kann ohne weitere Angaben die meisten Standard-Java-Datentypen (primitive Typen, viele der JDK-Klassen, Kollektionen) verarbeiten. 4.6.2.4. Beziehungen Falls eine persistente Klasse ein Feld enthält, das den Typ einer anderen persistenten Klasse hat, so haben diese beiden Entitäten eine Beziehung zueinander. Die einfachste Form ist eine 1:1-Beziehung von zwei Objekten der beiden Klassen zueinander, die mit der Annotation @OneToOne angegeben wird. @Entity public class Customer { @Id public Long getId() { return id; } @OneToOne @PrimaryKeyJoinColumn public Account getAccount() { return account; } ... } @Entity public class Account { @Id public Long getNumber() { return number; } ... } Code 67: Beispiel für 1:1-Beziehung mit Primär-Schlüssel - 103 - Datenhaltung - Hibernate Die zusätzliche Angabe von @PrimaryKeyJoinColumn bedeutet, daß der Primär-Schlüssel (@Id) von Account für die Zuordnung zu Customer verwendet wird, d.h. daß zusammengehörige Objekte beider Klassen denselben Wert für den Primär-Schlüssel erhalten. Im Gegensatz dazu kann auch ein zusätzlicher Fremd-Schlüssel für die Zuordnung eingeführt werden, der auf einer Seite der bidirektionalen Beziehung durch @JoinColumn deklariert wird, damit die Primär-Schlüssel der Klassen unabhängig voneinander vergeben werden können. @Entity public class Customer { @OneToOne @JoinColumn(name = "account_fk") public Account getAccount() { return account; } ... } @Entity public class Account { @OneToOne(mappedBy = "account") public Account getAccount() { return account; } ... } Code 68: Beispiel für 1:1-Beziehung mit Fremd-Schlüssel Auf der anderen Seite der bidirektionalen Beziehung wird die Annotation @OneToOne mit dem Parameter mappedBy verwendet, der angibt, welches Feld der ersten Klasse die Beziehung aufbaut. Diese Beziehung könnte jederzeit auch umgekehrt definiert werden. 4.6.2.5. Kollektionen Im nächsten Schritt werden 1:n und n:n-Beziehungen betrachtet, die durch die Java-Klassen Collection, List, Set und Map realisiert werden, wenn deren enthaltene Elemente wie bei 1:1-Beziehungen vom Typ einer persistenten Klasse sind. Kollektionen mit primitiven Typen oder Basis-Klassen werden dabei aber nicht unterstützt. Semantik Java Darstellung Annotation Bag java.util.List java.util.Collection @OneToMany oder @ManyToMany (u.U. mit @CollectionId) List java.util.List @OneToMany oder @ManyToMany Set java.util.Set @OneToMany oder @ManyToMany Map java.util.Map @MapKey Tabelle 12: Hibernate-Beziehungs-Semantik - 104 - Datenhaltung - Hibernate Auch hier ist die Einführung eines Fremd-Schlüssels, wie oben bereits durchgeführt, notwendig. Ohne die Angabe von @JoinColumn wird der Name des Schlüssels von Hibernate automatisch aus den Namen der beteiligten Entitäten erzeugt. @Entity public class Account { @OneToMany(mappedBy = "account") public Set<AccountingEntry> getAccountingEntries() { return accountingEntries; } ... } @Entity public class AccountingEntry { @ManyToOne public Account getAccount() { return account; } ... } Code 69: Beispiel für 1:n-Beziehung Die Klasse Account enthält eine Kollektionen von mehreren Objekten der Klasse AccountingEntry, so daß sich die beiden Entitäten in einer 1:n-Beziehung zueinander befinden, die durch die Annotations @OneToMany bzw. @ManyToOne auf der jeweiligen Seite der Beziehung definiert wird. Durch mappedBy wird wieder die zuordnende Eigenschaft auf der referenzierten Seite angegeben. Bei einer n:n-Beziehung zweier persistenter Klassen verhält es sich ähnlich, nur daß in der Datenbank dafür eine separate Tabelle und ein zugehöriger Fremdschlüssel bei jeder der Entitäten notwendig sind. @Entity public class Store { @ManyToMany public Set<Customer> getCustomers() { ... } } @Entity public class Customer { @ManyToMany(mappedBy = "customers") public Set<Store> getStores() { ... } } Code 70: Beispiel für n:n-Beziehung Durch die Annotation @ManyToMany, die auch durch Parameter wie mappedBy oder cascade ergänzt werden kann, wird die Beziehung etabliert. Durch eine weitere Annotation @JoinTable kann die zusätzlich erzeugte Tabelle manuell genau definiert werden [68]. - 105 - Datenhaltung - Hibernate 4.6.2.6. Kaskadierung Durch Kaskadierung, auch transitive Persistenz genannt, kann Hibernate angewiesen werden alle Objekte der Objekthierachie, von durch Beziehungen verbundenen persistenten Klassen, automatisch zu speichern. Dabei kann durch den Parameter cascade bei @OneToOne, @OneToMany und @ManyToMany genau angegeben werden, in welchen Fällen die referenzierten Objekte automatisch verarbeitet werden sollen. CascadeType.PERSIST CascadeType.MERGE CascadeType.REMOVE CascadeType.REFRESH CascadeType.ALL ruft die persist()-Methode für die Objekte auf ruft die merge()-Methode für die Objekte auf ruft die delete()-Methode für die Objekte auf ruft die refresh()-Methode für die Objekte auf ruft all diese Methoden auf Es können auch mehrere der Werte als Array gemeinsam angegeben werden. @OneToMany(cascade = CascadeType.ALL) @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) Wenn kein Kaskadierungstyp angegeben wird, müssen alle Objekte durch die Anwendung, durch expliziten Aufruf von persist() oder save() gespeichert werden, wobei bei der Reihenfolge auf die Abhängigkeiten von Fremd- und Primär-Schlüsseln geachtet werden muß. 4.6.3. Installation von Hibernate Zur Installation von Hibernate müssen die Hibernate-Pakete im Download-Bereich von http://www.hibernate.org heruntergeladen werden. Für die Verwendung der beschriebenen Funktionalitäten sind die Pakete "Hibernate Core" und "Hibernate Annotations" in der aktuellen Version 3.2.1.GA notwendig, die jeweils eine Vielzahl von Bibliotheken enthalten, die in den CLASSPATH der Anwendung aufgenommen werden müssen (siehe auch Anhang C). 4.6.3.1. Hibernate-Einstellungen Da Hibernate für die mögliche Verwendung in vielen verschiedenen Umgebungen konzipiert wurde, gibt es eine Vielzahl von Einstellungen. Dabei gilt jedoch der Grundsatz "Configuration by Exception", d.h. daß für fast alle Einstellungen die Standardwerte verwendet werden können, und nur sowenig wie nötig zusätzlich konfiguriert werden muß. Der Entwickler kann aber jederzeit in alle Aspekte der Konfiguration eingreifen. Die Konfiguration kann in der Anwendung programmiert oder in separaten XML- und Property-Dateien erfolgen. Die Datei hibernate.properties enthält meistens die Grundeinstellungen und wird von Hibernate automatisch als Ressource gesucht und verarbeitet. - 106 - Datenhaltung - Hibernate 4.6.3.2. Datenbankanbindung Die wichtigste Einstellung für Hibernate, die auf jeden Fall konfiguriert werden muß, ist die Anbindung an ein Datenbanksystem. Dafür müssen in der Konfiguration etliche Parameter angegeben werden. hibernate.dialect = org.hibernate.dialect.MySQLDialect hibernate.connection.driver_class = com.mysql.jdbc.Driver hibernate.connection.url = jdbc:mysql://localhost:3306/hibernate hibernate.connection.username = hibernate hibernate.connection.password = hibernate Resource 10: Hibernate Datenbankeinstellungen Für die gewünschte Datenbank müssen eine JDBC-Treiber-Klasse und der zugehörige Hibernate-Dialekt, der für die Erzeugung der passenden SQL-Anweisungen zuständig ist, verfügbar sein. Unter http://www.hibernate.org/hib_docs/v3/api/org/hibernate/dialect/package-summary.html kann die Liste der verfügbaren Dialekte und unterstützten Datenbanken abgerufen werden. 4.6.3.3. Weitere Einstellungen Zwei weitere Einstellungen können für die grundlegende Verwendung von Hibernate wichtig sein. Zum einen kann man sich mit hibernate.show_sql = true alle SQL-Anweisungen, die Hibernate erzeugt und an die Datenbank schickt, ausgeben lassen, was während der Entwicklungsphase einer Anwendung von großer Bedeutung sein kann. Zum anderen kann man festlegen wie Hibernate mit dem Datenbank-Schema für die persistenten Klassen umgeht. Der Parameter hibernate.hbm2ddl.auto = ... hat dafür mögliche vier mögliche Werte, die angeben auf welche Weise Hibernate das Datenbank-Schema automatisch verwaltet. das Schema wird nicht automatisch erzeugt, sondern nur auf seine Gültigkeit hin überprüft update das Schema wird erzeugt oder aktualisiert, wenn bereits vorhanden create das Schema wird automatisch erzeugt, wenn es nicht vorhanden ist create-drop das Schema wird erzeugt und beim expliziten Schließen von Hibernate wieder gelöscht validate Bei allen Einstellungen ist besonders auf die richtige Schreibweise zu achten, da sie sonst von Hibernate nicht erkannt und die Standardwerte verwendet werden. - 107 - Datenhaltung - Hibernate 4.6.4. Zugriff auf Hibernate Nachdem nun alle Vorbereitungen getroffen wurden, wird Hibernate angewiesen nach diesen Vorgaben zu arbeiten, wodurch man seine eigene Anwendung auf Hibernate aufsetzen kann. 4.6.4.1. Erzeugen der Konfiguration Zentraler Punkt zur Verwendung von Hibernate in einer Java-Anwendung ist ein Konfigurationsobjekt. In diesem Fall wird Eines verwendet, das mit Annotations arbeitet. Dem Konfigurationsobjekt werden alle durch Annotations gekennzeichneten persistenten Klassen hinzugefügt. Die Datei hibernate.properties mit den Grundeinstellungen wird automatisch im aktuellen CLASSPATH gesucht und, wenn vorhanden, eingelesen. Außerdem ist es möglich weitere XML-Konfigurationsdateien oder in der Anwendung erstellte Property-Objekte mit weiteren Einstellungen anzugeben. AnnotationConfiguration cfg = new AnnotationConfiguration(); cfg.addAnnotatedClass(AccountingService.class); cfg.addAnnotatedClass(Account.class); cfg.addAnnotatedClass(AccountingEntry.class); sessionFactory = cfg.buildSessionFactory(); Das fertige Konfigurationsobjekt wird schließlich dazu verwendet, eine Sitzungs-Fabrik zu erstellen, die nach den angegebenen Parametern arbeitet. Die Sitzungs-Fabrik ist threadsicher und dient von nun an als Ausgangspunkt für alle weiteren Aktionen mit Hibernate. 4.6.4.2. Sitzungen Die Arbeit mit den persistenten Objekten erfolgt innerhalb einer Sitzung, die man von der Sitzungs-Fabrik erhält. Session session = sessionFactory.openSession(); Ein Sitzungs-Objekt ist nicht threadsicher und darf deshalb nur von einem einzelnen Thread begrenzt verwendet werden. Für die Verwendung des Objekts gibt es verschiedene Ansätze bezüglich der Lebensdauer einer Sitzung. session-per-operation: Eine bequeme Möglichkeit wäre es, eine eigene Sitzung für jede Operation zu öffnen, die Operation auszuführen, und die Sitzung wieder zu schließen. Dies ist jedoch eine schlechte Vorgehensweise, da in einer Anwendung normalerweise mehrere Operationen logisch zusammengehören, und auch die Performance stark darunter leiden könnte. session-per-request: Besser wäre es, eine Sitzung zu öffnen, die solange geöffnet bleibt, bis ein Benutzeraufruf des Systems vollständig verarbeitet ist. Alle DatenbankOperationen die für den Aufruf notwendig sind, können innerhalb einer Sitzung von einem Thread ausgeführt werden. Jede Datenbank-Operation wird von Hibernate auf jeden Fall als Transaktion ausgeführt, so daß einer Sitzung 1:1 immer eine Transaktion zugeordnet wird. - 108 - Datenhaltung - Hibernate 4.6.4.3. Transaktionen Häufig reicht diese eine implizite Transaktion jedoch nicht aus. Viele Anwendungen führen während eines Benutzeraufrufs eine längere Konversation (conversation) mit dem Benutzer (auch genannt Anwendungstransaktion - application transaction) durch, wobei meist längere Wartezeiten auf Benutzereingaben (think time) eingeschlossen sind. Wird nun nach dem Muster session-per-request vorgegangen bleibt eine Datenbanktransaktion während der think time offen, und zugeordnete Datenbank-Ressourcen bleiben gesperrt, was z.B. für die Skalierung bezüglich der Benutzerzahl sehr schlecht ist. Deshalb umfaßt eine Anwendungstransaktion meistens mehrere Datenbanktransaktionen, zuerst zum Laden von Daten und später zum Speichern von geänderten Daten, die entsprechend programmiert werden müssen. In verwalteten Umgebungen, wie z.B. einem Applikationsserver (siehe 6), wird die Transaktions-Demarkation über JTA [51] normalerweise automatisch vorgenommen. In Einzelanwendungen, oder um die Anwendung portabel zu halten, kann die Transaktions-Demarkation auch einprogrammiert werden. Session session = sessionFactory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); ... // Datenbank-Operationen tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); } throw e; } finally { session.close(); } Code 71: Manuelle Transaktions-Demarkation (1) Um dabei jedoch flexibler vorgehen zu können, kann man die Verwaltung der Sitzung auch Hibernate überlassen, und sich ganz auf die benötigten Transaktionen konzentrieren. try { sessionFactory.getCurrentSession().beginTransaction(); ... // datenbankbezogene Operationen sessionFactory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException e) { sessionFactory.getCurrentSession().getTransaction().rollback(); throw e; } Code 72: Manuelle Transaktions-Demarkation (2) Ein Problem dabei ist jedoch eben der gleichzeitige Zugriff von mehreren Benutzern auf das System, wodurch mehrere Entity-Objekte, die sich aber auf den selben Datensatz in der Datenbank beziehen, im Umlauf sein können, wobei der Datensatz eben nicht für einen bestimmten Benutzer exklusiv gesperrt ist. - 109 - Datenhaltung - Hibernate 4.6.4.4. Fehlerbehandlung Wenn beim Aufruf von Hibernate-Methoden eine Ausnahme geworfen wird, sollte die aktuelle Transaktion unbedingt zurückgerollt werden (rollback()). Außerdem muß die aktuelle Sitzung geschlossen werden, da bei bestimmten Hibernate-Methoden die Sitzung nicht in einem konsistenten Zustand bleibt. Generell sind Ausnahmen bei Hibernate nicht behebbar und es muß in einer neuen Sitzung von vorne begonnen werden [68]. Hibernate wirft normalerweise eine HibernateException (abgeleitet von RuntimeException) für einen allgemeinen Fehler in der Persistenz-Schicht. Der häufigere Fall sind aber Datenbank-Ausnahmen (SQLException), die als JBDCException oder einer Unterklasse davon (siehe auch SQLExceptionConverterFactory), mit einer bestimmten Bedeutung, geworfen werden. JDBCConnectionException SQLGrammarException ConstraintViolationException LockAcquisitionException GenericJDBCException ein allgemeines Problem mit der Datenbankverbindung ein Grammatik- oder Syntax-Problem bei der ausgeführten SQL-Anweisung ein Datenbank-Einschränkung wäre verletzt worden eine notwendige Sperre für die aktuelle Operation kann nicht erhalten werden eine andere Ausnahme, die nicht in eine der genannten Kategorien fällt 4.6.4.5. Transaktions-Zeitlimit Neben der Kontrolle der auftretenden Fehler ist es außerdem wichtig, daß DatenbankRessourcen von ungünstig verlaufenden Transaktionen nicht beliebig lange belegt werden können. Dazu kann für jede Transaktion ein Zeitlimit (Timeout) angegeben werden. //Zeitlimit auf 3 Sekunden setzen session.getTransaction().setTimeout(3); Hibernate versucht über die Kontrolle von Datensatz-Sperren und der Größe von AbfrageErgebnissen sicherzustellen, daß die eingestellte Zeit eingehalten wird. In einer verwalteten Umgebung übergibt Hibernate die Verwaltung des Zeitlimits an JTA, sofern das Zeitlimit nicht sowieso von der Ablauf-Umgebung vorgegeben wird. - 110 - Datenhaltung - Hibernate 4.6.4.6. Versionskontrolle Durch Versionskontrolle (optimistic concurrency control) der persistenten Objekte kann die Datenintegrität bei gleichzeitigen Zugriffen sichergestellt werden. Im einfachsten Fall kann die Versionskontrolle selbst implementiert werden, und bei unterschiedlichen Versionen eine Fehlermeldung ausgegeben werden. Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); int oldVersion = foo.getVersion(); session.load(foo, foo.getKey()); if (oldVersion != foo.getVersion) { throw new StaleObjectStateException(); } foo.setProperty("bar"); tx.commit(); session.close(); Code 73: Selbst implementierte Versionskontrolle Dieses Vorgehen ist für Verarbeitung komplexer Objekthierarchien, besonders bei der Anwendung der Kaskadierung, sehr umständlich, weshalb die Versionskontrolle von Hibernate automatisch durchgeführt werden sollte. Dazu sollte eine persistente Klasse ein numerisches Feld (oder Timestamp) definieren, das mit der Annotation @Version für die automatische Versionskontrolle gekennzeichnet wird. @Entity @org.hibernate.annotations.Entity( optimisticLock = OptimisticLockType.VERSION) public class Account { @Version public Integer getVersion() { ... } } Code 74: Klasse mit automatischer Versionskontrolle durch Hibernate Bei der @Entity-Annotation von Hibernate kann durch den zusätzlichen Parameter optimisticLock angegeben werden, wie Hibernate die Versionskontrolle durchführen soll. OptimisticLockType.NONE OptimisticLockType.ALL OptimisticLockType.VERSION OptimisticLockType.DIRTY keine automatische Versionskontrolle Verwendung eines vorhandenen Versionsfeldes Versionskontrolle durch Vergleich aller Felder, die von Hibernate als geändert registriert wurden Versionskontrolle auch ohne spezielles Versionsfeld, durch Vergleich aller persistenten Felder der Klasse Wenn Hibernate durch die Versionskontrolle beim Speichern die mehrfache gleichzeitige Änderung eines Objekts festestellt wird eine Ausnahme geworfen. - 111 - Datenhaltung - Hibernate 4.6.4.7. Sperren Die andere Möglichkeit die Konsistenz der Daten zu gewährleisten sind Datenbank-Sperren (pessimistic locking), die von Hibernate automatisch bei bestimmten Zugriffen auf die Datenbank verwendet oder durch den Entwickler explizit gesetzt werden. Die Klasse LockMode definiert die möglichen Sperren, die Hibernate beim Zugriff auf den Datensatz für ein persistentes Objekt erhalten kann. LockMode.WRITE LockMode.UPGRADE LockMode.READ LockMode.NONE wird verwendet beim Aktualisieren oder Einfügen einer Zeile in die Datenbank wird nur beim expliziter Angabe verwendet wird beim wiederholten Laden von Objekten verwendet keine Sperren werden verwendet Bei den folgenden Hibernate-Methoden kann explizit angegeben werden, daß eine bestimmte Sperre verwendet werden soll. session.load() session.get() session.lock() Query.setLockMode() Hibernate verwendet immer die Sperr-Mechanismen der Datenbank und wird niemals Objekte im Speicher sperren. Wenn die Datenbank die angeforderte Sperre nicht unterstützt, verwendet Hibernate eine passende Alternative und gibt keinen Fehler zurück, damit die Anwendung portabel bleibt [68]. 4.6.5. Verwendung der Objekte Hibernate hält nicht nur die konkreten Details der zugrundeliegenden Datenbank vom Entwickler fern, sondern kümmert sich auch um die Verwaltung des Objekt-Zustands. Das bedeutet, daß sich ein Entwickler keine Gedanken darüber machen muß, wann welche SQLAnweisungen ausgeführt werden, er muß lediglich den aktuellen Zustands eines Objekts gegenüber Hibernate im Auge behalten. 4.6.5.1. Zustände Jedes Objekt, das mit dem new-Operator gerade erst erzeugt worden ist, ist bei Hibernate noch nicht bekannt und daher im Objektzustand transient. Es hat keinen entsprechenden Datensatz in der relationalen Datenbank und keinen von Hibernate zugewiesenen Identifikator. Account account = new Account(); account.setNumber(2400); account.setType(AccountType.ACTIVE); account.setDescription("Hibernate-Konto"); - 112 - Datenhaltung - Hibernate Nachdem ein Objekt durch den Aufruf von perist() oder save() an Hibernate übergeben wurde ist es persistent. Es ist eine geöffnete Hibernate-Sitzung gebunden und hat zugehörige Daten in der Datenbank, sowie einen erzeugten oder angegebenen Identifikator. Long id = (Long)session.save(account); session.save(id, account); Hibernate überwacht dann das Objekt, so daß am Ende einer Transaktion oder der Sitzung bei geänderten Daten eine Synchronisation mit der Datenbank stattfindet. Durch den Aufruf von session.flush(); kann der Abgleich mit der Datenbank auch manuell angestoßen werden. Wenn nun die Hibernate-Sitzung zu der ein Objekt gehört geschlossen wird, bleibt das JavaObjekt in der Virtuellen Maschine natürlich bestehen, es hat nur keine Verbindung zu Hibernate mehr, weshalb es in den Zustand detached übergeht. Man kann das Objekt normal weiterverwenden und auch seine Daten ändern. Später kann das Objekt dann wieder ein Sitzung zugeordnet, und so die geänderten Daten in der Datenbank gespeichert werden. session2.update(account); Dazu muß die Methode update() verwendet werden, da der Aufruf von persist() oder save() nur bei transienten Objekten möglich ist [67]. 4.6.5.2. Automatische Erkennung des Objektzustandes Durch die möglichen Vorgänge, bei denen ein Objekt an Hibernate gebunden und wieder von der Sitzung getrennt werden kann, kann es kompliziert werden, jeweils die richtige HibernateFunktion zu verwenden, z.B. wenn ein Objekt aus der Datenhaltungsschicht an die Benutzeroberfläche gegeben wird, dort verändert wird, und dann zum Speichern wieder an die Datenhaltung zurückgegeben wird. Dafür bietet Hibernate die weiteren Funktionen saveOrUpdate() und merge(), die den aktuellen Zustand eines Objekts in Betracht ziehen, und dann entsprechend verfahren. session2.saveOrUpdate(account); session2.merge(account); Die Funktion saveOrUpdate() verhält sich dabei folgendermaßen: wenn das Objekt bereits persistent ist, wird nichts unternommen wenn bereits ein Objekt mit dem selben Identifikator vorhanden ist, wird eine Ausnahme geworfen wenn das Objekt noch keinen Identifikator hat, wird es persistiert wenn das Objekt einen Identifikator hat, der noch nicht vergeben ist, wird es persistiert wenn das Objekt eine Versionsangabe (@Version) enthält, die auf ein neues Objekt hindeutet, wird es durch save() persistiert in allen anderen Fällen wird update() aufgerufen - 113 - Datenhaltung - Hibernate Die Funktion merge() verhält sich im Gegensatz dazu ganz unterschiedlich: wenn es bereits ein persistentes Objekt mit dem selben Identifikator gibt, werden nur die Daten des neuen Objekts übernommen wenn es kein passendes persistentes Objekt gibt, wird Eines aus der Datenbank geladen oder neu erzeugt die persistente Instanz wird zurückgegeben die übergebene Instanz wird nicht der aktuellen Sitzung zugeordnet und bleibt somit detached 4.6.5.3. Laden der Objekte Durch die load()-Methode wird eine persistente Instanz geladen, die sicher in der Datenbank vorhanden und deren Identifikator bekannt ist. Account account = (Account)session.load(Account.class, id); Die Methode instanziert ein Objekt der angegebenen Klasse mit den zugehörigen Daten aus der Datenbank und gibt es im Zustand persistent zurück. Wenn kein Datensatz zu dem angegebenen Identifikator existiert, wird eine Ausnahme geworfen. Falls von vorne herein nicht sichergestellt ist, daß ein zugehöriger Datensatz vorhanden ist, kann die get()-Methode verwendet werden. Account account = (Account)session.get(Account.class, id); if (account == null) { account = new Account(); session.save(account, id); } return account; Diese Methode wirft keine Ausnahme, sondern gibt null zurück, wenn der Identifikator ungültig ist. Wie bereits erwähnt, gibt es bei dieser Methode eine Variante, mit der explizit eine Sperre angefordert werden kann, z.B. account = (Account)session.get(Account.class, id, LockMode.UPGRADE); Außerdem können die Daten eines bereits instanzierten persistenten Objekts durch die refresh()-Methode erneut geladen werden. session.save(account); // Objekt speichern session.flush(); // SQL-Insert-Anweisung ausführen lassen session.refresh(account); // Daten erneut laden Dies ist für den Fall nützlich, daß Datenbank-Trigger beim Speichern verwendet werden, um Felder des persistenten Objekts zu initialisieren, wie z.B. einen Zeitstempel. - 114 - Datenhaltung - Hibernate 4.6.5.4. Abfragen Wenn die Identifikatoren von gewünschten Objekten nicht bekannt sind muß eine Abfrage ausgeführt werden. Hibernate bietet dafür eine eigene objektorientierte Abfragesprache (HQL) die ähnlich zu SQL aufgebaut ist, aber die Portabilität der Anwendung gegenüber verschiedenen Datenbanksystemen erhält. Es können aber auch native SQL-Abfragen direkt für die Datenbank formuliert und ausgeführt werden. Eine neue Abfrage wird über ein bestehendes Sitzungsobjekt erzeugt. Query debits = session.createQuery( "FROM accountingentries WHERE debitaccount = ?"); debits.setNumber(0, 2400); Collection result = debits.list(); Die Abfrage unterstützt ? Platzhalter im Stil von JDBC, nur mit dem Unterschied, daß die Numerierung bei 0 und nicht bei 1 beginnt. Nachdem alle Platzhalter mit einem Wert belegt worden sind, wird die Abfrage durch den Aufruf von list() ausgeführt, und eine Kollektionen mit den zutreffenden Objekten als Ergebnis zurückgeliefert. Wenn vorher bereits bekannt ist, daß eine Abfrage nur ein einzelnes Objekt als Ergebnis zurückliefert, kann auch die Methode uniqueResult() an Stelle von list() zur Ausführung der Abfrage benutzt werden. 4.6.5.5. Modifikationen an Objekten Persistente Objekte, die mit einer Sitzung verbunden sind, können über ihre Methoden direkt manipuliert werden. Die Änderungen werden in der Datenbank gespeichert, wenn flush() explizit aufgerufen oder die gerade laufende Transaktion abgeschlossen wird. Es ist nicht nötig SQL-Anweisungen wie UPDATE oder DELETE extra aufzurufen, und die Hibernate-API bietet deshalb auch keine Methoden dafür. Falls nötig kann man jedoch durch Session.connection() direkt auf die JDBC-Verbindung zur Datenbank zugreifen, z.B. um eine größere Anzahl Datensätze direkt zu ändern. Bei detached Objekten, deren zugehörige Sitzung geschlossen wurde, ist es nötig eine neue Sitzung zu öffnen, und ihr die Objekte zu übergeben, damit Änderungen gespeichert werden. Dies erfolgt durch eine der beschriebenen Methoden update(), saveOrUpdate() oder merge() (siehe 4.6.5.2). Außerdem ist möglich mit der lock()-Methode ein nicht modifiziertes Objekt einfach nur an eine neue Session zu binden und dabei ggf. eine Sperre in der Datenbank zu erwirken. 4.6.5.6. Löschen persistenter Objekte Persistente Objekte werden durch delete() aus der Hibernate-Sitzung und der Datenbank gelöscht, wobei das Java-Objekt für die Anwendung natürlich bestehen bleibt, solange eine Referenz darauf vorhanden ist, d.h. das Objekt wird wieder transient. session.delete(account); - 115 - Datenhaltung - Hibernate 4.6.6. Buchhaltung mit einer Singleton-Service-Klasse Ein Ansatz für ein persistentes Buchhaltungssystem mit Hibernate wäre die Betrachtung der Objekthierarchie von den Konten aus, denen dann jeweils die Buchungen zugeordnet sind. Abbildung 61: ERM-Modell 1 für die Buchhaltung mit Hibernate Die Service-Klasse ist dann keine Entität und es muß lediglich für die Implementierung der Methoden der Kontenobjekte und Buchungsobjekte eine Instanz der Service-Klasse als "Dienstleister" verfügbar sein. 4.6.6.1. Service-Klasse Dazu bietet sich die Verwendung des Singleton-Musters an. Die Service-Klasse wird daher mit einigen statischen Methoden ausgestattet, die immer nur die eine statische Instanz der Service-Klasse zurückgeben. package de.fhr.vs_iw.hibernate.v1; public final class AccountingService implements IAccountingService { private static AccountingService instance = null; private static Session session = null; private static SessionFactory sessionFactory = null; private static void initInstance() { AnnotationConfiguration cfg = new AnnotationConfiguration(); cfg.addAnnotatedClass(Account.class); cfg.addAnnotatedClass(AccountingEntry.class); sessionFactory = cfg.buildSessionFactory(); session = sessionFactory.openSession(); session.beginTransaction(); instance = new AccountingService(); instance.accountSet = new HashSet<Account>( session.createQuery("FROM Account").list()); instance.accountingEntrySet = new HashSet<AccountingEntry>( session.createQuery("FROM AccountingEntry").list()); } protected static void closeInstance() { if (instance != null) { session.getTransaction().commit(); session.close(); session = null; sessionFactory = null; instance = null; } } - 116 - Datenhaltung - Hibernate protected static AccountingService getInstance() { if (instance == null) { initInstance(); } return instance; } ... public void createAccount(Integer number, AccountType type, String description) { Account account = new Account(number, type, description); accountSet.add(account); session.save(account); } public Integer createAccountingEntry(Integer debitAccount, Integer creditAccount, Double amount, String text){ AccountingEntry entry = new AccountingEntry( debitAccount, creditAccount, amount, text); accountingEntrySet.add(entry); Integer id = (Integer)session.save(entry); return id; } } ... Code 75: Class AccountingService (Hibernate - Variante 1) Dabei werden alle Konten und Buchungen innerhalb der Service-Klasse verwaltet. Beim Erzeugen der Instanz der Service-Klasse durch initInstance() wird Hibernate konfiguriert, sowie eine Session und eine Transaktion gestartet. Alle in der Datenbank vorhandenen Konten und Buchungen werden durch Abfragen geladen. Die Konten- bzw. Buchungsobjekte verwenden dann zum Zugriff darauf die Singleton-Instanz der Service-Klasse. AccountingService.getInstance(); Alle von Hibernate aus der Datenbank geladenen Objekte sind persistent und Änderungen daran werden gespeichert. Nur beim Erzeugen neuer Objekte durch createAccount() bzw. createAccountingEntry() müssen diese durch den Aufruf von persist() oder save(), wie oben im Code gezeigt, bei der Hibernate-Sitzung registriert werden. Beim Speichern eines neuen Buchungsobjekts wird die zugewiesene ID des neuen Objekts zurückgegeben. - 117 - Datenhaltung - Hibernate 4.6.6.2. Konto-Klasse Ein Konto speichert seine zugeordneten Buchungen nicht selbst, sondern sucht diese über die Service-Klasse. Konten und Buchungen speichern nur ihre jeweiligen Basisdaten. Da dieser Ansatz bereits vollständig dem Buchhaltungs-Modell entspricht, und die Entitäten bereits über die Kontonummer einander zugeordnet werden, muß für Hibernate in dieser Hinsicht keine Beziehung zwischen den Entitäten konfiguriert werden, da dies von der implementierten Geschäftslogik des Buchhaltungssystems her bereits erfolgen muß. package de.fhr.vs_iw.hibernate.v1; @Entity @Table(name = "Account") public final class Account implements IAccount { private String description = null; private Integer number = null; private AccountType type = null; public Account(); public Account(Integer number, AccountType type, String description); @Transient public Set<Integer> getCreditEntries() { final Set<Integer> credit = new HashSet<Integer>(); for (AccountingEntry entry : AccountingService.getInstance().accountingEntrySet) { if (entry.getCreditAccount().equals(number)) { credit.add(entry.getId()); } } return credit; } @Transient public Set<Integer> getDebitEntries(); @Basic public String getDescription(); @Id @Column(name = "accountnumber") public Integer getNumber(); @Basic public AccountType getType(); public void setDescription(String description); public void setNumber(Integer number); public void setType(AccountType type); } Code 76: Class Account (Hibernate - Variante 1) Die Konto-Klasse wird als Entität und die Eigenschaften der Klasse als Basis-Elemente gekennzeichnet, wobei die Methoden zum Ermitteln der zugeordneten Buchungen transient sein müssen, da Hibernate hier nicht darauf zugreifen soll. - 118 - Datenhaltung - Hibernate 4.6.6.3. Buchungs-Klasse Auch die Buchungs-Klasse wird einfach als Entität gekennzeichnet. package de.fhr.vs_iw.hibernate.v1; @Entity @Table(name = "AccountingEntry") public final class AccountingEntry implements IAccountingEntry { private Double amount = null; private Integer creditAccount = null; private Integer debitAccount = null; private Integer id = null; private String text = null; public AccountingEntry(); public AccountingEntry(Integer debit, Integer credit, Double amount, String text); @Basic public Double getAmount(); @Basic public Integer getCreditAccount(); @Basic public Integer getDebitAccount(); @Id @GeneratedValue() public Integer getId(); @Basic public String getText(); public public public public public void void void void void setAmount(Double amount); setCreditAccount(Integer number); setDebitAccount(Integer number); setId(Integer id); setText(String text); } Code 77: Class AccountingEntry (Hibernate - Variante 1) Bei der Buchungs-Klasse treten von den Daten her sowieso nur Basis-Elemente auf. Da bei der Buchungs-ID die Vergabe durch das System vorgesehen ist, kann Hibernate dies durch die Annotation @GeneratedValue übernehmen. 4.6.6.4. Proxy-Klasse In der connect()-Methode der Proxy-Klasse wird lediglich die Singleton-Instanz der Service-Klasse zurückgegeben (getInstance()). Beim Schließen des Systems durch close() wird dann noch die Methode closeInstance() der Service-Klasse aufgerufen, um die Objekte zu speichern und die Hibernate-Sitzung abzuschließen. - 119 - Datenhaltung - Hibernate 4.6.7. Buchhaltung mit einer Entity-Service-Klasse Ein anderer Ansatz wäre es, die Objekthierarchie von der Buchhaltungsanwendung von der Service-Klasse aus zu betrachten, die ja sowieso schon alle vorhandenen Konten und Buchungen verwaltet. Abbildung 62: ERM-Modell 2 für die Buchhaltung mit Hibernate Die Service-Klasse ist dann selbst eine zu speichernde Entität des Systems, und die Konten und Buchungen werden mit ihr in eine feste Beziehung gesetzt. 4.6.7.1. Service-Klasse Dazu wird die Service-Klasse als Entität gekennzeichnet und um direkte Zugriffsmethoden für die Listen der Konten bzw. der Buchungen erweitert. Außerdem muß ein Primär-Schlüssel definiert werden, der allerdings immer 0 ist. package de.fhr.vs_iw.hibernate.v2; @Entity @Table(name = "AccountingService2") public final class AccountingService implements IAccountingService { private int nextId = 0; private Set<AccountingEntry> accountingEntrySet = new HashSet<AccountingEntry>(); private Set<Account> accountSet = new HashSet<Account>(); public AccountingService(); @OneToMany(mappedBy = "service", cascade = CascadeType.ALL) public Set<AccountingEntry> getAccountingEntrySet(); @OneToMany(mappedBy = "service", cascade = CascadeType.ALL) public Set<Account> getAccountSet(); @Basic public int getNextId(); @Id public Integer getServiceId() { public void setAccountingEntrySet( Set<AccountingEntry> accountingEntriesSet); public void setAccountSet(Set<Account> accountsSet); public void setNextId(int nextId); public void setServiceId(Integer serviceId); } ... Code 78: Class AccountingService (Hibernate - Variante 2) - 120 - Datenhaltung - Hibernate Der Primärschlüssel wird nicht ein einem Feld der Instanz der Klasse gespeichert, sondern ist immer 0, weil es nur eine Instanz der Service-Klasse geben soll. Außerdem ist es sinnvoll das Feld nextId, mit der nächsten zu vergebenden Buchungs-ID, auch mitzuspeichern, damit das System nach dem Laden aus der Datenbank keine Fehler beim Erfassen neuer Buchungen produziert. Die Buchungs-ID kann man hier nur schlecht von Hibernate vergeben lassen, da nicht vorgesehen ist, daß ein Buchungsobjekt nach dem Anlegen sofort von Hibernate persistiert wird, sondern später die vollständige Objekthierarchie auf einmal (siehe 4.6.7.4). Deshalb ist bei den @OneToMany-Beziehungen auch der Kaskadierungstyp angegeben 4.6.7.2. Konto-Klasse Die Konto-Klasse stellt ebenfalls wieder eine eigene Entität dar, die mit der Service-Klasse in einer bidirektionalen Beziehung steht. Dafür erhält die Konto-Klasse ein entsprechendes Feld mit Getter- und Setter-Methode für die zugehörige Instanz der Service-Klasse. package de.fhr.vs_iw.hibernate.v2; @Entity @Table(name = "Account2") public final class Account implements IAccount { private AccountingService service = null; @ManyToOne public AccountingService getService(); public void setService(AccountingService service); } ... Code 79: Class Account (Hibernate - Variante 2) Durch die Annotation @ManyToOne wird der Rückbezug zur Service-Klasse hergestellt. Ansonsten bleiben die Methoden und Annotations im Vergleich zur vorherigen Konto-Klasse (siehe 4.6.6.2) unverändert. 4.6.7.3. Buchungs-Klasse Bei der Buchungs-Klasse wird analog zur Konto-Klasse vorgegangen und die Beziehung zur Service-Klasse hergestellt. Ansonsten entspricht auch diese Klasse der vorigen (siehe 4.6.6.3). package de.fhr.vs_iw.hibernate.v2; @Entity @Table(name = "AccountingEntry2") public final class AccountingEntry implements IAccountingEntry { private AccountingService service = null; @ManyToOne public AccountingService getService(); public void setService(AccountingService service); } ... Code 80: Class AccountingEntry (Hibernate - Variante 2) - 121 - Datenhaltung - Hibernate 4.6.7.4. Proxy-Klasse Da die Service-Klasse jetzt selbst eine Entität darstellt wird der Aufruf von Hibernate in die Proxy-Klasse verlegt. Die Konfiguration erfolgt im Konstruktor wie vorher (siehe 4.6.6.1). package de.fhr.vs_iw.hibernate.v2; public final class HibernateProxy2 implements IAccountingProxy { private AccountingService service = null; private Session session = null; private SessionFactory sessionFactory = null; public HibernateProxy() { AnnotationConfiguration cfg = new AnnotationConfiguration(); cfg.addAnnotatedClass(AccountingService.class); cfg.addAnnotatedClass(Account.class); cfg.addAnnotatedClass(AccountingEntry.class); sessionFactory = cfg.buildSessionFactory(); } public void close() { if (service != null) { session.getTransaction().commit(); session.close(); session = null; sessionFactory = null; service = null; } } } public IAccountingService connect(String[] args) { if (service == null) { session = sessionFactory.openSession(); session.beginTransaction(); service = (AccountingService)session.get(AccountingService.class, 0); if (service == null) { service = new AccountingService(); session.save(service); } } return service; } Code 81: Class HibernateProxy2 (Hibernate - Variante 2) In der connect()-Methode wird eine Hibernate-Sitzung geöffnet und eine Transaktion gestartet. Dann wird das Service-Objekt, und durch Kaskadierung auch alle zugehörigen Konten- und Buchungsobjekte, aus der Datenbank geladen. So sind all diese Objekte, und auch neue Objekte die später den Sets der Service-Klasse hinzugefügt werden, als persistent anzusehen. Wenn in der Datenbank noch kein Service-Objekt vorhanden ist, wird ein neues Objekt erzeugt und an die Hibernate-Sitzung übergeben. Die Hibernate-Sitzung und die Transaktion werden bis zum Aufruf der close()-Methode offen gehalten. Dort wird dann commit() aufgerufen und so die aktuellen Daten der gesamten Hierarchie persistenter Objekte in die Datenbank gespeichert. - 122 - Datenhaltung - Hibernate 4.6.7.5. Fazit Hibernate ist ein mächtiges und ausgereiftes Framework, um die Persistenz-Schicht einer Anwendung zur Speicherung der Daten zu realisieren. Die Entwicklung von Hibernate war mit ausschlaggebend für die JPA-Spezifikation (Java Persistence API) im Rahmen der Enterprise JavaBeans 3.0 Spezifikation und stellt damit quasi selbst den Standard für die Datenspeicherung bei großen Java-Anwendungen dar. Die Einarbeitung ist im ersten Schritt etwas aufwendiger, da Hibernate sehr flexibel und für jede mögliche Situation anwendbar ist, dafür zahlt sich die Persistenz durch einfache JavaObjekte für jeden Anwendungsfall aus. Der hier beschriebene Umfang der Funktionalität von Hibernate stellt nur einen Bruchteil der Möglichkeiten zum Einstieg in die Verwendung des Frameworks dar und ist speziell auf das Buchhaltungsbeispiel bezogen. Die gesamten Möglichkeiten von Hibernate sind noch wesentlich umfangreicher. - 123 - Transport - Übersicht 5. Transport 5.1. Übersicht Nachdem nun mehrere Möglichkeiten besprochen wurden, die Daten der Buchhaltungsanwendung zu speichern und dabei die Geschäftslogik zu implementieren, soll die Anwendung jetzt auf mehrere Systeme verteilt werden. 5.1.1. Prinzipieller Ablauf Dazu wäre es einfach ein Protokoll zu definieren, und damit Daten über ein Netzwerk zu transportieren, da die Anbindung an Netzwerk-Sockets unter Java sehr einfach möglich ist, sei es durch asynchronches Versenden von Datenpakten oder durch ein einfaches synchrones AnfrageAntwort-Protokoll. Abbildung 64: Anfrage-Antwort Abbildung 63: Datenpakte Ziel ist aber die Verwendung von einem objektorientierten System, bei dem Methoden von entfernten Objekten aufgerufen werden, so als ob das Objekt lokal vorhanden wäre. Das System sollte im Idealfall vollständige Transparenz bieten, so daß sich ein Entwickler nicht darum kümmern muß, wo und wie genau der Aufruf einer Methode eines Objekts ausgeführt wird. Eine Middleware-Schicht übernimmt dabei den Transport des Aufrufs über das Netzwerk. Abbildung 65: Entfernter Methodenaufruf - 124 - Transport - Übersicht 5.1.2. Erstellung mit Eclipse Zur Realisierung der Middlewareschicht wird ein neues Modul für die Buchhaltungsanwendung erstellt (siehe 3.9.4). Wie beschrieben wird zuerst in Eclipse ein neues Package dafür angelegt, z.B. mit dem Namen de.fhr.vs_iw.socket, im Menü unter: File -> New -> Package Dann wird wieder je eine eigene Klasse für jede der Schnittstellen der Buchhaltung erzeugt. Durch die Implementierung der Schnittstellen kann das Modul nun wieder von der Benutzeroberfläche aufgerufen werden. Weiterhin benötigt das Modul aber noch eine Server-Seite, die in einer eigenen Klasse erstellt wird, über: File -> New -> Class Zusammen mit einem Modul aus der Datenhaltung ergibt sich im Package Explorer von Eclipse z.B. die nebenstehende Ansicht. Ein Modul aus der Datenhaltung kann von der Server-Klasse über die Buchhaltungsschnittstellen direkt verwendet werden, damit bereits vorhandene Funktionalitäten nicht nochmals implementiert werden müssen. Auf diese Weise entsteht der gewünschte modulare Aufbau des gesamten Systems. Abbildung 66: Eclipse Package Explorer Dazu wird die Proxy-Klasse des gewünschten Moduls (z.b. de.fhr.vs_iw.local) in der Server-Klasse entweder direkt instanziert IAccountingProxy proxy = new LocalProxy(); oder über die Methode loadProxy() der Klasse Utilities, durch ihren vollständigen Namen (über Reflection) geladen. proxy = Utilities.loadProxy("de.fhr.vs_iw.local.LocalProxy"); Durch die Verwendung des Names wird das Datenhaltungsmodul leicht austauschbar. Dies ist deshalb die Methode, die bevorzugt verwendet werden sollte (siehe 2.4.1). Anschließend kann nun die Datenübertragung zwischen der Proxy-Klasse (hier SocketProxy) und der ServerKlasse (hier SocketServer) implementiert werden. - 125 - Transport - Eigener Transport über Sockets 5.2. Eigener Transport über Sockets 5.2.1. Allgemeines Der Einstieg in die Transportschicht soll mit einer völlig eigenen Implementierung der Datenund Objektübertragung erfolgen. Dies soll einen Eindruck vermitteln welche Mechanismen und Maßnahmen für die Anwendungs-Kommunikation notwendig sind. Deshalb erfolgt der Ansatz auf einer grundlegenden Ebene, unter direkter Verwendung von Netzwerk-Sockets, und wird wie eine Middleware-Schicht aufgebaut. 5.2.2. Anbindung an die Datenhaltung Das Socket-Kommunikations-Modul soll auf ein beliebiges Modul der Datenhaltungsschicht aufsetzen können, die bereits implementierte Geschäftslogik nutzen und lediglich die Kommunikationsfähigkeit hinzufügen. 5.2.2.1. SocketServer Dazu verhält sich der SocketServer gegenüber einem Datenhaltungs-Modul wie ein Client. Er instanziert die auf der Kommandozeile angegebene Proxy-Klasse eines vorhandenen Moduls, und lädt damit die zugehörige Service-Klasse. package de.fhr.vs_iw.socket; public final class SocketServer extends Thread { private SocketServer(IAccountingService service, Socket socket); public void run(); private String genOid(Object obj); private String getOid(Object obj); private ServerSideResponse process(ServerSideRequest req); private ServerSideResponse remoteObjectResponse(Object o, Class c); public static void main(String[] args) { try { if (args.length <= 1) { System.err.println("Port und Proxy-Klasse angegeben"); return; } ServerSocket ss = new ServerSocket(Integer.parseInt(args[0])); IAccountingProxy localProxy = Utilities.loadProxy(args[1]); IAccountingService nextService = localProxy.connect(Utilities.shiftArgs(args, 2)); while (true) { try { new SocketServer(nextService, ss.accept()).start(); } catch (IOException e) { } } } catch (Exception e) { e.printStackTrace(); } } } Code 82: Class SocketServer - 126 - Transport - Eigener Transport über Sockets Als weiteres Kommandozeilenargument benötigt der SocketServer noch den Port auf dem er lauschen soll. Auf diesem Port wird ein ServerSocket eröffnet, der mit accept() Verbindungen von Clients entgegen nimmt. java de.fhr.vs_iw.socket.SocketServer <port> <proxy> [proxy-parameters] Die restlichen Kommandozeilenargumente werden der angegebenen Proxy-Klasse als Parameter für die connect()-Methode übergeben. Dazu ist die Utilities-Methode shiftArgs(String[] args, int number); hilfreich, die die angegebene Anzahl Argumente vom Anfang des String-Arrays entfernt. 5.2.2.2. Parallele Server-Instanzen Der SocketServer selbst ist von Thread abgeleitet. Damit wird für jeden Client, der sich mit dem Server verbindet, eine eigener SocketServer instanziert und gestartet, der den verbundenen Socket und die Instanz der geladenen Service-Klasse erhält. Dies stellt die einfachste Möglichkeit dar, die Anfragen von Clients parallel und unabhängig zu verarbeiten. Hier wird dann auch deutlich, warum die Implementierung eines Moduls der Datenhaltungsschicht threadsicher sein muß. Eben weil alle Threads auf die selbe Instanz eines Modules zugreifen. 5.2.3. Aufbau und Nutzung der Verbindung Aufbau und Verwendung der Verbindung gehen vom Client aus. Der Server verarbeitet die lediglich die eingehenden Anfragen. 5.2.3.1. Socket-Proxy-Klasse Um dem Konzept der Schnittstellen und Module der Buchhaltung zu entsprechen, wird eine Socket-Client-Klasse erstellt, welche die IAccountingProxy-Schnittstelle implementiert. Die connect()-Methode wird im Sinne ihres Namens tätig und stellt die Socket-Verbindung zum SocketServer her, die dann in der close()-Methode wieder geschlossen wird. package de.fhr.vs_iw.socket; public final class SocketProxy implements IAccountingProxy { private BufferedReader reader = null; private Socket socket = null; private PrintWriter writer = null; public void close() throws IOException { if (reader != null) { reader.close(); } if (writer != null) { writer.close(); } if (socket != null) { socket.close(); } } - 127 - Transport - Eigener Transport über Sockets public IAccountingService connect(String[] args) throws IOException { if (args.length < 2) { throw new IllegalArgumentException( "Hostname und Port müssen angegeben werden"); } socket = new Socket(args[0], Integer.parseInt(args[1])); writer = new PrintWriter(socket.getOutputStream()); reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); try { return (IAccountingService)requestObject( IAccountingService.class, "SocketService", null); } catch (AccountingException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } private ClientSideResponse request(String oid, String method, Object... args); protected Object requestObject(Class clas, String oid, String method, Object... args); protected Object request1(String oid, String method, Object... args) throws IOException; protected Object request2(String oid, String method, Object... args) throws IOException, AccountingException; } Code 83: Class SocketProxy Nach dem Herstellen der Verbindung wird der Ein- bzw. Ausgabestrom des Socket geöffnet. Die Request-Methoden der Klasse übernehmen dann die eigentliche Kommunikation nach dem definierten Protokoll (siehe 5.2.4) mit den dafür vorhandenen Klassen. 5.2.3.2. Verarbeitung auf dem Server Auf dem Server wird nach dem Instanzieren (siehe 5.2.2.2) ebenso verfahren. Sobald die Server-Instanz eigenständig läuft wird in der run()-Methode der Ein- bzw. Ausgabestrom des verbundenen Sockets geöffnet. public void run() { BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream()); while (true) { try { ServerSideRequest req = new ServerSideRequest(br); ServerSideResponse res = process(req); res.send(pw); } catch (SocketException e) { break; } catch (IOException e) { break; } } - 128 - Transport - Eigener Transport über Sockets } try { if (br != null) { br.close(); } if (pw != null) { pw.close(); } socket.close(); } catch (IOException e) { } Code 84: run()-Methode von SocketServer Dann werden kontinuierlich die Anfragen eines Clients gelesen, jeweils in der process()Methode verarbeitet und eine Antwort an den Client zurückgeschickt. 5.2.4. Definition eines eigenes Protokolls Zur Kommunikation wird ein selbst definiertes Request-Response-Protokoll verwendet, das auf einer Socket-Verbindung aufsetzt und alle Daten in String-Form zeilenweise überträgt. writer = new PrintWriter(socket.getOutputStream()) reader = new BufferedReader(new InputStreamReader(socket.getInputStream())) Dies erlaubt die unproblematische Verwendung der bei Java mitgelieferten Reader- und Writer-Klassen mit den Streams des Socket. Das Protokoll orientiert sich im Aufbau an der Objektorientierung von Java und bestehenden objektorientierten Kommunikationsverfahren (RMI, CORBA) und versucht den Aufruf einer Methode eines entfernten Objekts nachzubilden. Es wird in mehreren Klassen implementiert, die auf Client- und Serverseite die Verarbeitung der übertragenen Daten übernehmen. Abbildung 67: Socket-Anfrage-Antwort-Klassen - 129 - Transport - Eigener Transport über Sockets 5.2.4.1. Anfrage Eine Anfrage besteht aus bei diesem Protokoll aus mindestens drei Textzeilen: Objekt-Identifkator Methodenname [Parameter] [Parameter] [...] Anfrage-Trenner Resource 11: Socket-Protokoll - Anfrage Zuerst wird ein Objekt-Identifikator gesendet, der Angibt welches Objekt auf dem Server aufgerufen werden soll, sowie der Name der aufzurufenden Methode. Dann folgt eine beliebige Anzahl Parameter für den Methodenaufruf, die aber nicht notwendigerweise vorhanden sein müssen. Die Anfrage wird mit einer speziellen Trennzeile abgeschlossen. 5.2.4.2. Anfrage-Klassen Als Basis-Klasse für Anfragen an den Server dient die abstrakte Klasse Request, die die o.g. Elemente des Anfrage-Protokolls enthält. package de.fhr.vs_iw.socket; abstract class Request { String method = null; String oid = null; final List<Object> params; static final String DELIM; } Code 85: Class Request Davon werden die konkreten Klassen zur Verarbeitung einer Anfrage abgeleitet. Durch die Klasse ClientSideRequest wird das Erstellen und Versenden der Anfrage über den zuvor geöffneten PrintWriter durchgeführt. package de.fhr.vs_iw.socket; final class ClientSideRequest extends Request { ClientSideRequest(String oid, String method, Object... args) { super(); this.oid = oid; this.method = method; for (Object o : args) { params.add(o); } } - 130 - Transport - Eigener Transport über Sockets void send(PrintWriter pw) throws Exception { ArrayList<String> strparams = new ArrayList<String>(); for (Object o : params) { strparams.add(StringUnMarshaller.marshall(o)); } pw.println(oid); pw.println(method); for (String str : strparams) { pw.println(str); } pw.println(DELIM); pw.flush(); } } Code 86: Class ClientSideRequest Der Aufruf von flush() nach jeder Anfrage ist explizit notwendig, da sonst die Daten vom PrintWriter bzw. vom Socket vor dem eigentlichen Senden bis zu einer bestimmten Datenmenge gepuffert würden, die bei dieser Anwendung praktisch nicht erreicht wird. Auf der Server-Seite wird die Anfrage von der zugehörigen Klasse ServerSideRequest entgegengenommen. Mit dem vorhandenen BufferedReader wird solange vom Eingabestrom des Sockets gelesen bis ein Anfrage-Trenner gefunden wird. Nach dem Einlesen einer Anfrage stellt die Klasse Methoden zur Verfügung, mit denen die empfangenen Daten und Werte gezielt abgefragt werden können. package de.fhr.vs_iw.socket; final class ServerSideRequest extends Request { ServerSideRequest(BufferedReader br) throws Exception { super(); oid = readLine(br); method = readLine(br); ArrayList<String> strparams = new ArrayList<String>(); while (true) { String line = readLine(br); if (line.equals(DELIM)) { break; } strparams.add(line); } for (String str : strparams) { params.add(StringUnMarshaller.unmarshall(str)); } } private String readLine(BufferedReader br) throws IOException { String line = br.readLine(); if (line == null) { throw new IOException("end of input stream"); } return line; } - 131 - Transport - Eigener Transport über Sockets String method() { return method; } String oid() { return oid; } Object[] params() { return params.toArray(); } Class[] types() { Class[] types = new Class[params.size()]; for (int i = 0; i < params.size(); i++) { types[i] = params.get(i).getClass(); } return types; } } Code 87: Class ServerSideResponse Zusätzlich zur Rückgabe-Methode für die empfangenen Parameter params() gibt es die Methode types(), die die Typen der Parameter zurückgibt, was später für den Methodenaufruf über Reflection (siehe 5.2.6.1) benötigt wird. 5.2.4.3. Antwort Die Antwort ist ebenfalls zeilenweise Aufgebaut und hat immer drei Zeilen: Objekt-Identifikator Rückgabewert Antwort-Trennzeile Resource 12: Socket-Protokoll - Antwort Bei einer empfangenen Antwort müssen drei verschiedene Möglichkeiten unterschieden und entsprechend verarbeitet werden: Entferntes Objekt: Wenn die Antwort mit einem Objekt-Identifikator beginnt, hat der Server eine Referenz zu einem entfernten Objekt zurückgegeben, für das mit diesem Identifikator ein lokaler Stellvertreter erzeugt werden muß. Der Rückgabewert stellt den Typ (Class) des entfernten Objekts dar. (siehe 5.2.6) Ergebnis eines Methodenaufrufs: Wenn kein Objekt-Identifikator sondern eine Leerzeile am Beginn der Antwort steht handelt es sich um den normalen Rückgabewert eines Methodenaufrufs. Ausnahme: Eine Ausnahme wird wie der Rückgabewert eines Methodenaufrufs (ohne ObjektIdentifikator) übertragen, und dann vom Client erkannt und in einer ServerException (siehe 5.2.7) verpackt und geworfen. - 132 - Transport - Eigener Transport über Sockets Die Trennzeile würde hier im Prinzip nicht unbedingt benötigt werden, da die Länge der Antwort fest vorgegeben ist, wird aber trotzdem für einen "sauberen" Abschluß verwendet. 5.2.4.4. Antwort-Klassen Wie auch bei der Anfrage teilt sich die Verarbeitung der Antworten auf Server und Client auf, ausgehend von der abstrakten Klasse Response. package de.fhr.vs_iw.socket; abstract String Object String static } class Response { oid; value; method; final String DELIM = "-#-responseend-#-"; Code 88: Class Response Auf der Serverseite wird dann die Antwort als ServerSideResponse analog zu einer Anfrage auf der Clientseite erzeugt und verschickt. package de.fhr.vs_iw.socket; final class ServerSideResponse extends Response { ServerSideResponse(Object value) { super(); this.oid = null; this.value = value; } ServerSideResponse(String oid, Class clas) { super(); this.oid = oid; this.value = clas; } } void send(PrintWriter pw) throws Exception { String strvalue = StringUnMarshaller.marshall(value); pw.println(oid); pw.println(strvalue); pw.println(DELIM); pw.flush(); } Code 89: Class ServerSideResponse Der Client liest die Antwort über die Klasse ClientSideResponse ein. Hier wird dann geprüft ob die Antwort eine Ausnahme ist, und falls ja, diese entsprechend als ServerException geworfen (siehe 5.2.4.3). - 133 - Transport - Eigener Transport über Sockets package de.fhr.vs_iw.socket; final class ClientSideResponse extends Response { ClientSideResponse(BufferedReader br) throws Exception { super(); oid = readLine(br); String strvalue = readLine(br); if (!readLine(br).equals(DELIM)) { throw new IOException("delimiter expected"); } value = StringUnMarshaller.unmarshall(strvalue); } if (value instanceof Throwable) { throw new ServerException((Throwable)value); } private String readLine(BufferedReader br) throws IOException { String line = br.readLine(); if (line == null) { throw new IOException("end of input stream"); } return line; } } String getOid() { return oid; } String getMethod() { return method; } Object getValue() { return value; } Code 90: Class ClientSideResponse - 134 - Transport - Eigener Transport über Sockets 5.2.5. Un-/Marshalling der Objekte Ein wesentlicher Punkt ist nun die Umformung von Java-Objekten bzw. deren datenmäßigem Inhalt in ein Format, das zu dem String-basierten Protokoll paßt, und auch wieder zurück, das so genannte Marshalling und Unmarshalling. Dazu wird ein Objekt in folgender grundlegender Form dargestellt Java-Type (Klasse) : spezifische Werte Durch einen Doppelpunkt getrennt wird die Klasse eines Objekt und die für ein Objekt dieser Klasse spezifischen Werte als eine Zeile im Protokoll übertragen. 5.2.5.1. Die Klasse StringUnMarshaller Diese Umwandlungen übernimmt die statische Hilfs-Klasse StringUnMarshaller in beiden Richtungen über die entsprechenden Methoden marshall() und unmarshall(). package de.fhr.vs_iw.socket; final class StringUnMarshaller { static synchronized String marshall(Object o) throws Exception { static synchronized Object unmarshall(String line) ... } Code 91: Class StringUnMarshaller (Teil 1) Diese beiden Methoden werden beim Verarbeiten von Anfragen und Antworten aufgerufen. 5.2.5.2. Verarbeitung definierter Superklassen Da nicht wie z.B. bei der Java-Serialisierung annähernd alle Objekte verarbeitet werden müssen, wird der Vorgang über einen vorgegebenen Satz von Klassen gesteuert, deren Objekte bzw. Objekte von Unterklassen spezifisch umgewandelt werden. package de.fhr.vs_iw.socket; final class StringUnMarshaller { private static final HashSet<Class> CLASSES = new HashSet<Class>(); static { CLASSES.add(Class.class); CLASSES.add(Set.class); CLASSES.add(Integer.class); CLASSES.add(Throwable.class); CLASSES.add(String.class); CLASSES.add(AccountType.class); CLASSES.add(Double.class); } ... } Code 92: Class StringUnMarshaller (Teil 2) - 135 - Transport - Eigener Transport über Sockets 5.2.5.3. Aufruf der Un-/Marshal-Methoden Durch Prüfung eines Objekts mit isAssignableFrom() gegenüber allen in der obigen Auflistung festgelegten Klassen wird die seine Zulässigkeit festgestellt. Die entsprechende Methode zur Verarbeitung des Objekts wird aus dem Namen der festgelegten Klasse mit vorangestelltem "marshal" bzw. "unmarshal" ermittelt, und über Reflection aufgerufen. package de.fhr.vs_iw.socket; final class StringUnMarshaller { ... private private private private private private private private private private private private private private static static static static static static static static static static static static static static String String String String String String String Object Object Object Object Object Object Object marshalAccountType(Object o) marshalClass(Object o) marshalDouble(Object o) marshalInteger(Object o) marshalSet(Object o) marshalString(Object o) marshalThrowable(Object o) unmarshalAccountType(String s) unmarshalClass(String s) unmarshalDouble(String s) unmarshalInteger(String s) unmarshalSet(String s) unmarshalString(String s) unmarshalThrowable(String s) } Code 93: Class StringUnMarshaller (Teil 3) 5.2.5.4. Einfache Objekte Bei einfachen Objekten wird beim Marshalling von den Methoden der Datenwert als String dargestellt und zurückgegeben. Integer.toString(((AccountType)object).getId()); ((Class)object).getName(); ((Double)object).toString(); ((Integer)object).toString(); (String)object; Dem zurückgelieferten Wert wird wie oben angegeben (siehe 5.2.5) die Information über die Klasse des Objekts vorangestellt. Im umgekehrten Fall, beim unmarshalling, wird genauso verfahren und mit der Information aus einem String ein neues Objekt erzeugt. Die vorangestellte Information über die Klasse des Objekts wird zuvor benutzt um die richtige Methode aufzurufen. AccountType.getAccountTypeById(Integer.parseInt(string)); Class.forName(string); Double.valueOf(string); Integer.valueOf(string); string - 136 - Transport - Eigener Transport über Sockets 5.2.5.5. Kollektionen Die Verarbeitung einer Kollektion (Set) erfordert noch einen weiteren Schritt, da jedes enthaltene Objekt selbst auch verarbeitet werden muß. private static String marshallSet(Object object) throws Exception { StringBuffer sb = new StringBuffer(); for (Object obj : (Set)object) { sb.append(marshall(obj)); sb.append(","); } return sb.toString(); } Code 94: Method mashalSet() Für jedes enthaltene Objekt wird nochmals die marshall()-Methode aufgerufen. Die erhaltenen Teil-Strings werden durch Komma getrennt als Gesamt-String-Darstellung für die Kollektion zurückgegeben. private static Object unmarshallSet(String string) throws Exception { HashSet set = new HashSet(); for (String str : string.split(",")) { if (str != null && !str.equals("")) { set.add(unmarshall(str)); } } return set; } Code 95: Method unmarshalSet() Beim unmarshalling wird der Gesamt-String der Kollektion bei den Kommas in Teil-Strings getrennt und für jeden Teil-String die unmarshall()-Methode aufgerufen. Die daraus resultierenden Objekte werden in einem neuen Set zurückgegeben. 5.2.5.6. Ausnahmen Bei Ausnahmen werden neben dem allgemeinen Typen Throwable, auch der spezifische Typ der Ausnahme und die Nachricht, durch Komma getrennt, übertragen. private static String marshallThrowable(Object object) { Throwable throwable = (Throwable)object; return throwable.getClass().getName() + "," + t.getMessage(); } private static Object unmarshallThrowable(String string) throws Exception { int p = string.indexOf(","); String type = string.substring(0, p); String value = string.substring(p + 1); Class c = Class.forName(type); Constructor co = c.getDeclaredConstructor(new Class[] { String.class }); return co.newInstance(new Object[] { value }); } Code 96: Methoden marshalThrowable() und unmarshalThrowable() - 137 - Transport - Eigener Transport über Sockets 5.2.6. Entfernte Objekte Wie angesprochen (siehe 5.2.4) arbeitet das Protokoll mit dem Ansatz von eindeutig identifizierbaren entfernten Objekten deren Methoden aufgerufen werden. 5.2.6.1. Aufruf der Methoden über Reflection Der SocketServer erhält vom Client in jeder Anfrage einen Objekt-Identifikator (OID) durch den er das gewünschte Objekt in einer HashMap auffinden kann. private final HashMap<String, Object> objects = new HashMap<String, Object>(); Object object = objects.get(request.oid()); Sofern unter der angegebenen OID ein Objekt vorhanden ist, wird die in der Anfrage angegebene Methode davon über Reflection aufgerufen. Class clas = object.getClass(); Method m = clas.getMethod(request.method(), request.types()); Object result = m.invoke(object, params); Das Ergebnis (result) des Methodenaufrufs wird zurückgegeben und in der Antwort an den Client geschickt (siehe 5.2.4.3). response = new ServerSideResponse(result); Durch diese Vorgehensweise wird eine Anfrage in der process()-Methode durch den SocketServer verarbeitet. Durch das von den Anfrage- und Antwort-Klassen übernommene Un-/Marshalling kann der Server die Aufrufe der Methoden der "entfernten" Objekte völlig unproblematisch ausführen und das Ergebnis zurückschicken. 5.2.6.2. Übertragung der entfernten Objekte Lediglich wenn der Rückgabewert eines Methodenaufrufs (getAccountByNumber(), getAccountingEntryById()) ein Objekt liefert, das keinen Datenwert für sich, sondern selbst wieder ein entferntes Objekt (IAccount, IAccountingEntry) darstellt wird etwas von der normalen Verarbeitung abgewichen und das Objekt speziell behandelt. if (result instanceof IAccount) { response = remoteObjectResponse(result, IAccount.class); } else if (result instanceof IAccountingEntry) { response = remoteObjectResponse(result, IAccountingEntry.class); } else { response = new ServerSideResponse(result); } Durch die Methode remoteObjectResponse() wird eine passende Antwort erzeugt und der Client erhält nicht das Objekt selbst, sondern nur die OID, um es später entfernt aufrufen zu können. - 138 - Transport - Eigener Transport über Sockets private ServerSideResponse remoteObjectResponse(Object o, Class c) { String oid = getOid(o); if (oid == null) { oid = genOid(o); objects.put(oid, o); } return new ServerSideResponse(oid, c); } Code 97: Methode remoteObjectResponse() Sofern für das Objekt noch keine OID vergeben wurde (getOid()), wird eine neue erzeugt (genOid()), und damit das Objekt in der HashMap zur Verwaltung der entfernten Objekte abgelegt und in der Antwort zusammen mit dem Objekt-Typ (Class) an den Client geschickt. Um festzustellen ob für ein Objekt bereits eine OID vergeben wurde, müssen die Schlüssel der HashMap durchsucht werden. private String getOid(Object object) { for (Entry<String, Object> ent : objects.entrySet()) { if (ent.getValue() == object) { return ent.getKey(); } } return null; } private String genOid(Object object) { return Integer.toHexString(object.hashCode()); } Code 98: Methoden getOid() und genOid() Als OID für ein Objekt wird die hexadezimale Darstellung seines hashCode() in der Virtuellen Maschine verwendet. Aber auch jede andere Methode, die innerhalb einer SocketServerInstanz eine eindeutige OID liefert (z.B. einfach eine Nummer hochzählen), ist anwendbar, da ein Client bei diesem verteilten System immer nur mit genau einem Server verbunden ist und dessen Objekte aufruft. Da ein Client als Ausgangspunkt aller Aufrufe Zugriff auf eine Instanz der entfernten ServiceKlasse benötigt, wird eine spezielle OID für den Einstieg definiert. if (req.oid().equals("SocketService")) { return new ServerSideResponse(serviceOid, IAccountingService.class); } Weil die Erstellung eines eigenen Namensdienstes, der normalerweise zu einem verteilten System gehört, doch ein gewissen Aufwand darstellt, wird einfach ein festgelegter String "SocketService" wie eine OID verwendet und extra abgefragt. requestObject(IAccountingService.class, "SocketService", null); Bei einer Anfrage mit dieser speziellen OID wird als Antwort die eigentliche OID (serviceOid) eines entfernten Service-Objekts zurückgegeben mit der ein lokaler Stellvertreter dafür erzeugt werden kann. Das Objekt und seine OID werden zusammen mit einer Server-Instanz erzeugt und in der HashMap der entfernten Objekte abgelegt. - 139 - Transport - Eigener Transport über Sockets 5.2.6.3. Lokale Stellvertreter Da ein entferntes Objekt auch entfernt bleibt, auf der Clientseite jedoch mit den JavaObjekten der Buchhaltung gearbeitet werden soll, ist für jedes entfernte Objekt ein zugehöriges lokales Stellvertreter-Objekt (Stub) (AccountingService, Account, AccountingEntry) notwendig, das entsprechend die jeweilige Buchhaltungsschnittstelle implementiert. Für jede OID, die vom entfernten Server zurückgeschickt wird, wird ein Stellvertreter-Objekt erzeugt. Jede Methode eines solchen lokalen Objekts ruft lediglich mit der OID die zugehörige Methode des entfernten Objekts auf, unter Verwendung der in der Klasse SocketProxy definierten Methoden request1(), request2() und requestObject(). Die Buchhaltungsmethoden sind dann vom Prinzip her wie die folgenden Beispiele aus der Klasse AccountingService implementiert. public Set<Integer> getAccounts() throws IOException { return (Set<Integer>)proxy.request1(oid, "getAccounts"); } private HashMap<Integer, Account> accounts = new HashMap<Integer, Account>(); public IAccount getAccountByNumber(Integer number) throws IOException, AccountingException { Account account = accounts.get(number); if (account == null) { account = (Account)proxy.requestObject(IAccount.class, oid, "getAccountByNumber", number); accounts.put(number, account); } return account; } public void removeAccount(Integer number) throws IOException, AccountingException { proxy.request2(oid, "removeAccount", number); } Code 99: Implementierung einiger Buchhaltungsmethoden (Socket - AccountingService) Jede Methode einer Buchhaltungsschnittstelle ruft die passende Request-Methode mit allen übergebenen Parametern auf und gibt das Ergebnis direkt zurück. Lediglich die Methoden getAccountByNumber() und getAccountingEntryById() haben noch eine weitere Funktion. Hier werden die entfernten Objekte für Konten bzw. Buchungen angefragt und die erhaltenen lokalen Stellvertreter-Objekte werden in HashMaps gespeichert, damit nicht ständig neue Stellvertreter-Objekte erzeugt werden. 5.2.6.4. Anfrage-Methoden Die Anfrage-Methoden request1() und request2() geben jeweils einfache Werte zurück, unterscheiden sich in ihrer Signatur aber in den geprüften Ausnahmen, die geworfen werden, damit diese Anfrage-Methoden aus den jeweiligen Buchhaltungs-Methoden einfach aufgerufen werden können. - 140 - Transport - Eigener Transport über Sockets Object request1(String oid, String method, Object... args) throws IOException { try { return request2(oid, method, args); } catch (AccountingException e) { // Eine aufgetretene Buchhaltungsausnahme wird in eine // E/A-Ausnahme umgewandelt IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } } Code 100: Method request1() (SocketProxy) Die Methode request1() wirft eine lediglich eine IOException bei Kommunikationsfehlern, und wird von den Methoden verwendet, die nach der Definition in der Schnittstelle auch nur eine IOException werfen dürfen. Intern wird wiederum die Methode request2() aufgerufen und lediglich die zusätzliche AccountingException als IOException verpackt. Object request2(String oid, String method, Object... args) throws IOException, AccountingException { try { return request(oid, method, args).getValue(); } catch (IOException e) { // Eine E/A-Ausnahme wird direkt geworfen throw e; } catch (ServerException e) { // Eine Buchhaltungsausnahme auf dem Server wird direkt geworfen if (e.getCause() instanceof AccountingException) { throw (AccountingException)e.getCause(); } // Jede andere Server-Ausnahme wird als E/A-Ausnahme geworfen IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } catch (final Exception e) { // Jede andere Ausnahme wird als E/A-Ausnahme geworfen IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } } Code 101: Method request2() (SocketProxy) Auch die Methode request2() verpackt nur mögliche Ausnahmen auf die passende Weise, so daß letztlich nur IOExceptions und AccountingExceptions geworfen werden. Die eigentliche Funktionalität zur Datenübertragung durch die Request-Klasse wird in der privaten Methode request() aufgerufen. private ClientSideResponse request(String oid, String method, Object... args) throws Exception { ClientSideRequest req = new ClientSideRequest(oid, method, args); req.send(writer); return new ClientSideResponse(reader); } Code 102: Method request() (SocketProxy) - 141 - Transport - Eigener Transport über Sockets Die Methode requestObject() schließlich gibt nicht nur einen einfachen Wert, sondern ein Objekt der angegebenen Klasse als lokalen Stellvertreter für ein entferntes Objekt zurück. Damit das entfernte Objekt durch ein Stellvertreter-Objekt austauschbar ist, muß mit Schnittstellen gearbeitet werden. Die Verarbeitung von Ausnahmen erfolgt auf die gleiche Weise wie in der Methode request2(). Object requestObject(Class clas, String oid, String method, Object... args) throws IOException, AccountingException { if (!clas.isInterface()) { throw new IOException("requested type is not an interface"); } ClientSideResponse res; try { res = request(oid, method, args); } catch (...) { ... } if (res.getOid() == null) { throw new IOException("no remote object received"); } if (!res.getValue().equals(clas)) { throw new IOException("unexpected remote object type"); } if (clas.equals(IAccount.class)) { return new Account(this, res.getOid()); } else if (clas.equals(IAccountingEntry.class)) { return new AccountingEntry(this, res.getOid()); } else if (clas.equals(IAccountingService.class)) { return new AccountingService(this, res.getOid()); } throw new IOException("no proxy for remote object available"); } Code 103: Method requestObject() (SocketProxy) Je nachdem, ob eine OID von Server empfangen wurde, wird damit ein Objekt instanziert oder eine Ausnahme geworfen. Im Beispiel erfolgt die Instanzierung direkt, durch Unterscheidung der gewünschten Schnittstelle über if-Anweisungen, wobei es sicher elegantere Möglichkeiten gäbe ein Stellvertreter-Objekt für eine angegebene Schnittstelle zu erhalten (Zuordnung in einer Map, dynamische Stub-Generierung). 5.2.7. Fazit Dieses Beispiel zeigt, daß es einen nicht unerheblichen Aufwand darstellt, ein umfassendes objektorientiertes verteiltes System zu entwickeln, wobei hier etliche Punkte noch gar nicht betrachtet wurden (z.B. verteilte Garbage-Collection, Namensdienst, Trennzeichen im Protokoll). Glücklicherweise gibt es einige ausgereifte Technologien und Bibliotheken, die unter Java verwendbar sind, und alle notwendigen Funktionalitäten für den Aufbau von verteilten Systemen zur Verfügung stellen. - 142 - Transport - Remote Method Invocation (RMI) 5.3. Remote Method Invocation (RMI) 5.3.1. Allgemeines Remote Method Invocation (RMI) [33] ist die Java-eigene Art, die Methoden von entfernten Objekten aufzurufen. Das bedeutet, daß sich das Objekt auf jeden Fall in einer separaten virtuellen Maschine, die auch auf einem anderen Rechner laufen kann, befindet [34]. Abbildung 68: Schematischer Ablauf von RMI Als Basis dient ein Namensdienst, bei dem sich Objekte, die einen entfernten Dienst anbieten, unter einem festgelegten Namen registrieren (1), und dann über den Namen abgerufen werden können (2). Man erhält dann einen lokalen Stellvertreter für dieses Objekt, von dem die Methoden aufgerufen (3) und auf dem entfernten System ausgeführt (4) werden. Wegen dem Austausch durch lokale Stellvertreter werden die Methoden in einer Schnittstelle definiert. Die Komplexität der Netzwerk-Kommunikation wird vor der Anwendung verborgen [36]. Abbildung 69: RMI-Kommunikations-Architektur Auf die genaue Funktionsweise von RMI soll hier nicht eingegangen, sondern hauptsächlich die Anbindung an das Buchhaltungssystem beschrieben werden (siehe auch [35]). - 143 - Transport - Remote Method Invocation (RMI) 5.3.2. Entfernte Objekte Ein entferntes Objekt basiert auf einer Schnittstelle und einer implementierenden Klasse, die nach den folgenden Vorgaben aufgebaut wird. 5.3.2.1. Remote-Interface Wie bereits angesprochen wird eine Schnittstelle für die entfernten Methoden definiert, die lediglich einige Kriterien erfüllen muß: Vom Interface java.rmi.Remote abgeleitet, damit die Schnittstelle als Remote-Interface anerkannt wird Alle definierten Methoden müssen eine RemoteException oder eine Superklasse davon werfen Die verwendeten Parameter und Rückgabetypen müssen serialisierbar (Interface java.io.Serializable) sein Die im Buchhaltungsmodell definierten Schnittstellen (IAccountingServce, IAccount, IAccountingEntry, siehe 2.2) erfüllen diese Anforderungen bereits, und können direkt als Remote-Interface verwendet werden. 5.3.2.2. Remote-Object Wie in den vorigen Kapiteln muß nun eine Service-Klasse, Konto-Klasse und BuchungsKlasse erstellt werden, die wieder jeweils eine der Schnittstellen implementieren. Durch die Ableitung von java.rmi.Remote erkennt RMI Objekte als Remote-Object an. Zugriff auf die Service-Klasse selbst erhält ein Client über den Namensdienst. Aber wenn nun beim Aufruf von getAccountByNumber() oder getAccountEntryById() der Service-Klasse ein Konten- oder Buchungsobjekt zurückgegeben wird, erhält ein Client automatisch nur den lokalen Stellvertreter für das zurückgegebene Objekts. Es reicht aber nicht, nur das Remote-Interface zu implementieren. Die Klassen von entfernten Objekten müssen zusätzlich noch um einige Fähigkeiten bzgl. der Kommunikation über TCPVerbindungen erweitert werden. Dies geschieht durch das Ableiten von der Klasse java.rmi.server.UnicastRemoteObject und den Aufruf des Konstruktors der Superklasse. Dies gilt für alle drei Klassen unseres Systems (Service, Konto, Buchung). 5.3.3. Anbindung an die Datenhaltung Auch die RMI-Implementierung zur Datenübertragung wird an eines der bestehenden Module zur Datenhaltung angebunden. 5.3.3.1. Proxy-Instanzierung Wie bei jedem Client eines Buchhaltungssystems wird eine Instanz der angegebenen Proxy-Klasse gebildet und davon die connect()-Methode aufgerufen. Damit kann die RMIImplementierung auf die Objekte des Datenhaltungmoduls zugreifen. - 144 - Transport - Remote Method Invocation (RMI) 5.3.3.2. Weitergabe der Aufrufe Alle Aufrufe der entfernten Methoden werden direkt an die Datenhaltung weitergegeben. Das RMI-System "stülpt" sich praktisch nur über die Datenhaltung und übernimmt die Datenübertragung, was allerdings manuell zu implementieren ist. public Double calculateBalance(Integer number) throws IOException, AccountingException { return nextService.calculateBalance(number); } Code 104: Method calculateBalance() bei RMI Dies hat aber nichts mit RMI an sich zu tun, sondern mit dem Aufbau des Buchhahltungssystems, und muß deshalb für jede einzelne Methode manuell ausprogrammiert werden. 5.3.4. Server - RMI-Registry Im Prinzip kann man im ersten Ansatz nicht von einem RMI-Server sprechen, da zwei Dienste (siehe 5.3.1) benötigt werden, zum Einen der Namensdienst und zum anderen das entfernte Objekt selbst. 5.3.4.1. Registry starten Bei größeren Anwendungen läuft der Namensdienst, die RMI-Registry, meistens irgendwo als eigenständiger Dienst, bei dem sich Objekte, die auf verschiedenen Systemen zur Verfügung stehen zentral registrieren können. Beim JDK ist eine RMI-Registry enthalten, die durch rmiregistry [Optionen] [Port] auf dem angegebenen Port (standardmäßig 1099) gestartet werden kann. 5.3.4.2. Registry einbinden Über die Schnittstelle java.rmi.Registry wird in einer Anwendung auf den Namensdienst zugegriffen. Eine Instanz dieser Klasse, und damit eine Verbindung zu einer Registry erhält man durch die Klasse java.rmi.LocateRegistry. package java.rmi.registry; public class LocateRegistry { static Registry createRegistry(int port); static Registry getRegistry(); static Registry getRegistry(int port); static Registry getRegistry(String host); static Registry getRegistry(String host, int port); } Code 105: Class LocateRegistry - 145 - Transport - Remote Method Invocation (RMI) Mit den verschiedenen Methoden kann auf eine Registry, die auf dem angegebenen Host und dem angegebenen Port läuft zugegriffen werden. Interessant ist aber auch die Methode createRegistry(), wodurch eine Anwendung ihre eigene Registry erzeugen, und damit Objekte zur Verfügung stellen kann. 5.3.4.3. Objekt registrieren Nachdem man sich zu einer Registry verbunden, oder selbst eine erzeugt hat, können mit den Methoden einer Registry-Instanz die entfernten Objekte registriert oder geholt werden. package java.rmi.registry; public interface Registry extends Remote { void bind(String name, Remote obj); String[] list(); Remote lookup(String name); void rebind(String name, Remote obj); void unbind(String name); } Code 106: Interface Registry Durch den Aufruf von bind() bzw. rebind() wird ein Objekt unter dem angegebenen Namen registriert. Mit list() kann eine Liste aller registrierten Namen ausgegeben werden. 5.3.4.4. Der vollständige Server Der RMIServer stellt beide nötigen Dienste, Registry und entferntes Objekt, zur Verfügung. package de.fhr.vs_iw.rmi; public final class RMIServer { public static void main(String[] args) { try { if (args.length <= 0) { System.err.println("Eine Proxy-Klasse muß angegeben werden"); return; } IAccountingProxy localProxy = Utilities.loadProxy(args[0]); IAccountingService nextService = localProxy.connect(Utilities.shiftArgs(args, 1)); IAccountingService service = new AccountingService(nextService); } } Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); registry.rebind("RMIAccountingService", service); } catch (Exception e) { e.printStackTrace(); } Code 107: Class RMIServer - 146 - Transport - Remote Method Invocation (RMI) Zuerst wird von der als Kommandozeilenargument angegebenen Proxy-Klasse (von einem Modul der Datenhaltung) eine Instanz gebildet, um damit ein Objekt der zugehörigen ServiceKlasse zu erhalten. Alle weiteren Kommandozeilenargumente werden der connect()Methode zum Laden des Service-Objekts übergeben. Mit diesem Service-Objekt wird ein RMI-Service-Object erzeugt, das wie beschrieben (siehe 5.3.3.2) die RMI-Methodenaufrufe weitergibt. Anschließend wird eine eigene RMI-Registry auf dem Standardport 1099 erzeugt, und das RMI-Service-Object unter dem Namen "RMIAccountingService" dort registriert. 5.3.5. Entfernte Buchhaltungsobjekte Die Funktionalität der RMI-Buchhaltung basiert schließlich auf entfernten Objekten und deren lokalen Stellvertretern. 5.3.5.1. Service-Klasse Die RMI-Service-Klasse kapselt die Service-Klasse eines anderen Buchhaltungsmoduls (IAccountingService nextService) für die Verwendung mit RMI. package de.fhr.vs_iw.rmi; public final class AccountingService extends UnicastRemoteObject implements IAccountingService { private HashMap<Integer, Account> accountMap = new HashMap<Integer, Account>(); private HashMap<Integer, AccountingEntry> entryMap = new HashMap<Integer, AccountingEntry>(); private final IAccountingService nextService; protected AccountingService(IAccountingService next) throws IOException { super(); nextService = next; } public IAccount getAccountByNumber(Integer number) throws IOException, AccountingException { Account account = accountMap.get(number); if (account == null) { account = new Account(nextService.getAccountByNumber(number)); accountMap.put(number, account); } return account; } public IAccountingEntry getAccountingEntryById(Integer id) throws IOException, AccountingException { AccountingEntry entry = entryMap.get(id); if (entry == null) { entry = new AccountingEntry(nextService.getAccountingEntryById(id)); entryMap.put(id, entry); } return entry; } - 147 - Transport - Remote Method Invocation (RMI) public Double calculateBalance(Integer number) throws IOException, AccountingException { return nextService.calculateBalance(number); } ... } <More Service-Methods> Code 108: Class AccountingService (RMI) Die Klasse ist von UnicastRemoteObject abgeleitet, und ruft diese (super()) im Konstruktor auf. Jede Methode der RMI-Service-Klasse ruft lediglich die korrespondierende Methode des gekapselten Service-Objekts auf. Als einzige Funktionalität werden die Objekte für Konten und Buchungen in HashMaps verwaltet. 5.3.5.2. Konto-Klasse Die RMI-Konto-Klasse unterscheidet sich vom Prinzip her nicht von der Service-Klasse. Auch hier werden nur Methodenaufrufe an ein anderes gekapseltes Kontenobjekt weitergegeben. package de.fhr.vs_iw.rmi; public final class Account extends UnicastRemoteObject implements IAccount { private final IAccount nextAccount; } protected Account(IAccount next) throws IOException { super(); nextAccount = next; } public Set<Integer> getCreditEntries() throws IOException { return nextAccount.getCreditEntries(); } public Set<Integer> getDebitEntries() throws IOException { return nextAccount.getDebitEntries(); } public String getDescription() throws IOException { return nextAccount.getDescription(); } public Integer getNumber() throws IOException { return nextAccount.getNumber(); } public AccountType getType() throws IOException { return nextAccount.getType(); } public void setDescription(String description) throws IOException { nextAccount.setDescription(description); } public void setType(AccountType type) throws IOException { nextAccount.setType(type); } Code 109: Class Account (RMI) - 148 - Transport - Remote Method Invocation (RMI) 5.3.5.3. Buchungs-Klasse Die RMI-Buchungs-Klasse weicht auch nicht von Aufbau und Funktionalität der RMI-Service-Klasse bzw. der RMI-Konto-Klasse ab. package de.fhr.vs_iw.rmi; public final class AccountingEntry extends UnicastRemoteObject implements IAccountingEntry { private IAccountingEntry nextEntry; protected AccountingEntry(IAccountingEntry next) throws IOException { super(); nextEntry = next; } public Double getAmount() throws IOException { return nextEntry.getAmount(); } public Integer getCreditAccount() throws IOException { return nextEntry.getCreditAccount(); } public Integer getDebitAccount() throws IOException { return nextEntry.getDebitAccount(); } public Integer getId() throws IOException { return nextEntry.getId(); } public String getText() throws IOException { return nextEntry.getText(); } public void setAmount(Double amount) throws IOException { nextEntry.setAmount(amount); } public void setCreditAccount(Integer number) throws IOException, AccountingException { nextEntry.setCreditAccount(number); } public void setDebitAccount(Integer number) throws IOException, AccountingException { nextEntry.setDebitAccount(number); } public void setText(String text) throws IOException { nextEntry.setText(text); } } Code 110: Class AccountingEntry (RMI) 5.3.6. Aufruf der entfernten Buchhaltung Zum Aufruf der entfernten Buchhaltung muß die beim Start des Servers registrierte Instanz des entfernten Service-Objekts abgerufen werden. - 149 - Transport - Remote Method Invocation (RMI) 5.3.6.1. Anfrage an den Namensdienst Dazu wird auf der Clientseite die lookup()-Methode der Klasse java.rmi.Naming verwendet, die ein entferntes Objekt direkt, durch Angabe ein Art URL in der Form //host:port/name abruft. Bei diesem Vorgang wird automatisch (seit Java 5) ein lokales Stellvertreter-Objekt (Stub) erzeugt, das man direkt zum definierten Remote-Interface "casten" und verwenden kann (Strichwort: dynamische Proxy-Generierung). 5.3.6.2. RMI-Proxy-Klasse Die Proxy-Klasse muß dann auf der Clientseite nur noch diesen lookup()-Aufruf durchführen, wobei nur die Ausnahme NotBoundException abgefangen werden muß, die auftritt, falls das gewünschte Objekt mit dem angegebenen Namen nicht registriert ist. package de.fhr.vs_iw.rmi; public final class RMIProxy implements IAccountingProxy { private IAccountingService remoteService = null; public RMIProxy() {} public void close() { remoteService = null; } public IAccountingService connect(String[] args) throws IOException { if (args.length <= 0) { throw new IllegalArgumentException( "Als Argument muß der Host[:Port]" + " der RMI-Registry angegeben werden"); } try { remoteService = (IAccountingService) Naming.lookup("//" + args[0] + "/RMIAccountingService"); } catch (NotBoundException e) { throw new IOException( "Entferntes IAccountingService-Objekt nicht gebunden"); } return remoteService; } } Code 111: Class RMIProxy Da die Service-Schnittstelle der Buchhaltung als Remote-Interface verwendet wurde, kann das Objekt, das man durch den Aufruf der lookup()-Methode erhält, direkt von der connect()Methode zurückgegeben werden, ohne daß dafür ein Adapter erforderlich wäre. Und da auch die Konto- und die Buchungs-Schnittstelle von java.rmi.Remote abgeleitet wurden, werden deren Methoden entfernt aufgerufen, wobei diese Objekte nicht unter einem Namen registriert werden müssen, weil RMI automatisch dafür sorgt, daß ein Client nur den Stub eines entfernten Objekts erhält, wenn es als Rückgabewert einer Methode auftritt. - 150 - Transport - Remote Method Invocation (RMI) 5.3.7. Codebase Eine der Möglichkeiten von Java besteht darin, daß Java-Klassen zur Laufzeit dynamisch von jeder beliebigen URL (sofern nicht durch Sicherheitseinstellungen eingeschränkt) in eine Virtuelle Maschine nachgeladen werden können. Dadurch können Java-Programme ausgeführt werden, die auf einem System nicht im Vorfeld installiert wurden [77]. Für RMI bedeutet das konkret, daß die class-Dateien der entfernten Objekte und Schnittstellen nicht lokal auf einem Rechner vorhanden sein müssen, sondern bei Aufruf der lookup()Methode automatisch nachgeladen werden können. Abbildung 70: Nachladen von Java-Klassen Damit das RMI-System fehlende Klassen auffinden kann, muß eine Codebase eingerichtet werden. Dies ist normalerweise ein HTTP- oder FTP-Server, der die notwendigen classDateien zur Verfügung stellt. Es können aber auch "file:///" URLs verwendet werden, wenn sich Client und Server auf demselben System befinden, oder aber die Dateien auf andere Weise (NFS, CIFS, SMB, etc.) zugänglich sind. Bei der RMI-Registry wird dann durch die Eigenschaft java.rmi.server.codebase angegeben unter welcher URL die Codebase eingerichtet wurde. Beim Start der Virtuellen Maschine für den RMI-Server bzw. die RMI-Registry muß die Einstellung als Kommandozeilenparameter angegeben werden. -Djava.rmi.server.codebase=http://webserver/classdir/ Diese Einstellung wird dann jedem Client, der nach einem entfernten Objekt anfragt, automatisch mitgeteilt. 5.3.8. Fazit Durch die Verwendung von RMI können mit Java sehr einfach verteilte Systeme realisiert werden. Ein Problem besteht erst dann, wenn ein verteiltes System plattform- und systemunabhängig sein soll, weil RMI auf die ausschließliche Verwendung unter Java ausgelegt und im Kern der Sprache integriert ist. - 151 - Transport - CORBA 5.4. CORBA 5.4.1. Allgemeines CORBA (Common Object Request Broker Architecture) ist ein plattform- und systemunabhängiger Standard der Object Management Group (OMG) [79] zur Entwicklung von verteilten, objektorientierten Systemen mit dem IIOP-Protokoll, für den es entsprechende Anbindungen an verschiedene Programmierumgebungen gibt [80]. Der Standard gliedert sich grob in die folgenden kozeptionellen Bestandteile. 5.4.1.1. Object Request Broker (ORB) Ein ORB ist allgemein ein Prozess, der als Vermittler die Kommunikation von verteilten Objekten ermöglicht. Er übernimmt die Datenübertragung zwischen den Objekten und kümmert sich um die Konvertierung in ein einheitliches Datenformat (sog. Marshalling), was z.B. bei unterschiedlicher Byte-Reihenfolge (Endian) auf den beteiligten Systemen notwendig ist. Jedes Objekt, dessen Daten und Funktionen entfernt aufrufbar sein soll, muß an einen ORB gebunden werden. 5.4.1.2. Interoperable Object Reference (IOR) Eine IOR dient als systemübergreifende Referenz auf verteilte Objekte, die durch ORBs zur Verfügung gestellt werden. Die IOR enthält alle notwendigen Information zum Zugriff auf ein derartiges Objekt: Kommunikationsprotokoll Netzwerk-Adresse und Port (bei TCP) Angaben zur Dekodierung (Byte-Reihenfolge, Verschlüsselung, Kompression) Eindeutige Identität der Objekt-Instanz . Damit kann ein Kommunikationskanal aufgebaut werden, der den Zugriff auf die verteilten Objekte wie auf dem lokalen System ermöglicht. 5.4.1.3. Portable Object Adapter (POA) Der POA dient als Bindeglied zwischen ORB und Objekt-Instanzen auf dem Server. Dies trennt die plattformunabhängige (CORBA)-Schnittstelle eines Objekts von der Implementierung in einer bestimmten Programmiersprache (sog. Servant). Ein POA instanziert Servants, erzeugt die entsprechenden Objektreferenzen und leitet Client-Anfragen weiter. Um die Servant-Implementierung in objektorientierter Weise zu vereinfachen, ist vorgesehen, dass eine Servant-Klasse gleichzeitig zum eigenen POA wird, indem die Klasse die dazu notwendige Funktionalität erbt (z.B. bei Java von org.omg.PortableServer.Servant). - 152 - Transport - CORBA 5.4.1.4. Tie-Mechanismus Für Programmiersprachen, bei denen die „normale“ Art der Vererbung der POA-Funktionen nicht erwünscht bzw. nicht möglich ist (z.B. bei Java wegen fehlender Mehrfachvererbung), wurde der Tie-Mechanismus vorgesehen. Dabei wird zu jedem Servant eine eigene POA-Tie-Klasse generiert (siehe 5.4.2.4), welche die Servant-Implementierung kapselt und „nach außen“ die selbe anwendungsspezifische Schnittstelle wie der Servant anbietet. Dadurch kann diese Zusatzklasse völlig transparent in die eigene Anwendung eingebunden werden. 5.4.1.5. CORBA-Dienste Zusätzlich zum Verfahren für den Aufruf entfernter Objekte definiert CORBA noch einige Dienste mit einer definierten IDL-Schnittstelle (siehe 5.4.2) die bei der Verwaltung der Objekte und dem Aufbau eines verteilten Systems hilfreich sind [81]. Naming Service Trading Service Event Service Lifecycle Service Relationship Service Externalization Service Persistent Object Service Concurrency Control Service Transaction Service Property Service Licensing Service Object Collection Service Query Service Time Service Security Service Der wichtigste Dienst ist der Naming Service, der für benannte und registriete Objekte einer Anwendung die notwendigen Objektreferenzen (IOR) liefert. 5.4.1.6. Implementierungen Da CORBA nur ein allgemeiner Standard ist, benötigt man zur Software-Entwicklung eine konkrete Implementierung für die verwendete Programmiersprache und Zielplattform. TAO MICO JacORB omniORB Orbacus Bestandteil von Java http://www.cs.wustl.edu/~schmidt/TAO.html http://www.mico.org/ http://www.jacorb.org/ http://omniorb.sourceforge.net/ http://web.progress.com/en/orbacus/index.html http://java.sun.com/j2ee/corba/index.html Jede Implementierung enthält prinzipiell den statischen Teil von CORBA (ORB, Dienste), der konfiguriert und auf einem Server ausgeführt wird. Dazu kommt ggf. ein programmiersprachenabhängiger „Generator“ (IDL-Compiler), der die spezifischen Schnittstellen und Klassen (sog. Artefakte: POA, Tie) zur Verbindung von CORBA mit der eigenen Anwendung erzeugt. - 153 - Transport - CORBA 5.4.2. Interface Definition Language CORBA basiert auf einer eigenen Beschreibungssprache, die von Plattformen und Programmiersprachen unabhängig ist, um einen Dienst, der entfernt aufrufbar sein soll, zu beschreiben. In dieser Beschreibung werden im wesentlichen Schnittstellen definiert, deren Methodenaufrufe an ein entferntes System übertragen werden. Zu den Schnittstellen werden weitere notwendige Bestandteile des Systems definiert (Ausnahmen, Enumerationen, Arrays), die dann zusammen mit den Basisdatentypen von CORBA verwendet werden können. 5.4.2.1. Buchhaltungs-IDL Für die Buchhaltungsanwendung bedeutet dies, daß alle Funktionalitäten der Buchhaltungsschnittstellen nochmals als CORBA-IDL definiert werden müssen. module corba { typedef sequence<long> CorbaIntegerSet; exception CorbaAccountingException { string message; }; exception CorbaIOException { string message; }; enum CorbaAccountType { ACTIVE, AMOUNT, EXPENSE, PASSIVE, PROCEEDS }; interface CorbaAccount { CorbaIntegerSet getCreditEntries() raises (CorbaIOException); CorbaIntegerSet getDebitEntries() raises (CorbaIOException); string getDescription() raises (CorbaIOException); long getNumber() raises (CorbaIOException); CorbaAccountType getType() raises (CorbaIOException); void setDescription(in string description) raises (CorbaIOException); void setType(in CorbaAccountType type) raises (CorbaIOException); }; - 154 - Transport - CORBA interface CorbaAccountingEntry { double getAmount() raises (CorbaIOException); long getCreditAccount() raises (CorbaIOException); long getDebitAccount() raises (CorbaIOException); long getId() raises (CorbaIOException); string getText() raises (CorbaIOException); void setAmount(in double amount) raises (CorbaIOException); void setCreditAccount(in long creditAccount) raises (CorbaAccountingException, CorbaIOException); void setDebitAccount(in long debitAccount) raises (CorbaAccountingException, CorbaIOException); void setText(in string text) raises (CorbaIOException); }; interface CorbaAccountingService { double calculateBalance(in long number) raises (CorbaAccountingException, CorbaIOException); void createAccount(in long number, in CorbaAccountType type, in string description) raises (CorbaAccountingException, CorbaIOException); long createAccountingEntry(in long debitAccount, in long creditAccount, in double amount, in string text) raises (CorbaAccountingException, CorbaIOException); CorbaAccount getAccountByNumber(in long number) raises (CorbaAccountingException, CorbaIOException); CorbaIntegerSet getAccountingEntries() raises (CorbaIOException); CorbaAccountingEntry getAccountingEntryById(in long id) raises (CorbaAccountingException, CorbaIOException); CorbaIntegerSet getAccounts() raises (CorbaIOException); void removeAccount(in long number) raises (CorbaIOException, CorbaAccountingException); void removeAccountingEntry(in long id) raises (CorbaIOException, CorbaAccountingException); }; }; Resource 13: CORBA Accounting.idl Von der Logik her entspricht der Aufbau der IDL vollkommen den Schnittstellen und Klassen aus dem Buchhaltungsmodel. Die Kontotypen werden als Enumeration definiert und die beiden bekannten Typen für Ausnahmen eingeführt. Anstatt eines Set zur Auflistung der Kontonummern bzw. Buchungs-IDs wird in der IDL ein benutzerdefinierter Datentyp als sequence angegeben. Die sequence definiert hier als Inhaltstyp den Basisdatentyp long, könnte aber auch entfernte Objekte zurückgeben. Alle Definitionen werden von der globalen Angabe module umschlossen, die zur Abgrenzung von Namensräumen dient. Für den Aufbau von hierarchischen Namensräumen können die module Angaben geschachtelt werden. - 155 - Transport - CORBA 5.4.2.2. Zuordnung der IDL-Elemente zu Java-Typen Für die meisten Programmiersprachen gibt es eine Zuordnung von Datentypen zu den Elementen der IDL. Hier werden die definierten Schnittstellen und die CORBA-Basis-Typen entsprechend auf Java-Klassen und Java-Typen abgebildet (IDL-to-Java-Mapping). Einfache Datentypen boolean char wchar octet string wstring short unsigned short boolean char char byte java.lang.String java.lang.String short short long unsigned long long long unsigned long long float double integer integer long long float double long double unmapped Tabelle 13: Zuordnung der Basisdatentypen Die einfachen Datentypen der IDL werden in Java durch die Wrapper-Klassen der primitiven Java-Datentypen behandelt. Für andere Angaben in der IDL sind entsprechend komplexere Java-Konstrukte notwendig. Komplexe IDL-Angaben module Java-Package Schnittstelle (org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity) interface Stub-Klasse (org.omg.CORBA.portable.ObjectImpl) enum Klasse (org.omg.CORBA.portable.IDLEntity) exception Klasse, Ausnahme (org.omg.CORBA.UserException) sequence Array vom entsprechenden Inhaltstyp Tabelle 14: Zuordnung der komplexen IDL-Angaben Die Java-Klassen für eine CORBA-basierte Anwendung werden je nach Typ von den entsprechenden CORBA-Klassen abgeleitet bzw. müssen bestimmte CORBA-spezifische Schnittstellen implementieren. 5.4.2.3. Java-Artefakte generieren Dieses Java-to-IDL-Mapping wird nicht manuell durchgeführt. Dafür gibt es den IDLCompiler (idlj.exe) aus dem Java-SDK, der aus einer IDL-Datei alle benötigten JavaArtefakte (Klassen und Schnittstellen) erzeugt. Für den IDL-Compiler werden folgende Kommandozeilenargumente angegeben, die den Vorgang steuern. -pkgPrefix -td -fallTie zusätzliches Präfix für eine module Angabe, zur Einordnung der generierten Klassen in die gewünschte Java-Package-Hierarchie Target Directory: das Zielverzeichnis für die erzeugten Klassen gibt an, daß alle möglichen Artefakte erzeugt werden sollen, auch die für die TIE-Anbindung - 156 - Transport - CORBA <?xml version="1.0" standalone="yes"?> <project basedir=".." default="idl-generator"> <property file="${basedir}/ant/build.properties" /> <target name="idl-generator"> <exec executable="${jdk.home}/bin/idlj.exe"> <arg line="-pkgPrefix corba de.fhr.vs_iw" /> <arg line="-td ${basedir}/generated" /> <arg line="-fallTie" /> <arg value="${basedir}/resources/corba/Accounting.idl" /> </exec> </target> </project> Resource 14: Ant-Build-Datei corba-idlj.xml Der IDL-Compiler kann aus Eclipse heraus durch diese Ant-Build-Datei aufgerufen werden, sofern auch das bin-Verzeichnis des angegebenen JDK in den PATH aufgenommen wurde. 5.4.2.4. Erzeugte Klassen und Schnittstellen Durch den IDL-Compiler wird für die Buchhaltung eine größere Anzahl an Java-Klassen und Schnittstellen erzeugt. Klasse _CorbaAccountingEntryStub _CorbaAccountingServiceStub _CorbaAccountStub CorbaAccount CorbaAccountHelper CorbaAccountHolder CorbaAccountingEntry CorbaAccountingEntryHelper CorbaAccountingEntryHolder CorbaAccountingEntryOperations CorbaAccountingEntryPOA CorbaAccountingEntryPOATie CorbaAccountingException CorbaAccountingExceptionHelper CorbaAccountingExceptionHolder CorbaAccountingService CorbaAccountingServiceHelper CorbaAccountingServiceHolder CorbaAccountingServiceOperations CorbaAccountingServicePOA CorbaAccountingServicePOATie CorbaAccountOperations CorbaAccountPOA CorbaAccountPOATie CorbaAccountType CorbaAccountTypeHelper CorbaAccountTypeHolder CorbaIntegerSetHelper Java-Typ Klasse Klasse Klasse Schnittstelle Klasse Klasse Schnittstelle Klasse Klasse Schnittstelle Klasse Klasse Klasse Klasse Klasse Schnittstelle Klasse Klasse Schnittstelle Klasse Klasse Schnittstelle Klasse Klasse Klasse Klasse Klasse Klasse - 157 - Beschreibung Lokaler Stellvertreter Lokaler Stellvertreter Lokaler Stellvertreter Schnittstelle mit Mehrfachvererbung Werkzeug-Klasse Behälter für Objekte des Typs Schnittstelle mit Mehrfachvererbung Werkzeug-Klasse Behälter für Objekte des Typs Schnittstelle mit Geschäftsmethoden Superklasse für normale Anbindung Superklasse für TIE-Anbindung Klasse für definierte Ausnahme Werkzeug-Klasse Behälter für Objekte des Typs Schnittstelle mit Mehrfachvererbung Werkzeug-Klasse Behälter für Objekte Schnittstelle mit Geschäftsmethoden Superklasse für normale Anbindung Superklasse für TIE-Anbindung Schnittstelle mit Geschäftsmethoden Superklasse für normale Anbindung Superklasse für TIE-Anbindung Enumeration Werkzeug-Klasse Behälter für Objekte des Typs Werkzeug-Klasse Transport - CORBA CorbaIntegerSetHolder CorbaIOException CorbaIOExceptionHelper CorbaIOExceptionHolder Klasse Klasse Klasse Klasse Behälter für Objekte des Typs Klasse für definierte Ausnahme Werkzeug-Klasse Behälter für Objekte des Typs Tabelle 15: Erzeugte CORBA-Artefakte Für jedes in der IDL definierte Artefakt werden mehrere Java-Dateien erzeugt, die jeweils verschiedene Funktionalitäten übernehmen und deren Name mit einem entsprechenden Suffix versehen wird. Suffix Beschreibung lokale Stellvertreterklasse für entfernte Objekte, die den notwendigen Code für Stub CORBA-Kommunikation der Anwendung implementiert Hilfsklasse mit den notwendigen Methoden, um aus einer Referenz auf ein Helper entferntes Objekt ein lokales Stellvertreterobjekt zu erstellen Behälter-Klasse für ein Objekt des entsprechenden Typs, wird benötigt, da Java Holder keine out-Parameter bei Methoden unterstützt, CORBA und andere Programmiersprachen aber schon Schnittstelle, die nur die definierten Geschäftsmethoden aus der IDL-Datei Operations enthält Portable Object Adapter: Basisklasse zur Ableitung einer eigenen Klasse, die POA die Funktionalität von entfernten Objekten im Sinne der Geschäftslogik implementiert, und auf einem Server als Servant instanziert wird Adapter für die TIE-Anbindung einer Klasse, bei der die eigene Klasse nicht POATie davon abgeleitet wird, sondern nur die Schnittstelle mit den Methoden implementiert, damit die Vererbungsstruktur der eigenen Anwendung erhalten bleibt. Schnittstellen mit Mehrfachvererbung fassen die Operations-Schnittstelle mit kein den CORBA-Kommunikationsschnittstellen (Object, IDLEntity) zusammen, und werden dann von einer Anwendung für die konkreten Aufrufe verwendet Resource 15: Bedeutung der CORBA-Suffixe Durch diese Kombination aus anwendungsspezifisch erzeugten Dateien und den im JDK mitgelieferten CORBA-Klassen wird die Kommunikationsschicht der Anwendung realisiert. Was fehlt ist nun die Implementierung der Geschäftslogik auf Client- und Server-Seite. 5.4.3. Verwendung der erzeugten Klassen auf der Serverseite Auf der Serverseite werden die generierten POA- bzw. POATie-Klassen verwendet, um die Geschäftslogik der Buchhaltung zu Implementieren, bzw. um wieder auf die Klassen eines Moduls aus der Datenhaltung zuzugreifen, welches die Geschäftslogik bereits implementiert. 5.4.3.1. CORBA-Server Die Klasse CorbaServer enthält zum einen die main()-Methode zum Start des Server und ist zum anderen von CorbaAccountingServicePOA abgleitet, damit eine Instanz der Klasse als Servant für den entfernten Aufruf der Methoden der Service-Schnittstelle dienen kann. - 158 - Transport - CORBA package de.fhr.vs_iw.corba; public final class CorbaServer extends CorbaAccountingServicePOA { private = new private = new private Map<Integer, CorbaAccountingEntry> accountingEntries HashMap<Integer, CorbaAccountingEntry>(); Map<Integer, CorbaAccount> accounts HashMap<Integer, CorbaAccount>(); final IAccountingService next; private CorbaServer(final IAccountingService next) { this.next = next; } public void createAccount(int number, CorbaAccountType type, String description); public int createAccountingEntry(int debitAccount, int creditAccount, double amount, String text); public CorbaAccount getAccountByNumber(int number) throws CorbaAccountingException, CorbaIOException { try { CorbaAccount account = accounts.get(number); if (account == null) { IAccount nextaccount = next.getAccountByNumber(number); Servant servant = new CorbaServerAccount(nextaccount); rootPoa.activate_object(servant); org.omg.CORBA.Object ref = rootPoa.servant_to_reference(servant); account = CorbaAccountHelper.narrow(ref); accounts.put(number, account); } return account; } catch (IOException e) { throw new CorbaIOException(e.getMessage()); } catch (AccountingException e) { throw new CorbaAccountingException(e.getMessage()); } catch (ServantAlreadyActive e) { throw new CorbaIOException(e.getMessage()); } catch (WrongPolicy e) { throw new CorbaIOException(e.getMessage()); } catch (ServantNotActive e) { throw new CorbaIOException(e.getMessage()); } } public public public public public public } int[] getAccountingEntries(); double calculateBalance(int number); CorbaAccountingEntry getAccountingEntryById(int id); int[] getAccounts(); void removeAccount(int number); void removeAccountingEntry(int id); ... Code 112: Class CorbaServer (Methoden) Die Klasse fungiert als Adapter von CORBA zu einer Service-Klasse der Buchhaltung. Die Methodenaufrufe werden direkt umgesetzt, und in erster Linie müssen nur die Ausnahmen aus dem Buchhaltungsmodell in ihre korrespondierenden CORBA-Ausnahmen umgeformt werden. Lediglich bei der Rückgabe der Konten bzw. Buchungsobjekte (CorbaAccount bzw. - 159 - Transport - CORBA CorbaAccountingEntry) ist mehr Aufwand erforderlich. Das Objekt muß als Servant instanziert mit activate_object() zuerst aktiviert werden. Anschließend kann man sich durch die Methode servant_to_reference() die Objekt-Referenz des gerade aktivierten Objekts holen. Aus dieser Referenz wird durch die Helfer-Klasse mit narrow() ein Stub-Objekt er- zeugt, das dann an den Client geschickt wird. Damit es nicht zu Fehlern kommt, weil jedes Objekt nur einmal aktiviert werden darf, werden alle Servant-Objekte in HashMaps verwaltet. In der main-Methode wird der CorbaServer dann einmal instanziert und stellt die Funktionalitäten als entferntes Objekt zur Verfügung. package de.fhr.vs_iw.corba; public final class CorbaServer extends CorbaAccountingServicePOA { ... private static final String CORBA_NAME = "CorbaAccountingService"; private static POA rootPoa = null; public static void main(String[] args) { if (args.length < 1) { System.err.println( "Host, Port und Proxy-Klasse müssen angegeben werden"); return; } try { // process command line args, first is host, second is port String[] params = { "-ORBInitialHost", args[0], "-ORBInitialPort", args[1] }; IAccountingProxy proxy = Utilities.loadProxy(args[2]); IAccountingService next = proxy.connect(Utilities.shiftArgs(args, 2)); // initialize ORB and object adapter ORB orb = ORB.init(params, null); rootPoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootPoa.the_POAManager().activate(); // initantiate the server implementation Servant servant = new CorbaServer(next); org.omg.CORBA.Object ref = rootPoa.servant_to_reference(servant); // get root naming context org.omg.CORBA.Object namingref = orb.resolve_initial_references("NameService"); NamingContextExt ncref = NamingContextExtHelper.narrow(namingref); // bind service object to name NameComponent[] path = ncref.to_name(CORBA_NAME); ncref.rebind(path, ref); } } // let corba do its job orb.run(); } catch (final Exception e) { e.printStackTrace(); } Code 113: Class CorbaServer (main) - 160 - Transport - CORBA Doch vor der Instanzierung eines Servant-Objekts werden die CORBA-Klassen initialisiert. Eine ORB-Instanz (Object Request Broker) übernimmt bei CORBA die Kommunikation mit anderen Systemen, bzw. mit anderen ORBs auf diesen Systemen, und wird mit den Parametern -ORBInitialHost und -ORInitialPort für den Zugriff auf den ORB-Daemon instanziert, die in einem String-Array angegeben werden. Zusätzlich ist noch eine Instanz des RootPOA notwendig der die Verwaltung und Aktivierung aller über CORBA angebotenen Objekte übernimmt, und selbst wie ein entferntes Objekt abgerufen wird. Der CorbaServer wird als Servant instanziert und über die Methode narrow() erhält man eine Referenz darauf, die für entfernte Aufrufe verwendet werden kann. Diese Referenz wird dann an den Namensdienst gebunden (rebind()), damit sie über den angegebenen Namen (NameComponent[]) leicht abrufbar ist. 5.4.3.2. Konto-Klasse Die Klasse CorbaServerAccount dient nur als Adapter für IAccount-Objekte der Datenhaltungsschicht, gibt die Methodenaufrufe weiter und formt auftretende Ausnahmen um. package de.fhr.vs_iw.corba; public final class CorbaServerAccount extends CorbaAccountPOA { private final IAccount next; protected CorbaServerAccount(IAccount next) { this.next = next; } public int[] getDebitEntries() throws CorbaIOException { try { Set<Integer> set = next.getDebitEntries(); int[] ar = new int[set.size()]; int i = 0; for (Integer id : set) { ar[i++] = id; } return ar; } catch (IOException e) { throw new CorbaIOException(e.getMessage()); } } public void setType(CorbaAccountType type) throws CorbaIOException { try { next.setType(AccountType.getAccountTypeById(type.value())); } catch (IOException e) { throw new CorbaIOException(e.getMessage()); } } } ... Code 114: Class CorbaServerAccount Wie auch in der Service-Klasse müssen hier die Sets zu Arrays umgewandelt werden. - 161 - Transport - CORBA 5.4.3.3. Buchungs-Klasse Auch die Buchungs-Klasse dient nur als Adapter für die Datenhaltungsschicht und jede Instanz kapselt ein IAccountingEntry-Objekt. package de.fhr.vs_iw.corba; public final class CorbaServerEntry extends CorbaAccountingEntryPOA { private final IAccountingEntry next; protected CorbaServerEntry(IAccountingEntry next) { this.next = next; } public double getAmount() throws CorbaIOException { try { return next.getAmount(); } catch (IOException e) { throw new CorbaIOException(e.getMessage()); } } public void setCreditAccount(int creditAccount) throws CorbaAccountingException, CorbaIOException { try { next.setCreditAccount(creditAccount); } catch (IOException e) { throw new CorbaIOException(e.getMessage()); } catch (AccountingException e) { throw new CorbaAccountingException(e.getMessage()); } } } ... Code 115: Class CorbaServerEntry Die Methodenaufrufe werden direkt weitergegeben und die Ausnahmen entsprechend zu den in der IDL definierten CORBA-Ausnahmen adaptiert. 5.4.3.4. Der ORB-Daemon Die Kommunikation und alle entfernten Aufrufe werden zwar von ORB-Instanzen erledigt, damit sich diese aber untereinander auffinden können ist eine zentrale Anlaufstelle notwendig, der ORB-Daemon. Dieser stellt persistente Referenzen auf die am verteilten System beteiligten Dienste und Objekte bereit. Im Java-Development-Kit ist eine ORB-DaemonImplementierung vorhanden, die direkt verwendet werden kann. bin/orbd.exe -port 9050 Beim Start kann ein beliebiger Port angegeben werden, auf dem der ORB-Daemon Anfragen entgegennehmen soll, sonst wird der Standardport 3895 verwendet. - 162 - Transport - CORBA 5.4.4. Verwendung der erzeugten Klassen auf der Clientseite Auf der Clientseite wird im Prinzip dieselbe Vorgehensweise verfolgt, wie auf der Serverseite. Die vom IDL-Compiler generierten CORBA-Client-Schnittstellen (siehe 5.4.2.4, CorbaAccountingService, CorbaAccount, CorbaAccountingEntry) werden mit den zugehörigen Stub-Klassen für die CORBA-Kommunikation verwendet und durch Adapter-Klassen wieder an die Schnittstellen des Buchhaltungsmodells angebunden. 5.4.4.1. Service-Klasse Die Service-Klasse implementiert die Service-Schnittstelle aus dem Buchhaltungsmodell (IAccountingService) und kapselt ein CorbaAccountingService-Objekt. package de.fhr.vs_iw.corba; public final class AccountingService implements IAccountingService { private final CorbaAccountingService corba; protected AccountingService(CorbaAccountingService corba) { this.corba = corba; } private = new private = new Map<Integer, AccountingEntry> accountingEntries HashMap<Integer, AccountingEntry>(); Map<Integer, Account> accounts HashMap<Integer, Account>(); public Double calculateBalance(Integer number) throws IOException, AccountingException { try { return corba.calculateBalance(number); } catch (CorbaAccountingException e) { throw new AccountingException(e.message, e); } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } public IAccount getAccountByNumber(Integer number) throws IOException, AccountingException { try { Account account = accounts.get(number); if (account == null) { account = new Account(corba.getAccountByNumber(number)); accounts.put(number, account); } return account; } catch (CorbaAccountingException e) { throw new AccountingException(e.message, e); } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } - 163 - Transport - CORBA public Set<Integer> getAccounts() throws IOException { try { int[] ar = corba.getAccounts(); HashSet<Integer> set = new HashSet<Integer>(); for (int i : ar) { set.add(i); } return set; } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } ... } Code 116: Class AccountingService (CORBA) In den Methoden getAccountByNumber() bzw. getAccountingEntryById() werden entsprechende Adapter-Objekte für Konten bzw. Buchungen zurückgegeben, die über CORBA als entfernte Objekte aufgerufen werden. Diese Adapter-Objekte werden wie auf dem Server in HashMaps verwaltet. 5.4.4.2. Konto-Klasse Die Konto-Klasse implementiert entsprechend die IAccount-Schnittstelle und gibt alle Methodenaufrufe an ein CorbaAccount-Objekt weiter. Die CORBA-Ausnahmen werden entsprechend zu Ausnahmen aus dem Buchhaltungsmodell zurückgewandelt, die Elemente der übertragenen Arrays wieder in Sets aufgenommen und auch der Kontotyp entsprechend dem generierten IDL-Kontotyp (CorbaAccountType) übertragen. package de.fhr.vs_iw.corba; public final class Account implements IAccount { private final CorbaAccount corba; protected Account(CorbaAccount corba) { this.corba = corba; } public Set<Integer> getCreditEntries() throws IOException { try { int[] ar = corba.getCreditEntries(); HashSet<Integer> set = new HashSet<Integer>(); for (int i : ar) { set.add(i); } return set; } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } - 164 - Transport - CORBA public void setType(AccountType type) throws IOException { try { corba.setType(CorbaAccountType.from_int(type.getId())); } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } } ... Code 117: Class Account (CORBA) 5.4.4.3. Buchungs-Klasse Wie auch die Konto-Klasse dient auch die Buchungs-Klasse als Adapter auf die entsprechende Buchhaltungsschnittstelle (IAccountingEntry), und zwar für ein entferntes CorbaAccountingEntry-Objekt. package de.fhr.vs_iw.corba; public final class AccountingEntry implements IAccountingEntry { private final CorbaAccountingEntry corba; protected AccountingEntry(CorbaAccountingEntry corba) { this.corba = corba; } public Double getAmount() throws IOException { try { return corba.getAmount(); } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } public void setDebitAccount(Integer debitAccount) throws IOException, AccountingException { try { corba.setDebitAccount(debitAccount); } catch (CorbaAccountingException e) { throw new AccountingException(e.message, e); } catch (CorbaIOException e) { IOException ioe = new IOException(e.message); ioe.initCause(e); throw ioe; } } } ... Code 118: Class AccountingEntry (CORBA) - 165 - Transport - CORBA 5.4.4.4. Proxy-Klasse Die Proxy-Klasse übernimmt den Aufruf der entfernten CORBA-Service-Servant. Als Parameter wird der Host und der Port des ORB-Daemon benötigt, um die Kommunikation zu initialisieren. package de.fhr.vs_iw.corba; public final class CorbaProxy implements IAccountingProxy { public CorbaProxy() {} public void close() {} public IAccountingService connect(String[] args) throws IOException { if (args.length < 2) { throw new IllegalArgumentException( "Hostname und Port müssen angegeben werden"); } try { // process command line args, first is host, second is port String[] params = { "-ORBInitialHost", args[0], "-ORBInitialPort", args[1] }; // initialize the ORB ORB orb = ORB.init(params, null); // get root naming context org.omg.CORBA.Object namingref = orb.resolve_initial_references("NameService"); NamingContextExt ncref = NamingContextExtHelper.narrow(namingref); // get the corba bank object by its name NameComponent[] path = ncref.to_name(CORBA_NAME); org.omg.CORBA.Object ref = ncref.resolve(path); CorbaAccountingService service = CorbaAccountingServiceHelper.narrow(ref); return new AccountingService(service); } catch (Exception e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } private static final String CORBA_NAME = "CorbaAccountingService"; } Code 119: Class CorbaProxy Zuerst wird wieder ein ORB instanziert, der eine Verbindung zum ORB-Daemon und dann auch zum ORB auf dem CORBA-Server herstellt. Damit wird eine Referenz zum Namensdienst geholt, wodurch wiederum die Referenz auf den im Server instanzierten Servant durch den registrierten Namen abgerufen kann. Durch den Aufruf von narrow() der Helfer-Klasse erhält man ein Stub-Objekt für die Referenz auf das entfernte Objekt, mit dem man die entfernten Methoden aufrufen kann. Das CorbaAccountingService-Objekt wird dann von einem AccountingService-Objekt gekapselt und von der connect()-Methode zurückgegeben. - 166 - Transport - CORBA 5.4.5. JACORB Der JacORB [78] ist eine freie JavaImplementierung des CORBA-Standards als Alternative zur CORBA-Implementierung von SUN. Er arbeitet erwiesenermaßen gut mit den CORBA-Implementierungen für andere Programmiersprachen (z.B. C++) zusammen, was die Interoperabilität, die für CORBA eigentlich grundsätzlich gefordert wird, garantiert. 5.4.5.1. Features Die Implementierung von JacORB umfaßt im wesentlichen folgende Elemente: multithreaded ORB mit hoher Performance 100% Java, kompatibel mit JDK 1.1 bis JDK 1.6 IDL-Compiler für IDL-Mapping 2.3 natives IIOP, GIOP 1.2 und bidirektionales GIOP POA (Portable Object Adapter) mit POA-Monitor-GUI AMI (Asynchronous Method Invocation) Unterstützung für Quality of Service (QoS) ETF (Extensible Transport Framework) Dynamic Invocation Interface (DII) Dynamic Skeleton Interface (DSI) Portable Interceptors OMG Interoperable Naming Service mit GUI verbessertes IIOP über SSL mit KeyStoreManager OMG Notification-Service und Event-Service Transaktions-Service Trading-Service CORBA 2.3 Code-Unterstützung OMG Data Distribution Service (DDS) Interface Repository mit GUI Implementaion Repository mit GUI 5.4.5.2. Installation Zur Verwendung des JacORB muß das entsprechende Paket für Java heruntergeladen und entpackt werden. http://www.jacorb.org/download.html Dem Paket liegen Installationsanweisungen bei, um die Quellen mit Ant vollständig neu zu kompilieren, und dabei die Pfade passend zum Installationsort einzurichten. Dies ist jedoch nicht unbedingt nötig, da man die Pfade zu den nötigen Bibliotheken auch manuell bei Start einer JVM angeben kann. Die Bibliotheken müssen hier auch nicht zum CLASSPATH von Eclipse hinzugefügt werden. - 167 - Transport - CORBA 5.4.5.3. Verwendung Die JacORB-Klassen werden nicht direkt zur Programmierung verwendet. Das oben angegebene CORBA-Programm der Buchhaltung wird exakt so verwendet und durch Kommandozeilenparameter wird lediglich die Verwendung der ORB-Klasse von JacORB eingestellt. Wegen der Angabe der Pfade und Parameter ist es günstig diese einmalig für eine JacORBInstallation in eine Batch-Datei (jacorbsettings.cmd) zu schreiben und diese beim Start von JacORB-basierten Programmen zu verwenden. @echo off set LIB=D:\classpath\JacORB_2.3.0_beta2\lib set RT=c:\Program Files\Java\jdk1.6.0\jre\lib\rt.jar set BOOTCLASSPATH=%LIB%\jacorb.jar;%LIB%\logkit-1.2.jar; %LIB%\avalon-framework-4.1.5.jar;%RT%;%CLASSPATH% set JACOPTIONS=-Djacorb.home=D:\classpath\JacORB_2.3.0_beta2 -Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB -Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton Resource 16: jacorbsettings.cmd Hier werden die Bibliotheken von JacORB und die darin enthaltenen Klassen zur Verwendung bei CORBA-Anwendungen Ausgehend von diesen Einstellungen wird dann jeweils eine JVM für die entsprechenden Java-Programme gestartet. Im Gegensatz zur mitgelieferten CORBA-Implementierung muß bei JacORB nicht der orbd, sondern der NameServer separat gestartet werden, und zwar durch folgende Batch-Datei. @echo off call jacorbsettings.cmd java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS% org.jacorb.naming.NameServer -Djacorb.naming.ior_filename=d:/NS_Ref Resource 17: jacorbnaming.cmd Die zuvor definierten Einstellungen werden beim Start einer JVM angegeben und damit die Klasse des NameServers aufgerufen. Als zusätzlicher Parameter wird eine Datei angegeben, in der der NameServer seine IOR zur späteren Verwendung hinterlegt. Die Klasse CorbaServer wird auf dieselbe Weise mit den JacORB-Einstellungen aus einer Batch-Datei (jacorbserver.cmd) heraus gestartet. @echo off call jacorbsettings.cmd java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS% de.fhr.vs_iw.corba.CorbaServer localhost 9050 de.fhr.vs_iw.local.LocalProxy Resource 18: jacorbserver.cmd Die von der Implementierung der CorbaServer-Klasse her muß zwar die geforderte Angabe von Host und Port erfolgen, diese wird aber von JacORB nicht verwendet. Der JacORB lädt über seine Konfiguration die IOR des NameServer für alle weiteren Operationen. - 168 - Transport - CORBA Für den Client gilt natürlich selbiges, und es muß nicht die Implementierung angepaßt, sondern nur die Startparameter verwendet werden. @echo off call jacorbsettings.cmd java -Xbootclasspath/p:"%BOOTCLASSPATH%" %JACOPTIONS% de.fhr.vs_iw.Client de.fhr.vs_iw.corba.CorbaProxy localhost 9050 Resource 19: jacorbclient.cmd Auch hier ist die Angabe von Host und Port nur wegen der originalen CORBAImplementierung notwendig, da sonst eine Ausnahme wegen fehlender Parameter geworfen wird. 5.4.5.4. Einstellungen Jede JacORB-Instanz lädt die globale Konfigurationsdatei orb.properties aus dem CLASSPATH der Anwendung. Darin werden alle relevanten Einstellungen vorgenommen. Eine Beispieldatei, in der alle möglichen Einstellungen erklärt werden, ist im JacORB-Paket enthalten. Die wichtigsten Einstellungen die darin aber angepaßt werden sollten sind die folgenden: jacorb.config.dir = D:/eclipse/workspace/VS_IW_Complete/resources/jacorb ORBInitRef.NameService = file:/d:/NS_Ref Das ist zum einen ein Verzeichnis, in dem weitere spezifische Konfigurationsdateien für einzelne ORB-Instanzen abgelegt werden können. Zum anderen ist es die Angabe der Datei mit der IOR des NameServers, womit eine ORB-Instanz erst in der Lage ist, ihren Dienst zu verrichten. Hier kann eine beliebige URL zu der Datei angegeben werden, falls der NameServer auf einem anderen Host gestartet wurde und die Datei, z.B. über HTTP, bereitgestellt wird. 5.4.6. Fazit Wenn in der Praxis ein verteiltes System realisiert werden soll, muß man sich auf jeden Fall überlegen ob die Verwendung von CORBA den Vorgang nicht erheblich vereinfachen würde. In diesem Beispiel hier ist die Angelegenheit allerdings relativ umständlich, weil auf beiden Seiten der CORBA-Datenübertragung die CORBA-Klassen durch Adapter an die vorgegebenen Buchhaltungsschnittstellen angepaßt werden müssen. Wenn aber eine Anwendung keine eigenen Kommunikationsschnittstellen vorgibt und direkt auf die generierten CORBA-Schnittstellen aufsetzen kann, ist die Verwendung von CORBA auf jeden Fall ein möglicher Ansatz. - 169 - Java-Enterprise - Übersicht 6. Java-Enterprise 6.1. Übersicht In der Spezifikation der J2EE (Java 2 Enterprise Edition) [45] werden Softwarekomponenten und Dienste definiert, die primär in der Programmiersprache Java erstellt werden. Die Spezifikation dient dazu, einen allgemein akzeptierten Rahmen zur Verfügung zu stellen, um auf dessen Basis aus modularen Komponenten verteilte, mehrschichtige Anwendungen entwickeln zu können. Klar definierte Schnittstellen zwischen den Komponenten und Schichten sollen dafür sorgen, daß Softwarekomponenten unterschiedlicher Hersteller interoperabel sind, wenn sie sich an die Spezifikation halten, und daß die verteilte Anwendung gut skalierbar ist [08]. Abbildung 71: Java 2 Enterprise Komponenten-Model - 170 - Java-Enterprise - Übersicht Anwendungen, die sich auf dieses Model stützen und die angegebenen Komponenten und Dienste verwenden, sind theoretisch auf jedem zertifizierten J2EE-Applikations-Server ablauffähig. 6.1.1. Komponenten Im Groben gliedert sich der Aufbau einer Anwendung und damit des Applikations-Servers in verschiedenen logische Komponenten, die als Container bezeichnet werden und prinzipiell in jedem Applikations-Server vorhanden sein müssen [08]. 6.1.1.1. EJB-Container Der Enterprise-JavaBeans-Container stellt die Laufzeitumgebung für Enterprise JavaBeans (siehe 6.3) bereit, die die Geschäftslogik einer Anwendung implementieren und den Zugriff auf die Daten der Anwendung gestatten. Er arbeitet als Vermittlungsschicht (Middleware) zwischen den Clients einer Anwendung und den Daten in einer Datenbank [46]. 6.1.1.2. Application-Client-Container Als Application-Client-Container (AAC) wird ein Satz notwendiger Bibliotheken bezeichnet, die eine Java-Anwendung benötigt, um aus einer separat laufenden Virtuellen Maschine heraus auf die EJBs eines Applikations-Servers, der sich auch auf einem anderen Host befinden kann, zuzugreifen [47]. 6.1.1.3. Web-Container Der Web-Container stellt eine Laufzeitumgebung und Frameworks für web-basierte ClientAnwendungen, die durch Servlets und Java-Server-Pages (JSPs) realisiert werden (siehe 7) zur Verfügung. 6.1.1.4. Applet-Container Als Teil einer web-basierten Client-Anwendung können auch Applets zum Einsatz kommen. Von dem her ist der Applet-Container aber nur eine Ergänzung zum Web-Container, der sich im Zusammenspiel mit einem Applikations-Server nicht anders verhält als bei eigenständigen Applets. 6.1.1.5. Datenbank Als weitere Infrastrukturkomponente kommt zur persistenten Speicherung der Daten ein Datenbankmanagementsystem (DBMS) zum Einsatz, das nicht zwingend Teil des eigentlichen Applikations-Servers, sondern in der Regel unabhängig ist [08]. Der Applikations-Server stellt lediglich einen einheitlichen Zugriffsweg (JDBC, Persistenz-Manager) dafür bereit. - 171 - Java-Enterprise - Übersicht 6.1.2. Dienste Die Komponenten eines Applikations-Servers müssen verschiedene spezifizierte Dienste und technische Funktionalitäten zur Verfügung stellen, die von Anwendungen genutzt werden können. Außerdem kapselt der Server dadurch den Zugriff auf die Ressourcen des zugrundeliegenden Betriebssystems [08]. 6.1.2.1. JMS Der Java Messaging Service (JMS) stellt eine API für den asynchronen Austausch von Nachrichten zwischen den Komponenten zur Verfügung [15]. Als Empfänger der Nachrichten fungieren z.B. Message Driven Beans (siehe 6.3.4). 6.1.2.2. JMX Die Java Management Extension (JMX) [16] ist eine Spezifikation zur Überwachung und Steuerung von Java-Anwendungen. Auch die Kommunikation zwischen Anwendungen innerhalb einer VM wird dadurch vereinfacht. Durch ein Model von Adaptern und Connectoren läßt sich sehr leicht eine externe Steuerung der Objekte der Anwendung realisieren, z.B. mit einem HTTP-Adapter über einen Web-Browser oder mit einem SNMP-Adapter durch andere Verwaltungs-Tools [48]. 6.1.2.3. JAAS Der Java Authentication und Authorization Service (JAAS) [07] ist eine API, um Dienste zur Authentifizierung und Zugriffsberechtigung in Java-Programmen bereitzustellen. JAAS orientiert sich dabei an den Pluggable Authentication Modules (PAM) und unterstützt eine benutzerbasierte Autorisierung [50]. 6.1.2.4. JTA Die Java Transaction API (JTA) [51] ist eine Art Protokoll, das der Anwendung die Steuerung der Transaktionsverwaltung erlaubt, die wiederum vom Java Transaction Service (JTS) [52] nach der OMG Object Transaction Spezifikation implementiert wird [08]. 6.1.2.5. JavaMail Die JavaMail-API [53] erlaubt den Zugriff auf verbreitete Mail-Dienste, wie z.B. SMTP, POP3, IMAP oder NNTP [08]. 6.1.2.6. JAF Das Java Beans Activation Framework (JAF) [49] bietet die Möglichkeit, verschiedene Daten anhand des MIME-Headers zu erkennen und dementsprechend bestimmte Funktionalitäten oder Enterprise Java Beans zu aktivieren. - 172 - Java-Enterprise - Übersicht 6.1.2.7. JCA Die Java Connector Architecture (JCA) [56] ist eine Schnittstellen-Spezifikation zur Integration von heterogenen Anwendungen in die J2EE-Plattform, um den Austausch von Daten und die Verbindung von Geschäftsprozessen zu vereinfachen [57]. 6.1.2.8. JAXR Die Java-API for XML-Registries (JAXR) [54] stellt einen transparenten Zugriff auf so genannte Business-Registries, wie z.B. ebXML oder UDDI, zur Verfügung [08]. 6.1.2.9. JAX-RPC Die Java-API for XML-Based Remote Procedure Calls (JAX-RPC) [55] dient zum Zugriff auf entfernte RPC-Dienste, die als Web-Service angeboten werden, über das SOAP-Protokoll. 6.1.3. Verfügbare Implementierungen Die angegeben Dienste stellen in erster Linie Spezifikation und Definition dar, wobei es von SUN jeweils eine Referenz-Implementierung gibt. Verbreitet sind aber etliche kommerzielle und freie Implementierungen der Standards. Applikations-Server und Web-Container sind meist separate Produkte die aber oft in Kombination vertrieben werden [08]. 6.1.3.1. Open Source Server Apache Geronimo (benutzt Jetty) JBoss (benutzt Apache Tomcat) JOnAS (benutzt Apache Tomcat) GlassFish (benutzt Apache Tomcat) Enhydra Enterprise (benutzt Apache Tomcat) Sun Java System Application Server (Referenz-Implementierung) 6.1.3.2. Proprietäre Server ATG Dynamo Application Server (DAS) BEA WebLogic SAP Web Application Server Oracle Application Server IBM WebSphere 6.1.3.3. Web-Container Apache Tomcat Caucho Technology Resin Enhydra Server Jetty Open Source proprietär Open Source Open Source - 173 - Java-Enterprise - Der JBoss-Application-Server 6.2. Der JBoss-Application-Server 6.2.1. Allgemeines Für dieses Tutorial wird der JBoss-Application-Server [02] [03] verwendet, eine zertifizierte Anwendungs-Plattform nach dem J2EE (Java 2 Enterprise Edition) Standard. Vor allem die vereinfachte Entwicklung von Anwendungen mit Enterprise Java Beans (EJB) 3.0, bei der Annotations statt Deployment-Descriptoren benutzt werden, wird unterstützt. Der Applikations-Server bietet verschiedene frei konfigurierbare Dienste an, die jeweils in einem eigenen "Service Archive" verpackt und auf dem Server "deployed" werden [04]. Er enthält als Servlet-Container für Web-Anwendungen den "Tomcat" [06] von der Apache-Foundation [05]. 6.2.2. Installation Vorraussetzung für die Verwendung von JBoss ist ein installierter Java-Development-Kit (JDK) [01], das Java-Runtime-Environment (JRE) alleine reicht nicht aus. Eine Umgebungsvariable JAVA_HOME muß auf den Pfad des JDK gesetzt werden, damit der JBoss die Programme und Tools des JDK finden und verwenden kann. Abbildung 72: Windows Umgebungsvariablen Alternativ kann auch die Batch-Datei (siehe 6.2.4.1), die zum Starten des Servers verwendet wird, abgeändert und der Pfad zum JDK dort direkt als Variable eingetragen werden. - 174 - Java-Enterprise - Der JBoss-Application-Server 6.2.2.1. Aus dem beiliegenden Paket Der JBoss-Application-Server kann direkt aus dem an der FH vorbereiteten Archiv http://java.fh-regensburg.de/download/jboss-4.2.2.GA-package.zip entpackt, gestartet (siehe 6.2.4.1) und verwendet werden. Das Paket enthält den JBoss in der Version 4.2.2.GA mit der Unterstützung von EJB 3.0, sowie den vorkonfigurierten Datenquellen MySqlDS (siehe 6.2.3.2), OracleDS (siehe 6.2.3.4), PostgresDS (siehe 6.2.3.3), und den dafür nötigen JDBC-Treibern. 6.2.2.2. Originalinstallation von jboss.org Der JBoss kann auch direkt von der Download-Seite des Herstellers http://labs.jboss.com/portal/jbossas/download heruntergeladen werden. Wie dort angegeben muß zur Unterstützung von EJB 3.0 der Installer verwendet werden, das Herunterladen und Entpacken eines Archivs ist dafür nicht ausreichend. Die Installation über Java-Web-Start direkt gestartet werden. Alternativ kann der Installer als Paket von sourceforge.net heruntergeladen und gestartet werden: http://prdownloads.sourceforge.net/jboss/jems-installer-1.2.0.BETA3.jar java -jar jems-installer-1.2.0.BETA3.jar Abbildung 73: Paket-Auswahl bei der JBoss-Installation - 175 - Java-Enterprise - Der JBoss-Application-Server Der dialogbasierte Installer (erstellt mit IzPack [09]) führt durch die Installation von JBoss. Wichtig dabei ist die korrekte Auswahl des Typs der Installation. Nur bei der Auswahl von ejb3 oder ejb3-clustered ist später die Unterstützung für EJB 3.0 verfügbar. Da bei allen anderen Installations-Typen die nötigen Bibliotheken für EJB 3.0 nicht installiert werden, sind diese eher uninteressant. 6.2.2.3. Verzeichnisstruktur Abbildung 74: JBoss-Verzeichnisstruktur bin Startskripte (siehe 6.2.4.1) client jar-Dateien für externe Client-Anwendungen docs Beiliegende Dokumente dtd Document Type Definitions (DTDs) zum Validieren der XML-Konfigurationsdateien examples Vorlagen für verschiedene Dienste binding-manager Konfiguration der Port-Nummern jca Vorlagen für verschiedene Datenquellen / Datenbanken jms Vorlagen für die Persistenz des Java-Messaging jmx EJB-Management-Beans netboot Das Netboot-Archiv, mit dem ein JBoss ohne Installation gestartet werden kann varia Diverse Beispiele licenses Lizenzdokumente der verwendeten Komponenten schema XML-Schemas der Deployment-Descriptoren tests Testergebnisse von JBoss.org lib Bibliotheken für den Start des Servers scripts Skripte für zusätzliche Installationsvorgänge server Hauptverzeichnis mit verschiedenen möglichen Serverkonfigurationen default Standardkonfiguration conf Konfigurationsdateien für den Server data Datenverzeichnis hypersonic Datendateien der Hypersonic-Datenbank wsdl Schemata der verfügbaren Web-Services deploy Die Applikationen und Dienste auf dem Server lib Bibliotheken die von Anwendungen benötigt werden log Ablage der Log-Dateien des Servers tmp Ablage von ausgelagerten Stateful-Session-Beans und des Inhalts ausgepackter Archive work Hier werden JSP-Seiten von Tomcat zu Servlets compiliert 6.2.3. Datenbankanbindung Zum sinnvollen Betrieb von JBoss ist eine Datenbankanbindung notwendig. Alle Anwendungen, die auf dem Server laufen, können die konfigurierten Datenbankanbindungen zum Speichern ihrer Daten verwenden. Auch interne Verwaltungsdaten der Servers, wie z.B. zur Benutzerauthentifizierung mit JAAS (Java Authentication and Authorization Service) [07] werden in einer Datenbank gespeichert. - 176 - Java-Enterprise - Der JBoss-Application-Server Die Anbindungen werden in Dateien mit der Endung ...-ds.xml definiert. Diese Dateien werden im Deploy-Verzeichnis des Servers server/default/deploy/ abgelegt, wo der Server die Datei automatisch findet und verarbeitet. In einer JBossInstallation ist eine Reihe von Beispieldateien für die Konfiguration verschiedener Datenbanken im Verzeichnis docs/examples/jca/ enthalten. Dort müssen nur die passenden Parameter eingetragen und die Dateien im DeployVerzeichnis abgelegt werden. Im folgende ist jede Datenquelle in einer eigenen Datei definiert, die Angaben könnten aber jederzeit zusammengefaßt werden. 6.2.3.1. Hypersonic Die Hypersonic-Datenbank-Engine [14] ist eine integrierte relationale Java-DatenbankEngine und wird beim JBoss mitgeliefert. Sie ist als Standard-Datenquelle im DeployVerzeichnis in der Datei hsqldb-ds.xml unter dem Namen DefaultDS voreingestellt und muß nicht erst konfiguriert werden. <?xml version="1.0" encoding="UTF-8" ?> <datasources> <local-tx-datasource> <jndi-name>DefaultDS</jndi-name> <connection-url> jdbc:hsqldb:${jboss.server.data.dir}${/}hypersonic${/}localDB </connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> <min-pool-size>5</min-pool-size> <max-pool-size>20</max-pool-size> <idle-timeout-minutes>0</idle-timeout-minutes> <track-statements /> <metadata><type-mapping>Hypersonic SQL</type-mapping></metadata> <depends>jboss:service=Hypersonic,database=localDB</depends> </local-tx-datasource> <mbean code="org.jboss.jdbc.HypersonicDatabase" name="jboss:service=Hypersonic,database=localDB"> <attribute name="Database">localDB</attribute> <attribute name="InProcessMode">true</attribute> </mbean> </datasources> Resource 20: Hypersonic-Datenquelle hsqldb-ds.xml Die Daten und Einstellungen der Hypersonic-Datenbank-Engine werden im Unterverzeichnis server/default/data/hypersonic in der voreingestellten Datenbank-Datei localDB abgelegt. Diese Datenbank muß nicht explizit angelegt werden, da bei Hypersonic nichtvorhandene Datenbanken automatisch erzeugt werden. - 177 - Java-Enterprise - Der JBoss-Application-Server 6.2.3.2. MySQL MySQL ist ein freies Open-Source SQL-Datenbankverwaltungssystem [10], das momentan in der Version 5.0 vorliegt und von der Homepage des Herstellers MySQL AB [11] heruntergeladen werden kann. http://dev.mysql.com/downloads/mysql/5.0.html Auf die Installation und Einrichtung eines MySQL-Servers soll in diesem Zusammenhang nicht weiter eingegangen werden. Lediglich der Zugriff auf einen vorhandenen Server ist hier von Interesse. Dazu wird der ein JDBC-Treiber für MySQL benötigt, der auch vom Hersteller heruntergeladen werden kann. http://dev.mysql.com/downloads/connector/j/5.0.html Die heruntergeladene Datei mysql-connector-java-5.1.5.zip muß entpackt und das darin enthalte Jar-Archiv mit den Treiber-Klassen mysql-connector-java-5.1.5-bin.jar im Lib-Verzeichnis des Servers (unter der entsprechenden Konfiguration, hier default) server/default/lib abgelegt werden. Falls der JBoss-Server gerade läuft, muß er neu gestartet werden, damit die Klassen aus dem Jar-Archiv verfügbar sind. Nun muß der JBoss zu Verwendung der MySQL-Datenbank konfiguriert werden. Dazu wird die Vorlage docs/examples/jca/mysql-ds.xml entsprechend angepaßt und im Deploy-Verzeichnis abgelegt. <?xml version="1.0" encoding="UTF-8" ?> <datasources> <local-tx-datasource> <jndi-name>MySqlDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/jboss</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>jboss</user-name> <password>jboss</password> <exception-sorter-class-name> org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter </exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources> Resource 21: MySQL-Datenquelle mysql-ds.xml In der XML-Datei wird die Datenquelle unter dem Namen „MySqlDS“ eingerichtet. Die darin angegebene Datenbank muß auf dem MySQL-Server vorhanden sein, und der angegebene Benutzer eingerichtet sein, und darauf Zugriff haben. - 178 - Java-Enterprise - Der JBoss-Application-Server 6.2.3.3. PostgreSQL Ein anderes verbreitetes Open-Source Datenbank-Verwaltungs-System ist PostgreSQL [19]. Auch dafür ist der passende JDBC-Treiber erforderlich, der unter http://jdbc.postgresql.org/download.html heruntergeladen werden kann und im Lib-Verzeichnis abgelegt werden muß. Im VorlagenVerzeichnis existiert auch für eine Datei für PostgreSQL, die nach dem Anpassen der Verbindungsparameter im Deploy-Verzeichnis des Servers gespeichert wird. <?xml version="1.0" encoding="UTF-8" ?> <datasources> <local-tx-datasource> <jndi-name>PostgresDS</jndi-name> <connection-url> jdbc:postgresql://[servername]:[port]/[database name] </connection-url> <driver-class>org.postgresql.Driver</driver-class> <user-name>[benutzername]</user-name> <password>[passwort]</password> <metadata> <type-mapping>PostgreSQL 7.2</type-mapping> </metadata> </local-tx-datasource> </datasources> Resource 22: PostegreSQL-Datenquelle postgres-ds.xml Die Datenquelle würde hier mit dem Namen PostgresDS eingerichtet und die Angaben müssen für den entsprechenden PostgreSQL-Datenbank-Server erfolgen. 6.2.3.4. Oracle Auch die Anbindung an die zentrale Oracle-Datenbank der Fakultät ist von Interesse. Dafür ist der entsprechende JDBC-Treiber für Oracle ojdbc14.jar aus einer Oracle-Installation oder von der Oracle-Downloadseite http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html erforderlich, der im Lib-Verzeichnis des JBoss-Servers vorhanden sein muß. Danach wird die Datenquellen-Konfigurations-Datei /docs/examples/jca/oracle-ds.xml editiert und im Deploy-Verzeichnis von JBoss gespeichert, um die Datenquelle mit dem Namen OracleDS zu definieren. - 179 - Java-Enterprise - Der JBoss-Application-Server <?xml version="1.0" encoding="UTF-8" ?> <datasources> <local-tx-datasource> <jndi-name>OracleDS</jndi-name> <connection-url> jdbc:oracle:thin:@fbim.fh-regensburg.de:1523:ora10g </connection-url> <driver-class>oracle.jdbc.driver.OracleDriver</driver-class> <user-name>[nds-kürzel]</user-name> <password>[nds-kürzel]</password> <exception-sorter-class-name> org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter </exception-sorter-class-name> <metadata> <type-mapping>Oracle10g</type-mapping> </metadata> </local-tx-datasource> </datasources> Resource 23: Oracle-Datenquelle oracle-ds.xml In der Datei muß lediglich der Benutzername und das Paßwort angepaßt werden, damit jeder Benutzer Zugriff auf seine eigenen Daten hat. Bei der Übertragung der Daten zu Oracle muß aber zum Beispiel auf reservierte Wörter geachtet werden. So kann die Kontonummer in einer Oracle-Datenbank nicht als "number" gespeichert werden, da "number" bei Oracle ein reserviertes Wort ist. Dies muß durch eine entsprechende Annotation berücksichtigt werden. 6.2.4. Verwendung Nach der erfolgreichen Installation kann der JBoss gestartet und bestimmte Dienste über die voreingestellten TCP-Ports angesprochen werden. 1098 RMI-Registry 1099 Naming-Service 4444 RMI-Object-Port (JRMP) 4445 RMI-Object-Port, PooledInvoker 8009 Tomcat-AJP-Port 8080 Tomcat-HTTP-Connector 8083 Web-Service-Port 8093 UIL2-Port für JMS Von dieser Liste sind vor allem der Port 8080, von Tomcat für Aufrufe von WebAnwendungen mit einem Browser, und der Port 1099, um über den Namensdienst auf Enterprise JavaBeans zuzugreifen, wichtig. - 180 - Java-Enterprise - Der JBoss-Application-Server 6.2.4.1. Einfacher Start Gestartet werden kann der JBoss grundsätzlich immer mit den mitgelieferten Skripten. Wie o.g. wird unter Windows die Batch-Datei bin/run.bat aufgerufen. Diese setzt noch einige Umgebungsvariablen und Einstellungen für Java und startet dann die JVM mit der JBoss-Main-Klasse aus dem Archiv run.jar. Die JVM mit dem JBoss-Server läuft dann in einem Konsolenfenster ab, wo Statusmeldungen und etwaige Fehler (Ausnahmen) sofort zu sehen sind. Abbildung 75: Konsolenfenster eines laufenden JBoss Beendet wird der Server dann entweder durch das Drücken von Strg+C im Konsolenfenster oder durch den Aufruf des Shutdown-Skripts bin/shutdown.bat das dem laufenden Server über JMS das Herunterfahren signalisiert. - 181 - Java-Enterprise - Der JBoss-Application-Server 6.2.4.2. JBoss als Windows-Systemdienst Eine Java-Anwendung kann nicht einfach beliebig als Window-Systemdienst gestartet werden, da die Windows-API einige Anforderungen stellt, wie sich ein Dienst zu verhalten hat. Zu diesem Zweck gibt es unter http://javaservice.objectweb.org/ das Tool JavaService, das auf der einen Seite den Aufruf der JVM mit einer beliebigen Klasse für den gewünschten Dienst kapselt, und sich auf der anderen Seite konform zur API für Windows-Dienste verhält. Das Archiv mit der derzeit aktuellen JavaService-Version JavaService-2.0.10.zip enthält neben dem Service-Tool auch einige vorgefertigte Installationsskripten, unter anderem für den JBoss. Die Vorraussetzung dafür sind lediglich die beiden Umgebungsvariablen JAVA_HOME (Absoluter Pfad zum installierten JDK) und JBOSS_HOME (Absoluter Pfad zur Verzeichnis der JBoss-Installation). die von den Skripten ausgewertet und entsprechend bei einem solchen Java-Dienst eingetragen werden. Somit kann der JBoss relativ einfach durch Aufruf von InstallJBoss.bat [depends_on] [-auto / -manual] installiert werden. Die optionale Angabe [depends_on] gibt einen bereits vorhandenen Dienst an, der für den Start des neuen JBoss-Dienstes erforderlich ist, z.B. MySQL. Außerdem kann mit [-auto / -manual] angegeben werden, ob der Dienst beim Systemstart automatisch mitgestartet werden soll. Standardmäßig wird ein neu installierter Java-Service so eingestellt, daß er beim Systemstart mitgestartet wird. Anschließend ist der JBoss-Server als Windows-Systemdienst eingerichtet und wie alle anderen Dienste unter Systemsteuerung -> Verwaltung -> Dienste -> JBoss verfügbar, und kann dort gestartet und beendet werden. Direkt nach der Installation muß der Dienst einmal manuell gestartet oder aber der Rechner neu gebootet werden. Über den Status und die Operationen des Servers kann man sich bei dieser Betriebsvariante über die Log-Datei informieren. server/default/log/server.log Die darin enthaltenen Informationen sind in der Standardeinstellung noch wesentlicher detaillierter als die Ausgaben im Konsolenfenster. - 182 - Java-Enterprise - Der JBoss-Application-Server 6.2.4.3. Deployment von Anwendungen Um eine eigene Anwendung auf dem Server ablaufen zu lassen, muß diese, einschließlich irgendwelcher nötigen Zusatzdateien und Konfigurationen, auf dem Server deployed werden. Dabei sind die verwendeten Datei- und Archiv-Typen und -endungen von Bedeutung. Alle Archive sind ZIP-Archive, nur mit entsprechend angepaßter Endung [18]. .sar .rar .har .jar .war .ear .aop .xml Service-Archive für spezielle Dienste, besonders für MBeans Resource Archive, z.B. für Konnektoren zu Datenbanken Hibernate-Archive, um eine oder mehrere Hibernate-Instanzen zu deployen Java-Archive (ganz allgemein) Web-Archive für Webanwendungen, Servlets, JSP- und HTML-Seiten Enterprise Application Archive für komplette J2EE-Applikationen Archiv für die aspektorientierte Programmierung Konfigurationsdatei im XML-Format: *aop.xml Definition von Aspekten bei der aspektorientierten Programmierung *deployer.xml Konfiguration eines Deployers für bestimmte Dateitypen *ds.xml Definition von Resource-Adaptern und Datenquellen *service.xml Beschreibung von Server-Diensten .wsr Web-Service-Repository des zukünftigen JBoss.Net-Subsystems .bsh BeanShell-Skript. BeanShell ist ein in Java geschriebener Interpreter für Java Auch die Startreihenfolge hängt grundsätzlich von der Endung ab. Über den standardmäßig eingestellten DeploymentSorter werden die Dateien und Archive in folgender Reihenfolge verarbeitet bzw. gestartet: *deployer.xml, .sar, .rar, *ds.xml, *service.xml, .har, .jar, .war, .wsr, .ear, .zip, .bsh Damit wird z.B. sichergestellt, daß für eine Web-Applikation die auf EJBs zugreift, diese auch verfügbar sind, da EJBs vor Web-Applikationen gestartet werden. Ein in *deployer.xml definierter zusätzlicher Deployer kann in diese Reihenfolge allerdings weitere Typen einfügen. Um den Deploy-Vorgang anzustoßen, werden alle nötigen Dateien der Anwendung im Deploy-Verzeichnis server/default/deploy/ des laufenden JBoss-Servers abgelegt. Dieses Verzeichnis und alle Dateien darin werden ständig auf Änderungen hin überwacht, und sobald dort neue Dateien hinzukommen werden diese von JBoss verarbeitet und die darin spezifizierten Anwendungen gestartet. 6.2.5. Verwaltung über die JMX-Konsole Die JMX-Konsole [16] dient zur webbasierten Statusabfrage und Verwaltung des JBossServers. In der Ausgangskonfiguration ist sie in einem Web-Browser unter der URL http://localhost:8080/jmx-console/ abrufbar. Dort werden alle konfigurierten Dienste und MBeans nach Typ getrennt aufgelistet. - 183 - Java-Enterprise - Der JBoss-Application-Server 6.2.5.1. System-Eigenschaften Im Abschnitt "jboss" wird unter dem Eintrag "name=SystemProperties,type=Service" das MBean für die Anzeige der System-Eigenschaften aufgerufen. Die Methode showAll() zeigt eine Auflistung aller konfigurierten Einstellungen des laufenden JBoss bzw. der Betriebssystem-Umgebung an. Dies kann zum einen bei Problemen hilfreich sein, zum anderen können diese Eigenschaften auch in eigenen Anwendungen oder Konfigurationsdateien verwendet werden. Anhang A zeigt eine Auflistung der Eigenschaften. 6.2.5.2. JNDI-Übersicht Ebenfalls im Abschnitt "jboss" befindet sich der Eintrag "service=JNDIView", der zur Anzeige des JNDI-MBean führt. Hier gibt es die Methode list(), die eine Übersicht über alle registrierten Namensräume von Anwendungen und deren Einträge im JNDI-Verzeichnis des Servers anzeigt. Der allgemeine Java-Namensraum listet z.B. die vorher konfigurierten Datenquellen MySqlDS, OracleDS und DefaultDS auf. java: Namespace +++| +++++++| | | | ++| +++| +| +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory) DefaultDS (class: javax.sql.DataSource) SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory) OracleDS (class: javax.sql.DataSource) DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter) MySqlDS (class: javax.sql.DataSource) comp (class: javax.naming.Context) JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl) ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory) jaas (class: javax.naming.Context) +- HsqlDbRealm (class: | org.jboss.security.plugins.SecurityDomainContext) +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext) +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext) timedCacheFactory (class: javax.naming.Context) TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropagationContextFactory) StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory) Mail (class: javax.mail.Session) comp.ejb3 (class: javax.naming.Context) NonContext: null TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropagationContextImporter) TransactionManager (class: org.jboss.tm.TxManager) Resource 24: JNDI-Ansicht des allgemeinen Java-Namensraumes Diese Ansicht ist eine mögliche Anlaufstelle, um festzustellen ob eine eigene Anwendung richtig auf dem Server deployed wurde und verfügbar ist, bzw. um den genauen Namen unter dem die Anwendung erreichbar ist, zu erhalten. - 184 - Java-Enterprise - Der JBoss-Application-Server 6.2.5.3. Hypersonic-Manager Der Datenbank-Manager ist eine einfache Benutzeroberfläche zur Verwaltung der integrierten Hypersonic-Datenbank. Aufgerufen wird der Manager aus der JMX-Konsole über das MBean "database=localDB,service=Hypersonic" im Abschnitt "jboss". Davon muß die Methode startDatabaseManager() aufgerufen werden. Abbildung 76: Hypersonic Datenbank-Manager Links wird das Schema der vorhandenen Datenbanken und Tabellen als Baumstruktur angezeigt. Über das Menü "Command" erzeugt man SQL-Befehle die manuell vervollständigt werden müssen und dann mit "Execute SQL" an die Datenbank geschickt werden. Die Oberfläche ist allerdings sehr spartanisch, weil alle Aktionen nur auf diese Weise über SQL erfolgen können, lediglich das Laden und Speichern von SQL-Skripten ist möglich. Außerdem kann sich der Datenbank-Manager noch mit Anderen HSQL-Datenbanken als der integrierten Datenbank über das Netzwerk verbinden. - 185 - Java-Enterprise - Der JBoss-Application-Server 6.2.6. Produktiver Betrieb Auf die Möglichkeit JBoss als Applikations-Server im Produktiven Betrieb einzusetzen soll hier nicht näher eingegangen werden. Jedoch ein wichtigster Schritt dafür wäre die Sicherung der Administrations-Schnittstellen gegen unbefugten Zugriff. Ein Einstieg zu diesem Thema kann unter folgenden URL gefunden werden: http://sourceforge.net/docman/display_doc.php?docid=20143&group_id=22866 6.2.7. Integration in die IDE Für den JBoss-Server gibt es die auf Eclipse 3.2.1 basierende Entwicklungsumgebung "JBoss Eclipse IDE" [38] auf der entsprechenden Webseite bei jboss.com zum download: http://labs.jboss.com/portal/jbosside/download/index.html Das angebotene ZIP-Archive JBossIDE-2.0.0.Beta2-ALL.zip enthält die vollständige Eclipse-Entwicklungsumgebung mit den JBoss-Plugins, und muß nur heruntergeladen und entpackt werden. In der Eclipse Version 3.4.1 für Java-Enterprise Entwickler ist diese JBoss-Anbindung bereits enthalten. 6.2.7.1. Eclipse-Projekt anlegen Wenn man im Eclipse unter File -> New -> Project... ein neues Projekt anlegen will, erscheinen im Auswahlfenster für einen Projektassistenten etliche JBoss-spezifische Einträge: EJB 3.0 Project Enterprise Java Beans 3.0 Projekt [45] JBoss AOP Project Projekt zur aspektorientierten Programmierung mit JBoss [40] JBoss Cache Projekt das einen Cache zum Zwischenspeichern von Datenbankzugriffen einbindet [39] J2EE Projects Projekt mit älteren Enterprise Java Beans [45] JBoss jBPM Projekt mit der JBoss-Ablauf-Verwaltung [41] JBoss Rules Projekt zur Entwicklung von Geschäftsrichtlinien mit JBoss [42] Für unsere Zwecke wird ein "EJB 3.0 Project" benötigt. Optional kann auch ein "normales" Java-Projekt erzeugt, und bei diesem dann die EJB 3.0 Bibliotheken hinzugefügt werden. - 186 - Java-Enterprise - Der JBoss-Application-Server Abbildung 77: Eclipse-Dialog "New Project" Für das neue Projekt muß wie gewohnt zuerst ein Name vergeben werden. Als nächstes fragt der Assistent dann nach einem JBoss-Server den man konkret verwenden will, und dessen Bibliotheken eingebunden werden sollen. Neben den Standard-Bibliotheken des verwendeten JDK, sind in dem Projekt dann auch sofort die "JBoss EJB3 Libraries", also alle Archive aus dem JBoss-Verzeichnis, die für die Entwicklung von Enterprise Java Beans 3 notwendig sind, verfügbar und bereits in den "Java Build Path" eingebunden. - 187 - Java-Enterprise - Der JBoss-Application-Server Abbildung 78: Eclipse-Dialog "Select a JBoss configuration" 6.2.7.2. JBoss-Konfiguration anlegen Falls noch keine JBoss-Konfiguration vorhanden ist, muß mit "Create a JBoss Server" eine angelegt werden. Dafür wird zuerst die Version des verwendeten Servers, in unserem Fall "JBoss AS 4.0", ausgewählt. Abbildung 79: Eclipse-Dialog "New Server" - Define - 188 - Java-Enterprise - Der JBoss-Application-Server Im nächsten Dialogfenster wird ein eindeutiger Name für den Server vergeben, und das BasisVerzeichnis, in das der Server zuvor entpackt bzw. installiert wurde, angegeben. Daraufhin werden die vorhandenen Konfigurationen des Servers erkannt und aufgelistet. Mit "Finish" wird der Server dann endgültig im Eclipse angelegt. Abbildung 80: Eclipse-Dialog "New Server" - Create Der neu angelegte JBoss-Server erscheint dann in der Liste der verfügbaren Server und kann nun bei Projekten ausgewählt werden. 6.2.7.3. Den Server steuern Nach dem Anlegen eines JBoss-Servers ist im Package-Explorer neben den eigentlichen JavaProjekten ein neues Projekt Servers vorhanden, das alle angelegten Server enthält. Dadurch kann ein Server direkt aus Eclipse heraus gesteuert werden, sofern er nicht, wie oben beschrieben (siehe 6.2.4.2), als Systemdienst eingerichtet wurde. In diesem Fall kommt es zu Konflikten, wenn der Systemdienst nicht zuvor manuell beendet wird. Abbildung 81: Eclipse Package Explorer - Servers - 189 - Java-Enterprise - Der JBoss-Application-Server In der Servers-Ansicht werden der Server und sein aktueller Status aufgelistet. Über die Schaltflächen oder das Kontext-Menü kann er gestartet und gestoppt werden. Abbildung 82: Eclipse Servers-Ansicht Sollte Eclipse die Ansicht nach dem Anlegen eines Servers nicht selbständig einblenden, kann sie im Menü unter Window -> Show View -> Other... -> Server -> Servers aufgerufen werden. Die Ausgaben des Servers erfolgen, wie auch bei anderen Programmen, in einem Konsolenfenster von Eclipse. Abbildung 83: Eclipse-Console mit laufendem JBoss 6.2.7.4. Deployment eigener Anwendungen Grundsätzlich sollte das Deployment von selbst erstellten Anwendungen direkt aus der Oberfläche heraus auf einem eingerichteten Server erfolgen können. Da sich jedoch die "JBoss Eclipse IDE" in der immer noch in einem Beta-Stadium befindet, ist dies nicht möglich. Das Deployment muß daher manuell (siehe 6.2.4.3) oder durch Ant-Skripte erfolgen, die alle nötigen Dateien in ein Archiv verpacken und dieses in das Deploy-Verzeichnis des JBossServers kopieren. - 190 - Java-Enterprise - Enterprise JavaBeans 6.3. Enterprise JavaBeans 6.3.1. Allgemeines Enterprise JavaBeans sind die Komponenten innerhalb eines J2EE-Servers, mit denen eine Anwendung implementiert wird. Sie sind aus einfachen Java-Klassen (POJO) aufgebaut und nutzen die Dienste, die ein Applikations-Server zur Verfügung stellt. Durch zusätzliche XMLKonfigurationsdateien (Deployment-Descriptor) werden Eigenschaften von EJBs definiert, die nicht hart codiert sind [73]. 6.3.1.1. Annotations In der Version 3 der Enterprise Java (JSR-220) [66] wird die Konfiguration und Beschreibung der Klassen durch Annotations vorgenommen und die Klassen müssen keine Verwaltungsbzw. Typ-Schnittstellen (Component-Interface) implementieren, was ihre Entwicklung im Vergleich zu den vorherigen Versionen 1 und 2 stark vereinfacht, da wesentlich weniger komplexe Deployment-Descriptoren zusätzlich zu den Klassen der Anwendung geschrieben werden müssen und keine technischen Vorgaben bestehen. Durch die Annotations werden wie bei JAXB oder Hibernate die Meta-Informationen zu der Aufgabe, die eine Bean-Klasse innerhalb der Anwendung übernehmen soll, und ihrem Verhalten direkt im Quellcode beschrieben und vom Applikations-Server ausgewertet. 6.3.1.2. Home-Interface Das Home-Interface einer Bean dient zum Erzeugen, Laden oder Löschen von Instanzen der Bean-Klasse. Weil eine Client-Anwendung nicht direkt auf die Komponenten eines Applikations-Servers zugreifen kann, muß eine Schnittstelle für diese Funktionalitäten definiert werden. Bei EJB3 ist es allerdings nicht mehr notwendig, daß eine Bean-Klasse ein HomeInterface besitzt, weil man über die lookup()-Methode des EJBContext von außerhalb auf Bean-Objekte und andere Ressourcen zugreifen kann, die dafür vorgesehen sind. 6.3.1.3. Business-Interface Das Business-Interface wird vom Entwickler frei definiert (POJI) und gibt die Methoden für die Geschäftslogik einer Anwendung an. Es ist für Session-Beans und Message-Driven-Beans immer erforderlich. Wenn eine Klasse nur eine einzige Schnittstelle implementiert, wird diese Schnittstelle als lokales Business-Interface verwendet. Bei mehreren Schnittstellen müssen das/die Business-Interface(s) durch Annotations explizit angegeben werden. definiert ein lokales Business-Interface innerhalb des Applikations-Servers @Remote definiert ein Business-Interface das über RMI aufgerufen werden kann @Local Eine Schnittstelle kann nicht gleichzeitig beide Funktionalitäten erfüllen. Folgende Schnittstellen werden bei der Suche nach einem gültigen Business-Interface grundsätzlich ignoriert: java.io.Serializable java.io.Externalizable alle Schnittstellen aus dem Paket javax.ejb - 191 - Java-Enterprise - Enterprise JavaBeans 6.3.1.4. Life-Cycle und Callback-Methoden Jedes Objekt einer Bean-Klasse durchläuft innerhalb eines Applikations-Servers mehrere Zustände, wodurch sich ein eigener Lebenszyklus (Life-Cycle) für jedes Objekt ergibt, der aber je nach Art der Bean-Klasse unterschiedlich ist. Damit auf diesen Zyklus der Bean-Objekte, unabhängig von der Geschäftslogik der Anwendung eingegangen werden kann, ist die Verwendung von Callback-Methoden vorgesehen. Die Methoden müssen bei den alten EJB Versionen, je nach Bean-Typ, über eine bestimmte Schnittstelle (javax.ejb.SessionBean, javax.ejb.EntityBean, java.ejb.SessionBean) implementiert werden. Bei EJB3 gibt es die Annotations, um eine beliebige Methode als Callback-Methode für ein bestimmtes Ereignis des Lebenszyklus anzugeben [74]. EJB2-Methode EJB3-Annotation Bean-Typ ejbActivate() @PostActivate Session Entity ejbPassivate() @PrePassivate Session Entity ejbCreate() @PostConstruct ejbRemove() @PreDestroy @PreRemove @PostRemove ejbPostCreate() @PostConstruct ejbLoad() @PostLoad Entity ejbStore() @PrePersist @PostPersist @PreUpdate @PostUpdate Entity setSessionContext() setEntityContext() unsetEntityContext() setMessageDrivenContext() Session @Resource Entity @PersistenceContext Entity Message Session Entity Message Session Entity Message Session Entity Message Beschreibung wird vor den Aufruf einer Bean-Methode aufgerufen wird nach dem Aufruf einer BeanMethode aufgerufen wird vor dem Initialisieren einer neuen Instanz aufgerufen wird vor dem entfernen eines Objekts aufgerufen wird nach dem Initialisieren einer neuen Instanz aufgerufen wird beim Laden aus der Datenbank aufgerufen wird beim Speichern der Daten einer Entity-Bean in der Datenbank aufgerufen gibt den Kontext einer Bean an Tabelle 16: Bean-Callback-Methoden und Annotations Die Zuordnung der EJB3-Annotations zu den alten Callback-Methoden darf in dieser Tabelle nicht als absolut festgelegt aufgefaßt werden. Die Annotations haben zum Teil eine leicht abweichende Semantik und erlauben eine feiner abgestufte Kontrolle der Beans. Innerhalb des Lebenszyklus eines Bean-Objekts werden auch bestimmte andere Vorgänge definiert, für die nicht unbedingt eine Callback-Methode aufgerufen wird (z.B. DependencyInjection), aber unter Umständen über andere Mechanismen beeinflußt werden können. - 192 - Java-Enterprise - Enterprise JavaBeans 6.3.2. Session-Beans Session-Beans bilden die Vorgänge der Geschäftslogik ab, bei denen ein Nutzer auf das System zugreift. Sie bedienen sich dabei in der Regel mehrerer Entity-Beans um die Auswirkungen eines Vorgangs darzustellen. Man unterscheidet zwei Arten von Session-Beans [73]. 6.3.2.1. Zustandslose Session-Beans Zustandslose Session-Beans speichert keine Informationen aus vorherigen Aufrufen und unterscheiden auch nicht von welchem Client aus der Aufruf erfolgt ist. Bei jedem Methodenaufruf müssen alle notwendigen Parameter übergeben werden. Session-Bean-Objekte der gleichen Klasse haben daher keine eigene Identität und sind untereinander nicht unterscheidbar. Vom Applikations-Server wird in der Regel ein bestimmte Anzahl Beans instanziert (ejbCreate) und in einem Pool abgelegt. Bei einem Aufruf der Bean-Klasse wird dann ein beliebiges Bean-Objekt aus dem Pool verwendet. Der Container kann die Anzahl der Beans jederzeit reduzieren (ejbRemove). Der Lebenszyklus einer zustandslosen Session-Bean sieht daher folgendermaßen aus. Abbildung 84: Lebenszyklus einer zustandslosen Session-Bean Eine Klasse für zustandslose Session-Beans wird bei EJB3 durch die Annotation @Stateless gekennzeichnet. 6.3.2.2. Zustandsbehaftete Session-Beans Zustandsbehaftete Session-Beans können in jeder Instanz Informationen speichern, die bei aufeinanderfolgenden Aufrufen immer wieder zur Verfügung stehen. Durch die Vergabe einer eindeutigen ID durch den Applikations-Server sind die Bean-Instanzen unterscheidbar, und die richtige Zuordnung zu den Aufrufen des Systems ist möglich. Durch diese Zuordnung können die Bean-Objekte nicht beliebig aus einem Pool verwendet und bei zu großer Anzahl einfach verworfen werden. Dafür hat der Container die Möglichkeit die Beans in einen passiven Zustand zu versetzen und sie dabei aus dem Arbeitsspeicher zu entfernen und anderweitig zu speichern, bis ein Bean-Objekt wieder bei einem Aufruf benötigt wird. Abbildung 85: Lebenszyklus einer zustandsbehafteten Session-Bean Eine Klasse für zustandsbehaftete Session-Beans wird durch die Annotation @Stateful gekennzeichnet. Zur Initialisierung bzw. Löschung einer Bean-Instanz können Methoden mit @Init bzw. @Remove annotiert werden. - 193 - Java-Enterprise - Enterprise JavaBeans 6.3.2.3. Dependency-Injection Damit der Aufbau eines Bean-Objekts einfach bleibt, muß es sich nicht selbst um benötigte Ressourcen kümmern. Es bekommt die gewünschten Ressourcen durch Dependency-Injection vom Container zur Verfügung gestellt. Dazu müssen lediglich die nötigen Instanzvariablen in der Klasse definiert und durch Annotations gekennzeichnet werden. Dies entspricht dem Prinzip "Inversion of Control" (IoC) oder "don't call us, we call you", und drückt aus, daß die Verantwortlichkeit der Programmausführung stärker beim Applikations-Server liegt. Annotation Beschreibung gibt eine Instanzvariable als Ressource an, die vom Container inji@Resource ziert werden soll @Resources faßt mehrere @Resource Annotation @PersistenceContext injiziert einen EntityManager zur Verwaltung von Entity-Beans Tabelle 17: Annotation für Dependency-Injection Durchgeführt wird die Dependency-Injection durch den Container bevor Methoden aus einem Business-Interface oder Callback-Methoden für den Lebenszyklus aufgerufen werden. Im Konstruktor einer Bean-Klasse muß darauf geachtet werden, daß diese Ressourcen dort nicht verfügbar sind. Die Art der Resource wird durch den Typ der Instanzvariablen bestimmt. @Resource(mappedName = "java:/DefaultDS") private DataSource ds; @Resource(mappedName = "java:/ConnectionFactory") private ConnectionFactory factory; Der zusätzliche Parameter mappedName gibt den JNDI-Namen einer vorhandenen Ressource an, für die der Container eine Referenz injizieren soll. 6.3.2.4. Interceptoren (AOP) Durch Interceptoren wird der Ansatz der aspektorientierten Programmierung (AOP) realisiert. Eine Bean-Methode enthält dabei nur die Funktionalitäten, die von der Geschäftslogik der Anwendung vorgesehen sind. Allgemeine Funktionalitäten, wie z.B. Logging, Timing oder Authentifizierung, werden von Interceptoren übernommen, die den Aufruf von beliebigen Methoden "umschließen" und die zusätzliche Funktionalität davor bzw. danach einfügen. Abbildung 86: Interceptor-Aufruf - 194 - Java-Enterprise - Enterprise JavaBeans Durch die Annotation @AroundInvoke wird eine Methode als Interceptor definiert. Jede Klasse darf nur eine solche Methode besitzen, die aber die folgende Signatur haben muß. @AroundInvoke public Object intercept (InvocationContext ctx) throws Exception { // Funktionalitäten vor dem Methodenaufruf ... Object result = ctx.proceed(); ... // Funktionalitäten nach dem Methodenaufruf return result; } Code 120: Interceptor-Methode Über den InvocationContext kann die Interceptor-Methode auf die Parameter des ursprünglichen Methodenaufrufs zugreifen. Durch die Methode proceed() wird der Aufruf der ursprünglichen Methode fortgesetzt, sofern die Implementierung der Interceptor-Methode den Aufruf nicht verhindern will. Schließlich kann auch der Rückgabewert des Methodenaufrufs ausgewertet und verändert werden. Soll die Methode in einer separaten Interceptor-Klasse implementiert werden, muß dies bei der Bean-Klasse durch die zusätzliche Annotation @Interceptor(MyInteceptor.class) angegeben werden. 6.3.2.5. EntityManager Der im folgenden Beispiel verwendete EntityManager ist ein weiterer Teil der JavaPersistence-API und damit des im JBoss verwendeten Hibernate-Frameworks, der in Kapitel 4.6 noch nicht angesprochen wurde. Im Prinzip besitzt ein EntityManager dieselben Methoden zum Arbeiten mit persistenten Objekten, wie eine Hibernate-Session. Er ist aber für die Verwendung in einem Applikations-Server zusammen mit einem PersistenceContext vorgesehen. Man muß sich nicht selbst um die Konfiguration der Datenbankanbindung, der persistenten Klassen und das Öffnen von Hibernate-Sitzungen kümmern, sondern kann die Instanz des EnitityManagers, die man vom Container erhält, ohne weiteres sofort verwenden. 6.3.2.6. Service-Klasse Die Service-Klasse wird als zustandslose Session-Bean (@Stateless) aufgebaut. Die Schnittstelle IExtendedAccountingService wird implementiert kann durch die entsprechende Annotation (@Remote) direkt als Business-Interface für entfernte Aufrufe angegeben werden. Durch Dependency-Injection erhält die Klasse eine Instanz des Hibernate-EntityManagers (@PersistenceContext), zur Verwaltung von Entity-Beans. Der angegebene unitName wird später in einer separaten Konfigurationsdatei (persistence.xml) genau definiert. - 195 - Java-Enterprise - Enterprise JavaBeans package de.fhr.vs_iw.ejb; @Stateless @Remote(IExtendedAccountingService.class) public class EJBAccountingService implements IExtendedAccountingService { @PersistenceContext(unitName = "EJBAccountingServiceDS") private EntityManager manager; public EJBAccountingService() {} public IAccount accountChange(IAccount account) { return manager.merge(account); } public IAccountingEntry accountingEntryChange(IAccountingEntry entry) { return manager.merge(entry); } public Double calculateBalance(Integer number) throws IOException, AccountingException { double saldo = 0.0d; Account account = (Account)getAccountByNumber(number); for (IAccountingEntry entry : account.getDebits()) { saldo += entry.getAmount(); } for (IAccountingEntry entry : account.getCredits()) { saldo -= entry.getAmount(); } return saldo; } public void createAccount(Integer number, AccountType type, String description) throws AccountingException { if (number <= 0) { throw new AccountingException("Die Nummer darf nicht negativ sein"); } checkAccountNumber(number, false); Account acc = new Account(); acc.setNumber(number); acc.setType(type); acc.setDescription(description); manager.persist(acc); } public Integer createAccountingEntry(Integer debit, Integer credit, Double amount, String text) throws AccountingException { checkAccountNumber(debit, true); checkAccountNumber(credit, true); if (debit.equals(credit)) { AccountingEntry.exceptionIdenticalAccounts(); } AccountingEntry entry = new AccountingEntry(); entry.setText(text); entry.setAmount(amount); entry.setDebit(manager.find(Account.class, debit)); entry.setCredit(manager.find(Account.class, credit)); manager.persist(entry); return entry.getId(); } - 196 - Java-Enterprise - Enterprise JavaBeans public IAccount getAccountByNumber(Integer number) throws AccountingException { checkAccountNumber(number, true); return manager.find(Account.class, number); } public Set<Integer> getAccountingEntries() { ArrayList list = (ArrayList)manager.createQuery( "SELECT id FROM accountingentry").getResultList(); return new HashSet<Integer>(list); } public IAccountingEntry getAccountingEntryById(Integer id) throws AccountingException { checkAccountingEntryId(id); return manager.find(AccountingEntry.class, id); } public Set<Integer> getAccounts() { ArrayList list = (ArrayList)manager.createQuery( "SELECT number FROM account").getResultList(); return new HashSet<Integer>(list); } public void removeAccount(Integer number) throws AccountingException { try { final Account account = checkAccountNumber(number, true); manager.remove(account); } catch (final RuntimeException e) { throw new AccountingException(e); } } public void removeAccountingEntry(Integer id) throws AccountingException { try { final AccountingEntry entry = checkAccountingEntryId(id); entry.getDebit().getDebits().remove(entry); entry.getCredit().getCredits().remove(entry); manager.remove(entry); } catch (final RuntimeException e) { throw new AccountingException(e); } } } ... Code 121: Class EJBAccountingService Diese Session-Bean verhält sich im Prinzip wie eine Kombination aus der RMI-ServiceKlasse (siehe 5.3.5.1) und der Hibernate-Service-Klasse (siehe 4.6.6.1). Über den Applikations-Server können die Methoden der Klasse von einem Client entfernt aufgerufen werden. Die Implementierung der Methoden benutzt dann die Funktionalitäten des HibernateEntityMangers um mit den Konten- und Buchungsobjekten (Entity-Beans) zu Arbeiten, und so die Buchhaltungsdaten zu verwalten. Beim Löschen von Buchungsobjekten ist darauf zu achten, daß die Objekte auch aus den zugehörigen Sets bei den zugeordneten Konten entfernt werden, da sonst eine Referenz darauf bestehen bleibt und so der EntityManager in einen inkonsistenten Zustand kommt. - 197 - Java-Enterprise - Enterprise JavaBeans 6.3.3. Entity-Beans Durch Entity-Beans werden die dauerhaften (persistenten) Daten einer Anwendung modelliert, und zur Laufzeit zur Verfügung gestellt. In der Regel stellt eine Entity-Bean-Klasse ein real existierendes Objekt (Person, Rechnung, Konto, Buchung) dar. Die Daten einer BeanInstanz stammen z.B. aus einem Datensatz einer Datenbank [73]. Die Instanzen von Entity-Beans dienen auf dem Applikations-Server als Datenzugriffsobjekte (Data Access Object - DAO) und werden vom Container verwaltet, wodurch sich auch wieder ein bestimmter Lebenszyklus ergibt. Abbildung 87: Lebenszyklus einer Entity-Bean Durch diesen Lebenszyklus sind Entity-Beans an den Applikations-Server gebunden. Um die Daten an einen Client zu übertragen wird das Entwicklungs-Muster "Datentransferobjekt" (Data Transfer Object - DTO) verwendet und ein separates Objekte verwendet. Bei der Java Enterprise Edition 5 und dem Enterprise JavaBeans 3.0 Standard gibt es diese Unterscheidung zwischen DAO und DTO mittlerweile nicht mehr. Die Entity-Beans werden im Sinne der Java-Persistence-API verwaltet und besitzen auch keinen vorgegebenen Lebenszyklus mehr. Ein persistentes Objekt kann von der Persistenzschicht der Anwendung abgetrennt, in anderen Anwendungsteilen verwendet, und später wieder angefügt werden (siehe 4.6.5.1). Eine Entity-Bean-Klasse wird durch die Annotation @Entity gekennzeichnet. 6.3.3.1. Bean-Managed-Persistence Die Persistenz der Daten kann auf verschiedene Weise realisiert werden. Man kann den Zugriff auf die persistenten Daten in den Methoden der Entity-Beans selbst implementieren, was als Bean-Managed-Persistence (BMP) bezeichnet wird. Dies würde z.B. dem Ansatz wie in Kapitel 4.5 entsprechen, eine direkte Datenbankverbindung zu öffnen, und über eigene SQL-Anweisungen manuell auf die Daten in der Datenbank zuzugreifen. 6.3.3.2. Container-Managed-Persistence Der bessere Ansatz ist aber die vom Prinzip her bereits angesprochene Variante der Container-Managed-Persistence (CMP), wobei sich der Container um die Persistenz der Daten kümmert. Konkret wird dies eben durch die Verwendung des EntityMangers erreicht, der die persistenten Objekte verwaltet. Derartige persistente Entity-Bean-Klassen werden dazu mit den Annotations der Java-Persistence-API bzw. Hibernate-Annotations, wie sie in Kapitel 4.6 beschrieben wurden, ausgezeichnet. Mit einigen Änderungen entsprechen die Klassen aus jenem Kapitel bereits den Notwendigkeiten für Java-Enterprise Entity-Beans. - 198 - Java-Enterprise - Enterprise JavaBeans 6.3.3.3. FetchType Der Parameter fetch für den FetchType bei der Annotation @OneToMany gibt an, wie die in einer Beziehung referenzierten Objekte aus der Datenbank geladen werden sollen. Dazu gibt es zwei mögliche Varianten FetchType.EAGER FetchType.LAZY referenzierte Objekte werden zusammen mit dem Basisobjekt instanziert und ihre Daten vollständig aus der Datenbank geladen, sofern ein Objekt nicht bereits geladen wurde es wird dynamisch ein Proxy-Objekt für ein referenziertes Objekt erzeugt und verwendet, auf die Datenbank wird erst zugegriffen, wenn die Daten des Objekts wirklich abgerufen werden Bei den Entity-Bean-Klassen wird der Typ EAGER verwendet, damit ein Objekt mit allen relevanten Daten und referenzierten Objekten vollständig geladen wird. Objekte, die von einem Client verwendet werden, haben sonst später keinen Zugriff auf die Datenbank mehr, weil die Entity-Beans vom Server abgekoppelt werden. 6.3.3.4. Entity-Bean-Prinzip Das Prinzip bei der Arbeit mit Entity-Beans beruht bei Java-Enterprise-Anwendungen darauf, daß die Bean-Objekte zwischen den Schichten und Komponenten einer Anwendung ausgetauscht werden. Bei entfernten Aufrufen der Enterprise-Anwendung soll dies auch die Performance, z.B. gegenüber RMI erhöhen, da nur ein größeres Objekt übertragen wird, und weitere Methodenaufrufe dann lokal erfolgen. Abbildung 88: Ablauf des Entity-Bean-Prinzips bei einem Kontenobjekt - 199 - Java-Enterprise - Enterprise JavaBeans Bei der Buchhaltung bedeutet dies konkret, daß zwar die Methoden der Service-Session-Bean, wie bei der RMI-Implementierung des Buchhaltungssystems, entfernt aufgerufen werden, die Methoden getAccountByNumber() und getAccountingEntryById() aber ihrerseits keine entfernten Objekte zurückgeben, sondern Objekte der definierten Entity-Bean-Klassen. Diese Objekte sind serialisierbar und werden vollständig zum Client übertragen, weshalb es eben wichtig ist, daß die Objekte vom EntityManager vorher vollständig geladen wurden. Die persistenten Objekte werden dabei natürlich von der Persistenzschicht der EnterpriseAnwendung abgekoppelt. Der Client kann nun die Objekte und ihre Daten beliebig verwenden. Ein Problem besteht natürlich dann, wenn der Client die Daten eines solchen Objekts ändert. Da das Entity-BeanObjekt keine Verbindung zur Datenbank mehr hat, wäre die Änderung nicht persistent, und andere Clients würden auch nichts davon mitbekommen. Deshalb muß das Objekt nach einer Änderung an den Applikations-Server zurückgeschickt werden. An dieser Stelle kommen erstmals die beiden zusätzlichen Methoden der erweiterten ServiceSchnittstelle (IExtendedAccountingService), die bei der Service-Session-Bean als Business-Interface verwendet wird, zum Einsatz. Durch den Aufruf von changeAccount() bzw. changeAccountingEntry() kann ein geändertes Konten- bzw. Buchungsobjekt wieder an den EntityManager auf dem Applikations-Server, zum Speichern der Daten in der Datenbank, übergeben werden. Jede Client-Anwendung der Enterprise-JavaBean-Buchhaltung muß nach dem Ändern von Daten die entsprechende Methode aufrufen. 6.3.3.5. Konto-Klasse Die Konto-Klasse wird als CMP-Entity-Bean mit Annotations ausgezeichnet und entsprechend implementiert. Abbildung 89: Beziehung der Entity-Beans Jedem Kontenobjekt werden die Soll- und Haben-Buchungen jeweils als Set von Objekten mit der Annotation @OneToMany zugeordnet, um die 1:n-Beziehung herzustellen. Neben der üblichen Implementierung der Konto-Schnittstellte wird die Klasse mit Getter/SetterMethoden für die Sets der Buchungsobjekte und die Kontonummer ausgestattet. package de.fhr.vs_iw.ejb; @Entity @Table(name = "account") public final class Account implements IAccount, Serializable { private Set<AccountingEntry> credits; private Set<AccountingEntry> debits; public Account() {} - 200 - Java-Enterprise - Enterprise JavaBeans @Transient public Set<Integer> getCreditEntries() { HashSet<Integer> ret = new HashSet<Integer>(); for (AccountingEntry entry : credits) { ret.add(entry.getId()); } return ret; } @Transient public Set<Integer> getDebitEntries() { HashSet<Integer> ret = new HashSet<Integer>(); for (AccountingEntry entry : debits) { ret.add(entry.getId()); } return ret; } @Id public Integer getNumber() { return number; } @OneToMany(mappedBy = "credit", fetch = FetchType.EAGER, cascade = CascadeType.ALL) public Set<AccountingEntry> getCredits() { return credits; } @OneToMany(mappedBy = "debit", fetch = FetchType.EAGER, cascade = CascadeType.ALL) public Set<AccountingEntry> getDebits() { return debits; } public void setCredits(Set<AccountingEntry> credits) { this.credits = credits; } public void setDebits(Set<AccountingEntry> debits) { this.debits = debits; } public void setNumber(Integer number) { this.number = number; } } ... Code 122: Class Account (EJB) Der EntityManager lädt alle Daten für ein Konto und die Sets mit den referenzierten Buchungen, so daß eine Instanz der Konto-Bean-Klasse dann völlig unabhängig verwendet werden kann. - 201 - Java-Enterprise - Enterprise JavaBeans 6.3.3.6. Buchungs-Klasse Die Buchungs-Klasse wird mit Annotations passend zur Konto-Klasse versehen. Da es nicht mehr reicht nur die Nummern der referenzierten Soll- und Haben-Konten anzugeben, wird die Klasse mit Instanzvariablen vom Typ Account ausgestattet. Die zugehörigen GetterMethoden werden durch @ManyToOne und @JoinColumn als Gegenstück für die 1:nBeziehung mit der Konto-Klasse angegeben. package de.fhr.vs_iw.ejb; @Entity @Table(name = "accountingentry") public class AccountingEntry implements IAccountingEntry, Serializable { private Account credit; private Account debit; @PersistenceContext(unitName = "EJBAccountingServiceDS") private EntityManager manager; public AccountingEntry() {} @Transient public Integer getCreditAccount() { return credit.getNumber(); } @Transient public Integer getDebitAccount() { return debit.getNumber(); } @Id @GeneratedValue public Integer getId() { return id; } public void setCreditAccount(Integer number) throws AccountingException { checkAccountNumber(number, true); if (number.equals(getDebitAccount())) { exceptionIdenticalAccounts(); } setCredit(manager.find(Account.class, number)); } public void setDebitAccount(Integer number) throws AccountingException { checkAccountNumber(number, true); if (number.equals(getCreditAccount())) { exceptionIdenticalAccounts(); } setDebit(manager.find(Account.class, number)); } public void setText(String text) { this.text = text; } @ManyToOne @JoinColumn(name = "credit") public Account getCredit() { return credit; } - 202 - Java-Enterprise - Enterprise JavaBeans @ManyToOne @JoinColumn(name = "debit") public Account getDebit() { return debit; } public void setCredit(Account credit) { this.credit = credit; } public void setDebit(Account debit) { this.debit = debit; } public void setId(Integer id) { this.id = id; } ... } Code 123: Class AccountingEntry (EJB) Damit auch Objekte dieser Bean-Klasse unabhängig sind, wird zur Zuordnung von Soll- und Haben-Kontenobjekten direkt ein EntityManager benutzt, der wie bei der Service-SessionBean-Klasse durch Dependency-Injection vom Container zur Verfügung gestellt wird ist. 6.3.4. Weitere Bean-Arten Neben den Session-Beans und den Entity-Beans, die in der Regel den Großteil der benötigten Beans in einer Anwendung darstellen, gibt es noch weitere Arten von Enterprise JavaBeans und andere Dienste die von einem Applikations-Server zur Verfügung gestellt werden. 6.3.4.1. JMS - Message-Driven-Beans Message-Driven-Beans (MDB) machen Java-Enterprise-Anwendungen für asynchrone Kommunikation zugänglich. Hierzu wird der Java-Messaging-Service (JMS) [15] verwendet, der vom JBoss zur Verfügung gestellt wird. Diese Beans werden häufig bei der Kommunikation mit Legacy-Systemen verwendet [73]. Wie die anderen Bean-Arten auch, werden MDBs von Applikations-Server verwaltet. Sie haben dabei fast den gleichen Lebenszyklus wie zustandslose Session-Beans. Abbildung 90: Lebenszyklus einer Message-Driven-Bean Der Java-Messaging-Service unterstützt zwei verschiedene Varianten der Nachrichtenverarbeitung, Nachrichtenschlangen (message queues) und ein Anmelde-Versende-System (publish-subscribe - topic) [75]. - 203 - Java-Enterprise - Enterprise JavaBeans Bei Nachrichtenschlangen gibt es immer genau einen Abnehmer, der die eingehenden Nachrichten erhält. Wenn es für eine Nachrichtenschlange zu einem Zeitpunkt keinen Abnehmer gibt, so schlägt des senden von Nachrichten an diese Schlange fehl. Wenn die Nachrichten schneller eintreffen als der Abnehmer sie verarbeiten kann, werden die Nachrichten in der Warteschlange eingereiht und der Reihe nach abgearbeitet. Abbildung 91: Nachrichtenschlange Beim Anmelde-Versende-System werden die Nachrichten einfach verschickt, egal ob sich jemand als Empfänger angemeldet hat. Jeder angemeldete Empfänger erhält jede Nachricht, und wenn eine Nachricht nicht konsumiert wird, dann ist dies unerheblich. Abbildung 92: Anmelde-Versende-System Im Gegensatz zu anderen Bean-Klassen muß eine Message-Driven-Bean zwingend eine bestimmte Schnittstelle, nämlich javax.jms.MessageListener, implementieren. Diese fungiert im Prinzip wie ein lokales Business-Interface. package javax.jms; public interface MessageListener { void onMessage(Message message); } Code 124: Interface MessageListener Wenn aufgrund der angegebenen Aktivierungskonfiguration eine Nachricht eintrifft wird die Methode onMessage() mit einem Objekt der Klasse javax.jmx.Message aufgerufen. Message-Driven-Beans werden durch die Annotation @MessageDriven gekennzeichnet. Bei dieser Annotation wird durch den Parameter activationConfig angegeben, wann eine Instanz einer solchen Bean-Klasse aktiviert werden soll. - 204 - Java-Enterprise - Enterprise JavaBeans package de.fhr.vs_iw.ejb; @MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/vs_iw") }) public class MessageBean implements MessageListener { public void onMessage(Message msg) { System.out.println("Nachricht erhalten: " + msg.toString()); } } Code 125: Class MessageBean Der Parameter activationConfig ist ein Array von @ActivationConfigProperty Annotations. Jede dieser Annotations gibt eine Eigenschaft der Aktivierungskonfiguration an. Die beiden erforderlichen Eigenschaften mit ihren möglichen Werten sind: destinationType gibt die Art des Nachrichtensystems an o javax.jmx.Queue Nachrichtenschlange o javax.jmx.Topic Anmelde-Versende-System (Publish-Subscribe) destination legt den Namen für das Ziel fest, beginnt mit einem Präfix bei einer Nachrichtenschlange o "queue/..." bei einem Anmelde-Versende-System o "topic/..." Aufgrund dieser Angaben erstellt der JBoss eine Queue oder ein Topic, mit deren Hilfe die Nachrichten weitergeleitet werden. Alternativ kann diese Konfiguration auch in einer separaten XML-Datei erfolgen, damit sie von den Klassen der Anwendung unabhängig ist. <server> <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=queue/vs_iw"> <attribute name="JNDIName">queue/vs_iw</attribute> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean> </server> Resource 25: jmx-queue-service.xml Diese Art der Konfiguration kann besonders bei einem Anmelde-Versende-System sinnvoll sein, da hier auch oft vorkommt, daß weder der Sender noch der Empfänger einer Nachricht als Java-Bean auf dem Server laufen, sondern jeweils als Client-Anwendungen (siehe 6.3.6.5) realisiert werden, die den Server lediglich zur Kommunikation benutzen. <server> <mbean code="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=topic/vs_iw"> <attribute name="JNDIName">topic/vs_iw</attribute> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean> </server> Resource 26: jms-topic-service.xml - 205 - Java-Enterprise - Enterprise JavaBeans 6.3.4.2. JMX - Management-Beans Management-Beans (MBeans) ermöglichen den Zugriff auf eine Anwendung die JavaManagement-Extension (JMX) [16]. Eine Management-Bean muß ein Management-Interface implementieren, genauso wie jede Session-Bean ein Business-Interface implementieren muß. Im Prinzip kann jede Bean-Klasse als Management-Bean angegeben werden, indem eine definierte Management-Schnittstelle hinzugefügt wird. Der Name einer ManagementSchnittstelle sollte normalerweise mit dem Suffix "...MBean" enden. @Management public interface MyManagementMBean { void create() throws Exception; void start() throws Exception; void stop(); void destroy(); ... } <More Methods> Code 126: Interface MyManagementMBean Jede Management-Schnittstelle kann grundsätzlich aus beliebigen Methoden bestehen. Es gibt aber die vier Basis-Methoden create(), start(), stop() und destroy() die zwar nicht zwingend vorhanden sein müssen, aber wenn sie vorhanden sind, als Callback-Methoden durch den Lebenszyklus der Management-Bean aufgerufen werden. Abbildung 93: MBean-Lebenszyklus Der Sinn darin besteht, daß z.B. die Nachrichtenverarbeitung einer Message-Driven-Bean von außen gestoppt und wieder gestartet werden kann, auch wenn dieser Vorgang nicht zur eigentlichen Geschäftslogik einer Anwendung gehört, aber trotzdem möglich sein soll. Beim JBoss ist es möglich die Methoden aller MBeans von der JMX-Console aus aufzurufen. Man kann aber natürlich jederzeit eine eigene Verwaltungsanwendung entwickeln. - 206 - Java-Enterprise - Enterprise JavaBeans 6.3.4.3. Service-Beans Ein Service-Bean ist eigentlich keine Bean-Art im eigentlichen Sinne, sondern eine zusätzliche Eigenschaft für eine der anderen Bean-Klassen. Die Annotation @Service bei einer BeanKlasse bewirkt, daß diese Klasse als Singleton genau einmal instanziert wird, sofort nachdem die Anwendung auf einem Server deployed bzw. gestartet wurde. Der Applikations-Server sorgt dafür, daß alle Aufrufe von Methoden der Klasse durch diese eine Instanz ausgeführt werden. Diese Annotation ist eine JBoss-spezifische Erweiterung und nicht Teil des EJBStandards. 6.3.4.4. TimerService Der TimerService ist ein Dienst in der EJB-Spezifikation, der zu einer bestimmten Zeit den Aufruf einer Methode einer Bean veranlaßt. Die Methode der Bean wird durch @Timeout annotiert und muß folgende Signatur haben. @Timeout public void timeoutHandler(Timer timer); Wenn ein Timer abgelaufen ist wird diese Methode vom Container aufgerufen und das abgelaufene Timer-Objekt als Parameter übergeben. Erstellt wird ein Timer-Objekt durch die create()-Methoden einer TimerService-Instanz. package javax.ejb; public interface TimerService { Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info); Timer createTimer(Date expiration, Serializable info); Timer createTimer(long initialDuration, long intervalDuration, Serializable info); Timer createTimer(long duration, Serializable info); Collection getTimers(); } Code 127: Interface TimerService Zugriff auf den TimerService wiederum erhält man über den SessionContext. Dieser wird wie bereits angesprochen als Resource vom Container injiziert (siehe 6.2.3.2). @Resource private SessionContext ctx; ctx.getTimerService().createTimer(interval, interval, "Hourly Backup"); Ein Timer wird vom Applikations-Server verwaltet und normalerweise auch persistent gespeichert, damit er bei einem Neustart des Servers erhalten bleibt. Beim JBoss wird das Verhalten des TimerService in der Datei ejb-timer-service.xml konfiguriert. - 207 - Java-Enterprise - Enterprise JavaBeans 6.3.4.5. Management-Service-Bean mit Timer-Service Die angesprochenen Möglichkeiten für JavaBeans werden nun in einem Beispiel umgesetzt. Der praktische Nutzen für die Buchhaltungsanwendung könnte z.B. die zeitgesteuerte Ausführung eines Backup-Vorgangs sein. Da dieser Vorgang von der der Geschäftslogik her nicht erforderlich ist, und auch von außen nicht aufgerufen werden soll, wird ein in einem MBean mit der folgenden ManagementSchnittstelle IBackupTimerMBean implementiert. package de.fhr.vs_iw.ejb; @Management public interface IBackupTimerMBean { void create() throws Exception; void destroy(); } Code 128: Interface IBackupTimerMBean Es sind hier nur die Methoden create() und destroy() jeweils zum Starten bzw. Stoppen des Timers notwendig, wenn eine MBean-Instanz erstellt bzw. entfernt wird. Die Schnittstelle bzw. die beiden Methoden werden in der Klasse BackupTimer implementiert. package de.fhr.vs_iw.ejb; @Service @Management(IBackupTimerMBean.class) public final class BackupTimer implements IBackupTimerMBean { @Resource private SessionContext ctx; private Timer theTimer = null; public BackupTimer() {} public void create() throws Exception { if (theTimer == null) { theTimer = ctx.getTimerService().createTimer(3600000, 3600000, "xx"); } } public void destroy() { if (theTimer != null) { theTimer.cancel(); } theTimer = null; } } @Timeout public void timeoutHandler(Timer timer) { if (timer == theTimer) { System.out.println("Received Timer event: " + timer.getInfo()); } else { timer.cancel(); } } Code 129: Class BackupTimer - 208 - Java-Enterprise - Enterprise JavaBeans Die Klasse erhält den SessionContext durch Dependency-Injection und kann damit in der create()-Methode der Bean-Klasse einen Timer erstellen, der alle Stunde (3600000 Sekunden) abläuft. Dadurch wird die Methode timeoutHandler aufgerufen, die dann das angesprochene Backup der Buchhaltungsdaten durchführen könnte. Damit die create()-Methode überhaupt aufgerufen wird, muß das Bean instanziert werden, was aber normalerweise nur geschieht, wenn ein Aufruf der Bean-Klasse erfolgt, also wenn irgendwie von außen (lokal oder remote) zugegriffen wird. Dieses Problem wird durch die @Service Annotation gelöst, die ja die einmalige Instanzierung einer Bean-Klasse beim deployen bzw. starten der Anwendung bewirkt. Außerdem wird dadurch der Timer auch sicher nur einmal gestartet. Durch den Aufruf von cancel() in der destroy()-Methode wird der Timer beendet und gelöscht, sobald die MBean-Instanz vom Server entfernt wird. Die Methoden der Klasse BackupTimer müssen aber die Timer-Instanz bei jedem Aufruf überprüfen, weil der JBoss-Application-Server in der vorliegenden Version ein Problem bei der gemeinsamen Verwendung der Annotations @Management und @Service hat, das bewirkt, daß die Methoden der Management-Schnittstelle immer zweimal aufgerufen werden. 6.3.5. Deployment Alle notwendigen Klassen und Dateien müssen nun noch in Archive verpackt und auf dem Applikations-Server zur Verfügung gestellt werden. Bei einer Enterprise-JavaBeansAnwendung sollte nun nicht ein einfaches JAR-Archiv, das einfach nur alle Klassen und Dateien enthält, sondern ein Enterprise-Archiv (EAR) verwendet werden. Außerdem ist hier anzumerken, daß der JBoss-Application-Server, zumindest in der vorliegenden Version 4.2.2.GA, ein Problem mit Enterprise-JavaBeans in einfachen JAR-Archiven hat. Das Redeployment eines solchen Archivs schlägt immer fehl und der JBoss-ServerProzeß muß neu gestartet werden. 6.3.5.1. Enterprise-Archiv Ein Enterprise-Archiv faßt andere Archive mit den einzelnen unterschiedlichen Komponenten einer Anwendung zusammen. Dazu muß das Enterprise-Archiv, neben den untergeordneten Archiven selbst, einen Deployment-Descriptor META-INF/application.xml enthalten. <?xml version="1.0" encoding="UTF-8"?> <application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"> <display-name>EJBAccountingService</display-name> <module> <ejb>beans.ejb3</ejb> </module> <module> <java>persistence.par</java> </module> <module> <java>model.jar</java> </module> </application> Resource 27: application.xml (EJB) - 209 - Java-Enterprise - Enterprise JavaBeans In diesem Deployment-Descriptor werden die einzelnen Module der Anwendung, die der Applikations-Server entpacken und laden muß, je nach ihrem Typ (<ejb>, <java>, <web>) beschrieben. 6.3.5.2. Archiv für Session-Beans Die Session-Beans der Buchhaltung (EJBccountingService, BackupTimer, MessageBean) werden mit einem Ant-Vorgang in ein EJB3-Archiv verpackt. Nicht alle diese BeanKlassen stellen Session-Beans dar, weshalb es bei genauer Betrachtung so ist, daß eigentlich alle Nicht-Entity-Beans zusammen in ein Archiv verpackt werden. <target name="ejb3"> <delete file="${basedir}/temp/beans.ejb3" /> <jar destfile="${basedir}/temp/beans.ejb3"> <fileset dir="${basedir}/bin/"> <include name="**/ejb/EJBAccountingService.class" /> <include name="**/ejb/BackupTimer.class" /> <include name="**/ejb/IBackupTimerMBean.class" /> <include name="**/ejb/MessageBean.class" /> </fileset> <fileset dir="${basedir}/modules/"> <include name="**/ejb/EJBAccountingService.java" /> <include name="**/ejb/BackupTimer.java" /> <include name="**/ejb/IBackupTimerMBean.java" /> <include name="**/ejb/MessageBean.java" /> </fileset> </jar> </target> Resource 28: Ant-Vorgang für EJB3-Archiv Wichtig dabei ist, daß die Endung des Archivs wirklich exakt ejb3 ist, damit die BeanKlassen durch den JBoss-Application-Server richtig verarbeitet werden. Dafür ist nach dem EJB3-Standard kein zusätzlicher XML-Deployment-Descriptor notwendig, da die Annotations der Klassen verwendet werden und ausreichen. 6.3.5.3. Archiv für Entitiy-Beans Auch die Entity-Beans (Account, AccountingEntry) werden durch einen Ant-Vorgang in ein separates JAR-Archiv verpackt. Seit JBoss 4.2 wird nicht mehr die spezielle DateiErweiterung par (Persistence Archive) verwendet, sondern die Standard-Endung jar. <target name="entity"> <delete file="${basedir}/temp/persistence.jar" /> <jar destfile="${basedir}/temp/persistence.jar"> <fileset dir="${basedir}/bin/"> <include name="**/ejb/Account.class" /> <include name="**/ejb/AccountingEntry.class" /> </fileset> <fileset dir="${basedir}/modules/"> <include name="**/ejb/Account.java" /> <include name="**/ejb/AccountingEntry.java" /> </fileset> - 210 - Java-Enterprise - Enterprise JavaBeans <metainf dir="${basedir}/resources/ejb"> <include name="persistence.xml" /> </metainf> </jar> </target> Resource 29: Ant-Vorgang für Persistence-Archiv Als zusätzlicher Deployment-Descriptor ist die XML-Datei persistence.xml notwendig, die die Verwaltung der Entity-Beans durch den Applikations-Server definiert. <?xml version="1.0" encoding="UTF-8"?> <persistence> <persistence-unit name="EJBAccountingServiceDS"> <jta-data-source>java:/MySqlDS</jta-data-source> <properties> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> </properties> </persistence-unit> </persistence> Resource 30: Deployment-Descriptor persistence.xml Hier wird der Name der Persistence-Unit definiert, auf den der @PersistenceContext für den in den Bean-Klassen verwendeten EntityManager verweist (siehe 6.3.2.6). Mit den weiteren Angaben wird das Verhalten der Instanz des EntityManager konfiguriert. Der Name einer definierten Datenquelle, sowie einige zusätzliche Parameter für Hibernate werden angegeben. 6.3.5.4. Archiv für weitere Klassen Zusätzliche Klassen, die für die Anwendung notwendig sind, werden durch einen weiteren Ant-Vorgang in eine zusätzliche jar Datei verpackt. <target name="model"> <delete file="${basedir}/temp/model.jar" /> <jar destfile="${basedir}/temp/model.jar"> <fileset dir="${basedir}/bin/"> <include name="**/model/*.class" /> </fileset> <fileset dir="${basedir}/main/"> <include name="**/model/*.java" /> </fileset> </jar> </target> Resource 31: Ant-Vorgang für JAR-Archiv Konkret sind das hier die Schnittstellen und Klassen aus dem Buchhaltungsmodell (de.fhr.vs_iw.model), auf denen die Bean-Klassen basieren. - 211 - Java-Enterprise - Enterprise JavaBeans 6.3.5.5. Gemeinsame Ant-Build-Datei Die verschiedenen Vorgänge zur Erstellung der Teil-Archive können in einer gemeinsamen Ant-Build-Datei zusammengefaßt werden. <?xml version="1.0" encoding="UTF-8"?> <project name="EJBAccountingService" default="deploy" basedir=".."> <property file="${basedir}/ant/build.properties" /> <target name="ejb3"> ... </target> <target name="entity"> ... </target> <target name="model"> ... </target> <target name="package-ear" depends="ejb3, entity, model"> <delete file="${basedir}/temp/EJBAccountingService.ear" /> <ear destfile="${basedir}/temp/EJBAccountingService.ear" appxml="${basedir}/resources/ejb/application.xml"> <fileset dir="${basedir}/temp/"> <include name="beans.ejb3" /> <include name="persistence.jar" /> <include name="model.jar" /> </fileset> <metainf dir="${basedir}/resources/ejb/"> <include name="jms-topic-service.xml" /> </metainf> </ear> </target> <target name="deploy" depends="undeploy, package-ear"> <copy file="${basedir}/temp/EJBAccountingService.ear" todir="${jboss4.deploy}" overwrite="true" /> </target> <target name="undeploy"> <delete file="${jboss4.deploy}/EJBAccountingService.ear" failonerror="false" /> </target> </project> Resource 32: Ant-Build-Datei enterprise-deploy-jboss4.xml Mit den Teil-Archiven und dem Deployment-Descriptor application.xml wird in einem weiteren Ant-Vorgang, der von den o.g. Vorgängen abhängig ist, das Enterprise-Archiv für die Buchhaltung erstellt. Im META-INF Unterverzeichnis innerhalb des Archivs wird die Konfigurationsdatei für das Anmelde-Versende-System (jms-topic-service.xml, siehe 6.3.4.1) mit aufgenommen, damit der JBoss das entsprechende Topic anlegen kann. Nach dem Erstellen wird das Enterprise-Archiv in das Deploy-Verzeichnis des JBossApplication-Servers kopiert und so die Anwendung zur Verfügung gestellt und gestartet. - 212 - Java-Enterprise - Enterprise JavaBeans 6.3.5.6. Einstellungen Zur Konfiguration aller verwendeten Ant-Build-Scripte dient die Datei build.properties. # Projekt-Eigenschaften version = 3.3 manifest = ${basedir}/temp/MANIFEST.MF developer = Florian Lutz defaultmain = de.fhr.vs_iw.Client # Externe Pfade jdk.home = C:/Program Files/Java/jdk1.6.0 jboss4.home = D:/daemon/jboss-4.2.2.GA jboss5.home = D:/daemon/jboss-5.1.0.GA axis.home = D:/classpath/axis hibernate.home = D:/classpath/hibernate jacorb.home = D:/classpath/jacorb # Modul-Eigenschaften jboss4.deploy = ${jboss4.home}/server/default/deploy/ jboss5.deploy = ${jboss5.home}/server/default/deploy/ webservice.path = de/fhr/vs_iw/webservice # Sonstiges sun.javadoc = http://java.sun.com/javase/6/docs/api/ Resource 33: build.properties Darin werden vor allem Pfade zu Ressourcen eingestellt, die sich außerhalb des EclipseProjekt-Verzeichnisses befinden, wie z.B. der Deploy-Pfad für den JBoss-Application-Server, und auch noch weitere Angaben zum Projekt gemacht. - 213 - Java-Enterprise - Enterprise JavaBeans 6.3.6. Client-Anwendung Nun ist die Buchhaltung als Java-Enterprise-Anwendung auf dem JBoss-Application-Server verfügbar. Was fehlt ist eine Client-Anwendung, die auf den Server zugreift. 6.3.6.1. Einbinden der nötigen Bibliotheken Für die Clientseite einer Enterprise-Anwendung liefert der JBoss-Application-Server eine Reihe von Bibliotheken mit. Diese Bibliotheken befinden sich alle im Verzeichnis client/ einer JBoss-Installation, und sind im Prinzip die Gleichen, wie auch im lib- oder deployVerzeichnis, nur daß sie im client-Verzeichnis noch mal separat zusammengefaßt wurden. Alle diese Client-Bibliotheken müssen in den CLASSPATH der Anwendung aufgenommen werden. Dieser Satz von Bibliotheken stellt den bereits erwähnten Application-Client-Container (AAC) einer Java-Enterprise-Anwendung dar. 6.3.6.2. Aufruf der Anwendung auf dem Server Die Anwendung arbeitet auf der Clientseite eigentlich direkt mit einer Session-Bean-Instanz der Anwendung (IExtendedAccountingService) auf dem Server bzw. einem StellvertreterObjekt, welches das entsprechende entfernte Business-Interface der Bean implementiert und für die Verbindung zum Applikations-Server sorgt. Die Proxy-Klasse der Enterprise-Buchhaltung muß daher nur auf die Service-Session-Bean auf dem Server zugreifen. Die Benutzeroberfläche kann dann die Methoden der Session-Bean, die ja von den bekannten Buchhaltungsschnittsstellen stammen, direkt verwenden. package de.fhr.vs_iw.ejb; public final class EJBProxy implements IAccountingProxy { public EJBProxy() {} public void close() {} public IAccountingService connect(String[] args) throws IOException { try { InitialContext ctx = new InitialContext(); return (IAccountingService)ctx.lookup(JNDI_NAME); } catch (Exception e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } } private static final String JNDI_NAME = "EJBAccountingService/EJBAccountingService/remote"; Code 130: Class EJBProxy - 214 - Java-Enterprise - Enterprise JavaBeans Der Zugriff auf den Applikations-Server erfolgt über den InitialContext. Man erhält Zugriff auf die gewünschte Session-Bean oder ein anderes Objekt auf dem Server durch die lookup()-Methode unter Verwendung des zugehörigen JNDI-Namens. Den richtigen JNDINamen erhält man entweder durch die entsprechende Kenntnis des Aufbaus und der Konfiguration einer Anwendung oder aber einfach über die JNDI-Übersicht (siehe 6.2.5.2) des JBossApplication-Servers. Zur Auflösung des Namens wird der JNDI-Namensdienst verwendet. Die Datei jndi.properties (siehe 6.3.6.4) wird vom InitialContext automatisch gesucht und geladen, um die notwendigen Parameter zum Zugriff auf den JNDI-Namensdienst zu erhalten. 6.3.6.3. Senden einer Nachricht an die MessageBean Auch wenn die eingeführte Message-Driven-Bean (siehe 6.3.4.1) mit der Geschäftslogik der Buchhaltungsanwendung nichts zu tun hat, und eigentlich gar nicht benötigt wird, soll an einem einfachen Beispiel demonstriert werden, wie eine Nachricht gesendet wird, die einen Aufruf der Bean bewirkt. package de.fhr.vs_iw.ejb; public final class MessageClient { private MessageClient() {} private static final String QUEUE_NAME = "queue/vs_iw"; public static void main(String[] args) throws Exception { InitialContext ctx = new InitialContext(); Queue queue = (Queue)ctx.lookup(QUEUE_NAME); QueueConnectionFactory factory = (QueueConnectionFactory)ctx.lookup("QueueConnectionFactory"); QueueConnection con = (QueueConnection)factory.createConnection(); QueueSession session = con.createQueueSession(true, Session.SESSION_TRANSACTED); Message m = session.createMessage(); m.setStringProperty("bla", "blubb"); m.setStringProperty("foo", "bar"); QueueSender sender = session.createSender(queue); sender.send(queue, m); session.commit(); } } sender.close(); session.close(); con.close(); Code 131: Class MessageClient Auch hier wird über den InitialContext mit dem JNDI-Namen die Nachrichtenschlange aufgerufen, welche für die MessageBean definiert wurde. Anschließend muß eine Verbindung zu dieser Nachrichtenschlange hergestellt werden. Dazu wird, ebenfalls über JNDI, eine entsprechende Verbindungs-Fabrik (QueueConnectionFactory) vom Server abgerufen, mit der - 215 - Java-Enterprise - Enterprise JavaBeans dann eine Verbindung (QueueConnection) erstellt wird. Da auch das Messaging transaktionsbasiert abläuft, muß eine Sitzung (QueueSession) eröffnet werden. Über ein Message-Objekt wird nun die Nachricht erzeugt, indem verschiedene Parameter, Werte und JMS-Daten angegeben werden. Anschließend kann über einen QueueSender für die Sitzung eine Nachricht an die Nachrichtenschlange verschickt werden. Durch commit() erfolgt dann der transaktionsorientierte Versand. Schließlich werden der Sender, die Sitzung und die Verbindung zur Nachrichtenschlange noch geschlossen. 6.3.6.4. Allgemeine Client-Einstellungen Neben den Java-Klassen sind noch einige weitere Dateien bzw. Einstellungen für die Anwendungen notwendig. In der Datei jndi.properties werden Einstellungen zum Zugriff auf den JNDINamensdienst des JBoss-Application-Servers vordefiniert. Wenn die Datei im CLASSPATH vorhanden ist, wird sie vom InitialContext automatisch geladen und verarbeitet. java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=127.0.0.1:1099 Resource 34: jndi.properties In der Datei wird im wesentlichen die URL eingestellt, unter der der JNDI-Namensdienst erreichbar ist. Dies ist der Host des JBoss-Application-Servers (hier 127.0.0.1 - localhost) und der Port des Dienstes (standardmäßig 1099). Man kann aber auch auf eine solche Properties-Datei verzichten und die notwendigen Angaben direkt im Client-Programm vornehmen. Dazu werden die o.g. Einstellungen als String in einer Hashtable gespeichert. String host = "localhost"; String port = "1099"; Hashtable<String, String> env = new Hashtable<String, String>(); env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); env.put("java.naming.provider.url", host + ":" + port); InitialContext ctx = new InitialContext(env); Code 132: InitialContext mit Hashtable Mit dieser Hashtable wird dann der InitialContext erzeugt. Dadurch können die Einstellungen für Host und Port des JNDI-Dienstes auch zur Laufzeit vorgenommen werden. - 216 - Java-Enterprise - Enterprise JavaBeans Die Datei log4j.properties wird von den Klassen des Apache Logging Service Project Log4j verwendet. Dieses Logging-Framework wird von den JBoss-Klassen sowohl auf der Server- als auch auf der Client-Seite verwendet. log4j.rootLogger=debug, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}%5p%c{1}:%L-%m%n Resource 35: log4j.properties In der Datei werden Logger-Objekte definiert, und auch auf welche Weise die Ausgaben erfolgen sollen, in welchem Format und mit welchem Ziel. Mit den obigen Angaben erfolgen alle Ausgaben aller Log-Level kontinuierlich auf die Konsole in folgendem Format: 02:34:14,556 DEBUG SocketManager:499 - Begin WriteTask.run Für eine genaue Übersicht über die Konfgurationsmöglichkeiten des Logging-Frameworks sollte die zugehörige Dokumentation herangezogen werden [76]. - 217 - Java-Enterprise - Web-Services 6.4. Web-Services 6.4.1. Allgemeines Beim JBoss in der Version 4.2.2.GA sind die JBoss-Web-Services-1.0.5 bereits integriert [21] [22] [23]. Auf der Serverseite enthält das Archiv server/default/deploy/jbossws.sar die nötigen Klassen und Dateien, so daß ein Web-Service ohne weitere Vorbereitung auf dem Server deployed und gestartet werden kann. Für Web-Service-Clients müssen die Bibliothek client/jbossws-client.jar und einige weitere dafür grundlegende Bibliotheken eingebunden werden. Die Implementierung der JBoss-Web-Services entspricht dem WS-I Basic-Profile [24] und stellt drei Arten von Endpunkten zur Verfügung: RPC/literal Document/literal Message style In der ursprünglichen Web-Services-Spezifikation gibt es dann noch die Arten: RPC/encoded Document/encoded die von JBoss allerdings nicht unterstützt werden, da sie nicht WS-I konform sind. Anhand der WS-I Spezifikation wird ein Web-Service schließlich in einer WDSL-Datei definiert, die die verfügbaren Methoden des Dienstes und die Art ihres Aufrufs als XML-Nachrichten beschreibt. Da dieses Format frei spezifiziert und unabhängig ist, sind Web-Services, die auf derselben WSDL-Beschreibung beruhen, im Prinzip System- und Plattformunabhängig. (Java, C++, VB, C#, .NET). Deshalb gibt es für viele Programmiersprachen Tools, die aus einer WSDL-Datei die nötigen Artefakte erzeugen, um den beschriebenen Web-Service anzubieten bzw. darauf zuzugreifen. Zur Übertragung der Daten kommt bei den Web-Services normalerweise das auf XML basierende SOAP-Protokoll zum Einsatz [26]. SOAP definiert ein Header-Element, das zusätzliche Informationen beinhalten kann, und ein Body-Element, das dann die entsprechende WSDLkonforme Nachricht enthält. Ursprünglich wurde für die Web-Services im JBoss das AXIS-Framework [32] der ApacheFoundation [05] verwendet. Bei der Version 4 haben die Entwickler begonnen, basierend auf AXIS-1.1, die Web-Service-Funktionalitäten selbst zu implementieren. In der vorliegenden Version 4.2.2 ist diese Entwicklung größtenteils abgeschlossen und alle Teile, die von AXIS stammen, wurden ersetzt. Allerdings gibt es noch Defizite bei den JBoss-Web-Service-Tools (siehe 6.4.3.1) für die automatisierte Erstellung von Web-Service-Clients, dort scheint die Implementierung noch nicht vollständig zu sein. - 218 - Java-Enterprise - Web-Services 6.4.1.1. RPC-Style vs. Document-Style Diese beiden Arten eines Web-Services unterscheiden sich in der spezifizierten WSDL-Struktur [25]. Bei RPC ist der Name der aufgerufenen Methode als XML-Element im XML-Baum der SOAP-Nachricht enthalten, bei Document nicht. Der Hintergrund dafür ist die Idee, daß beim RPC-Style, wie bei RPC allgemein üblich, nur ein einzelner Methodenaufruf ausgeführt, und dessen Rückgabewert als Antwort zurückgeliefert werden soll. Der Document-Style zielt auf den vereinbarten Austausch von komplexen Daten-Dokumenten im XML-Format bei einer aufwendigen Geschäftslogik ab. Dabei ist der Name der aufgerufenen Methode weniger wichtig, da nur die Verarbeitung des Anfragedokuments an sich von Belang ist. Und auch bei der Antwort wird ein aufwendigeres Dokument erzeugt, daß eine festgelegte Struktur hat und sich nicht mehr auf den ursprünglich aufgerufenen Methodennamen beziehen muß. RPC/literal: Document/literal: <soap:envelope> <soap:body> <myMethod> <x>5</x> <y>5.0</y> </myMethod> </soap:body> </soap:envelope> <soap:envelope> <soap:body> <xElement>5</xElement> <yElement>5.0</yElement> </soap:body> </soap:envelope> Tabelle 18: Vergleich von SOAP-Nachrichten für RPC-Style und Document-Style Dies führt auch dazu, daß der Document-Style vollkommen WSDL- bzw. SOAP-Schema konform ist, und sehr leicht mit XML-Tools validiert werden kann, was bei RPC-Style schwierig ist, da die Namen der aufgerufenen Methoden vom Benutzer bestimmt werden. 6.4.1.2. literal vs. encoded Der Unterschied zwischen den Unterarten literal und encoded besteht darin, daß bei literal keine Informationen über den Datentyp von Parametern und Rückgabewerten im SOAP-Protokoll mitübertragen wird. literal: encoded: <soap:envelope> <soap:body> <myMethod> <x>5</x> <y>5.0</y> </myMethod> </soap:body> </soap:envelope> <soap:envelope> <soap:body> <myMethod> <x xsi:type="xsd:int">5</x> <y xsi:type="xsd:float">5.0</y> </myMethod> </soap:body> </soap:envelope> Tabelle 19: Vergleich von SOAP-Nachrichten für literal und encoded Die Datentyp-Information ist normal nur Overhead, der die Performance verringert [25]. - 219 - Java-Enterprise - Web-Services 6.4.2. Die Buchhaltung als Web-Service-RPC-Endpunkt Ein Web-Service wird in Java durch eine Endpunkt-Schnittstelle (SEI - Service Endpoint Interface) definiert. Da ein Web-Service bzw. SOAP keine verteilten Objekte unterstützt, muß hier von den bisher verwendeten bekannten Schnittstellen mit ihrer Objekthierarchie etwas Abstand genommen, und eine neue "flache" Schnittstelle definiert werden, die sich aber vollständig an den Methoden der bekannten Schnittstellen orientiert. 6.4.2.1. Service-Schnittstelle Die Methoden der Service-Schnittstelle können an sich unverändert bleiben, jedoch die beiden Factory-Methoden getAccountByNumber() und getAccountingEntryById(), die bisher die entsprechenden Objekte lieferten, entfallen. Dadurch ergibt sich folgende Schnittstelle: package de.fhr.vs_iw.webservice; @WebService(targetNamespace = "http://webservice.vs_iw.fhr.de") public interface IWebAccounting extends Remote { Double calculateBalance(Integer number) throws RemoteException, IOException, AccountingException; void createAccount(Integer number, int type, String description) throws RemoteException, IOException, AccountingException; Integer createAccountingEntry(Integer debit, Integer credit, Double amount, String text) throws RemoteException, IOException, AccountingException; Integer[] getAccountingEntries() throws RemoteException, IOException; Integer[] getAccounts() throws RemoteException, IOException; void removeAccount(Integer number) throws RemoteException, IOException, AccountingException; void removeAccountingEntry(Integer id) throws RemoteException, IOException, AccountingException; } ... Code 133: Interface IWebAccounting Weitere Abweichungen ergeben sich durch die drei Grundregeln für zulässige Web-ServiceEndpunkt-Schnittstellen bei Java: Die Schnittstelle muß von java.rmi.Remote abgeleitet sein Alle Methoden müssen mit der throws-Angabe java.rmi.RemoteException versehen werden, wobei Superklassen davon nicht zulässig sind Die Typen der Parameter und Rückgabewerte sind auf jene Typen beschränkt, welche in der JAX-RPC-1.1 Spezifikation angegeben sind, wobei keine der ParameterKlassen in irgendeiner Weise von java.rmi.Remote abgeleitet sein darf Schließlich wird die Schnittstelle noch mit der Annotation @WebService gekennzeichnet, wodurch alle Methoden später zu Methoden des Web-Services werden. - 220 - Java-Enterprise - Web-Services 6.4.2.2. Konto-Schnittstelle Wie oben erwähnt ist für Web-Services eine "flache" Schnittstelle erforderlich. Deshalb werden die Methoden der Konto-Schnittstelle in die IWebAccounting-Schnittstelle integriert. public interface IWebAccounting extends Remote { ... Integer[] getCreditEntries(Integer number) throws RemoteException, IOException; Integer[] getDebitEntries(Integer number) throws RemoteException, IOException; String getDescription(Integer number) throws RemoteException, IOException; int getType(Integer number) throws RemoteException, IOException; void setDescription(Integer number, String description) throws RemoteException, IOException; void setType(Integer number, int type) throws RemoteException, IOException; } Code 134: Konto-Methoden für Interface IWebAccounting Sie müssen lediglich in der Art abgeändert werden, daß jede einzelne Methode die Nummer des gewünschten Kontos als Parameter übernimmt und die Grundregeln für EndpunktSchnittstellen beachtet. 6.4.2.3. Buchungs-Schnittstelle Mit der Buchungs-Schnittstelle wird auf die gleiche Weise verfahren, wie mit der KontoSchnittstelle. Die Methoden werden um die Buchungs-ID als Parameter erweitert und in die IWebAccounting-Schnittstelle integriert. public interface IWebAccounting extends Remote { ... Double getAmount(Integer id) throws RemoteException, IOException; Integer getCreditAccount(Integer id) throws RemoteException, IOException; Integer getDebitAccount(Integer id) throws RemoteException, IOException; String getText(Integer id) throws RemoteException, IOException; void setAmount(Integer id, Double amount) throws RemoteException, IOException; void setCreditAccount(Integer id, Integer number) throws RemoteException, IOException, AccountingException; void setDebitAccount(Integer id, Integer number) throws RemoteException, IOException, AccountingException; void setText(Integer id, String text) throws RemoteException, IOException; } Code 135: Buchungs-Methoden für Interface IWebAccounting - 221 - Java-Enterprise - Web-Services 6.4.2.4. Implementierung Das so entstandene Interface muß nun in einer Klasse implementiert werden. Damit die Geschäftslogik nicht noch mal neu programmiert werden muß, soll der Web-Service auf die bereits vorhandenen Bean-Klassen der Buchhaltung zurückgreifen. Dazu wird im Konstruktor der Klasse das Service-Bean über seinen JNDI-Namen referenziert und alle Methodenaufrufe werden lediglich weitergegeben. package de.fhr.vs_iw.webservice; @WebService( name = "WebAccounting", serviceName = "WebAccounting", targetNamespace = " http://webservice.vs_iw.fhr.de", endpointInterface = "de.fhr.vs_iw.webservice.IWebAccounting") @SOAPBinding(style = SOAPBinding.Style.RPC) public class WebAccountingImpl implements IWebAccounting { private final IExtendedAccountingService ejbservice; private final static String JNDI_NAME = "EJBAccountingService/EJBAccountingService/remote"; public WebAccountingImpl() throws NamingException { ejbservice = (IExtendedAccountingService) new InitialContext().lookup(JNDI_NAME); } ... public Double calculateBalance(Integer number) throws AccountingException, IOException { return ejbservice.calculateBalance(number); } public Integer[] getAccounts() throws IOException { final Set<Integer> set = ejbservice.getAccounts(); return set.toArray(new Integer[0]); } ... public Double getAmount(Integer id) throws IOException { try { IAccountingEntry entry = ejbservice.getAccountingEntryById(id); return entry.getAmount(); } catch (AccountingException e) { throw new RuntimeException(e); } } ... } <Service-Methods> <Account-Methods> <AccountingEntry-Methods> Code 136: Class WebAccountingImpl - 222 - Java-Enterprise - Web-Services Der Aufruf der einzelnen Konten- und Buchungsobjekte erfolgt innerhalb der Methoden, und es wird nur der gewünschte Wert zurückgegeben. Weiterhin wird der Web-Service mit den entsprechenden Annotations beschrieben. Die Klasse selbst erhält die Annotation @WebService, mit zusätzlichen Angaben für die Bezeichnung des Services und des gewünschten XML-Namespace. Hier wird das implementierte Interface noch mal explizit als Endpunkt-Schnittstelle angegeben und der Typ des Web-Service wird mit der Annotation @SOAPBinding für die SOAP-Nachrichten auf RPC gesetzt. Eine Tücke beim JBoss 4.2.2 besteht allerdings darin, daß der angegebene targetNamespace nicht verwendet wird. Der XML-Namespace für den Web-Service wird aus dem Java-PaketNamen generiert und lautet dann in diesem Fall: http://webservice.vs_iw.fhr.de/jaws In neueren Versionen des JBoss (4.2) bzw. der integrierten JBoss-WS (3.0) wurde dieses Fehlverhalten behoben. 6.4.2.5. Deployment Der Web-Service wird wie eine Web-Anwendung in ein war Archiv verpackt und vom JBoss wie ein Servlet behandelt. Dazu ist folgende web.xml Beschreibungsdatei nötig: <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <servlet> <servlet-name>WebAccountingService</servlet-name> <servlet-class> de.fhr.vs_iw.webservice.WebAccountingService </servlet-class> </servlet> <servlet-mapping> <servlet-name>WebAccountingService</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> Resource 36: Web-Service-Descriptor web.xml Dadurch wird angegeben, daß der Web-Service, unter dem Namen bzw. URL-Pfad /WebAccountingService verfügbar sein soll. Nun kann über ein Ant-Script alles Nötige in ein entsprechendes war Archiv verpackt werden. <?xml version="1.0" encoding="UTF-8" ?> <project name="WebAccountingService" default="ws" basedir=".."> <property file="${basedir}/ant/build.properties" /> <target name="ws"> <delete file="${basedir}/temp/webservice.war" /> <war warfile="${basedir}/temp/webservice.war" webxml="${basedir}/resources/webservice/web.xml"> - 223 - Java-Enterprise - Web-Services <classes dir="${basedir}/bin"> <include name="**/webservice/IWebAccounting.class" /> <include name="**/webservice/WebAccountingImpl.class" /> </classes> <classes dir="${basedir}/modules"> <include name="**/webservice/IWebAccounting.java" /> <include name="**/webservice/WebAccountingImpl.java" /> </classes> </war> <copy file="${basedir}/temp/webservice.war" todir="${jboss4.deploy}" overwrite="true" /> </target> </project> Resource 37: Ant-Build-Datei webservice-deploy.xml Mit diesem Build-Script werden die Klassen für den Web-Service in die war Archiv-Datei webservice.war gepackt. Die Datei web.xml dient als Deployment-Descriptor und wird in das WEB-INF-Verzeichnis des Archivs gepackt. Das fertige Archiv wird in das DeployVerzeichnis des JBoss-Servers kopiert. Die weiteren benötigten Klassen der referenzierten EJBs und des Buchhaltungs-Modells müssen bereits auf dem JBoss-Server verfügbar sein. Deshalb bietet es sich an, den Web-Service mit den EJB-Klassen zusammen als EnterpriseArchiv zu verpacken, und alles zusammen zu deployen. Dazu wird das o.g. war Archiv als Web-Modul im Deployment-Descriptor application.xml des Enterprise-Archivs eingetragen, und bei dessen der Erzeugung mit aufgenommen. <?xml version="1.0" encoding="UTF-8"?> <application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"> <display-name>EJBAccountingService</display-name> <module> <ejb>beans.ejb3</ejb> </module> <module> <java>persistence.jar</java> </module> <module> <java>model.jar</java> </module> <module> <web> <web-uri>webservice.war</web-uri> <context-root>WebAccountingService</context-root> </web> </module> </application> Resource 38: application.xml (Web-Service) Außerdem sollte der Ant-Vorgang zur Erstellung des Web-Archivs in die Ant-Build-Datei für das Enterprise-Archiv (enterprise-deploy-jboss4.xml) integriert werden, damit alles in einem Vorgang verarbeitet und deployed werden kann. - 224 - Java-Enterprise - Web-Services 6.4.3. JBoss-Web-Service-Tools Aus der Java-Schnittstelle, die den Endpunkt des Web-Service spezifiziert, können alle weiteren Artefakte (Beschreibungsdateien und Client-Klassen) automatisiert erzeugt werden. Dazu sind die JBoss-Web-Service-Tools erforderlich, die sich im JBoss-Verzeichnis in client/jbossws-client.jar befinden. Das Archiv enthält eine Batch-Datei wstools.bat, mit der die Web-Service-Tools auf der Kommandozeile aufgerufen werden können. Dabei muß als man Argumente wstools.bat –dest <zielverzeichnis> -config <konfigurationsdatei> ein Zielverzeichnis und eine Konfigurationsdatei angeben. 6.4.3.1. Verwendung mit Ant Günstiger ist die Einbindung der Web-Service-Tools als Ant-Task, der die gleichen Argumente erfordert. Der classpath mit den benötigten Bibliotheken muß definiert werden. <?xml version="1.0" encoding="UTF-8" ?> <project name="AccountingServiceWeb" default="generate" basedir=".."> <property file="${basedir}/ant/build.properties" /> <property name="ws.package" value="de/fhr/vs_iw/webservice" /> <path id="classpath"> <pathelement path="${basedir}/bin" /> <pathelement path="${java.home}" /> <pathelement path="${jboss4.home}/lib/endorsed/xercesImpl.jar" /> <fileset dir="${jboss4.home}/client"> <include name="jbossws-client.jar" /> <include name="jbossall-client.jar" /> <include name="jboss-xml-binding.jar" /> <include name="activation.jar" /> <include name="mail.jar" /> <include name="log4j.jar" /> <include name="javassist.jar" /> <include name="jbossretro-rt.jar" /> <include name="jboss-backport-concurrent.jar" /> </fileset> </path> <taskdef name="wstools" classname="org.jboss.ws.tools.ant.wstools"> <classpath refid="classpath" /> </taskdef> <target name="generate"> <wstools dest="${basedir}/generated/${ws.package}" config="${basedir}/src/${ws.package}/wstools-java2wsdl.xml" /> <sleep seconds="3" /> <wstools dest="${basedir}/generated/" config="${basedir}/src/${ws.package}/wstools-wsdl2java.xml" /> </target> </project> Resource 39: Ant-Build-Datei webservice-generate.xml - 225 - Java-Enterprise - Web-Services 6.4.3.2. WSDL-Datei Die jeweils spezifizierte Konfigurationsdatei für die Web-Service-Tools gibt an, was genau, mit welchen Parametern erzeugt werden soll. Zur Erzeugung der WSDL-Datei muß die Konfiguration ein <java-wsdl>-Element enthalten. <?xml version="1.0" encoding="UTF-8" ?> <configuration xmlns="http://www.jboss.org/jbossws-tools" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jboss.org/jbossws-tools http://www.jboss.org/jbossws-tools/schema/jbossws-tool_1_0.xsd"> <java-wsdl> <service name="WebAccountig" style="rpc" endpoint="de.fhr.vs_iw.webservice.WebAccounting"/> <namespaces target-namespace="http://www.fh-regensburg.de/vs_iw/webservice" type-namespace="http://www.fh-regensburg.de/vs_iw/webservice/types"/> <mapping file="jaxrpc-mapping.xml"/> <webservices servlet-link="WebAccountingService"/> </java-wsdl> </configuration> Resource 40: WSTools-Konfiguration wstools-java2wsdl.xml Durch ein <service>-Element wird mit folgenden Attributen, der Web-Service angegeben: Die Bezeichnung des Web-Services style Die Art des Services (rpc, document, bare) endpoint Die Java-Endpunkt-Schnittstelle name Das <namepaces>-Element legt die zu verwendenden XML-Namensräume fest: target-namespace type-namspace XML-Namensraum für die Methoden von diesem Web-Service Separater Namensraum für die verwendeten Parametertypen Das Element <mapping-file> gibt an, ob und wo eine Datei mit den Datentyp-Zuordnungen für JAX-RPC erzeugt werden soll. Schließlich wird noch mit dem Element <webservices> und seinem Attribut servlet-link, der Name des Servlets angegeben, das den Web-Service später zur Verfügung stellen soll. In der erzeugten WSDL-Datei fehlt noch die zu diesem Web-Service passende SOAPAdresse, unter der Service später verfügbar ist. An der Stelle des Platzhalters REPLACE_WITH_ACTUAL_URL wird die URL des entsprechenden Web-Service-Sevlets http://localhost:8080/WebAccountingService unter JBoss, hier für den lokalen Rechner, eingetragen. Die vollständige WSDL-Datei für den Buchhaltungs-Service ist im Anhang E zu finden. - 226 - Java-Enterprise - Web-Services Der JBoss-Server bietet aber auch die Funktionalität, das WSDL-Schema eines Web-Service, der auf dem Server deployed wurde, direkt abzurufen. Dazu wird die Service-URL http://localhost:8080/WebAccountingService?wsdl mit dem Parameter ?wsdl aufgerufen. Das generierte Schema enthält bereits die richtige SOAP-Adresse und kann direkt, durch Angabe der URL, verwendet werden, was für einen dynamischen Client besonders interessant ist. 6.4.3.3. Client-Artefakte Die Client-Artefakte werden durch eine Konfiguration mit einem <wsdl-java>-Element aus einer WSDL-Datei erzeugt. Dies kann die soeben generierte Datei sein, aber auch jede andere, von einem anderen spezifizierten Web-Service. <?xml version="1.0" encoding="UTF-8" ?> <configuration xmlns="http://www.jboss.org/jbossws-tools" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jboss.org/jbossws-tools http://www.jboss.org/jbossws-tools/schema/jbossws-tool_1_0.xsd"> <global> <package-namespace package="de.fhr.vs_iw.webservice.client" namespace="http://www.fh-regensburg.de/vs_iw/webservice" /> </global> <wsdl-java file="de/fhr/vs_iw/webservice/wsdl/WebAccountigService.wsdl"> </wsdl-java> </configuration> Resource 41: WSTools-Konfiguration wstools-wsdl2java.xml Hier wird in einem globalen Abschnitt eine zusätzliche Einstellung definiert. Durch ein <package-namespace>-Element wird angegeben, daß ein bestimmter XML-Namensraum einer bestimmten Java-Package zugeordnet werden soll, der die erzeugten Dateien dann angehören. 6.4.3.4. Erfahrungen mit den JBoss-Web-Service-Tools Leider hat sich in der Praxis gezeigt, daß die JBoss-Web-Service-Tools bzw. die Implementierung der generierten Client-Klassen nach dem JAX-RPC-Standard bei den JBoss-WebServices noch nicht vollständig genug für eine einfache und unkomplizierte Verwendung ist. Die Arbeit damit gestaltet sich bei der vorliegenden Version 1.0.3 noch schwierig bis unmöglich, weshalb für die Java-Client-Seite des Buchhaltungs-Web-Service ein anderes Framework zu Einsatz kommt. - 227 - Java-Enterprise - Web-Services 6.4.4. Web-Services mit AXIS Das AXIS-Framework [32] ist eine Implementierung des SOAP-Protokolls [26] von der Apache Foundation [05], war die erste ihrer Art, und hat sich in der Vergangenheit vielfach bewährt. Deshalb wird die Implementierung der Client-Seite des Buchhaltungs-Web-Service damit ausgeführt. 6.4.4.1. Installation Um das AXIS-Framework auf der Clientseite verwenden zu können, ist lediglich der Download des Pakets (aktuelle Version 1.4) von der Homepage der Apache-Foundation nötig. http://ws.apache.org/axis/java/releases.html Die darin enthaltenen Bibliotheken aus dem lib-Unterverzeichnis müssen nach dem entpacken im CLASSPATH aufgenommen werden. 6.4.4.2. Ant-Vorgang In den AXIS-Bibliotheken ist ein Ant-Vorgang zum Erzeugen der Client-Artefakte aus der Web-Service-Beschreibungsdatei vorhanden. Die Beschreibung wird allerdings nicht aus der vorhandenen WSDL-Datei, sondern direkt von einer URL des laufenden JBoss-Servers bezogen. Dies hat den Vorteil, daß die SOAP-Adresse bereits richtig eingetragen ist, und es wird auch gleich der richtige XML-Namespace des JBoss verwendet. Dieser Namenraum (http://webservice.vs_iw.fhr.de/jaws) wird einem Java-Paket (de.fhr.vs_iw.webservice.axis) zugeordnet, damit die später erzeugten Klassen richtig eingeordnet werden. <?xml version="1.0" standalone="yes"?> <project basedir=".." default="generate-client"> <property file="${basedir}/ant/build.properties" /> <target name="generate-client"> <path id="classpath">...</path> <taskdef resource="axis-tasks.properties" classpathref="classpath" /> <axis-wsdl2java output="${basedir}/generated" testcase="true" verbose="true" url="http://localhost:8080/WebAccountingService?wsdl"> <mapping namespace="http://www.fh-regensburg.de/vs_iw/webservice" package="de.fhr.vs_iw.webservice" /> <mapping namespace="http://webservice.vs_iw.fhr.de/jaws" package="de.fhr.vs_iw.webservice.axis" /> </axis-wsdl2java> </target> </project> Resource 42: Ant-Build-Datei axis-generate-client.xml - 228 - Java-Enterprise - Web-Services 6.4.4.3. Client-Artefakte Aus den Angaben in der WSDL-Beschreibung wurden die nötigen Schnittstellen und Klassen generiert, die, unter Verwendung der von AXIS bereistgestellten Funktionalitäten, den Zugriff auf einen konkreten Web-Service von Java aus ermöglichen. Klasse AccountingException IOException WebAccounting_PortType WebAccounting_Service WebAccounting_ServiceLocator WebAccountingBindingStub Beschreibung Buchhaltungsausnahme Kommunikationsausnahme Schnittstelle mit den Methoden der Geschäftslogik Schnittstellte mit Methoden zum Zugriff auf den Web-Service Hilfsklasse um ein Port-Objekt für den WebService zu erhalten Lokale Stellvertreterklasse, die alle Methodenaufrufe an den Web-Service weiterleitet Tabelle 20: Von AXIS erzeugte Client-Klassen Die hier automatisch erzeugte Schnittstelle WebAoccounting_PortType entspricht im Prinzip der ursprünglichen, von Hand definierten, Web-Service-Schnittstelle, was ja auch dem Sinn der Verwendung von Web-Services entspricht. Nur wird diese Schnittstelle durch die Namensraumzuordnung einem anderen Java-Paket zugeordnet und es darf wegen der Verbindung mit AXIS auch keine Verwechslung erfolgen. 6.4.5. Client-Implementierung Die erzeugten Klassen und Schnittstellen können nun verwendet werden, um eine ClientAnwendung im Sinne der Logik der Buchhaltung zu implementieren. 6.4.5.1. Proxy-Klasse Die Proxy-Klasse verwendet eine Instanz der erzeugten Klasse WebServiceLocator um eine Verbindung mit dem Anbieter des Web-Service herzustellen. Die SOAP-Adresse des WebService braucht nicht mehr explizit angegeben zu werden, da sie bei der Generierung durch die WSDL-Datei vorgegeben wurde. Durch den Aufruf der Methode getWebAccountingPort() erhält man einen Port für den Web-Service, der über die Web-Service-Schnittstelle WebAccounting angesprochen wird, und durch eine Instanz der Stellvertreterklasse WebAccountingBindingStub realisiert wird. Durch diesen Port können nun die Methoden des entfernten Web-Service aus Java heraus aufgerufen werden. Allerdings entspricht ja die Web-Service-Schnittstelle nicht mehr der ursprünglichen Buchhaltungsschnittstelle, weshalb eine Adapter-Klasse für den Port notwendig ist, die wieder auf der ursprünglichen Buchhaltungsschnittstelle basiert und als Service-Klasse instanziert wird. - 229 - Java-Enterprise - Web-Services package de.fhr.vs_iw.webservice; public final class WebServiceProxy implements IAccountingProxy { public WebServiceProxy() {} public void close() {} } public IAccountingService connect(String[] args) throws IOException { try { WebAccountingServiceLocator locator = new WebAccountingServiceLocator(); WebAccounting port = locator.getWebAccountingPort(); return new AccountingService(port); } catch (Exception e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } } Code 137: Class WebServiceProxy Mit dem Port-Objekt wird eine Instanz der Service-Klasse gebildet, die dann von der connect()-Methode der Proxy-Klasse zurückgegeben wird. 6.4.5.2. Service-Klasse Die Methoden der Service-Klasse rufen die entsprechenden Methoden des Web-Service bzw. des Stellvertreter-Objekts für den Web-Service (Port) auf. Dabei müssen vor allem die Ausnahmen, für die aus der WSDL-Beschreibung neue Klassen generiert wurden, auf die originalen Ausnahmen aus dem Buchhaltungsmodell zurückgeführt werden. package de.fhr.vs_iw.webservice; public final class AccountingService implements IAccountingService { private final Map<Integer, AccountingEntry> accountingEntries = new HashMap<Integer, AccountingEntry>(); private Map<Integer, Account> accounts = new HashMap<Integer, Account>(); private final WebAccounting port; public AccountingService(WebAccounting port) { this.port = port; } public Double calculateBalance(Integer number) throws IOException, AccountingException { try { return port.calculateBalance(number); } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } catch (de.fhr.vs_iw.webservice.axis.AccountingException e) { throw new AccountingException(e.getMessage1()); } } - 230 - Java-Enterprise - Web-Services ... public IAccount getAccountByNumber(Integer number) throws IOException, AccountingException { try { port.checkAccountNumber(number); } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } catch (de.fhr.vs_iw.webservice.axis.AccountingException e) { throw new AccountingException(e.getMessage1()); } Account account = accounts.get(number); if (account == null) { account = new Account(number, port); accounts.put(number, account); } return account; } public Set<Integer> getAccounts() throws IOException { try { Integer[] ar = port.getAccounts(); HashSet<Integer> set = new HashSet<Integer>(); if (ar != null) { for (Integer i : ar) { set.add(i); } } return set; } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } } ... } Code 138: Class AccountingService (Web-Services) Die Auflistung der Kontonummern bzw. Buchungs-IDs muß manuell von einem int-Array wieder in einen Set umgewandelt werden. Damit von der flachen Web-Service-Schnittstelle ausgehend wieder eine objektorientierte Verwendung des Systems möglich ist, werden die entsprechenden Methoden der WebService-Schnittstelle durch Konten- und Buchungsobjekte adaptiert. Die Service-Methoden getAccountByNumber() bzw. getAccountingEntryById() erzeugen mit dem verwendeten Web-Service-Port jeweils ein Adapter-Objekt für die angegebene Kontonummer bzw. Buchungs-ID. Diese Adapter-Objekte werden von der Service-Klasse in HashMaps verwaltet, damit nicht unnötig viele Instanzen erzeugt werden. 6.4.5.3. Konto-Klasse Bei der Konto-Klasse werden auch nur die passenden Methoden des Web-Service-Port aufgerufen. Jedes Objekt der Klasse kennt dabei seine Kontonummer, die bei jedem Web-ServiceAufruf mit übergeben wird. - 231 - Java-Enterprise - Web-Services package de.fhr.vs_iw.webservice; public final class Account implements IAccount { private final Integer number; private final WebAccounting port; protected Account(Integer number, WebAccounting port) { this.number = number; this.port = port; } public Set<Integer> getCreditEntries() throws IOException { try { Integer[] ar = port.getCreditEntries(number); HashSet<Integer> set = new HashSet<Integer>(); for (Integer i : ar) { set.add(i); } return set; } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } } public Integer getNumber() { return number; } public AccountType getType() throws IOException { try { return AccountType.getAccountTypeById(port.getType(number)); } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } } public void setType(AccountType type) throws IOException { try { port.setType(number, type.getId()); } catch (de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } } } ... Code 139: Class Account (Web-Service) Auch bei der Konto-Klasse werden die Auflistungen der Soll- bzw. Haben-Buchungen manuell wieder zu Sets umgeformt. Zusätzlich muß der Kontotyp speziell behandelt werden, da Elemente einer Java-Enumeration natürlich nicht direkt übertragen werden können. Dafür werden die Methoden getId() und getAccountTypeId() der Klasse AccountType verwendet, wodurch der Kontotyp als numerischer Wert übertragen wird. - 232 - Java-Enterprise - Web-Services 6.4.5.4. Buchungs-Klasse Für die Buchungs-Klasse gilt natürlich dasselbe wie für die Konto-Klasse. Alle Methodenaufrufe werden unter Verwendung der Buchungs-ID lediglich an den Web-Service-Port weitergegeben und die Ausnahmen entsprechend umgewandelt. package de.fhr.vs_iw.webservice; public final class AccountingEntry implements IAccountingEntry { private final Integer id; private final WebAccounting port; protected AccountingEntry(final Integer id, final WebAccounting port) { this.id = id; this.port = port; } public Double getAmount() throws IOException { try { return port.getAmount(id); } catch (final de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } } public Integer getId() { return id; } public void setCreditAccount(final Integer creditAccount) throws IOException, AccountingException { try { port.setCreditAccount(id, creditAccount); } catch (final de.fhr.vs_iw.webservice.axis.IOException e) { throw new IOException(e.getMessage1()); } catch (final de.fhr.vs_iw.webservice.axis.AccountingException e) { throw new AccountingException(e.getMessage1()); } } ... } Code 140: Class AccountingEntry (Web-Service) 6.4.6. Fazit Die Verwendung von Web-Services zur Realisierung eines Verteilten Systems kann den Entwicklungsaufwand durch die vielfachen Möglichkeiten der automatisierten Codegenerierung einer Anwendung in den unterschiedlichsten Programmiersprachen stark vereinfachen. Der Nachteil besteht allerdings darin, daß 90% der Datenübertragung Overhead durch die Verwendung von XML sind, und deshalb die Performance von Web-Services eher kritisch zu betrachten ist (siehe Anhang D). - 233 - Buchhaltung Mobil - Übersicht 7. Buchhaltung Mobil 7.1. Übersicht Da dieses Tutorial unter der Thematik "Verteilte Systeme" steht, sollte auch die Betrachtung mobiler Endgeräte nicht fehlen. 7.1.1. Pocket-PC Konkret soll die mögliche Anbindung eines Pocket-PC an die bestehenden Buchhaltungssysteme veranschaulicht werden. Als Testgerät kommt ein HP iPAQ hx2750 mit Intel ARMProzessor zum Einsatz. Das Gerät hat die folgenden Hardware-Spezifikationen: Taktung: 624 MHz Display: 320x240 Pixel, 256000 Farben ROM-Speicher: 128MB RAM-Speicher: 128MB Wireless LAN und Bluetooth Kartenslots für Compact-Flash II und SD-Karten 7.1.2. Windows-Mobile Das Betriebssystem des Pocket-PC ist Windows Mobile 2003 (Version 4.21). Es hält sich soweit wie möglich und praktikabel an die von Desktop Rechnern her bekannte Windows API. Normalerweise werden die Programme dafür in C bzw. C++ erstellt, speziell für den Pocket-PC compiliert, und können dort dann nativ ablaufen. Microsoft stellt zu diesem Zweck die "eMbedded Visual Tools" [60] und den "Pocket-PC 2003 SDK" [61] zur Verfügung. Außerdem gibt es eine "eMbedded Visual Basic Runtime" und das "Microsoft .NET Compact Framework", unter anderem zur Integration in "Visual Studio .NET", um Anwendungen in diesen beiden Programmiersprachen für den Pocket-PC entwickeln zu können. Auch von anderen Herstellern gibt es Entwicklungsumgebungen und Tools für Pocket-PC Anwendungen. [59] - 234 - Buchhaltung Mobil - Übersicht 7.1.3. Java Micro Edition Mit der Java Micro Edition (J2ME) [63] wird eine Java-Umgebung für mobile Geräte im allgemeinen, und damit auch für den Pocket-PC im speziellen, spezifiziert. Diese API ist in Konfigurationen und Profile unterteilt, wobei im wesentlichen zwei Standardkonfigurationen unterschieden werden [62]: CDC - Connected Device Configuration - Die allgemeine Konfiguration bzw. Spezifikation für mobile Geräte mit 32-bit Prozessor und ca. 2.5 MB Speicher für die Java Umgebung (CVM – Connected Device Configuration Virtual Machine). Die Umgebung zielt auf Kompatibilität mit der API und den Werkzeugen der Java Standard Edition (J2SE) ab. CLDC - Connected Limited Device Configuration - Eine sehr schlanke Java Konfiguration für Geräte mit nur 128kB bis 512 kB Speicher, die nicht J2SE-konform sein muß. CDC-Anwendungen sind durch die angestrebte Kompatibilität mit der J2SE auch auf jedem Desktop Rechner ablauffähig. Umgekehrt wird der von CDC unterstützte Umfang der J2SE-API durch das verwendete Profil definiert: Foundation Profile - Keine Unterstützung für Benutzeroberflächen Personal Basic Profile - Unterstützung für einfache AWT-Komponenten Personal Profile - Vollständige Unterstützung von AWT und Applets Die Funktionalitäten dieser drei Profile bauen aufeinander auf und können durch spezifische Zusatzprofile erweitert werden: RMI Optional Package - Remote Method Invocation aus dem java.rmi Paket JDBC Optional Package - Datenbankzugriff mit der JDBC 3.0 API Mobile Media Optional Package - Zugriff auf Audio und Video Dienste Schließlich gibt es für mobile Geräte zwei brauchbare kommerzielle Implementierungen einer Virtuellen Java Maschine nach diesen Spezifikationen [62]: IBM J9 JVM NSICOM CrEme JVM 7.1.4. Mysaifu JVM Neben den kommerziellen JVMs gibt es aber eine freie Implementierung unter der GPL, die Mysaifu JVM von "freebeans", die unter http://www2s.biglobe.ne.jp/~dat/java/project/jvm/index_en.html heruntergeladen werden kann und für die Buchhaltungsanwendung verwendet wird. Sie hält sich nicht an die J2ME-Spezifikation, da sie ca. 10MB Speicher zur Ausführung benötigt, hat aber zum Ziel die API der J2SE (Java 2 Standard Edition) möglichst vollständig auf dem Pocket-PC zu unterstützen [64]. - 235 - Buchhaltung Mobil - Installation 7.2. Installation Der erste Schritt für die Buchhaltung auf dem Pocket-PC ist die Installation der JVM. 7.2.1. Laufzeitumgebung Nach dem Herunterladen und entpacken der Mysaifu JVM (jvm.Release.zip) wird die in dem Archiv enthaltene cab-Datei (jvm.Release.cab) auf den Pocket-PC kopiert (ActiveSync, WLAN, etc.). Durch das Ausführen der cab-Datei (antippen) wird die JVM im Verzeichnis \Programme\Mysaifu JVM installiert. Deinstalliert werden kann die JVM unter Einstellungen -> Programme entfernen. 7.2.2. Aufruf der JVM Aufgerufen wird die JVM selbst direkt im Installationsverzeichnis \Programme\Mysaifu JVM\bin\jre durch Ausführen (antippen) von jvm.exe. In der Hauptansicht wird nun eine Java-Klasse oder ein jar-Archiv (vs_iw_fibu_pocketpc_3.3.jar) ausgewählt, das gestartet werden soll. Außerdem kann man angeben ob die Konsole angezeigt werden soll, und es können einige Optionen für das Programm eingestellt werden. Abbildung 94: Haupansicht der Mysaifu JVM 7.2.3. Entwicklungsumgebung Da sich die Mysaifu JVM an die J2SE-Spezifikationen hält, können zur Entwicklung die selben Werkzeuge (Compiler, usw.) wie auch auf Desktop-PCs verwendet werden, ohne daß spezielle Maßnahmen zur Vorbereitung der Programme für den Pocket-PC notwendig sind. Von dem her wurde die Buchhaltungsanwendung für den Pocket-PC "ganz normal" in Eclipse entwickelt. Wichtig ist nur, daß man keine API's und Klassen der J2SE verwendet, die auf dem Pocket-PC nicht verfügbar sind. - 236 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3. Buchhaltungsoberfläche Für die Buchhaltung auf dem Pocket-PC wurde eine entsprechende Anwendung mit AWT-Oberfläche erstellt, die unter der Mysaifu-JVM lauffähig ist. 7.3.1. Verfügbare Proxy-Module Die Oberfläche dient wiederum nur als Client, der auf vorhandene Buchhaltungssysteme zugreifen soll. Allerdings wird ein fester Satz Proxy-Klassen vorgegeben, die alle direkt im jar-Archiv der Anwendung enthalten sind, und es wird nicht nach beliebigen, auf dem PocketPC vorhandenen, Proxy-Klassen gesucht. Die verfügbaren Proxy-Klassen umfassen: dummy exception notimpl local socket Dummy-Klasse, die alle Methoden implementiert Dummy-Klasse, bei der jede Methode eine Ausnahme wirft Dummy-Klasse, bei der jede Methode NotImplemented wirft Lokale Datenhaltung (siehe 4.2) (Standard-Einstellung) Socket-Verbindung mit Host und Port (siehe 5.2) Die gewünschte Proxy-Klasse wird mit ihren notwendigen Parametern auf der Kommandozeile angegeben bzw. vor dem Aufruf der JVM im Feld für die Kommandozeilenargumente eingegeben. Außerdem kann ein CLASSPATH für weitere nötige Bibliotheken definiert, und das aktuelle Verzeichnis eingestellt werden. Abbildung 95: Allgemeine Optionen der Mysaifu JVM - 237 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3.2. Aufbau der Oberfläche Nach dem Starten der Anwendung wird über die angegebene Proxy-Klasse die Service-Klasse geladen und im Statusbereich die erfolgreiche Ausführung oder eine aufgetretene Ausnahme angezeigt. Mit den zwei Schaltflächen "Konto" bzw. "Buchung" am oberen Fensterrand kann die entsprechende Ansicht ausgewählt werden, die dann im freien mittleren Bereich eingeblendet wird. Windows Mobile typisch wird das Menü der Anwendung am unteren Bildschirmrand angezeigt. Abbildung 96: Erste Ansicht der Pocket-Buchhaltung 7.3.2.1. Konto und Buchungsliste Ja nach Auswahl wird eine Liste der vorhandenen Konten bzw. Buchungen angezeigt. Durch das Selektieren eines Listeneintrags werden die genaueren Daten im Statusbereich eingeblendet. Mit den Schaltflächen neben der Liste können die entsprechenden Aktionen aufgerufen werden. Abbildung 97: Ansicht der Konten und Buchungen der Poket-Buchhaltung - 238 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3.2.2. Konto anlegen oder ändern Zum Anlegen eines neuen Kontos werden die entsprechenden Eingabefelder für die Daten angezeigt. Beim Ändern eines Kontos wird dieselbe Ansicht verwendet, nur daß die Daten des zu ändernden Kontos vorher eingetragen werden. Abbildung 98: Pocket-Buchhaltung: Konto anlegen oder ändern 7.3.2.3. Buchung anlegen oder ändern Bei den Buchungen verhält es sich analog zu den Konten. In der Ansicht werden die Eingabefelder entweder leer oder mit den Daten der zu ändernden Buchung angezeigt. Abbildung 99: Pocket-Buchhaltung: Buchung anlegen oder ändern - 239 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3.2.4. Konto oder Buchung löschen Auch für das Löschen von Konten und Buchungen wird eine gemeinsame Ansicht verwendet, in der nur die jeweiligen Daten eingetragen werden. Abbildung 100: Pocket-Buchhaltung: Konto oder Buchung löschen 7.3.2.5. Konto-Details Für Konten gibt es noch eine weitere Ansicht, für die separate Auflistung der Soll- und Haben-Buchungen eines Kontos (getCreditAccounts(), getDebitAccounts()). Außerdem sei hier das Aussehen der Anwendung auf einem Desktop-PC dargestellt. Abbildung 101: Pocket-Buchhaltung: Konto-Details - 240 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3.3. Ausnahmen Wenn bei einem Aufruf einer Buchhaltungsmethode eine Ausnahme, ein Problem oder ein anderes Ereignis auftritt, wird dieses im Statusbereich dargestellt und passend farblich hinterlegt (siehe 3.4.2). Abbildung 102: Pocket-Buchhaltung: Einblendungen im Statusbereich 7.3.4. Menü Das Menü der Anwendung wird am unteren Bildschirmrand angezeigt. Das Symbol ganz links gehört jedoch nicht zur Anwendung, sondern dient zum Ausklappen der "Tastatur" von Windows Mobile. 7.3.4.1. Schnittstellen-Menü Über das Schnittstellen-Menü können einige Verwaltungsfunktionen aufgerufen werden. Abbildung 103: Pocket-Buchhaltung: Schnittstellen-Menü Neu verbinden - Schließt die gerade geladene Service-Klasse (close()) und lädt sie neu (conect()). Das ist nützlich falls sich der Pocket-PC in den StandBy-Modus schaltet und dadurch die Verbindung zu Buchhaltungssystem abreißt. Daten erzeugen - Benutzt die Utilities-Klasse, um Testdaten zu erzeugen (siehe 3.7.1.1) Beenden - Schließt die Anwendung - 241 - Buchhaltung Mobil - Buchhaltungsoberfläche 7.3.4.2. Hilfe-Menü Im Hilfe-Menü kann eine Info-Ansicht aufgerufen werden, die einige Fakten zu der Anwendung wiedergibt, und eine Auflistung der verfügbaren ProxyKlassen. Abbildung 104: Pocket-Buchhaltung: Hilfe-Menü Abbildung 105: Pocket-Buchhaltung: Info-Ansicht und Auflistung der Proxy-Klassen - 242 - Buchhaltung Mobil - Anbindung an die vorhandenen Systeme 7.4. Anbindung an die vorhandenen Systeme Wie bereits mehrfach erwähnt soll die Buchhaltungsoberfläche auf dem Pocket-PC über die entsprechenden Proxy-Klassen an die vorhandenen Module des Buchhaltungssystems angebunden werden. 7.4.1. Lokale und Dummy-Klassen Die Dummy-Klassen und die lokale Datenhaltung können direkt wie auf einem Desktop-PC benutzt werden, dienen jedoch mehr zum Testen der Oberfläche. 7.4.2. An das Socket-Modul Auch das bereits vorhandene Socket-Modul kann direkt verwendet werden, um die Verbindung zu einem Buchhaltungs-Server herzustellen, da die Funktionalitäten und Klassen aus den dafür notwendigen Paketen java.io und java.net vollständig vorhanden sind. 7.4.3. Über RMI Die Verwendung von RMI ist im ersten Ansatz nicht möglich, da die Mysaifu JVM diese API nicht unterstützt und die notwendigen Klassen nicht implementiert wurden. 7.4.4. Andere Technologien Auch die Verwendung von anderen Technologien ist auf dem Pocket-PC mit der MysaifuJVM nicht möglich, da deren Anforderungen derzeit von der Mysaifu JVM nicht erfüllt werden. 7.5. Erfahrungen und Probleme Die Programmierung der Oberfläche konnte wirklich direkt so erfolgen, wie für einen Desktop-Rechner auch. Nur bei der Verwendung des List-Steuerelements gab es einen Unterschied während der Programmausführung. Auf dem Pocket-PC ist das programmgesteuerte Auswählen eines Elements der Liste nicht möglich. Ansonsten ist die Anwendung auf dem Pocket-PC genauso brauchbar wie auf dem DesktopPC. Die Mysaifu JVM könnte wohl auch im produktiven Einsatz für mobile Anwendungen verwendet werden. - 243 - Schlußwort Schlußwort Dieses Tutorial führt schrittweise von eigenen Implementierungen benötigter Funktionalitäten hin zur Verwendung von umfangreichen Frameworks. Dabei dient das Thema Buchhaltung als Ausgangspunkt, der den Einstieg erleichtern soll, und als gemeinsame Basis. Dabei hat sich gezeigt, daß eben der Einstieg durch die Vorgaben aus dem Buchhaltungsmodell mit eigenen Implementierungen sehr gut möglich war, diese Vorgaben allerdings bei der Verwendung zusammen mit den verschiedenen Technologien zum Teil auch hinderlich waren und manchmal unnötige Umstände verursacht haben. Bei der Anwendungsentwicklung muß also im Vorfeld gezielt darauf geachtet werden, welche Technologien zum Einsatz kommen sollen, und die Anwendung sollte dann entsprechend auf deren Verwendung hin entwickelt werden, um deren Vorteile vollständig nutzen zu können und um unnötige Probleme zu vermeiden. Vor allem der neue Enterprise JavaBeans 3.0 Standard kann die Anwendungsentwicklung aus der Sicht des Programmierers erheblich vereinfachen, wobei aber die Kompatibilität mit verschiedenen (Versionen von) Applikations-Servern zum Teil nicht einfach herzustellen ist. - 244 - Verzeichnisse Verzeichnisse Abkürzungsverzeichnis AAC API AJP AS AWT BMP BPEL BPM CDC CIFS CLDC CMP CORBA CVM DAO DBMS DD DII DOM DSI DTO EJB EJB3 ERM HQL HSQLDB HTTP HTTPS GIOP IDE IDL IDLJ IIOP IMAP IoC IOR J2ME J2SE J2EE JAAS JAF JAR JAX-RPC JAXB JAXR JBPM JCA Application Client Container Application Programming Interface Apache JServ Protocol Application Server [45] Abstract Windowing Toolkit Bean-Managed-Persistence Business Process Execution Language [43] Business Process Management [44] Connected Device Configuration Common Internet File System Connected Limited Device Configuration Container-Managed-Persistence Common Object Request Broker Architecture [79] Connected Device Configuration Virtual Machine Data Access Object (Entity-Bean) Database Management System Deployment-Descriptor Dynamic Invokation Interface Document Object Model Dynamic Skeleton Interface Data Transfer Object Enterprise JavaBeans [45] Endung für Enterprise Java Archive, Version 3.0 Entity-Relationship-Model Hibernate Query Language Hypersonic-SQL-Database-Engine [14] Hypertext Transfer Protocol Hypertext Transfer Protocol Secure General Inter-ORB Protocol [79] Integrated Development Environment [30] Interface Definition Language IDL-to-Java-Compiler Internet Inter-ORB Protocol [79] Internet Message Access Protocol Inversion of Control Interoperable Object Reference Java 2 Platform, Micro Edition [63] Java 2 Platform, Standard Edition [01] Java 2 Platform, Enterprise Edition [01] Java Authentication and Authorization Service [07] Java Beans Activation Framework [49] Java Archive Java-API for XML-Based RPC [55] Java Architecture for XML Binding [69] Java-API for XML-Registries [54] JBoss Business Process Management J2EE Connector Architecture [56] - 245 - Verzeichnisse JCP JDBC JDK JEMS JMS JMX JNDI JPA JRMP JSF JSP JSR JTA JTS JVM JWSDP MDA MIME MOM MVC NFS NNTP OID OMG OTS PAR PCMCIA PDL POA POJI POJO POP3 RMI RPC SAX SDK SEI SMB SMTP SOA SOAP StAX UIL2 URL VM WAR W3C WS WSE WSDK WSDL Java Community Process Java Database Connectivity Java Development Kit [01] JBoss Enterprise Middleware System Java Messaging Service [15] Java Management Extensions [16] Java Naming and Directory Interface [17] Java Persistence API Java Remote Method Protocol Java Server Faces Java Server Page Java Specification Request Java Transaction API [51] Java Transaction Service [52] Java Virtual Machine [01] Java Web-Services Developer Pack Model Driven Architecture Multipurpose Internet Mail Extensions Message Oriented Middleware [15] Model-View-Controller Network File System Network News Transfer Protocol Object Identifier Object Management Group [79] Object Transaction Service Persistence Archive People Can't Memorize Computer Industry Acronyms Process Definiton Language Portable Object Adapter Plain Old Java Interface Plain Old Java Object Post Office Protocol, Version 3 Remote Method Invocation [34] Remote Procedure Call Simple API for XML Software Development Kit Service Endpoint Interface Server Message Block Simple Mail Transfer Protocol Service-oriented Architecture Simple Object Access Protocol [26] Streaming API for XML Unified Invocation Layer Version 2 Uniform Resource Locator Virtual Machine Web (Application) Archive World Wide Web Consortium Web-Services [24] Web-Services Enhancements Web-Services Development Kit Web-Service Description Language - 246 - Verzeichnisse WSTools XML XML-RPC XOP JBoss-Web-Service-Tools [23] Extensible Markup Language Extensible Markup Language - Remote Procedure Call XML-binary Optimized Packaging - 247 - Verzeichnisse Abbildungsverzeichnis Abbildung 1: Objektmodell der Buchhaltung ............................................................................ 4 Abbildung 2: Schnittstellenmodell der Buchhaltung ................................................................. 5 Abbildung 3: Vekettung der Module des Buchhaltungssystems ............................................. 15 Abbildung 4: Mögliche Szenarien der Verkettung von Implementierungen ........................... 16 Abbildung 5: Hauptansicht der Buchhaltungsoberfläche......................................................... 17 Abbildung 6: Schnittstellen-Menü ........................................................................................... 18 Abbildung 7: Service-Klasse laden .......................................................................................... 19 Abbildung 8: Hinweisanzeige bei der Auswahl der Proxy-Klasse .......................................... 19 Abbildung 9: Kontenliste ......................................................................................................... 21 Abbildung 10: Neues Konto anlegen ....................................................................................... 21 Abbildung 11: Konto bearbeiten .............................................................................................. 22 Abbildung 12: Konto löschen .................................................................................................. 22 Abbildung 13: Buchungsliste ................................................................................................... 23 Abbildung 14: Neue Buchung erfassen.................................................................................... 23 Abbildung 15: Buchung bearbeiten.......................................................................................... 24 Abbildung 16: Buchung löschen .............................................................................................. 24 Abbildung 17: Liste der Haben- und Soll-Buchungen eines Kontos ....................................... 25 Abbildung 18: Der Saldo eines Kontos.................................................................................... 25 Abbildung 19: Nachrichtenfenster bei Ausnahmen ................................................................. 26 Abbildung 20: Statusleiste grau ............................................................................................... 26 Abbildung 21: Statusleiste grün ............................................................................................... 27 Abbildung 22: Statusleiste gelb................................................................................................ 27 Abbildung 23: Statusleiste rot .................................................................................................. 27 Abbildung 24: Einblendung bei nicht implementierten Methoden (einzelne Werte) .............. 27 Abbildung 25: Einblendung bei nicht implementierten Methoden (ganze Auflistung)........... 27 Abbildung 26: Liste der laufenden Vorgänge .......................................................................... 33 Abbildung 27: Ansicht-Menü................................................................................................... 33 Abbildung 28: Threading-Menü............................................................................................... 35 Abbildung 29: Ablauf im Modus Singlethreaded .................................................................... 36 Abbildung 30: Ablauf im Modus Synchron einreihen ............................................................. 37 Abbildung 31: Ablauf im Modus Asynchron und Multithreaded ............................................ 38 Abbildung 32: Ablauf beim Abbruch eines Vorgangs............................................................. 39 Abbildung 33: Test-Menü ........................................................................................................ 41 Abbildung 34: Funktions-Test-Dialog ..................................................................................... 41 Abbildung 35: Geschwindigkeits-Test-Dialog......................................................................... 42 Abbildung 36: Threadsicherheits-Testdialog ........................................................................... 42 Abbildung 37: Logging-Menü ................................................................................................. 43 Abbildung 38: Look & Feel Menü........................................................................................... 46 Abbildung 39: Benutzeroberfläche im "Metal" Look & Feel .................................................. 46 Abbildung 40: Hilfe-Menü....................................................................................................... 47 Abbildung 41: Verzeichnis-Auswahl-Dialog........................................................................... 48 Abbildung 42: Eclipse-Dialog "New Project".......................................................................... 52 Abbildung 43: Eclipse-Dialog "New Java Project" ................................................................. 53 Abbildung 44: Eclipse Package Explorer................................................................................. 53 Abbildung 45: Eclipse-Dialog "New Folder" .......................................................................... 54 Abbildung 46: Eclipse Package Explorer................................................................................. 54 Abbildung 47: Eclipse-Dialog "Import" - Select ..................................................................... 55 Abbildung 48: Eclipse-Dialog "Import" - File system............................................................. 56 Abbildung 49: Eclipse Package Explorer................................................................................. 56 - 248 - Verzeichnisse Abbildung 50: Eclipse-Dialog "Properties for VS_IW_FiBu" ................................................ 57 Abbildung 51: Eclipse-Dialog "JAR Selection" ...................................................................... 57 Abbildung 52: Eclipse Package Explorer................................................................................. 58 Abbildung 53: Eclipse-Dialog "Run" - Empty......................................................................... 58 Abbildung 54: Eclipse-Dialog "Run" - Java Application ........................................................ 59 Abbildung 55: Eclipse-Dialog "New Java Package" ............................................................... 60 Abbildung 56: Eclipse Package Explorer................................................................................. 60 Abbildung 57: Eclipse-Dialog "Implemented Interfaces Selection"........................................ 61 Abbildung 58: Eclipse-Dialog "New Java Class" .................................................................... 61 Abbildung 59: Eclipse Package Explorer................................................................................. 62 Abbildung 60: Eclipse-Gesamtansicht des neu erstellten Projekts .......................................... 62 Abbildung 61: ERM-Modell 1 für die Buchhaltung mit Hibernate ....................................... 116 Abbildung 62: ERM-Modell 2 für die Buchhaltung mit Hibernate ....................................... 120 Abbildung 63: Datenpakte ..................................................................................................... 124 Abbildung 64: Anfrage-Antwort............................................................................................ 124 Abbildung 65: Entfernter Methodenaufruf ............................................................................ 124 Abbildung 66: Eclipse Package Explorer............................................................................... 125 Abbildung 67: Socket-Anfrage-Antwort-Klassen.................................................................. 129 Abbildung 68: Schematischer Ablauf von RMI..................................................................... 143 Abbildung 69: RMI-Kommunikations-Architektur ............................................................... 143 Abbildung 70: Nachladen von Java-Klassen ......................................................................... 151 Abbildung 71: Java 2 Enterprise Komponenten-Model......................................................... 170 Abbildung 72: Windows Umgebungsvariablen ..................................................................... 174 Abbildung 73: Paket-Auswahl bei der JBoss-Installation...................................................... 175 Abbildung 74: JBoss-Verzeichnisstruktur ............................................................................. 176 Abbildung 75: Konsolenfenster eines laufenden JBoss ......................................................... 181 Abbildung 76: Hypersonic Datenbank-Manager ................................................................... 185 Abbildung 77: Eclipse-Dialog "New Project"........................................................................ 187 Abbildung 78: Eclipse-Dialog "Select a JBoss configuration" .............................................. 188 Abbildung 79: Eclipse-Dialog "New Server" - Define .......................................................... 188 Abbildung 80: Eclipse-Dialog "New Server" - Create........................................................... 189 Abbildung 81: Eclipse Package Explorer - Servers ............................................................... 189 Abbildung 82: Eclipse Servers-Ansicht ................................................................................. 190 Abbildung 83: Eclipse-Console mit laufendem JBoss ........................................................... 190 Abbildung 84: Lebenszyklus einer zustandslosen Session-Bean........................................... 193 Abbildung 85: Lebenszyklus einer zustandsbehafteten Session-Bean .................................. 193 Abbildung 86: Interceptor-Aufruf.......................................................................................... 194 Abbildung 87: Lebenszyklus einer Entity-Bean .................................................................... 198 Abbildung 88: Ablauf des Entity-Bean-Prinzips bei einem Kontenobjekt ............................ 199 Abbildung 89: Beziehung der Entity-Beans........................................................................... 200 Abbildung 90: Lebenszyklus einer Message-Driven-Bean.................................................... 203 Abbildung 91: Nachrichtenschlange ...................................................................................... 204 Abbildung 92: Anmelde-Versende-System ........................................................................... 204 Abbildung 93: MBean-Lebenszyklus..................................................................................... 206 Abbildung 94: Haupansicht der Mysaifu JVM ...................................................................... 236 Abbildung 95: Allgemeine Optionen der Mysaifu JVM........................................................ 237 Abbildung 96: Erste Ansicht der Pocket-Buchhaltung .......................................................... 238 Abbildung 97: Ansicht der Konten und Buchungen der Poket-Buchhaltung ........................ 238 Abbildung 98: Pocket-Buchhaltung: Konto anlegen oder ändern ......................................... 239 Abbildung 99: Pocket-Buchhaltung: Buchung anlegen oder ändern ..................................... 239 Abbildung 100: Pocket-Buchhaltung: Konto oder Buchung löschen .................................... 240 - 249 - Verzeichnisse Abbildung 101: Pocket-Buchhaltung: Konto-Details ............................................................ 240 Abbildung 102: Pocket-Buchhaltung: Einblendungen im Statusbereich............................... 241 Abbildung 103: Pocket-Buchhaltung: Schnittstellen-Menü .................................................. 241 Abbildung 104: Pocket-Buchhaltung: Hilfe-Menü ................................................................ 242 Abbildung 105: Pocket-Buchhaltung: Info-Ansicht und Auflistung der Proxy-Klassen....... 242 - 250 - Verzeichnisse Codeverzeichnis Code 1: Interface IAccount ........................................................................................................ 6 Code 2: Enumeration AccountType........................................................................................... 7 Code 3: Interface IAccountingEntry .......................................................................................... 7 Code 4: Interface IAccountingService ....................................................................................... 8 Code 5: Interface IAccountingProxy.......................................................................................... 9 Code 6: Interface IExtendedAccountingService ........................................................................ 9 Code 7: Class AccountingException........................................................................................ 10 Code 8: "Verpacken" einer beliebigen Exception als IOException ......................................... 11 Code 9: Class NotImplemented (Exception)............................................................................ 11 Code 10: Methoden loadProxy(…) der Utilities-Klasse .......................................................... 12 Code 11: Class NoInterfaceException ..................................................................................... 13 Code 12: Klassenvariable ARGS ............................................................................................. 20 Code 13: Ausgabe des Stacktrace mit Hilfe von StringWriter und PrintWriter ...................... 26 Code 14: Class AbstractRowTableModel<T> ......................................................................... 28 Code 15: Class AccountTableModel........................................................................................ 29 Code 16: Class AccountingEntryTableModel.......................................................................... 29 Code 17: Class RunnableServiceTask...................................................................................... 30 Code 18: Class MethodCallTask (Adapterschicht).................................................................. 30 Code 19: Class AbstractSwingTask ......................................................................................... 31 Code 20: Class Caller (GUI-Vorgang)..................................................................................... 31 Code 21: Interface SwingTaskListener .................................................................................... 32 Code 22: Class ThreadingService ............................................................................................ 34 Code 23: Verwendung von ExecutorService im ThreadingService......................................... 35 Code 24: Class Logger ............................................................................................................. 43 Code 25: Ermittlung des Klassennamens und des Threadnamens aus dem Stacktrace ........... 44 Code 26: In Spalten formatierte Log-Ausgabe ........................................................................ 44 Code 27: Ermittlung des Methodennamens aus dem Stacktrace ............................................. 44 Code 28: Logger-Ausgabe der aufgerufenen Methoden .......................................................... 45 Code 29: Look & Feel Initialisierung ...................................................................................... 45 Code 30: Utilities-Methode setLookAndFeel()........................................................................ 47 Code 31: Class ClassFileLoader .............................................................................................. 49 Code 32. Interface ClassFileLoaderListener............................................................................ 49 Code 33: Interface ClassFileReflection.................................................................................... 50 Code 34: Class ClassFileDecoder ............................................................................................ 50 Code 35: Method classFound() in ServiceSelectDialog .......................................................... 51 Code 36: Class ClassFileEntry................................................................................................. 51 Code 37: HashMaps für Konten- und Buchungs-Objekte ....................................................... 64 Code 38: HashMaps für Soll- und Haben-Buchungen............................................................. 64 Code 39: Class AccountingService (Lokal) ............................................................................. 67 Code 40: Class Account (Lokal) .............................................................................................. 68 Code 41: Class AccountingEntry (Lokal) ................................................................................ 70 Code 42: Class LocalProxy ...................................................................................................... 70 Code 43: Class Account (Serialize) ......................................................................................... 71 Code 44: Class AccountingEntry (Serialize) ........................................................................... 72 Code 45: Method loadData() (Serialize) .................................................................................. 72 Code 46: Method storeData() (Serialize) ................................................................................. 73 Code 47: Class SerializeProxy ................................................................................................. 73 Code 48: Class AccountingService (JAXB - Variante 1) ........................................................ 80 Code 49: Class AccountingEntry (JAXB - Variante 1) ........................................................... 80 - 251 - Verzeichnisse Code 50: Methode afterUnmarshal()........................................................................................ 81 Code 51: Class Account (JAXB - Variante 1) ......................................................................... 82 Code 52: Class AccountingService (JAXB - Variante 2) ........................................................ 85 Code 53: Class Account (JAXB - Variante 2) ......................................................................... 86 Code 54: Class AccountingEntry (JAXB - Variante 2) ........................................................... 87 Code 55: Class JAXBProxy1 ................................................................................................... 89 Code 56: Class JAXBUtil ........................................................................................................ 90 Code 57: Method writeXML() (JAXBUtil) ............................................................................. 90 Code 58: Method readXML() (JAXBUtil)............................................................................... 91 Code 59: Method getSchema() (JAXBUtil)............................................................................. 91 Code 60: Class MySQLProxy .................................................................................................. 93 Code 61: Method query() (MySQL) ........................................................................................ 93 Code 62: Method querySet() (MySQL) ................................................................................... 94 Code 63: Method execute() (MySQL) ..................................................................................... 94 Code 64: Method prepare() (MySQL) ..................................................................................... 95 Code 65: Verwaltung der Konten- und Buchungsobjekte (MySQL)....................................... 96 Code 66: Class Account (Hibernate-Beispiel) ....................................................................... 101 Code 67: Beispiel für 1:1-Beziehung mit Primär-Schlüssel .................................................. 103 Code 68: Beispiel für 1:1-Beziehung mit Fremd-Schlüssel................................................... 104 Code 69: Beispiel für 1:n-Beziehung ..................................................................................... 105 Code 70: Beispiel für n:n-Beziehung ..................................................................................... 105 Code 71: Manuelle Transaktions-Demarkation (1)................................................................ 109 Code 72: Manuelle Transaktions-Demarkation (2)................................................................ 109 Code 73: Selbst implementierte Versionskontrolle................................................................ 111 Code 74: Klasse mit automatischer Versionskontrolle durch Hibernate ............................... 111 Code 75: Class AccountingService (Hibernate - Variante 1) ................................................ 117 Code 76: Class Account (Hibernate - Variante 1).................................................................. 118 Code 77: Class AccountingEntry (Hibernate - Variante 1).................................................... 119 Code 78: Class AccountingService (Hibernate - Variante 2) ................................................ 120 Code 79: Class Account (Hibernate - Variante 2).................................................................. 121 Code 80: Class AccountingEntry (Hibernate - Variante 2).................................................... 121 Code 81: Class HibernateProxy2 (Hibernate - Variante 2).................................................... 122 Code 82: Class SocketServer ................................................................................................. 126 Code 83: Class SocketProxy .................................................................................................. 128 Code 84: run()-Methode von SocketServer ........................................................................... 129 Code 85: Class Request.......................................................................................................... 130 Code 86: Class ClientSideRequest......................................................................................... 131 Code 87: Class ServerSideResponse...................................................................................... 132 Code 88: Class Response ....................................................................................................... 133 Code 89: Class ServerSideResponse...................................................................................... 133 Code 90: Class ClientSideResponse ...................................................................................... 134 Code 91: Class StringUnMarshaller (Teil 1).......................................................................... 135 Code 92: Class StringUnMarshaller (Teil 2).......................................................................... 135 Code 93: Class StringUnMarshaller (Teil 3).......................................................................... 136 Code 94: Method mashalSet()................................................................................................ 137 Code 95: Method unmarshalSet() .......................................................................................... 137 Code 96: Methoden marshalThrowable() und unmarshalThrowable().................................. 137 Code 97: Methode remoteObjectResponse() ......................................................................... 139 Code 98: Methoden getOid() und genOid() ........................................................................... 139 Code 99: Implementierung einiger Buchhaltungsmethoden (Socket - AccountingService) . 140 Code 100: Method request1() (SocketProxy)......................................................................... 141 - 252 - Verzeichnisse Code 101: Method request2() (SocketProxy)......................................................................... 141 Code 102: Method request() (SocketProxy)........................................................................... 141 Code 103: Method requestObject() (SocketProxy)................................................................ 142 Code 104: Method calculateBalance() bei RMI..................................................................... 145 Code 105: Class LocateRegistry ............................................................................................ 145 Code 106: Interface Registry.................................................................................................. 146 Code 107: Class RMIServer................................................................................................... 146 Code 108: Class AccountingService (RMI)........................................................................... 148 Code 109: Class Account (RMI)............................................................................................ 148 Code 110: Class AccountingEntry (RMI).............................................................................. 149 Code 111: Class RMIProxy.................................................................................................... 150 Code 112: Class CorbaServer (Methoden)............................................................................. 159 Code 113: Class CorbaServer (main)..................................................................................... 160 Code 114: Class CorbaServerAccount................................................................................... 161 Code 115: Class CorbaServerEntry........................................................................................ 162 Code 116: Class AccountingService (CORBA)..................................................................... 164 Code 117: Class Account (CORBA)...................................................................................... 165 Code 118: Class AccountingEntry (CORBA)........................................................................ 165 Code 119: Class CorbaProxy ................................................................................................. 166 Code 120: Interceptor-Methode ............................................................................................. 195 Code 121: Class EJBAccountingService ............................................................................... 197 Code 122: Class Account (EJB)............................................................................................. 201 Code 123: Class AccountingEntry (EJB)............................................................................... 203 Code 124: Interface MessageListener .................................................................................... 204 Code 125: Class MessageBean .............................................................................................. 205 Code 126: Interface MyManagementMBean ......................................................................... 206 Code 127: Interface TimerService ......................................................................................... 207 Code 128: Interface IBackupTimerMBean ............................................................................ 208 Code 129: Class BackupTimer............................................................................................... 208 Code 130: Class EJBProxy .................................................................................................... 214 Code 131: Class MessageClient............................................................................................. 215 Code 132: InitialContext mit Hashtable................................................................................. 216 Code 133: Interface IWebAccounting.................................................................................... 220 Code 134: Konto-Methoden für Interface IWebAccounting ................................................. 221 Code 135: Buchungs-Methoden für Interface IWebAccounting ........................................... 221 Code 136: Class WebAccountingImpl................................................................................... 222 Code 137: Class WebServiceProxy........................................................................................ 230 Code 138: Class AccountingService (Web-Services)............................................................ 231 Code 139: Class Account (Web-Service)............................................................................... 232 Code 140: Class AccountingEntry (Web-Service)................................................................. 233 - 253 - Verzeichnisse Resourcenverzeichnis Resource 1: Ant-Build-Datei jaxb-generate-schema-v1.xml ................................................... 82 Resource 2: schema-v1.xsd ...................................................................................................... 84 Resource 3: fibu-v1.xml ........................................................................................................... 84 Resource 4: schema-v2.xsd ...................................................................................................... 88 Resource 5: fibu-v2.xml ........................................................................................................... 88 Resource 6: SQL-Befehle zum Anlegen der Konten- und Buchungs-Tabelle......................... 95 Resource 7: SQL-Befehle der Service-Klasse (MySQL)......................................................... 96 Resource 8: SQL-Befehle der Konto-Klasse (MySQL)........................................................... 97 Resource 9: SQL-Befehle der Buchungs-Klasse (MySQL)..................................................... 98 Resource 10: Hibernate Datenbankeinstellungen .................................................................. 107 Resource 11: Socket-Protokoll - Anfrage .............................................................................. 130 Resource 12: Socket-Protokoll - Antwort .............................................................................. 132 Resource 13: CORBA Accounting.idl ................................................................................... 155 Resource 14: Ant-Build-Datei corba-idlj.xml ........................................................................ 157 Resource 15: Bedeutung der CORBA-Suffixe....................................................................... 158 Resource 16: jacorbsettings.cmd............................................................................................ 168 Resource 17: jacorbnaming.cmd ............................................................................................ 168 Resource 18: jacorbserver.cmd .............................................................................................. 168 Resource 19: jacorbclient.cmd ............................................................................................... 169 Resource 20: Hypersonic-Datenquelle hsqldb-ds.xml ........................................................... 177 Resource 21: MySQL-Datenquelle mysql-ds.xml ................................................................. 178 Resource 22: PostegreSQL-Datenquelle postgres-ds.xml ..................................................... 179 Resource 23: Oracle-Datenquelle oracle-ds.xml.................................................................... 180 Resource 24: JNDI-Ansicht des allgemeinen Java-Namensraumes ...................................... 184 Resource 25: jmx-queue-service.xml ..................................................................................... 205 Resource 26: jms-topic-service.xml ....................................................................................... 205 Resource 27: application.xml (EJB)....................................................................................... 209 Resource 28: Ant-Vorgang für EJB3-Archiv......................................................................... 210 Resource 29: Ant-Vorgang für Persistence-Archiv ............................................................... 211 Resource 30: Deployment-Descriptor persistence.xml .......................................................... 211 Resource 31: Ant-Vorgang für JAR-Archiv .......................................................................... 211 Resource 32: Ant-Build-Datei enterprise-deploy-jboss4.xml ................................................ 212 Resource 33: build.properties................................................................................................. 213 Resource 34: jndi.properties................................................................................................... 216 Resource 35: log4j.properties................................................................................................. 217 Resource 36: Web-Service-Descriptor web.xml .................................................................... 223 Resource 37: Ant-Build-Datei webservice-deploy.xml ......................................................... 224 Resource 38: application.xml (Web-Service) ........................................................................ 224 Resource 39: Ant-Build-Datei webservice-generate.xml....................................................... 225 Resource 40: WSTools-Konfiguration wstools-java2wsdl.xml............................................. 226 Resource 41: WSTools-Konfiguration wstools-wsdl2java.xml............................................. 227 Resource 42: Ant-Build-Datei axis-generate-client.xml ........................................................ 228 - 254 - Verzeichnisse Tabellenverzeichnis Tabelle 1: Beispiel-Buchungsatz................................................................................................ 4 Tabelle 2: Verwendete Begriffe ................................................................................................. 5 Tabelle 3: Modulares Konzept der Programmteile .................................................................. 12 Tabelle 4: JAXB Paket-Annotations ........................................................................................ 76 Tabelle 5: JAXB Klassen-Annotations .................................................................................... 76 Tabelle 6: JAXB Enum-Annotations ....................................................................................... 77 Tabelle 7: JAXB Feld-Annotations.......................................................................................... 78 Tabelle 8: JAXB Objekt-Fabrik-Annotations .......................................................................... 79 Tabelle 9: JAXB Adapter-Annotations .................................................................................... 79 Tabelle 10: Einbindung von Hibernate als zusätzliche Schicht ............................................... 99 Tabelle 11: Schematisher Aufbau der Hibernate-Komponenten ............................................. 99 Tabelle 12: Hibernate-Beziehungs-Semantik......................................................................... 104 Tabelle 13: Zuordnung der Basisdatentypen.......................................................................... 156 Tabelle 14: Zuordnung der komplexen IDL-Angaben........................................................... 156 Tabelle 15: Erzeugte CORBA-Artefakte ............................................................................... 158 Tabelle 16: Bean-Callback-Methoden und Annotations........................................................ 192 Tabelle 17: Annotation für Dependency-Injection................................................................. 194 Tabelle 18: Vergleich von SOAP-Nachrichten für RPC-Style und Document-Style ............ 219 Tabelle 19: Vergleich von SOAP-Nachrichten für literal und encoded................................. 219 Tabelle 20: Von AXIS erzeugte Client-Klassen .................................................................... 229 - 255 - Verzeichnisse Quellenverzeichnis [01] Homepage der Firma Sun-Microsystems http://java.sun.com/ [02] Homepage des JBoss-Application-Server http://www.jboss.com/ [03] Deutsche Version der JBoss-Homepage http://de.jboss.com/ [04] Wikipedia-Artikel über den JBoss http://de.wikipedia.org/wiki/JBoss_Application_Server [05] Homepage der Apache-Foundation http://www.apache.de/ [06] Hompage des Tomcat Web-Containers http://tomcat.apache.org/ [07] Homepage des Java Authentication and Authorization Service (JAAS) http://java.sun.com/products/jaas/ [08] Wikipedia-Artikel über die Java 2 Enterprise Edition (J2EE) http://de.wikipedia.org/wiki/J2EE [09] Homepage des Java-basierten Installer-Generators IzPack http://www.izforge.com/izpack/ [10] Wikipedia-Artikel über MySQL http://de.wikipedia.org/wiki/MySQL [11] Homepage der MySQL-Datenbank http://www.mysql.com/ [12] Wikipedia-Artikel über Finanzbuchhaltung http://de.wikipedia.org/wiki/Finanzbuchhaltung [13] Wikipedia-Artikel über Buchhaltung http://de.wikipedia.org/wiki/Buchhaltung [14] Homepage der Hypersonic-Datenbank (HSQLDB) http://www.hsqldb.org/ [15] Homepage des Java Messaging Service (JMS) http://java.sun.com/products/jms/ [16] Homepage der Java Management Extensions (JMX) http://java.sun.com/javase/technologies/core/mntr-mgmt/javamanagement/ - 256 - Verzeichnisse [17] Homepage des Java Naming and Directory Interface (JNDI) http://java.sun.com/javase/technologies/core/jndi/index.jsp [18] Heiko W. Rupp, JBoss - Server-Handbuch für J2EE-Entwickler und Administratoren, dpunkt-Verlag, 1. Auflage 2005, ISBN 3-89864-318-2 [19] Homepage der Open-Source-Datenbank PostgreSQL http://www.postgresql.org/ [20] Violet, ein einfacher schlanker Java-UML-Editor http://horstmann.com/violet/ [21] FAQ zu den Web-Services beim JBoss http://wiki.jboss.org/wiki/Wiki.jsp?page=JBWSFAQ [22] Homepage der JBoss-Web-Services http://labs.jboss.com/portal/jbossws/ [23] JBoss-Web-Services User-Guide http://labs.jboss.com/portal/jbossws/user-guide/en/html/index.html [24] Homepage der Web-Services Interoperability Organization, Basic Profile Version 1.0 http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html [25] IBM-Homepage: Welche Art von Web-Service sollte man benutzen? http://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/ [26] SOAP-Spezifikation vom W3C http://www.w3.org/TR/soap/ [27] Homepage der Hibernate-Projekts http://www.hibernate.org/ [28] Java-Web-Services Homepage http://java.sun.com/webservices/jwsdp/index.jsp [29] Artikel "Threads and Swing" bei Sun http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html [30] Homepage des Eclipse-Projects http://www.eclipse.org/ [31] Sun-Dokument über die abgelehnten Methoden bei Threads http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html [32] Homepage des AXIS-Framework bei der Apache-Foundation http://ws.apache.org/axis/ [33] Homepage zu Remote Method Invocation (RMI) http://java.sun.com/javase/technologies/core/basic/rmi/index.jsp - 257 - Verzeichnisse [34] Wikipedia-Artikel zu RMI http://de.wikipedia.org/wiki/Remote_Method_Invocation [35] RMI-Tutorial von Sun http://java.sun.com/docs/books/tutorial/rmi/index.html [36] Ziele und Funktionsweisen von RMI http://www.sbgl.de/rmi/ [37] Homepage der Java Database Connectivity (JDBC) http://java.sun.com/javase/technologies/database/index.jsp [38] Homepage der Eclipse Enterprise Java Variante http://www.eclipse.org/home/categories/index.php?category=enterprise [39] Homepage des JBoss Cache Projekts http://www.jboss.org/products/jbosscache [40] Homepage des JBoss Framework zur aspektorientierten Programmierung http://labs.jboss.com/portal/jbossaop [41] Homepage von JBoss jBPM http://www.jboss.com/products/jbpm [42] Homepage der JBoss Rules http://www.jboss.com/products/rules [43] Wikipedia-Artikel zu BPEL http://de.wikipedia.org/wiki/Business_Process_Execution_Language [44] Wikipedia-Artikel zu BPM http://de.wikipedia.org/wiki/Prozessmanagement [45] Java 2 Enterprise Edition Homepage http://java.sun.com/javaee/ [46] Wikipedia-Artikel über EJB-Container http://de.wikipedia.org/wiki/EJB-Container [47] Verwendung des Application-Client-Container (AAC) http://docs.sun.com/source/817-2173-10/dcacc.html [48] Wikipedia-Artikel über die Java Management Extensions (JMX) http://de.wikipedia.org/wiki/JMX [49] Homepage des Java Beans Activation Framework (JAF) http://java.sun.com/products/javabeans/jaf/downloads/index.html [50] Wikipedia-Artikel über den Java Authentication and Authorization Service http://de.wikipedia.org/wiki/JAAS - 258 - Verzeichnisse [51] Homepage der Java Transaction API (JTA) http://java.sun.com/products/jta/ [52] Homepage des Java Transaction Service (JTS) http://java.sun.com/products/jts/ [53] Homepage der JavaMail-API bei Sun http://java.sun.com/products/javamail/ [54] Homepage der Java-API for XML Registries (JAXR) http://java.sun.com/webservices/jaxr/index.jsp [55] Homepage der Java-API for XML-Based RPC (JAX-RPC) http://java.sun.com/webservices/jaxrpc/ [56] Homepage der Java Connector Architecture (JCA) http://java.sun.com/j2ee/connector/ [57] Wikipedia-Artikel über die Java Connector Architecture (JCA) http://de.wikipedia.org/wiki/J2EE_Connector_Architecture [58] Java on Pocket-PC (Unofficial FAQ) http://blog.vikdavid.com/2004/12/java_on_pocketp.html [59] Introduction to Pocket-PC Development http://www.codeproject.com/ce/Pocket_PC_Development.asp [60] Windows Mobile Developer Center http://msdn.microsoft.com/windowsmobile/default.aspx [61] Windows Mobile Developer Resources http://www.microsoft.com/windowsmobile/developers/default.mspx [62] Sun CDC FAQ http://java.sun.com/products/cdc/faq.html [63] Homepage der Java Micro Edition (J2ME) http://java.sun.com/javame/index.jsp [64] Homepage der Mysaifu JVM http://www2s.biglobe.ne.jp/~dat/java/project/jvm/index_en.html [65] JSR-250: Common Annotations for the Java Platform http://jcp.org/en/jsr/detail?id=250 [66] JSR-220: Enterprise Java Beans 3.0 http://jcp.org/en/jsr/detail?id=220 [67] Hibernate Reference Doumentation http://www.hibernate.org/hib_docs/v3/reference/en/html_single/ - 259 - Verzeichnisse [68] Hibernate Annotations Reference Guide http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/ [69] Home der Java Architecture for XML Bindung (JAXB) http://java.sun.com/webservices/jaxb/ [70] Wikipedia-Artikel über die Java Architecture for XML Binding (JAXB) http://de.wikipedia.org/wiki/JAXB [71] The Java EE 5 Tutorial, JAXB, Page 5 http://java.sun.com/javaee/5/docs/tutorial/doc/JAXB5.html [72] JAXB: Issue 268 - Map handling broken https://jaxb.dev.java.net/issues/show_bug.cgi?id=268 [73] Wikipedia-Artikel über Enterprise JavaBeans http://de.wikipedia.org/wiki/Enterprise_Java_Beans [74] EJB Glossar http://www.objects-at-work.de/pmwiki/EJBGlossar/HomePage [75] Wikipedia-Artikel zum Java Messaging Service (JMS) http://de.wikipedia.org/wiki/Java_Message_Service [76] Homepage des Apache Logging Services Project Log4j http://logging.apache.org/log4j/docs/ [77] Dynamic code downloading using Java RMI http://java.sun.com/j2se/1.5.0/docs/guide/rmi/codebase.html [78] JacORB Homepage http://www.jacorb.org/ [79] Homepage der Object Management Group (OMG) http://www.omg.org/ [80] Wikipedia-Artikel über CORBA http://de.wikipedia.org/wiki/CORBA [81] CORBA-Dienste - Ausarbeitung zum Hauptseminar Middleware http://www.marclayer.de/stud/proj/cos/CORBAservices.php [82] Eclipse Europa Projektseite http://www.eclipse.org/europa/ [83] Spezifikation des Formats von class-Dateien http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html [84] Werner Eberling, Jan Leßner, Enterprise JavaBeans 3 - Das EJB3-Praxisbuch für Einund Umsteiger, Hanser-Verlag, 1. Auflage 2007, ISBN 978-3-446-41085-5 - 260 - Anhänge Anhänge Anhang A: System-Eigenschaften des JBoss awt.toolkit sun.awt.windows.WToolkit catalina.base D:\daemon\jboss-4.2.2.GA\server\default catalina.ext.dirs D:\daemon\jboss-4.2.2.GA\server\default\lib catalina.home D:\daemon\jboss-4.2.2.GA\server\default catalina.useNaming false com.arjuna.ats.arjuna.objectstore.objectStoreDir D:\daemon\jboss-4.2.2.GA\server\default\data/tx-object-store com.arjuna.ats.jta.lastResourceOptimisationInterface org.jboss.tm.LastResource com.arjuna.ats.tsmx.agentimpl com.arjuna.ats.internal.jbossatx.agent.LocalJBossAgentImpl com.arjuna.common.util.logger log4j_releveler com.arjuna.common.util.logging.DebugLevel 0x00000000 com.arjuna.common.util.logging.FacilityLevel 0xffffffff com.arjuna.common.util.logging.VisibilityLevel 0xffffffff common.loader ${catalina.home}/lib,${catalina.home}/lib/*.jar file.encoding Cp1252 file.encoding.pkg sun.io file.separator \ hibernate.bytecode.provider javassist java.awt.graphicsenv sun.awt.Win32GraphicsEnvironment java.awt.printerjob sun.awt.windows.WPrinterJob java.class.path D:\daemon\jboss-4.2.2.GA\bin\\run.jar java.class.version 50.0 java.endorsed.dirs D:\daemon\jboss-4.2.2.GA\bin\..\lib\endorsed java.ext.dirs C:\Program Files\Java\jre6\lib\ext java.home C:\Program Files\Java\jre6 java.io.tmpdir C:\DOCUME~1\luf39862\LOCALS~1\Temp\ java.library.path .; C:\WINDOWS; C:\WINDOWS\system32; C:\WINDOWS\Sun\Java\bin; C:\Program Files\Java\jre6\bin; java.naming.factory.initial org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs org.jboss.naming:org.jnp.interfaces java.protocol.handler.pkgs org.jboss.net.protocol java.rmi.server.RMIClassLoaderSpi org.jboss.system.JBossRMIClassLoader java.rmi.server.codebase http://127.0.0.1:8083/ java.rmi.server.hostname 127.0.0.1 java.runtime.name Java(TM) SE Runtime Environment java.runtime.version 1.6.0_11-b03 java.specification.name Java Platform API Specification java.specification.vendor Sun Microsystems Inc. java.specification.version 1.6 java.vendor Sun Microsystems Inc. java.vendor.url http://java.sun.com/ java.vendor.url.bug http://java.sun.com/cgi-bin/bugreport.cgi java.version 1.6.0_11 java.vm.info mixed mode java.vm.name Java HotSpot(TM) Client VM java.vm.specification.name Java Virtual Machine Specification java.vm.specification.vendor Sun Microsystems Inc. java.vm.specification.version 1.0 java.vm.vendor Sun Microsystems Inc. java.vm.version 11.0-b16 jboss.bind.address 127.0.0.1 jboss.home.dir D:\daemon\jboss-4.2.2.GA - 261 - Anhänge jboss.home.url file:/D:/daemon/jboss-4.2.2.GA/ jboss.identity bf3152b003d17e24x33389935x11b0ccf6736x-8000461 jboss.lib.url file:/D:/daemon/jboss-4.2.2.GA/lib/ jboss.remoting.domain JBOSS jboss.remoting.instanceid bf3152b003d17e24x33389935x11b0ccf6736x-8000461 jboss.remoting.jmxid rfhinf176_1258974783595 jboss.remoting.version 22 jboss.server.base.dir D:\daemon\jboss-4.2.2.GA\server jboss.server.base.url file:/D:/daemon/jboss-4.2.2.GA/server/ jboss.server.config.url file:/D:/daemon/jboss-4.2.2.GA/server/default/conf/ jboss.server.data.dir D:\daemon\jboss-4.2.2.GA\server\default\data jboss.server.home.dir D:\daemon\jboss-4.2.2.GA\server\default jboss.server.home.url file:/D:/daemon/jboss-4.2.2.GA/server/default/ jboss.server.lib.url file:/D:/daemon/jboss-4.2.2.GA/server/default/lib/ jboss.server.log.dir D:\daemon\jboss-4.2.2.GA\server\default\log jboss.server.name default jboss.server.temp.dir D:\daemon\jboss-4.2.2.GA\server\default\tmp jbossmx.loader.repository.class org.jboss.mx.loading.UnifiedLoaderRepository3 line.separator org.apache.commons.logging.Log org.apache.commons.logging.impl.Log4JLogger org.w3c.dom.DOMImplementationSourceList org.apache.xerces.dom.DOMXSImplementationSourceImpl os.arch x86 os.name Windows XP os.version 5.1 path.separator ; program.name run.bat server.loader shared.loader sun.arch.data.model 32 sun.boot.class.path D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jaxb-api.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxrpc.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxws-ext.j D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-jaxws.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\jbossws-native-saaj.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\serializer.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\xalan.jar; D:/daemon/jboss-4.2.2.GA/bin/../lib/endorsed\xercesImpl.jar; C:\Programme\Java\jre6\lib\resources.jar; C:\Programme\Java\jre6\lib\rt.jar; C:\Programme\Java\jre6\lib\sunrsasign.jar; C:\Programme\Java\jre6\lib\jsse.jar; C:\Programme\Java\jre6\lib\jce.jar; C:\Programme\Java\jre6\lib\charsets.jar;C:\Programme\Java\jre6\classes sun.boot.library.path C:\Programme\Java\jre6\bin sun.cpu.endian little sun.desktop windows sun.io.unicode.encoding UnicodeLittle sun.jnu.encoding Cp1252 sun.management.compiler HotSpot Client Compiler sun.os.patch.level Service Pack 3 user.country DE user.dir D:\ daemon\jboss-4.2.2.GA\bin user.home C:\Documents and Settings\luf39862 user.language de user.name luf39862 user.timezone Europe/Berlin - 262 - Anhänge Anhang B: Erweiterte Darstellung des asynchronen Threading - 263 - Anhänge Anhang C: Notwendige Bibliotheken Java Architecture for XML Binding (JAXB) jaxb1-impl.jar jaxb-api.jar jaxb-impl.jar jaxb-xjc.jar jsr173_api.jar sjsxp.jar Hibernate-Core ant-1.6.5.jar ant-antlr-1.6.5.jar ant-junit-1.6.5.jar ant-launcher-1.6.5.jar antlr-2.7.6.jar ant-swing-1.6.5.jar asm.jar asm-attrs.jar c3p0-0.9.0.jar cglib-2.1.3.jar checkstyle-all.jar cleanimports.jar commons-collections-3.2.jar commons-logging-1.0.4.jar concurrent-1.3.2.jar connector.jar dom4j-1.6.1.jar ehcache-1.2.3.jar jaas.jar jacc-1_0-fr.jar javassist.jar jaxen-1.1-beta-7.jar jboss-cache.jar jboss-common.jar jboss-jmx.jar jboss-system.jar jdbc2_0-stdext.jar jgroups-2.2.8.jar jta.jar junit-3.8.1.jar log4j-1.2.11.jar oscache-2.1.jar proxool-0.8.3.jar swarmcache-1.0rc2.jar syndiag2.jar versioncheck.jar xerces-2.6.2.jar xml-apis.jar Hibernate-Annotations ejb3-persistence.jar lucene-core-2.0.0.jar Web-Services jaxws-api.jar jaxws-rt.jar jaxws-tools.jar jsr181-api.jar jsr250-api.jar JBoss-Web-Services-Tools jaxb-api.jar jaxb-impl.jar jboss-jaxws.jar jbossws-core.jar jbossws-jboss-integration.jar policy-1.0.jar stax-api-1.0.jar wsdl4j.jar xmlsec.jar - 264 - Anhänge AXIS axis.jar axis-ant.jar commons-discovery-0.2.jar commons-logging-1.0.4.jar jaxrpc.jar log4j-1.2.8.jar saaj.jar wsdl4j-1.5.1.jar JBoss-Client jbossha-client.jar jboss-iiop-client.jar jboss-jaxrpc.jar jbossjmx-ant.jar jboss-jsr77-client.jar jbossmq-client.jar jboss-remoting.jar jboss-saaj.jar jboss-serialization.jar jboss-srp-client.jar jboss-system-client.jar jbossws-client.jar jboss-xml-binding.jar jmx-client.jar jmx-invoker-adaptor-client.jar juddisaaj.jar log4j.jar logkit.jar mail.jar scout.jar trove.jar wsdl4j.jar xmlsec.jar activation.jar antlr-2.7.6.jar avalon-framework.jar commons-httpclient.jar commons-logging.jar concurrent.jar ejb3-persistence.jar getopt.jar hibernate-annotations.jar hibernate-client.jar jacorb.jar javassist.jar javax.servlet.jar jbossall-client.jar jboss-annotations-ejb3.jar jboss-aop-jdk50-client.jar jboss-aspect-jdk50-client.jar jboss-client.jar jboss-common-client.jar jbosscx-client.jar jboss-deployment.jar jboss-ejb3-client.jar jboss-ejb3x.jar JacORB antlr-2.7.2.jar avalon-framework-4.1.5.jar backport-util-concurrent.jar idl.jar jacorb.jar logkit-1.2.jar picocontainer-1.2.jar wrapper.dll wrapper-3.1.0.jar - 265 - Anhänge Anhang D: Performance-Messung Durch die Möglichkeit der Geschwindigkeitsmessung in der Buchhaltungsoberfläche, die 1000 Buchungen anlegt und wieder löscht, ist eine grobe Aussage über die Performance der verschiedenen Technologien möglich. Datenhaltung Da bei der Datenhaltung die Zugriffe auf die Konten- und Buchungsobjekte fast immer direkt im Speicher stattfinden, ist die Dauer der Geschwindigkeitsmessung meist konstant. Die Abweichungen ergeben sich hier bei der Zeit die nötig ist, um die Daten zu Laden bzw. zu Speichern, wozu jeweils die von der Utility-Klasse erzeugten Testdatensätze verwendet werden. Technologie Local Serialize JAXB (V1) JAXB (V2) MySQL Hibernate (V1) Hibernate (V2) Laden 00:00:010 00:00:060 00:00:600 00:00:700 00:00:030 00:13:130 00:00:240 Speichern 00:00:010 00:00:010 00:00:030 00:00:030 00:00:010 00:00:010 00:00:020 Geschwindigkeitstest 00:00:020 00:00:020 00:00:020 00:00:020 00:05:400 02:40:750 (mit commit()) 00:00:060 Die hohen Werte bei Hibernate (V1) ergeben sich dadurch, daß nach jeden Aufruf manuell ein commit() ausgeführt wird. Bei der zweiten Variante erfolgen die Zugriffe nur im Speicher. Transport Die Transport-Technologien werden alle mit einer Instanz der lokalen Datenhaltung auf der Serverseite getestet. Server und Client laufen dabei beide auf demselben Rechner. Technologie Socket RMI CORBA JacORB Verbinden 00:00:020 00:00:300 00:00:400 00:30:410 Schließen 00:00:010 00:00:010 00:00:010 00:00:010 Geschwindigkeitstest 00:03:525 00:03:060 00:05:870 00:02:460 Auffallend ist die deutlich längere Ladezeit beim JacORB, der dafür später im laufenden Betrieb eine bessere Performance erzielt. Enterprise JavaBeans Hier kommen die Enterprise JavaBeans auf dem JBoss-Application-Server mit Hibernate und einer MySQL-Datenbank zum Einsatz. Technologie JBoss - EJB JBoss - Web-Services Verbinden 00:00:930 00:00:810 Schließen 00:00:010 00:00:010 - 266 - Geschwindigkeitstest 02:37:497 04:24:410 Anhänge Anhang E: WSDL-Datei für den Buchhaltungsservice <?xml version="1.0" encoding="UTF-8"?> <definitions name='WebAccountigService' targetNamespace='http://www.fh-regensburg.de/vs_iw/webservice' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:ns1='http://www.fh-regensburg.de/vs_iw/webservice/types' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:tns='http://www.fh-regensburg.de/vs_iw/webservice' xmlns:xsd='http://www.w3.org/2001/XMLSchema'> <types> <schema targetNamespace='http://www.fh-regensburg.de/vs_iw/webservice/types' xmlns='http://www.w3.org/2001/XMLSchema' xmlns:soap11-enc='http://schemas.xmlsoap.org/soap/encoding/' xmlns:tns='http://www.fh-regensburg.de/vs_iw/webservice/types' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <complexType name='AccountingException'> <sequence> <element name='message' nillable='true' type='string'/> </sequence> </complexType> <complexType name='IOException'> <sequence> <element name='message' nillable='true' type='string'/> </sequence> </complexType> <complexType name='Integer.Array'> <sequence> <element maxOccurs='unbounded' minOccurs='0' name='value' nillable='true' type='int'/> </sequence> </complexType> <element name='AccountingException' type='tns:AccountingException'/> <element name='IOException' type='tns:IOException'/> </schema> </types> <message name='IWebAccountingService_calculateBalance'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_calculateBalanceResponse'> <part name='result' type='xsd:double'/> </message> <message name='IOException'> <part element='ns1:IOException' name='IOException'/> </message> <message name='AccountingException'> <part element='ns1:AccountingException' name='AccountingException'/> </message> <message name='IWebAccountingService_createAccount'> <part name='Integer_1' type='xsd:int'/> <part name='int_2' type='xsd:int'/> <part name='String_3' type='xsd:string'/> </message> <message name='IWebAccountingService_createAccountResponse'/> <message name='IWebAccountingService_createAccountingEntry'> <part name='Integer_1' type='xsd:int'/> <part name='Integer_2' type='xsd:int'/> <part name='Double_3' type='xsd:double'/> <part name='String_4' type='xsd:string'/> </message> <message name='IWebAccountingService_createAccountingEntryResponse'> <part name='result' type='xsd:int'/> </message> <message name='IWebAccountingService_getAccountingEntries'/> <message name='IWebAccountingService_getAccountingEntriesResponse'> <part name='result' type='ns1:Integer.Array'/> </message> <message name='IWebAccountingService_getAccounts'/> <message name='IWebAccountingService_getAccountsResponse'> <part name='result' type='ns1:Integer.Array'/> </message> <message name='IWebAccountingService_getAmount'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getAmountResponse'> <part name='result' type='xsd:double'/> </message> <message name='IWebAccountingService_getCreditAccount'> - 267 - Anhänge <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getCreditAccountResponse'> <part name='result' type='xsd:int'/> </message> <message name='IWebAccountingService_getCreditEntries'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getCreditEntriesResponse'> <part name='result' type='ns1:Integer.Array'/> </message> <message name='IWebAccountingService_getDebitAccount'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getDebitAccountResponse'> <part name='result' type='xsd:int'/> </message> <message name='IWebAccountingService_getDebitEntries'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getDebitEntriesResponse'> <part name='result' type='ns1:Integer.Array'/> </message> <message name='IWebAccountingService_getDescription'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getDescriptionResponse'> <part name='result' type='xsd:string'/> </message> <message name='IWebAccountingService_getText'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getTextResponse'> <part name='result' type='xsd:string'/> </message> <message name='IWebAccountingService_getType'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_getTypeResponse'> <part name='result' type='xsd:int'/> </message> <message name='IWebAccountingService_removeAccount'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_removeAccountResponse'/> <message name='IWebAccountingService_removeAccountingEntry'> <part name='Integer_1' type='xsd:int'/> </message> <message name='IWebAccountingService_removeAccountingEntryResponse'/> <message name='IWebAccountingService_setAmount'> <part name='Integer_1' type='xsd:int'/> <part name='Double_2' type='xsd:double'/> </message> <message name='IWebAccountingService_setAmountResponse'/> <message name='IWebAccountingService_setCreditAccount'> <part name='Integer_1' type='xsd:int'/> <part name='Integer_2' type='xsd:int'/> </message> <message name='IWebAccountingService_setCreditAccountResponse'/> <message name='IWebAccountingService_setDebitAccount'> <part name='Integer_1' type='xsd:int'/> <part name='Integer_2' type='xsd:int'/> </message> <message name='IWebAccountingService_setDebitAccountResponse'/> <message name='IWebAccountingService_setDescription'> <part name='Integer_1' type='xsd:int'/> <part name='String_2' type='xsd:string'/> </message> <message name='IWebAccountingService_setDescriptionResponse'/> <message name='IWebAccountingService_setText'> <part name='Integer_1' type='xsd:int'/> <part name='String_2' type='xsd:string'/> </message> <message name='IWebAccountingService_setTextResponse'/> <message name='IWebAccountingService_setType'> <part name='Integer_1' type='xsd:int'/> <part name='int_2' type='xsd:int'/> </message> - 268 - Anhänge <message name='IWebAccountingService_setTypeResponse'/> <portType name='IWebAccountingService'> <operation name='calculateBalance' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_calculateBalance'/> <output message='tns:IWebAccountingService_calculateBalanceResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='createAccount' parameterOrder='Integer_1 int_2 String_3'> <input message='tns:IWebAccountingService_createAccount'/> <output message='tns:IWebAccountingService_createAccountResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='createAccountingEntry' parameterOrder='Integer_1 Integer_2 Double_3 String_4'> <input message='tns:IWebAccountingService_createAccountingEntry'/> <output message='tns:IWebAccountingService_createAccountingEntryResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='getAccountingEntries'> <input message='tns:IWebAccountingService_getAccountingEntries'/> <output message='tns:IWebAccountingService_getAccountingEntriesResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getAccounts'> <input message='tns:IWebAccountingService_getAccounts'/> <output message='tns:IWebAccountingService_getAccountsResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getAmount' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getAmount'/> <output message='tns:IWebAccountingService_getAmountResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getCreditAccount' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getCreditAccount'/> <output message='tns:IWebAccountingService_getCreditAccountResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getCreditEntries' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getCreditEntries'/> <output message='tns:IWebAccountingService_getCreditEntriesResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getDebitAccount' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getDebitAccount'/> <output message='tns:IWebAccountingService_getDebitAccountResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getDebitEntries' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getDebitEntries'/> <output message='tns:IWebAccountingService_getDebitEntriesResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getDescription' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getDescription'/> <output message='tns:IWebAccountingService_getDescriptionResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getText' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getText'/> <output message='tns:IWebAccountingService_getTextResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='getType' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_getType'/> <output message='tns:IWebAccountingService_getTypeResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='removeAccount' parameterOrder='Integer_1'> <input message='tns:IWebAccountingService_removeAccount'/> <output message='tns:IWebAccountingService_removeAccountResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='removeAccountingEntry' parameterOrder='Integer_1'> - 269 - Anhänge <input message='tns:IWebAccountingService_removeAccountingEntry'/> <output message='tns:IWebAccountingService_removeAccountingEntryResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='setAmount' parameterOrder='Integer_1 Double_2'> <input message='tns:IWebAccountingService_setAmount'/> <output message='tns:IWebAccountingService_setAmountResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='setCreditAccount' parameterOrder='Integer_1 Integer_2'> <input message='tns:IWebAccountingService_setCreditAccount'/> <output message='tns:IWebAccountingService_setCreditAccountResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='setDebitAccount' parameterOrder='Integer_1 Integer_2'> <input message='tns:IWebAccountingService_setDebitAccount'/> <output message='tns:IWebAccountingService_setDebitAccountResponse'/> <fault message='tns:IOException' name='IOException'/> <fault message='tns:AccountingException' name='AccountingException'/> </operation> <operation name='setDescription' parameterOrder='Integer_1 String_2'> <input message='tns:IWebAccountingService_setDescription'/> <output message='tns:IWebAccountingService_setDescriptionResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='setText' parameterOrder='Integer_1 String_2'> <input message='tns:IWebAccountingService_setText'/> <output message='tns:IWebAccountingService_setTextResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> <operation name='setType' parameterOrder='Integer_1 int_2'> <input message='tns:IWebAccountingService_setType'/> <output message='tns:IWebAccountingService_setTypeResponse'/> <fault message='tns:IOException' name='IOException'/> </operation> </portType> <binding name='IWebAccountingServiceBinding' type='tns:IWebAccountingService'> <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/> <operation name='calculateBalance'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='createAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='createAccountingEntry'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' use='literal'/> </output> - 270 - Anhänge <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='getAccountingEntries'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getAccounts'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getAmount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getCreditAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getCreditEntries'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getDebitAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> - 271 - use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> Anhänge </operation> <operation name='getDebitEntries'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getDescription'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getText'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='getType'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='removeAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='removeAccountingEntry'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> - 272 - use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> Anhänge </operation> <operation name='setAmount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='setCreditAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='setDebitAccount'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> <fault name='AccountingException'> <soap:fault name='AccountingException' use='literal'/> </fault> </operation> <operation name='setDescription'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='setText'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> </operation> <operation name='setType'> <soap:operation soapAction=''/> <input> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </input> <output> <soap:body namespace='http://www.fh-regensburg.de/vs_iw/webservice' </output> <fault name='IOException'> <soap:fault name='IOException' use='literal'/> </fault> - 273 - use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> use='literal'/> Anhänge </operation> </binding> <service name='WebAccountigService'> <port binding='tns:IWebAccountingServiceBinding' name='IWebAccountingServicePort'> <soap:address location='http://localhost:8080/WebAccountingService'/> </port> </service> </definitions> - 274 - Anhänge Anhang F: Changelog der Anwendung Version 3.3: - Anpassungen für JBoss 5.1.0 - Geänderte Anzeige der benötigten Argumente einer Proxy-Klasse - Bugfix im Socket2-Modul: null wurde als OID übergeben - Bugfix im Account-Task: setDescription() wurde als setAccountType() angezeigt Version 3.2: - Anpassungen für JBoss 5.0.0 - Unterstützung des neuen Nimbus Look & Feel - Bugfix beim Laden des MacOS Look & Feel Version 3.1: - Alternative Socket-Implementierung mit Data-Streams - Anpassung an JBoss 4.2.2, JBoss-WS 3.0.3 und Axis 1.4 - Gleichzeitige Kompatibilität mit Java 1.5 und 1.6 sichergestellt - Bugfixes in etlichen Klassen Version 3.0: - Interner Umbau der Benutzeroberfläche - Erweitertes Menü mit mehr Funktionen - Icons im Menü und auf Schaltflächen - besser kontrollierbare Vorgänge im Vorgangsfenster - mehrere Performance-Modi - schnellere Verarbeitung bei vielen Buchungen - zusätzliche Look & Feel Integration - Jar-Integration von Beschreibung und JavaDoc Version 2.3: - Performance verbessert, langsame Codeteile überarbeitet - Bugfixes bei den Test-Funktionen - Kompatibilität mit Java 1.6 Version 2.2: - Verbesserung und Vereinfachung der Oberfläche - Fortschrittsanzeige - Threading bei vielen Vorgängen verbessert - Locking in der Adapterschicht berichtigt - Test-Funktionen für erweiterte Schnittstelle berichtigt Version 2.1: - Reduzierte Anzahl separater Vorgänge zur Performance-Steigerung - Bugfix: Oberfläche friert unter Linux nicht mehr ein - Verbesserte Speicherung der eingestellten Menü-Optionen in einer Properties-Datei - Eingestelltes Look & Feel wird gespeichert - Unnütze Bedienelemente werden je nach Anwendungszustand deaktiviert - Eingegebene Argumente werden je Proxy-Klasse gespeichert Version 2.0: - Generische Adapterschicht zur Reduzierung von redundantem Code - Generische Vorgänge mit Callback-Schnittstellen - FiBu-Schnittstellen und Geschäftslogik für J2EE-Anwendungen angepasst - EJB-Implementierung angepasst - Schnittstelle zur Entkopplung der Geschäftslogik - Vollständiger eigener Klassenlader für den Proxy-Suchvorgang - 275 - Anhänge Version 1.6: - Zusammengefaßte Methodenaufrufe getrennt - Eigener Vorgang mit separatem Thread für jeden Aufruf - Bessere Entkopplung der Oberfläche durch eine Adapter-Schicht - Adapter-Schicht in eigenes Paket ausgelagert Version 1.5: - Absolutes Layout durch Komponenten mit Layout-Manager ersetzt - Dynamische Unterstützung von mehreren Look & Feel Auswahlmöglichkeiten - Mehr Einstell-Möglichkeiten im Menu - Threading in eigenes Paket ausgelagert - Verbesserter Code durch Eclipse-Plugins Checkstyle und Findbugs Version 1.2: - Fängt Ausnahmen besser ab - Komplett überarbeitetes Laden von Proxy-Klassen - Eigener Klassenlader mit Listener-Schnittstelle - Decoder für class-Dateien mit eigener Reflection-Schnittstelle - Menu-Einstellungen werden automatisch in Properties-Datei gespeichert Version 1.1.2: - Erste Version zur Herausgabe in der Vorlesung - Fängt mehr Ausnahmen ab - Verbesserung des Ladens von Proxy-Klassen - Beim Laden von Buchungen und Konten werden mehrere Methodenaufrufe zusammengefasst - Verbesserte Testfunktionen Version 1.1: - Entkoppelt die Schnittstellenaufrufe in separaten Threads - Kann Proxy-Klassen laden, die in einem Unterverzeichnis und gleichzeitig im CLASSPATH vorhanden sind - Eine Modul-Implementierung für jede Technologie - Testfunktionen für die Implementierungen Version 1.0: - Erste vollständig lauffähige Version - Oberfläche kann alle FiBu-Methoden ansprechen - Feste Bindung an die Schnittstellen - 276 -