Christoph Hartmann on October 20th, 2006

Einführung

geissbock_ch_2.gif


Komponentenprogrammierung mit JEE5, .Net und Web Services

Projektmitglieder:

Lars Blumberg, Michael Perscheid, Martin Sprengel, Christoph Hartmann

Aufgabenstellung:

Im Rahmen der Vorlesung “Komponentenprogrammierung und Middleware’ soll eine Projektarbeit erstellt werden. Zur Umsetzung soll eine Auswahl aus vier vorgegebenen Middelware-Plattformen (CORBA, COM, J2EE/J2ME, .NET/Compact Framework) erfolgen. Dabei sollte das Ergebnis mindestens zwei verschiedene Middleware-Plattformen oder zwei verschiedene Programmiersprachen verwenden. Um der Komponentenentwicklung gerecht zu werden, soll das Nachladen von Komponenten ohne ein erneutes Übersetzen der gesamten Anwendung möglich sein. Komponenten sollten möglichst über Rechnergrenzen hinweg verwendet werden können.

Keywords:

Autovermietung, JEE5, .Net, Web Service

Anwendungsfälle:

Um die gewählte Aufgabenstellung überhaupt einmal erfassen zu können, wurden von uns anfangs einige Anwendungsfälle ermittelt. Sie steckten den groben Funktionsumfang ab und dienten dem besseren Verständnis. Im weiteren Verlauf halfen sie bei Überlegungen der Komponentenaufteilung durch ein Durchspielen der Anwendungsfälle.

  • Kunden registrieren und authentifizieren
  • Produkte hinzufügen, bestellen, anschauen und erhalten
  • Verträge erstellen
  • Rechnungen erstellen, anschauen und ausdrucken

Dokumentation

Im Folgenden werden diese Themen näher betrachtet:

  • Technische Analyse
  • Design und Architektur
  • Implementierung
  • Design Patterns
  • Konfiguration

Zusammenfassung:

Während des gesamten Projekt wurden viele Erfahrungen gesamelt. Von der Planung bis zur Umsetzung konnte alles angewandt werden. Dabei wurde festgestellt, wie viel Zeit für welche Abschnitte benötigt wird. Für spätere Projekte kann solche Erfahrung nur sinnvoll sein. Weiterhin kann festgehalten werden, dass im Team die Aufgaben ungefähr immer gleich verteilt waren. Es kam so gut wie nie zu Streit im Team, welcher nicht einvernehmlich geklärt werden konnte. Weiterhin wurde die verteilte Arbeit an Entwicklung und Dokumentation in großem Umfang genutzt. Das Projekt kann daher mit den Worten zusammengefasst werden: „Das Leben war schön mit dem geissbock. Wir hatten viel Spaß.

Technische Analyse

Die Aufgabe einer Autovermietung verlangte förmlich nach einem Client-Server basierten Aufbau. Aus diesem Grunde mussten zwei Middleware- Plattformen gefunden werden, die auf der einen Seite für den Server und auf der anderen Seite für den Client geeignet waren. Aufgrund des selbst gesteckten Ziels nach der Verwendung von Standardtechniken, fiel die Entscheidung für die Serverseite nicht außerordentlich schwer. Das Zauberwort hieß Java. Kenntnisse mit der J2SE waren im Team schon weit verbreitet. Aus diesem Grund waren die Grundlagen schon geschaffen und wir entschieden uns für J2EE. Dies geschah vor allem deshalb, weil diese Art der Serverentwicklung heutzutage weit verbreitet ist und wir eine Einarbeitung für spätere Zeiten als sinnvoll angesehen haben. Zur Zeit des Projektbeginns liefen die Entwicklungen an der Nachfolgeversion, der Java Enterprise Edition 5 (JEE5), auf Hochtouren. Inspiriert durch einen IX-Artikel, welcher verdeutlichte, welche Vorteile die neuen Enterprise Java Beans in der Version 3 gegenüber 2.1 haben, beschlossen wir uns damit zu beschäftigen. Es sollte nur noch im Notfall auf die alte Version 1.4 zurückgegriffen werden.

Geissbock Technology

Der Persistenzmanager ist in der neuen Version ist einfacher zu handhaben und die Benutzung von Remoteschnittstellen sowie Web Services wurde dabei stark vereinfacht. Somit erfüllte die neue Version gleich zwei gestellte Ziele. Einmal eine schnelle Entwicklung von Remotekomponenten, welches die Benutzung von XDoclet erübrigte und einen guten Persistenzverwalter. Da die Implementierungen des so genannten EntityManagers auf schon vorhandenen Objekt/Relationalen Abbildungsprogrammen aufbauten (z.B. Hibernate, Toplink), erschien es nur sinnvoll diese Versionen zu testen. Allerdings muss dabei festgestellt werden, dass es sich bei JEE5 bei Projektbeginn nicht um ausgereifte Version handelte. Dies stellt daher einen kritischen Faktor während des Entwicklungsverlaufes dar. Potentiell können nicht nur eigene Fehler in der Entwicklung zum Nichtfunktionieren der Anwendung führen.Nachdem eine Entscheidung für die serverseitige Implementierung auf Basis von JEE5 gefallen war, mussten die dazugehörigen passenden Implementierungen gefunden werden. Grundsätzlich standen dabei als Application Server der JBoss mit EJB3.0 Extension und der Sun Application Server 9 (Codename: glassfish) zur freien Verfügung. Nach anfänglichem Start mit Eclipse und JBoss stellten wir dabei in dieser Konfiguration einige Unzulänglichkeiten fest. Einerseits war der JBoss nach unserem persönlichen Geschmack zu flexibel in der Anwendung von erstellten Komponenten. Dies bedeutet, dass er auch nicht standardkonforme Entwicklungen korrekt ausführt. Grundsätzlich ist solch ein Verhalten lobenswert, steht aber in Kontrast zu einem Ziel, dass die entwickelten Komponenten auf verschiedene Implementierungen der JEE5-Spezifikationen lauffähig sind. Hier sind z.B. die JBoss spezifische Extension .ejb3 oder die spezifischen JBoss Annotations zu nennen. Zusätzlich hat an Eclipse mit dem Standard Jboss-Plugins nicht gefallen, dass Build-Skripte zum Erstellen einer Komponente aus den SourceCode manuell erfolgen musste. Gerade durch den ständigen manuellen Eingriff ist es möglich, nicht standard-konforme Komponenten entstehen. Solche ein Vorgehen kann nur in der Lernphase empfohlen werden. Allerdings können auch bei automatisch generierten Komponenten Probleme entstehen. Solche Vorgehensweisen bieten aber meist eine gewisse Art an Grundabsicherung. Aus diesen Gründen suchten wir nach einer besseren Lösung.

Dabei erschien der Sun Application Server 9 als Referenzimplementierung gerade zum rechten Zeitpunkt. Er gab uns die Möglichkeit, möglichst nahe am Standard zu entwickeln. Die Arbeiten an diesem Server schritten auch kontinuierlich voran. Aus diesem Grund erschien der Application Server als Entwicklungsplattform für ideal. Weiterhin dem Sun-Lager entstammend, bedienten wir uns Netbeans in eine Development-Version. Sie unterstützte den Sun Application Server 9 und erstellte automatisch die Ant-Build-Skripte. Die nunmehr vorhandene Version 5.5 bzw. 6 enthält die Unterstützung für JEE5 von Haus aus.

Bisher wurde nur über die technischen Analysen der Serverseite berichtet. Im Folgenden wird näher auf den Client eingegangen. Als Erinnerung soll hier erwähnt werden, dass die Aufgabenstellung vorsah, dass ein Projekt aus zwei Middleware-Plattformen bestehen sollte. Daher entschieden wir uns für das .NET Framework 1.1. Eine Benutzung der Nachfolgerversion erschien uns als nicht sinnvoll. Wir sahen am Anfang keine notwendigen Features der neuen Version. Zusätzlich war die Entwicklung durch die Serverseite schon stark genug gefährdet. Aus diesem Grunde beschlossen wir auch Clientseite lieber auch Nummer sicher zu gehen. Das .NET Framework 1.1 ist mittlerweile ausgereift und weit verbreitet. Es wird häufiger auf PCs anzufinden sein, als die neue Version. (Auch wenn Microsoft die aktuelle Version per Auto-Update nachinstallieren lässt.) Das Aufsetzen eines neuen Servers ist meist einfacher, als in einem Unternehmen mit einer konservativen Haltung für alle Rechner eine neue Version freigegeben.

Einer potentielle Umsetzung für das Mono-Framework würde die Benutzung von .NET 2.0 Techniken auch entgegenstehen, da dieses Projekt noch nicht alle Features umsetzt. Allerdings ist es mit Mono auch bei .NET 1.1 nicht ohne weiteres möglich, die gesamten Features zu verwenden. Einige der von uns verwendeten Funktionen werden in dieser Version noch nicht vollständig unterstützt, so z.B. Windows Forms. Die Entscheidung für Windows Forms fiel aus dem Grund der vorhandenen Integration eines Form Designers im Visual Studio. Zwar liefert Mono mit GTK# eine Version mit FormDesigner an, doch ist eben keine integrierte Lösung vorhanden. Dieser Bereich steckt noch in der Entwicklung. Für einen ausführlicheren Vergleich sei hier auf [6] verwiesen. Als Sprache sollte dabei C# benutzt werden. Wir haben eine Implementierung für .Net in mehreren Sprachen ausgeschlossen, da allgemein bekannt ist, dass dies unter der Microsoft-Plattform möglich ist. Als Entwicklungsumgebung kam nur das Visual Studio in Frage. Dieses bietet mit Abstand die meisten Funktionen für das .Net Framework. Zum Beispiel ist die Erstellung und Verwendung von Web Services problemlos möglich. Die Funktionalität von MonoDevelop konnte uns dabei nicht überzeugen. Nachdem die Plattformen auf Clientseite und Serverseite festgelegt wurden, steht die Frage nach der Kommunikation zwischen beiden Plattformen. Hierbei kommen CORBA und Web Services in Frage. CORBA ist weit verbreitet und plattformunabhängig, hat aber den Nachteil relativ schwer benutzbar zu sein. Auch aus dem Grund, dass mehrere Teammitglieder schon Erfahrungen mit Web Services[2] hatten, viel die Entscheidung hier auf Web Services.

Dabei ist zu Beachten, dass die Einarbeitung in JEE5 schon als schwer genug angesehen wurde und daher eine zusätzliche Einarbeitung in CORBA noch einmal erheblichen zusätzlichen Aufwand bedeutet hätte. Neben der Kommunikation zwischen den Komponenten ist eine Datenhaltung unabdingbar. Dies geschieht in der heutigen Zeit am Besten mit einer relationalen Datenbank. Dabei kann aus einer Vielzahl von Produkten zu gewählt werden. Wir haben uns für MySQL 4 entschieden, da wir mit dieser Version schon gute Erfahrungen gesammelt hatten. Durch die Nutzung von Enterprise Java Beans der Version 3.0 und einem Entity-Managers sind wir jedoch nicht zwangsweise an diese gebunden. So wurde zu Beginn des Projektes auch mit der NetBeans integrierten Derby-Datenbank experimentiert. Eine Umstellung war problemlos möglich.

Als Implementierung des Entity-Managers stand die Wahl zwischen Hibernate und Toplink. Nach einigen Experimenten mit Hibernate im Sun Application Server, entschieden wir uns für die Standardkonfiguration von Sun Application Server in Verbindung mit Toplink. Nachdem hier schon beschrieben wurde, warum CORBA nicht angewandt wurde, soll noch kurz auf COM eingegangen werden. COM wird klassischerweise hauptsächlich im Bereich C++ verwendet. C++ wird von uns als gute Systemsprache angesehen, bietet aber für Businessapplikationen nicht den geeigneten Komfort. Durch den Verzicht von C++ verliert man die Möglichkeit speichereffiziente Programme schreiben zu können und gewinnt auf der anderen Seite eine automatische Garbage Collection. Besonders diese Feature haben wir als unverzichbar angesehen und deshalb auf COM als Lösungsansatz ausgeschlossen.

Pfd

Middleware:

Java Application Server:

IDE:

Database:

Entity Manager:

Literatur:

Design und Architektur

Das Gesamtsystem lässt sich zunächst als Client-/Server-Anwendung charakterisieren. In diesem Kapitel soll die Architektur anhand dieser beiden Teilsysteme (Client und Server) erläutert werden. Allgemein wird in diesem Projekt eine klassische Drei-Schichten-Architektur verwendet, wobei alle Teilsysteme auf die Schichten

  • Presentation Layer
  • Business Logic Layer
  • Persistence Layer

aufgeteilt werden. Dies hat den Vorteil, dass für verschiedene Oberflächen die gleiche Applikationslogik verwendet werden kann. Diese Aufteilung hat sich gerade im Bereich der Business Anwendungen bewährt und ist heute weit verbreitet.

Complex Geissbock Architecture
Die Serveranwendung ist für die Bereitstellung der Services verantwortlich, die ein Vermieter zum Abwickeln von Mietverträgen und zum Administrieren von Mietgegenständen benötigt. Sie implementiert die gesamte Geschäftslogik und ist für die Haltung der persistenten Daten zuständig. Weiterhin bietet sie eine grafische Nutzeroberfläche für Kunden, die über das Internet erreicht werden kann.

Die Clientanwendung ist diejenige Software, die auf ein oder mehreren Personalcomputern der Vermieter in den Büros der Autovermietung ausgeführt wird. Sie soll die komplette Administration des Datenbestandes in einfachster Weise unter Verwendung eines Graphical User Interfaces erlauben. Die Clientanwendung kommuniziert mit dem Serversystem, um Datenbestand anzufordern und um den Datenbestand zu aktualisieren. Grundlage für die Clientanwendung ist ein von uns entwickeltes, allgemeines Plugin Framework, das erst zur Laufzeit die domänespezifischen Plugins der Autovermietung nachlädt.

Server

Während der Analyse der Anwendungsdomäne wurde festgestellt, dass es sich bei einer Vermietung um ein allgemeines Problem handelt, welches unabhängig von zu vermietenden Produkten besteht. Aus diesem Grund beinhaltet die Serveranwendung einen Plugin-Mechanismus, der die Vermietung verschiedenster Produkte ermöglicht. Gleichzeitig besteht nicht die Notwendigkeit Preise, Produkte sowie Mietverträge in einer Komponente zu verwalten.

Die Serveranwendung wurde deshalb auf die fünf Komponenten CarManagement und RentalManagement, UserManagement sowie WebInterface und WebServices aufgeteilt. Zu Demonstrations- und Testzwecken wurde eine weitere Komponente ObjectManagerPlugIn eingeführt, welche einen DVDManager ohne Datenbankanbindung realisiert und hier nicht näher erläutert werden soll.

Geissbock Server Architecture

CarManagement

Das CarManagement hat die Aufgabe der serverseitigen Fuhrparkverwaltung. Die Komponente besteht aus einem Main-Controller, der als Stateless Session Bean implementiert ist und mehreren Entity-Beans, die persistente Daten repräsentieren.

Die Datenstruktur des CarManagement wurde zunächst sehr allgemein gewählt und resultiert in den Klassen Vehicle und VehicleModel, die ein allgemeines Fahrzeug und seine Zugehörigkeit zu einem Fahrzeugmodell repräsentieren. Diese allgemeine Herangehensweise dient der Widerverwendbarkeit in anderen Komponenten, wie zum Beispiel einem Fahrrad-Management. Von diesen allgemeinen Fahrzeugklassen sind die Klassen Car und CarModel abgeleitet, die ein Auto und das dazugehörige Automodell abbilden. Zusätzlich ist der Datentyp CarClass eingeführt worden, der die Möglichkeit bietet, Autos zu bestimmten Gruppen bzw. Fahrzeugklassen zuzuordnen.

Um zu garantieren, dass zu einem konkreten Vehicle immer das richtige VehicleModel erzeugt wird, enthält das CarManagement die Factory
CarFactory (s. Abstract Factory), die das Interface VehicleFactory implementiert.

Wie schon erwähnt, bietet die die Komponente RentalManagement einen Plugin-Mechanismus, der die Vermietung von verschiedenen Produkten ermöglicht. Um einem Plugin zu genügen, verfügt das CarManagement über den Adapter CarManagementRentalPlugIn (s. Adapter), der dem RentalManagement die Methoden des Main-Controllers ControllerBean zugänglich macht. Dazu wird zusätzlich zum Main-Controllers in einer separaten Stateless Session Bean das Interface RentalManagementPlugIn implementiert.

Abschließend sollen hier noch die Transfer Objekte (s. Transfer Objects) genannt werden, welche einer eins zu eins Abbildung der Entity-Beans
Car, CarModel und CarClass entsprechen.

RentalManagement

Das RentalManagement ist die zentrale Komponente der Anwendung auf Serverseite. Sie dient als Bindeglied zwischen den einzelnen Komponenten und bietet alle Funktionen zum Verwalten und Erzeugen von Verträgen. Wie schon erwähnt, ist es dem RentalManagement möglich, durch seinen Plugin-Mechanismus, unterschiedlichste Produkte an einen Vertrag zu binden.

Das RentalManagement besteht zunächst auch aus einem Main-Controller, dem RentalAdmin, der wiederum als Stateless Session Bean implementiert ist. Des Weiteren sind in der Komponente mehrere Entity-Beans enthalten, die zur Repräsentation der Vertragsdaten genutzt werden.

Ein Vertrag im RentalManagement wird durch die Klasse Contract dargestellt. Mit diesem Vertrag verbunden ist zum einen, eine gebuchte Kategorie BookedCategory und ein zugewiesener Artikel BookedArticle und zum anderen ein bestimmter Nutzer. BookedCategory und BookedArticle implementieren jeweils das Interface CommonBooking.

Unabhängig von den bisher beschriebenen Datenstrukturen ist der RentalAdmin in der Lage Rechnungen für Verträge zu erstellen. Hierfür stehen die Entity-Beans Bill und BillRow zur Verfügung. Ein Objekt der Klasse Bill entspricht einer Rechnung, die aus mehreren Rechnungszeilen (BillRow) besteht. Beim Abschließen eines Vertrags wird eine Rechnungszeile generiert, die zunächst keiner Rechnung zugewiesen ist und erst durch den Aufruf der Methode generateBill() an eine gebunden wird.

Wesentlicher Bestandteil des RentalManagement ist der PlugInFinder. Dieser ist eine einfache Java-Klasse, die das Design Pattern Singleton nutzt, um nicht durch mehrere Instanzen unnötige Ressourcen zu belegen. Die Aufgabe des PlugInFinder besteht darin, Komponenten zu finden, die zum einen das Interface RentalManagementPlugIn implementieren und zum anderen unter einem bestimmten JNDI-Verzeichnis registriert sind. Alle Komponenten, welche die Suchklasse findet, werden durch diese in einer lokalen Liste gehalten und können vom RentalManagement zur Vermietung herangezogen werden.

Auch im RentalManagement werden Transfer Objekte zur Übertragung der Daten genutzt. Ein spezielles Transfer Objekt ist das PlugInInfoTO, zu dem es keine zugehörige Entity-Bean gibt. Dieses stellt Informationen über ein Plugin zur Verfügung.

UserManagement

Eine weitere Komponente ist das UserManagement, dessen Aufgabe darin besteht alle Nutzer des Systems zu verwalten. Das UserManagement ist ein Paradebeispiel für die Implementierung einer Komponente, da eine Nutzerverwaltung ein immer wieder auftretendes Problem bei Enterprise Anwendungen darstellt.

Wie schon die zuvor beschriebenen Komponenten besteht auch das UserManagement aus einem Main-Controller (UserAdminBean) und mehreren Entity-Beans. Ein Nutzer des Systems wird durch die Klasse UserData repräsentiert, die alle wesentlichen Daten dieses Nutzers enthält. Wichtig ist an dieser Stelle, dass auch Benutzername und Password gespeichert werden, die für einen Login genutzt werden.

Das UserManagement bietet das Interface IUserFactory an, über das man Zugriff auf eine Factory erhalten kann. Diese ist für die Bereitstellung
aller UserData-Objekte verantwortlich.

Web Interface

Das Web Interface implementiert die Anmeldung und regelt die Steuerung. Alle Zustände einer Bestellung werden in dieser gespeichert. Es kommen in dieser Umsetzung nur JSP-Seiten und Servlets zur Geltung. JSP hat aber einige Nachteile. Eine vollständige komponentenbasierte Entwicklung ist damit nur mit Abstrichen möglich. Eine neue Komponente kann dabei nicht seine eigene Darstellung mitbringen und sich somit vollständig selbst beschreiben. Hierbei kommt das häufige Problem zum Vorschein, dass nur die Syntax einer Komponentenbeschreibung vorhanden ist und eben keine Information über die Semantik bekannt ist. An dieser Stelle wurden auch andere Lösungen evaluiert. Die Portletspezifikation bietet dabei klare Vorteile. So kann im Web ein Desktop erstellt werden, welcher das Design vorgibt. In einen Portal Server werden dann die neuen Portlets hineindeployed. Allerdings war es zu diesem Zeitpunkt nicht einfach möglich, ein Portal Server mit dem Sun Application Server zu verwenden. Dabei gibt es verschiedene Portal Server von der Apache Foundation (z.B. Jetspeed) oder auch von JBoss
(JBoss Portal Server). Da allein die Programmierung von Java Enterpise Software mit dem Sun Application Server und Netbeans schon mindestens 1GB RAM voraussetzt, wurde auf eine Erweiterung auf einen Portal Server verzichtet. Sollte allerdings später eine weitere Verwendung geplant werden, so sollte die Erstellung von Portlets noch einmal überdacht werden.

Web Services

Damit die Schnittstellen der Komponenten auch außerhalb der Javawelt benutzt werden können, gibt es eine extra WebServices-Komponente. Sie ist eine SecurityFacade (s. Facade) und kapselt die Zugriffe auf die einzelnen Komponenten. WebServices sind mit der JEE5 besonders einfach zu realisieren, da nur zusätzliche Annotations wie @WebServices oder @WebMethod benötigt werden. Der Application Server erzeugt dann beim deployen automatisch das benötigte WSDL-File und registriert den angegebenen Endpoint im Server. Es ist dabei nicht nötig WebServices in einer separaten Komponente zu erstellen. Dies hat sogar ein gravierenden Nachteil. Alle Aufrufe müssen einmal umgeleitet werden. Obwohl der Vorgang dadurch etwas länger dauert, gewinnt man bei der Verwendung den Vorteil, WebServices während der Laufzeit des Servers an und auszuschalten. Dies ist durch deployen und undeployen möglich. Andernfalls müssten die zu benutzenden Komponenten ausgetauscht werden, was im Echtbetrieb zu Problemen führen kann.

Implementierung

Server (JEE5)

Verwendung eines Entity Managers

Mit der Einführung der Java Persistence API als Teil der EJB 3.0 Spezifikation wird die Persistenz über einen so genannten Entity Manager abgebildet. Wie anfangs schon erwähnt, gibt es verschiedene Implementierungen des Entity Manager, wobei in diesem Projekt der Toplink Entity Manager zum Einsatz kommt. Der Entity Manager dient als eine Art Datenzugriffsschicht und hat die Aufgabe persistente Objekte zu verwalten. Es gibt zwei verschiedene Möglichkeiten einen Entity Manager zu verwalten, zum einen durch die Applikation und zum anderen durch den J2EE Container, wobei die zweite Variante der von uns im Projekt verwendeten entspricht. Als erster Schritt muss eine Konfigurationsdatei persistence.xml (s. Listing) angelegt werden, welche Informationen über den Entity Manager, persistente Klassen und Objekt/Relationale-Mappings enthält. Diese wird dann später einer Komponente im META-INF Ordner beigefügt.

Listing persistence.xml

Ein Knoten persistence-unit entspricht einer möglichen Implementierung des Entity Managers, zu dem dann zusätzlich der JNDI-Name einer Datenquelle angegeben wird. Um den Entity Manager benutzen zu können, muss die zu verwendende Persistenzeinheit als Attribut der @PersistenceContext Annotation im Source Code angegeben werden. Der Entity Manager bietet dann eine Menge von Funktionen, die zur Erzeugung und Verwaltung von Datenklassen benutzt werden kann (s. Listing).

// Erzeugung des Entity Managers
@PersistenceContext(name="Toplink")
private EntityManager em;
// Beispiele für die Benutzung des Entity Managers
// Persistentmachen eines Objektes
...
tempCarClass = new CarClass();
em.persist(tempCarClass);
em.flush();
... // Erzeugen einer Query mit der EJB Query Language
...
Query query = em.createQuery(
"UPDATE CarClass cc " +
"SET cc.carClassName = :carClassName " +
"WHERE cc.carClassID = :carClassID")
.setParameter("carClassID",carClassTO.carClassID)
.setParameter("carClassName",carClassTO.carClassName);
...

Plugin Mechanismus RentalManagement

Listing Plugin über JNDI suchen:

// Kontext aufbauen
InitialContext ic = new InitialContext();
// Root für die PlugIns
String plugInRoot = "rentalmanagement/plugins";
String separator = "/";
// Fragt alle registrierten JNDI Namen ab
NamingEnumeration allPlugIns = ic.list(plugInRoot);
// geht alle gefundenen Klassen durch
while( allPlugIns.hasMore()) {
try {
NameClassPair ncp = (NameClassPair)allPlugIns.next();
// holt sich eine Objektreferenz
Object ob = ic.lookup(plugInRoot + separator
+ ncp.getName());
// kontrolliert, ob das korrekte Interface
// implementiert wird
if (ob instanceof RentalManagementPlugIn) {
// Schaut, ob die Objektinstanz schon in der Liste
// eingetragen ist. Beim glassfish passiert dies
// immer. Er erstellt automatisch interne Jndi-Namen,
// welche allerdings alle das gleiche Objekt
// zurückgeben
RentalManagementPlugIn plugInProxy =
(RentalManagementPlugIn)ob;
if (!plugInMap.containsValue(plugInProxy))
// Der Klassenname wird als eindeutiger Identifier
// benutzt. Nur so kann ein gespeichertes Objekt in
// der Autovermietung nach einem Neustart des
// Servers wieder einem PlugIn zugeordnet werden
// speichert dies in der PlugIn Liste
plugInMap.put(plugInProxy.getIdentifier() ,
plugInProxy );
}
} catch (Exception e) {
// falls ein PlugIn nicht funktioniert,
// können dennoch die anderen verwendet werden.
e.printStackTrace();
}
}
return plugInMap;

Entity-Vererbung im RentalManagement

Im RentalManagement sind die Datenklassen BookedArticle, BookedCategory sowie Contract als Entity-Beans implementiert. Mit der Annotation @Entity wird eine Datenklasse gekennzeichnet, die vom Entity Manager verwaltet werden soll. Die @Id Annotation gibt an, welches der Attribute einer Klasse für die eindeutige Identifikation genutzt werden soll. Im Listing EntityInheritanceOne ist für die Klasse CommonBooking das Attribut objectId gewählt worden. Die Generierung dieser wird durch den Entity Manager übernommen, der eine passende Generierungsmethode wählt. Da die Klassen BookedArticle und BookedCategory eine andere Entity-Bean erweitern, muss man sich Gedanken darüber machen, welche Strategie zur Aufteilung auf Tabellen der Entity Manager für die Speicherung dieser verwenden soll. Dem Listing EntityInheritanceOne ist die Annotation Inheritance zu entnehmen, mit deren Hilfe man genau diese Speicherungsstrategie angeben kann. Hier ist eine Strategie gewählt worden, bei der jede Hierarchieebene in einer eigenen Tabelle gespeichert wird. Für eine detailiertere Beschreibung der Annotationen verweisen wir an dieser Stelle auf die Spezifikation der EJB 3.0 Persistence API.

Listing EntityInheritanceOne:

@Entity
public class BookedArticle extends CommonBooking {
}
@Entity
public class BookedCategory extends CommonBooking {
}
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class CommonBooking {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int objectId;
/**
* ObjektID
*/
protected String objectType = null;
...
}
Entity-Vererbung im CarManagement

Auch im CarManagement besteht eine Vererbungshierarchie, bei der Entity-Beans beteiligt sind. Anders als im RentalManagement, sind die Entity-Beans jedoch nicht von anderen Entity-Beans abgeleitet, sondern von abstrakten Java Klassen. Das bedeutet also, dass dem Entity Manager mitzuteilen ist, welcher Aspekt der Superklasse mit zum persistenten Zustand einer Entity-Bean gehört. Durch die Angabe der Strategie InheritanceType.JOINED, werden Subklassen sowie Superklassen jeweils in eigenen Tabellen gehalten. Die Tabellen der Subklassen beinhalten dabei nur die Attribute der Subklasse, die nicht vererbt wurden. Durch die Verwendung einer InheritanceType.SINGLE_TABLE Strategie werden alle Klassen einer Hierarchieebene in einer eigenen Tabelle gespeichert. Die zusätzliche Angabe einer Discriminator-Column legt die Spalte und den Wert fest, der zur Unterscheidung mehrerer Subklassen genutzt wird. Diese beiden Strategien bieten gute Unterstützung für polymorphe Relationen zwischen Entity-Beans. Es existiert noch eine dritte Strategie InheritanceType.-TABLE_PER_CLASS, bei der jede Klasse in einer eigenen Tabelle gehalten wird. Diese Variante wurde im Projekt nicht genutzt, da sie schlechte Unterstützung für polymorphe Beziehungen bietet.

@Entity
@Table(name="VehicleModel")
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class VehicleModel implements Serializable{
/*
*ID eines Automodells
*/
@Id
@GeneratedValue(strategy=GenerationType.TABLE)
protected int vehicleModelID;
/*
* Repräsentiert den Namen des Fahrzeugmodells
*/
protected String modelName;
/*
* Enthaltenes Bild
*/
@Lob()
@Basic(fetch=FetchType.LAZY)
// nur notwendig, da EntityManager Blob nicht
// automatisch erkennt
@Column(name="PICTURE", columnDefinition="BLOB")
protected byte[] picture;
...
}
@Entity
@Table(name="CarModel")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="CARMODEL",
discriminatorType=DiscriminatorType.STRING)
public class CarModel extends VehicleModel implements Serializable {
/**
* Autoklasse
*/
@ManyToOne()
private CarClass carClass;
...
}

Beziehungen zwischen Entities im RentalManagement

Die im RentalManagement verwendete Entity-Bean Contract enthält Assoziationen zu den Entity-Beans BookedCategory und BookedArticle. Die Persistence API bietet hier mehrere Annotationen, die zur Beschreibung von Assoziationen benutzt werden können. Die Annotation @OneToOne gibt eine 1:1 Beziehung zu einer anderen Entity-Bean an, wobei über das Attribut CascadeType.PERSIST der Entity Manager angehalten wird, auch die assoziierte Entity-Bean zu speichern. Des Weiteren existieren die Annotationen @ManyToOne, @OneToMany sowie @ManyToMany die analog n:1, 1:n und n:m-Beziehungen angeben.

Listing Beziehnungen zwischen Entities

@Entity()
@Table(name="CONTRACT")
public class Contract implements Serializable{
...
/**
* Vertrag gilt ab
*/
@Temporal(TemporalType.DATE)
private Date fromDate = null;
/**
* Vertrag gilt bis
*/
@Temporal(TemporalType.DATE)
private Date toDate = null;
...
/**
* Gebuchte Kategory
*/
@OneToOne(cascade=CascadeType.PERSIST, optional=true)
private BookedCategory bookedCategory = null;
/**
* Erhaltenen Artikel
*/
@OneToOne(cascade=CascadeType.PERSIST, optional=true)
private BookedArticle receivedArticle = null;
...
}

Implementierung von Web Services

Web Services in Java werden mit Hilfe der Java API for XML Web Services (JAX-WS) und der Java API for XML-based RPC (JAX-RPC) umgesetzt, welche Unterstützung für den Aufruf von Web Services über das SOAP/HTTP-Protokoll bieten. Die Abbildung von Java Klassen auf XML wird durch die Java Architecture for XML Binding (JAXB) und JAX-WS definiert. Der Entwickler einer JEE5 Anwendung muss diese Details jedoch nicht berücksichtigen. Für eine Java Klasse, die mit der Annotation @WebService gekennzeichnet ist, wird automatisch eine Web Service Beschreibung generiert. Dazu gehört unter anderem ein XML-Schema, das die Abbildung der Datentypen beschreibt, sowie eine WSDL-Datei, die den Web Service allgemein beschreibt. Der Service Endpoint eines Web Service wird abschließend im Application Server registriert.
Mit Hilfe der @WebMethod Annotation können gezielt Methoden angegeben werden, die mittels Web Service veröffentlicht werden sollen. Wird keine Methode derart annotiert, so werden alle als public gekennzeichneten Methoden der Klasse angenommen.

Listing Implementierung von Web Services

@WebService( serviceName="CarManagementWebService")
public class CarAdmin { //implements ControllerRemote{
/**
* Referenz auf die Komponente
*/
@EJB(name =
"de.geissbock.carmanagement.session.ControllerRemote")
private ControllerRemote carAdmin;
...
/**
*Fügt eine neue Autoklasse hinzu
*
* @param carClassName - Name der Autoklasse
* @return int - ID der neuen Klasse, -1 bei Fehler
*/
@WebMethod
public int addCarClass(String carClassName) {
try {
return carAdmin.addCarClass(carClassName);
}
catch (Exception e)
{
e.printStackTrace();
return -1;
}
}
...
}

Wer sich die Quellcodeprojekte etwas näher anschaut, wird feststellen, dass eine gemeinsame verwendete InterfaceLibrary vorhanden ist. In dieser werden die Schnittstellen der einzelnen Komponenten gehalten, sowie die gemeinsam benötigten Transfer Objekte. Dadurch können die Interfaces für EJB-Referenzen benutzt werden. Bei dem Web Service Projekt konnte dies leider nicht ausgenutzt werden, da der ClassLoader die Dateien aus jars nicht findet. Konkret machte hier die anfägliche Implementierung des Sun Application Servers bei der Generierung des WSDL-Files Probleme. Aus diesem Grunde befinden sich hier die Elemente der InterfaceLibrary noch einmal.

Design Patterns

Während der Designphase des Projektes wurden von uns mehrere Design Patterns [3], sowohl auf Client als auch auf Serverseite identifiziert. Einige dieser Design Patterns sind schon durch die Benutzung des JEE5- bzw. .Net-Frameworks vorgegeben, andere wiederum wurden von uns implementiert.

Abstract Factory

Auf Serverseite wurde unter anderem das Design Pattern „Abstract Factory“ verwendet. Die Komponenten CarManagement und UserManagement implementieren jeweils eigene Factories.

Im CarManagement wurde ein abstrakter Datentyp Vehicle eingeführt, der wesentliche Fahrzeugeigenschaften aufweist. Andere konkrete Fahrzeuge, wie Autos (Klasse Car) können von diesem abgeleitet werden. Ein Fahrzeug hat in unserer Komponente immer einen Modell zugewiesen. Um sicherzustellen, dass zu einem Modell immer das richtige Fahrzeugexemplar zurückgegeben wird, haben wir an dieser Stelle eine Factory implementiert, die genau diese Aufgabe übernimmt. Es existiert dazu ein Interface VehicleFactory, das von speziellen Factories wie der CarFactory implementiert werden kann. Die CarFactory liefert hier nur Car- und CarModel-Objekte zurück. Auch im UserManagement wird eine Factory genutzt um UserData-Objekte zu erzeugen.

Durch die Verwendung von Session-Beans des JEE5-Frameworks wird implizit das Design Pattern „Factory“ genutzt. Um beispielsweise einen Service aufzufinden, muss zunächst ein Service Locator (InitialContext und JNDILookup) aufgerufen werden, welcher eine ServiceFactory nutzt.

Adapter

Für uns sehr essentiell ist die Nutzung des Design Patterns „Adapter“. Nur durch dieses ist es der Komponente RentalManagement möglich Komponenten, die Objekte verwalten, zu nutzen. Jede objektverwaltende Komponente hat zumeist ihre eigene individuelle Schnittstelle, die erst auf die einheitliche Schnittstelle des RentalManagement abgebildet werden muss. Zur Realisierung dessen, kann jede objektverwaltende Komponente (z.B. CarManagement) ihren eigenen Adapter haben, der ein Interface RentalManagement-PlugIn des RentalManagement implementiert. Dieses Adapter-Interface bietet gleichzeitig die Möglichkeit Informationen über die Komponente zurückzugeben.

Builder

In Abbildung 16 sieht man ein Klassendiagramm, welches das Buildern Pattern beschreibt. Es dient dazu den komplexen Erzeugungsmechanismus der .Net Reflection vom restlichen Plugin Host zu kapseln. Nebenbei bietet es
aber auch Flexibilität in dem der PluginDirector dem PluginBuilder die Baureihenfolge für die Plugins vorgibt. Am Ende dieses Anweisungsprozesses steht das fertige Plugin im Builder zur Verfügung. Eigentlich sollte es laut [3] eine Funktion GetResult() geben, doch wurde diese mittels der Eigenschaft currentPlugin ersetzt.

Facade

Eine Facade bietet eine einheitliche Zugriffsmöglichkeit auf Subsysteme bzw. eine Menge von Interfaces. Auf Serverseite wird eine Web Service Facade verwendet, die alleWeb Service Methoden zusammenfasst und zur Verfügung stellt. Diese Komponente WebServices wird durch mehrere Stateless Session Beans implementiert, die ihrerseits schon das J2EE Design Pattern „Session Facade“ nutzen. Zurzeit leitet die Web Service Facade Methodenaufrufe nur an die einzelnen Komponenten weiter (fungiert also als Adapter), jedoch ist die Erweiterung zu einer Securityfacade leicht möglich.

Observer

Im Vermieterclient greifen mehrere Plugins auf die selben Web Servies zu. So wird der User Management Web Servie von den clientseitigen Plugins UserManagement Plugin sowie Rental Management Plugin benutzt. Ebenfalls das Rental Management Plugin sowie das Car Management Plugin verwenden den Car Management Web Service. Da wir die dem Benutzer die Möglichkeit offen lassen wollten, die URLs zu editieren, unter denen die Web Services erreichbar sind, sind diese in den Programmeinstellungen änderbar. Um nun noch zu verhindern, dass jedes Plugin die URLs zu den gemeinsam verwendeten Web Services separat verwaltet, haben wir das Global Rental Settings Plugin entwickelt. Hierbei stellt das Global Rental Settings Plugin das Subject dar, welches globale Einstellungen, wie eben die Web Service URLs, bereitstellt (Abb. 20). Das Subject kennt seine Observer. Das sind diejenigen Plugins, die sich beim Initialisieren beim Subject registrieren und jedesmal informiert werden, wenn sich die URLs ändern. Daraufhin können die Plugins ihre verwendeten Web Service URLs aktualisieren. Ein ausführliches
Listing der Implementierung dieses Patterns befindet sich im Abschnitt Implementierung 3.2.8.

Singleton

Das Design Pattern „Singleton“ ist ein einfach zu implementierendes Pattern. Von einer Singleton-Klasse existiert immer nur ein Objekt, welches beim ersten Zugriff automatisch instanziert wird. In unserem System haben wir die Klasse PlugInFinder der Komponente Rentalmanagement als Singleton implementiert, da diese nur einmal initialisiert werden muss und mehrere Instanzen hier nicht sinnvoll erschienen.

Strategy

Das Strategy Pattern wird intern im .Net Framework zur beliebigen Sortierung von Arraylisten benutzt. Es wird mit dem Interface IComparer zur Verfügung gestellt, welches der Anwender implementieren darf. Zur Übergabe an die Liste wird es als Parameter der Funktion Sort() übergeben. Die Funktion Compare() aus dem Interface liefert einen Integer Wert zurück, welcher die zwei Vergleichsobjekte in Relation tellt. Anwendung findet das Ganze im Rental Management Plugin. Hier werden zwei Vertragsobjekte miteinander verglichen um sie danach nach ihrem Datum zu sortieren.

Transfer Objects

Das Design Pattern „Transfer Objects“ entstammt dem J2EE Design Pattern Katalog und bietet die Möglichkeit zusammenhängende Daten über Komponentengrenzen bzw. Plattformgrenzen hinweg auszutauschen. Ein Transfer Objekt enthält dabei nur Wert- und keine Referenzattribute. Durch die Verwendung dieses Design Patterns haben wir in unserer Applikation mehrere Probleme gelöst. Zum einen ist es problematisch Referenzparameter mittels Web Service zwischen zwei verschiedenen Middlewareplattformen auszutauschen. Außerdem konnten wir unsere Service-Methoden derart gestalten, dass keine langen Parameterlisten übergeben werden müssen (diese könnten sich ohnehin häufig ändern oder verlängern). Auf der anderen Seite werden unsere Entity-Beans nicht nach außen gereicht, was wiederum bedeutet, dass eine Änderung an einer Entity-Bean nicht zwangsweise die Änderung einer Service-Methode zur Folge hat. Es gibt mehrere Möglichkeiten der Umsetzung dieses Design Patterns. Wir möchten hier kurz die von uns gewählte Variante darstellen, bei der jede Enity-Bean mit entsprechenden Transformationsmethoden ausgestattet wurde (am Beispiel der Klasse Car):

  • [3] E. Gamma, R. Helm, R Johnson und J. Vlissides Design Patterns
    Addision-Wesley, 1995

Leave a Reply