Introspektive Konfiguration eines Portalsystems für das Wissensmanagement Ausarbeitung zum Systementwicklungsprojekt WS 2003/04 Katharina Brendebach Katy Kirsche Betreuer: Dipl. Ing. Thomas Büchner Lehrstuhl für Software Engineering betrieblicher Informationssysteme Prof. Dr. Florian Matthes Technische Universität München Inhaltsverzeichnis Inhaltsverzeichnis _______________________________________________________________ i Abbildungsverzeichnis __________________________________________________________ ii 1. 2. Einleitung ________________________________________________________________ 1 Java Properties ____________________________________________________________ 2 2.1. 2.2. 2.3. 2.4. 3. Konzept _____________________________________________________________________ 4 Funktionsweise _______________________________________________________________ 6 Vorteile ____________________________________________________________________ 12 Nachteile ___________________________________________________________________ 13 Zusammenfassung ____________________________________________________________ 13 Introspektion_____________________________________________________________ 14 4.1. 4.2. 4.3. 4.4. 4.5. 5. 2 2 3 4 Java Preferences ___________________________________________________________ 4 3.1. 3.2. 3.3. 3.4. 3.5. 4. Funktionsweise _______________________________________________________________ Vorteile _____________________________________________________________________ Nachteile ____________________________________________________________________ Zusammenfassung _____________________________________________________________ Konzept ____________________________________________________________________ Funktionsweise ______________________________________________________________ Vorteile ____________________________________________________________________ Nachteile ___________________________________________________________________ Zusammenfassung ____________________________________________________________ 14 14 18 19 19 Resümee_________________________________________________________________ 19 Literaturverzeichnis ___________________________________________________________ 20 i Abbildungsverzeichnis Abbildung 1: Namensräume in Properties- Datei _________________________________________________ 2 Abbildung 2 UML- Diagramm der Preferences API (Teilausschnitt) __________________________________ 5 Abbildung 3 Java- Klasse mit Preferences ______________________________________________________ 7 Abbildung 4 Knotenhierarchie in der Windows- Registry___________________________________________ 7 Abbildung 5 Codebeispiel für node()- Methode __________________________________________________ 8 Abbildung 6 Resultat der node()- Methode in der Windows- Registry _________________________________ 8 Abbildung 7 Exportierung von Preferences in XML- Dateien _______________________________________ 9 Abbildung 8 SystemNode.xml _______________________________________________________________ 10 Abbildung 9 UserTree.xml _________________________________________________________________ 11 Abbildung 10 Import aus XML- Datei in Preference- Baum________________________________________ 11 Abbildung 11 Ausschnitt aus der GUI des Online Brokers des SEBIS- Lehrstuhls ______________________ 15 Abbildung 12 Dieser Code erzeugt einen Attribut- Knoten in der GUI _______________________________ 16 Abbildung 13 Interfaces und Klassen des Connect- Packages (Teilausschnitt) _________________________ 17 1. Einleitung Bei der Benutzung von Software ist es oftmals nötig, dass der Benutzer oder der Systemadministrator benutzerspezifische Einstellungen machen möchte, um die Software an die persönlichen Vorlieben anzupassen oder um die Software auf die zu erfüllenden Anforderungen besser einzustellen. Das Festlegen dieser Voreinstellungen nennt man Konfiguration. Beim Konfigurieren der Software, d.h. der Voreinstellung oder Veränderung der Bedienungsparameter, werden Konfigurationsdaten erzeugt. Diese Konfigurationsdaten müssen verwaltet werden, damit sie abgerufen und bearbeitet werden können. Bei den Konfigurationsdaten unterscheidet man zwischen Benutzerdaten (User data) und Systemdaten (System data). Je nachdem um welche Art von Konfigurationsdaten es sich handelt, ergeben sich unterschiedliche Ansprüche an die Verwaltungskonzepte. Während der Softwarebenutzung werden neben den Konfigurationsdaten auch so genannte Applikationsdaten erzeugt (wie z.B. Schriftdokumente, Datenbankeinträge). Die Verwaltung dieser Daten soll aber nicht das Thema dieser Arbeit sein. Im Folgenden werden die zwei Arten von Konfigurationsdaten genauer spezifiziert: User Data • Benutzerdaten sind aktive Daten, die ständig erzeugt und geändert werden. • Diese Daten werden dynamisch zur Laufzeit generiert. • Das Handling der Daten ist unwichtig, weil das System die Daten selbstständig verwaltet. Der Administrator muss keine Konfigurationen vornehmen, d.h. die Konfigurationsdaten müssen nicht eingesehen werden. • Die Daten sind nicht essentiell für die Funktionalität der Applikation. • Es bedarf keiner Kenntnis über Art und Speicherort der Attribute. • Es ist keine Dokumentation notwendig. Beispiel: Der Benutzer muss Möglichkeiten haben für sich spezifische Einstellungen vornehmen zu können, wie z.B. in der Entwicklungsumgebung Eclipse durch die Wahl der JDK- Version. In Eclipse ist dies über den Menüpunk Preferences möglich. System Data • Systemdaten sind statische Daten. • Diese Daten werden einmalig vor dem Systemstart gesetzt. • Das Handling ist sehr wichtig, weil die Daten editiert und konfiguriert werden müssen und dabei eingesehen werden müssen. • Die Daten sind systemkritisch und essentiell für die Funktionalität der Applikation. • Es ist wichtig zu wissen, wo ein Attribut gesetzt und wo es gespeichert wird. • Die Dokumentation der Einträge ist sehr wichtig. Beispiel: Die Entwicklungsumgebung Eclipse, die in Java programmiert wurde, benötigt zum Systemstart bestimmte Konfigurationsdaten. 1 Im Folgenden werden drei Ansätze vorgestellt, wie man Konfigurationsdaten verwaltet: • Der althergebrachte java.util.Properties- Ansatz • Der neue komfortablere java.util.prefs.*- Ansatz • Der innovative Ansatz der Introspektion mit GUI Wie sich jeweils die vorgestellten Konzepte für die Verwaltung von User und System data eignen, wird am Ende jedes Kapitels resümiert. 2. Java Properties 2.1. Funktionsweise Dieses Konzept zur Konfigurationsdatenverwaltung wurde von Sun entwickelt und ist seit dem JDK 1.0 verfügbar. Die Properties werden in Properties- Objekte geschrieben, die wiederum in Textdateien gespeichert werden. Um Properties zu bündeln, wie z.B. die Properties eines Packages, kann eine Hierarchie von Namensräumen eingeführt werden. myapp.payment.SSLPort=10256 myapp.payment.SETPort=10257 myapp.admin.listenPort=10258 Abbildung 1: Namensräume in Properties- Datei Die Properties eines Softwaresystems können allesamt in eine Datei geschrieben werden, oder auch in verschiedene Dateien, um z.B. die Properties für bestimmte Teile einer Anwendung voneinander zu trennen. 2.2. Vorteile Das Properties- Konzept stellt einen sehr simplen Ansatz für die Verwaltung von Konfigurationsdaten dar. Durch die Speicherung der Properties in Dateien ist ein Plattformwechsel einfach möglich. Es muss nur die Properties- Datei kopiert werden, damit alle Konfigurationen erhalten bleiben. Es ist ebenfalls leicht ein Vergleich zwischen verschiedenen Versionen von Konfigurationen über diverse Diff- Tools wie z.B. ExamDiff Pro möglich. 2 Zudem ist es dem Programmierer möglich, die Properties ausführlich in der Properties- Datei zu dokumentieren. 2.3. Nachteile Das Properties- Konzept erschwert die Skalierbarkeit. Je mehr Properties- Dateien angelegt werden, desto schwieriger wird es für den Programmierer den Überblick zu behalten. Problematisch sind auch die im Code fest verdrahteten Properties- Dateinamen. Zudem kann es bei der Namensgebung der Properties zu Namenskollisionen kommen, wenn zwei Properties in einer Datei gleich benannt werden. Sehr schwierig gestaltet sich auch die Konsistenzhaltung der Konfigurationsdaten. Der Programmierer muss die Properties in den verschiedenen Dateien aktiv überwachen. Es gibt keinen Mechanismus, der ihm dabei hilft. Zudem gibt keine Verknüpfung der PropertiesDateien mit dem Code. Somit gibt es auch keine Möglichkeit, die Properties auf Vollständigkeit zu überprüfen. Die Properties API unterstützt die Hierarchie von Namensräumen nicht, d.h. es werden keine Funktionen bereitgestellt, die es ermöglichen, auf den Propertiesnamen zu navigieren. Der Programmier muss deshalb eigenen Code schreiben, um die (Sub)Properties auslesen und verarbeiten zu können. Das Properties- Konzept sieht nur String- Typen vor. Alle anderen Typen müssen umständlich in String- Typen umgewandelt werden, bevor sie in den Properties gespeichert werden können. Beim Auslesen ist ebenfalls wieder eine Typkonvertierung der String- Typen in die jeweils geforderten Typen notwendig. Auch hier stellt die Properties API dem Programmierer keine Funktionen zur Verfügung, um ihm die Programmierarbeit für die Typenkonvertierung und das Auslesen abzunehmen. Es ist keine Angabe von Default- Werten möglich. Ist dies dennoch gewünscht, müssen diese Werte umständlich innerhalb des Codes, z.B. durch Null- Abfragen gesetzt werden. Der Programmierer muss die Daten explizit speichern und laden, d.h. die Konfigurationsdaten mit einem Stream in die Properties- Datei schreiben. Die Properties API stellt keine Funktionen zur Verfügung, die das Ein- und Auslesen der Properties in die Properties Dateien übernehmen. Für die Anwendung des Properties- Konzeptes gibt es keinerlei Konventionen. Der Speicherort der Properties- Datei ist z.B. nicht spezifiziert, im Gegensatz z.B. zum WEB-INF Ordner beim Tomcat, in dem die web.xml- Datei für die Anwendung gespeichert werden muß. Dies erschwert den gemeinsamen Zugriff von verschiedenen Applikationen auf dieselben Konfigurationsdaten, da zunächst geklärt werden muss, wo die entsprechende Properties- Datei zu finden. Zudem kann es zu Problemen mit den Leserechten an den Properties- Dateien kommen, wenn sie in Verzeichnissen abgespeichert werden, auf die der Programmierer nicht zugreifen darf. Und zu guter Letzt gibt es keinerlei Namenskonventionen für die Properties- Dateien. 3 2.4. Zusammenfassung Grundsätzlich ist es möglich mit dem Properties- Konzept Benutzer- und Systemdaten zu speichern und zu konfigurieren. Für beide Arten von Konfigurationsdaten stellt dieses Konzept allerdings keine optimale Lösung dar. Die oben angeführten Nachteile sprechen für sich. Sowohl für die Benutzer- wie auch die Systemdaten stellt das Properties- Konzept eine sehr unkomfortable Art der Verwaltung dar. Die Daten müssen umständlich in die PropertiesDateien geschrieben und wieder aus ihnen geladen werden. Es wird keinerlei Hilfe bei der Verwaltung der Properties durch die API gegeben. 3. Java Preferences 3.1. Konzept Java Preferences sind seit dem J2SE 1.4 verfügbar. Preferences stellen ein Konzept zur Verwaltung von system- oder benutzerspezifischen Konfigurationsdaten dar. Das Preferences Konzept basiert auf einem einfachen Ansatz: Das Package java.util.prefs.* enthält lediglich 3 Schnittstellen, 4 Klassen und 2 Ausnahmen. 4 java.util.prefs.Preferences +addNodeChangeListener(NodeChangeListener ncl) : void +addPreferenceChangeListener(PreferenceChangeListener pcl) : void +childrenNames() : String [] +exportNode(OutputStream os) : void +exportSubtree(OutputStream os) : void +get(String key, String def) : String +getBoolean(String key, boolean def) : boolean +getDouble(String key, double def) : double +getInt(String key, int def) : int +put(String key, String value) : void +putBoolean(String key, boolean value) : void +putDouble(String key, double value) : void +putInt(String key, int value) : void +removeNode() : void +node(String path) : Preferences +toString() : String +static importPreferences(InputStream is) : void +static systemNodeForPackage(Class c) : Preferences java.util.prefs.PreferencesFactory <<interface>> java.util.EventListener <<interface>> +systemRoot() : Preferences +userRoot() : Preferences java.util.prefs.NodeChangeListener <<interface>> +childAdded(NodeChangeEvent evt) : void +childRemoved(NodeChangeEvent evt) : void +static userNodeForPackage(Class c) : Preferences +static systemRoot() : Preferences +static userRoot() : Preferences java.util.prefs.AbstractPreferences +addNodeChangeListener(NodeChangeListener ncl) : void +addPreferenceChangeListener(PreferenceChangeListener pcl) : void +exportNode(OutputStream os) : void +exportSubtree(OutputStream os) : void #getChild(String nodeName) : AbstractPreferences +get(String key, String def) : String +getBoolean(String key, boolean def) : boolean +getDouble(String key, double def) : double +getInt(String key, int def) : int +put(String key, String value) : void +putBoolean(String key, boolean value) : void +putDouble(String key, double value) : void +putInt(String key, int value) : void +removeNode() : void +node(String path) : Preferences java.util.prefs.PreferenceChangeListener <<interface>> +preferencesChange(PreferenceChangeEvent evt) : void java.util.EventObject java.util.prefs.NodeChangeEvent java.util.prefs.PreferenceChangeEvent +getChild() : Preferences +getParent() : Preferences +getKey() : String +getNewValue() : String +getNode() : Preferences java.lang.Exception java.util.prefs.BackingStoreException Abbildung 2 UML- Diagramm der Preferences API (Teilausschnitt) 5 java.util.prefs.InvalidPreferencesFormatException Die Preferences API ist back- end neutral, d.h. der Programmierer kümmert sich nicht darum, ob die Daten in Dateien, Datenbanken oder plattformspezifischen Speicher (z.B. der Windows Registry) gespeichert werden. Wichtig für die Implementierung des Preferences- Konzept ist die Preferences- Klasse, die alle wesentlichen Funktionalitäten bereitstellt. Die Grundlage für die Preferences API ist eine hierarchische Struktur. Die Verwaltung der Konfigurationsdaten wird durch baumähnliche Sammlungen von Knoten realisiert, ähnlich der Verzeichnisse in einem hierarchischen Dateisystem. Ein Knoten kann mehrere Attribute enthalten, die durch Name- Wert- Paare repräsentiert werden. Man unterscheidet Knoten, die Verzeichnisse darstellen und Attribute und weitere Verzeichnisse enthalten, und Knoten, die Blätter auf der untersten Hierarchieebene darstellen und nur Attribute enthalten. Der Preferences Baum kann wie ein Verzeichnisbaum durchlaufen werden. Es gib einen Wurzelknoten, der durch ‘‘/‘‘ angesprochen werden kann. Die verschiedenen Hierarchieebenen können durch absolute und relative Pfadabgaben erreicht werden. Es gibt zwei 2 Arten von Preference- Bäumen: Der System Preference- Baum, der Konfigurationsdaten für alle Benutzer des Systems verwaltet, wie z.B. die Installationskonfigurationsdaten für eine Anwendung. Der User Preference- Baum, der die Konfigurationsdaten für jeweils einen Benutzer verwaltet, z.B. die Schriftart, die Farbwahl, die Fensteranordnung und –größe für eine bestimmte Anwendung. 3.2. Funktionsweise Die Implementierung der Preferences API wird wie folgt realisiert: Zunächst wird ein Preferences Objekt durch eine von 4 statischen Factory- Methoden erzeugt. Der Konstruktor der Preferences- Klasse ist nicht aufrufbar. public static Preferences systemNodeForPackage(Object o) public static Preferences userNodeForPackage(Object o) Diese beiden Methoden liefern ein Preferences Objekt bzw. Knoten zurück, dessen Pfad den Package- Namen des zu übergebenden Objektes repräsentiert. Der Knoten muss erzeugt werden, wenn er vor dem Methodenaufruf noch nicht existiert. Das zu übergebende Objekt ist die Instanz einer Klasse, in der die Konfigurationen gesetzt und gespeichert werden sollen. Das Package, zu der die Klasse gehört, korrespondiert also mit dem Pfad des PreferencesKnoten. Diese Verbindung bleibt auch erhalten, wenn der Package- Name verändert wird. Es gibt zwei Methoden, mit denen ein Preferences- Knoten erzeugt werden kann, je nachdem ob ein Knoten im System Preference- Baum oder im User Preference- Baum zurückgegeben werden soll. 6 package com.acme.myapp.user; import java.util.prefs.Preferences; public class Payer { private static final String PREFERRED_CHANNEL = "PreferredChannel"; private PreferredChannel preferredChannel = PreferredChannel.WML; public void storePreferences() { Preferences prefs = Preferences.userNodeForPackage(this); prefs.put(PREFERRED_CHANNEL, preferredChannel.toString()); // The rest of the code here... } public static void main(String[] args) { // The test code new Payer().storePreferences(); } } Abbildung 3 Java- Klasse mit Preferences Abbildung 4 Knotenhierarchie in der Windows- Registry public static Preferences systemRoot() public static Preferences userRoot() Diese beiden Methoden geben die Wurzel des System- bzw. User- Preference- Baumes zurück, ohne dass es eines Übergabeparameters bedarf. Es können neben einzelnen Knoten auch ganze Äste erzeugt und natürlich auch durchlaufen werden. public abstract Preferences node(String pathName) Diese Methode ermöglicht das Traversieren der Preference- Bäume. Als Übergabeparameter können absolute oder relative Pfade angegeben werden. 7 Preferences prefs = Preferences.systemRoot().node("/the/first/node"); Preferences prefs2 = prefs.node("the/second/node"); Preferences prefs3 = prefs.node("/yet/another/branch"); Abbildung 5 Codebeispiel für node()- Methode Abbildung 6 Resultat der node()- Methode in der Windows- Registry Um Konfigurationsdaten schließlich in den erzeugten Preference- Bäumen setzen und abfragen zu können, gibt es put- und get- Methoden. Für alle primitiven Datentypen gibt es eigene Implementierungen dieser Methoden, die jeweils zwei Übergabeparametern (Knotenname und Knotenwert) benötigen. Mit Hilfe des Beobachter- Musters können die Preferences auch überwacht werden. Dazu wird ein NodeChangeListener bei einem Knoten registriert, der Auskunft gibt, ob ein Kind unterhalb des Knotens hinzugefügt oder entfernt wurde. Der Import/ Export von Preferences, z.B. um einen Plattformwechsel zu bewerkstelligen, ist ebenfalls möglich. Dazu werden einzelne Knoten oder ganze (Sub)Bäume in XML exportiert. Aus diesen XML- Dateien können die Preferences wieder importiert werden. In Abbildung 7 ist im Code der Klasse PreferencesExporter nachzulesen, wie die Preferences in XML- Dateien exportiert werden. 8 package org.acme.testexim; import java.util.prefs.*; import java.io.*; public class PreferencesExporter { private static final String PACKAGE = "/org/acme/testexim"; public static void main(String[] args) { doThings(Preferences.systemRoot().node(PACKAGE)); doThings(Preferences.userRoot().node(PACKAGE)); } public static void doThings(Preferences prefs) { prefs.putBoolean("Key0", false); prefs.put("Key1", "Value1"); prefs.putInt("Key2", 2); Preferences grandparentPrefs = prefs.parent().parent(); grandparentPrefs.putDouble("ParentKey0", Math.E); grandparentPrefs.putFloat("ParentKey1", (float)Math.PI); grandparentPrefs.putLong("ParentKey2", Long.MAX_VALUE); String fileNamePrefix = "System"; if(prefs.isUserNode()) { fileNamePrefix = "User"; } try { OutputStream osTree = new BufferedOutputStream( new FileOutputStream(fileNamePrefix + "Tree.xml")); grandparentPrefs.exportSubtree(osTree); osTree.close(); OutputStream osNode = new BufferedOutputStream( new FileOutputStream(fileNamePrefix + "Node.xml")); grandparentPrefs.exportNode(osNode); osNode.close(); } catch(IOException ioEx) { // ignore } catch(BackingStoreException bsEx) { // ignore too } } } Abbildung 7 Exportierung von Preferences in XML- Dateien 9 Die Klasse PreferencesExporter erzeugt 4 XML- Dateien: SystemNode.xml SystemTree.xml UserNode.xml UserTree.xml Der Inhalt von zwei der vier erzeugten Dateien wird in Abbildung 8 und Abbildung 9 dargestellt. Der Unterschied zwischen der Speicherung eines einzelnen Knotens und eines ganzen (Sub)Baumes wird dabei ersichtlich. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'> <preferences EXTERNAL_XML_VERSION="1.0"> <root type="system"> <map /> <node name="org"> <map> <entry key="ParentKey0" value="2.718281828459045" /> <entry key="ParentKey1" value="3.1415927" /> <entry key="ParentKey2" value="9223372036854775807" /> </map> </node> </root> </preferences> Abbildung 8 SystemNode.xml 10 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'> <preferences EXTERNAL_XML_VERSION="1.0"> <root type="user"> <map /> <node name="org"> <map> <entry key="ParentKey0" value="2.718281828459045" /> <entry key="ParentKey1" value="3.1415927" /> <entry key="ParentKey2" value="9223372036854775807" /> </map> <node name="acme"> <map /> <node name="testexim"> <map> <entry key="Key0" value="false" /> <entry key="Key1" value="Value1" /> <entry key="Key2" value="2" /> </map> </node> </node> </node> </root> </preferences> Abbildung 9 UserTree.xml Um die XML- Dateien wieder in einen Preference- Baum importieren zu können, bedarf es nur der wenigen Zeilen Code in Abbildung 10. InputStream is = new BufferedInputStream( new FileInputStream(args[0])); Preferences.importPreferences(is); is.close(); Abbildung 10 Import aus XML- Datei in Preference- Baum Das Speichermedium, in dem die Konfigurationsdaten schlussendlich gespeichert werden, ist von der Implementierung abhängig. In J2SE 1.4 Beta wird die Implementierung spezifiziert, indem die Systemvariable java.util.prefs.PreferencesFactory auf den Namen der Implementierungsklasse gesetzt wird. 11 Angenommen man hätte eine eigene Implementierung der Preferences Factory geschrieben, die z.B. PostGreSQL als Hintergrundspeicher benutzt, und demzufolge mit PostGreSQLPreferencesFactory betitelt ist. Dann würde man mit der folgenden Zuweisung die eigene Preferences Factory setzen: -Djava.util.prefs.PreferencesFactory= PostGreSQLPreferencesFactory Sun sieht standardmäßig für Windows die Windows Registry als Hintergrundspeicher vor. Um eine Preferences-Instanz zu erzeugen, wird eine Factory verwendet, die als Umgebungsvariable von Java festgelegt ist. Unter Windows ist das standardmäßig die Factory java.util.prefs.WindowsPreferencesFactory. Diese Fabrik erzeugt Instanzen der Klasse java.util.prefs.WindowsPreference. Als Datenspeicher wird durch diese Factory die Windows-Registry-Datenbank festgelegt. Die System- Preferences werden unter dem Schlüssel ”HKEY LOCAL MACHINE\Software\JavaSoft\Prefs“ abgelegt und die BenutzerPreferences unter dem Schlüssel ”HKEY CURRENT USER\Software\JavaSoft\Prefs“ . 3.3. Vorteile Die Organisation der Konfigurationsdaten ist durch die Verwendung der Preferences API sehr einfach, weil die Daten mit dem Package verbunden werden, zu dem sie gehören. Durch die Möglichkeit des Export/ Import mit XML müssen die Preferences bei einem Plattformwechsel nicht neu gesetzt werden, ähnlich den Properties- Dateien, die einfach kopiert werden können. Preferences sind nicht auf Strings beschränkt, es werden alle primitiven Datentypen unterstützt: Boolean, byte[], double, float, int, long. Es sind unterschiedliche (eigene) Preferences- Implementierungen möglich, z.B. um unterschiedliche Speichermedien zu integrieren. Dazu muss der Standard Java Mechanismus geändert werden und jeweils eine andere Factory eingestellt werden. Da die get- Methoden mit Defaultwerten arbeiten (siehe nachfolgend die Nachteile) funktioniert die Preferences API auch, wenn der Zusatzspeicher ausfällt. Der Programmierer muss sich nicht um das Speichern und Laden der Konfigurationsdaten explizit kümmern, im Gegensatz zu dem Properties- Ansatz. Die Preferences API arbeitet asynchron, d.h. die Rückkehr zum Programm geschieht, bevor die Daten tatsächlich in den Speicher geschrieben werden. Somit ist auch der Einsatz langsamerer Speichermedien möglich (aber siehe Nachteile Schnelligkeit der Preferences API). 12 3.4. Nachteile Beim Aufruf der get- Methoden ist die Angabe eines Defaultwertes zwingend erforderlich (siehe nachfolgend die Signatur der get- Methoden). Somit wird der reibungslose Betrieb auch bei Nichtverfügbarkeit des Speichermediums gewährleistet. Signatur der get- Methode: <Datentyp> get<Datentyp> ("<Präferenzbezeichnung>",<Standardwert>) Die Defaultwerte sind aber nicht immer passend, besonders bei der Konfiguration der System Daten. U.U. ist eine Applikation mit Defaultwerten nicht lauffähig, wenn z.B. plattformspezifische Konfigurationen gesetzt werden müssen. Man denke nur an Verzeichnispfade oder ähnliches, die mit Defaultwerten überhaupt nicht gesetzt werden können. Zudem wird dem Benutzer nicht mitgeteilt, ob der Defaultwert oder der „richtige“ gespeicherter Wert verwendet wird. Die Preferences API ist nicht zur Speicherung der Applikationsdaten geeignet, wie z.B. vom Benutzer erstellte Dokumente. Die API ist also nicht als Persistenzdienst bzw. Datenbank zu gebrauchen, weil dieser Ansatz nur eine geringe Speicherkapazität (8,192 Bytes pro Wert) zur Verfügung stellt. Die Preferences API ist nicht sehr schnell, weshalb häufige Zugriffe auf die gespeicherten Daten vermieden werden sollten. Es ist dem Programmierer nicht möglich, die Konfigurationsdaten zu dokumentieren, da sie ohne seinen Einfluss in das angeschlossene Speichermedium geschrieben werden. Ebenfalls aus diesem Grund ist das Handling der Konfigurationsdaten sehr umständlich. Die in den Bäumen abgelegten Konfigurationsdaten sind schlecht zu überblicken. Wird das System an einen neuen Administrator abgegeben, so muss sich dieser in die vorhandene Datenstruktur langwierig einarbeiten. Die bereits oben angeführte fehlende Dokumentationsmöglichkeit kommt hier noch erschwerend hinzu. Denn es ist nicht ersichtlich, welche Daten in welcher Klasse, bzw. welchen Knoten zu konfigurieren sind. 3.5. Zusammenfassung Die Preferences API stellt grundsätzlich einen komfortableren Ansatz zur Verwaltung von Konfigurationsdaten dar als der Properties- Ansatz. Allerdings eignet sich die Preferences API im Prinzip nur für Benutzerdaten, da die Unübersichtlichkeit der Datenspeicherung das Konfigurieren erschweren und die zwingend erforderliche Defaultwertangabe, die zu Systemabstürzen führen kann, den Einsatz für Systemdaten nicht empfehlen. 13 4. Introspektion 4.1. Konzept Der Begriff Introspektion heißt ins Deutsche übersetzt Selbstbeobachtung, Erlebnisbeobachtung, Innenschau. Er bezeichnet die bewusste Beobachtung des eigenen Erlebens und Verhaltens. U. a. wurde der Begriff im Zusammenhang mit der Psychoanalyse gebraucht und bezeichnete eine Behandlungsform von psychisch kranken Menschen, die selber Auskunft über ihre Leiden geben sollten, womit man sich einen Behandlungserfolg versprach. Heute taucht der Begriff vor allem im philosophischen Kontext auf. Man kann den Begriff auch auf die Informatik übertragen. Angewandt auf ein SoftwareSystem bedeutet Introspektion soviel wie ein System, dass über sich selber Auskunft gibt. Z.B., um auf das Thema dieser Arbeit zurückzukommen, gibt das System Auskunft darüber, welche Konfigurationen vorgenommen werden sollen, damit das System ordnungsgemäß funktioniert. 4.2. Funktionsweise Die Introspektion arbeitet mit einer GUI, die dem Benutzer bzw. Systemadministrator anzeigt, welche Attribute für das System konfiguriert werden sollen. 14 Abbildung 11 Ausschnitt aus der GUI des Online Brokers des SEBIS- Lehrstuhls 15 Die zu konfigurierenden Daten einer Klasse sind primitive Datentypen oder Objekte, d.h. Klassen, die wiederum primitive Datentypen und Objekte als Konfigurationsdaten enthalten. Dadurch entsteht in der GUI eine hierarchische Struktur. In der GUI werden die Konfigurationsdaten durch Knoten repräsentiert. Diese Knoten werden durch so genannte Konnektoren im Code erzeugt. preferences.connect(new StringConnector() { public String getKey() { return "genericExceptionMessage"; } public String getDescription() { return "If this property is set, uncaught runtime exceptions are indicated to the end user by displaying this generic error message. Otherwise, a Java stack trace is diplayed to the end user. The message value can also be set to the empty string '' to suppress any error output."; } public String getExample() { return "Die Operation konnte nicht erfolgreich abgeschlossen werden. Bitte kehren Sie mit der 'Zurueck'-Funktion Ihres Browsers zur zuletzt angezeigten Seite zurueck."; } public void setValue(String newString) { genericExceptionMessage = newString; } public String getValue() { return genericExceptionMessage; } }); Abbildung 12 Dieser Code erzeugt einen Attribut- Knoten in der GUI Neben diesen Konnektoren müssen in einer Klasse, deren Attribute konfiguriert werden sollen, auch die connect()- Methode und initNew()- Methode des Connectable- Interfaces implementiert werden. Alle Funktionalitäten, die für die Introspektion nötig sind, werden vom connect- Package zur Verfügung gestellt, insbesondere die Interfaces für die verschieden Typen von Konnektoren. 16 Connector <<interface>> +getKey () :String +getDescription () :String BooleanConntor <<interface>> StringConnector <<interface>> +getValue () :boolean +setValue (boolean newValue) :void PipelineConnector <<interface>> +getValue () :String +setValue (String newString) :void +getExample () :String ObjectConnector <<interface>> ObjectsConnector <<interface>> +replaceObject (Object o) :void +getClassName () :String +getKey () :String +setObjectFactory (ObjectFactory objectFactory) :void +initNew () :void Connectable <<interface>> +connect (Preferences preferences) :void +initNew () :void +getClassName () :String +getObjectConnector (int position) :ObjectConnector +getObjects () :List +getClassName () :String +setObjectFactory (ObjectFactory objectFactory) :void +setCreateCopy (boolean createCopy) :void +createCopy () :boolean +initNew () :void ObjectFactory +createAndInitNewObject () :Object +getClassName () :String Abbildung 13 Interfaces und Klassen des Connect- Packages (Teilausschnitt) 17 Preferences <<interface>> +connect (ObjectConnector objectConnector) :void +connect (ObjectsConnector objectsConnector) :void +connect (PipelineConnector pipelineConnector) :void +connect (StringConnector stringConnector) :void +connect (BooleanConnector booleanConnector) :void +connect (LogConnector logConnector) :void +getSubPreferences (Connector connector) :Preferences +handleEvent (NewObjectEvent e) :Preferences +setReference (String name, Object reference) :void +getReference (String name) :Object Es besteht die Möglichkeit die Konfigurationsdaten in XML- Dateien abzuspeichern. Es können einzelne Objekte abgespeichert werden oder auch ganze (Sub)Systeme. 4.3. Vorteile Die Introspektion zwingt zu einer anderen Sicht auf das System und unterstützt somit den Fortschritt bei der Entwicklung großer Systeme. Durch die Anwendung der Introspektion wird ein nachvollziehbarer Initialisierungsablauf erwirkt. Die Initialisierung der Objekte folgt bei Anwendung der Introspektion einem bestimmten Muster bzw. Reihenfolge. Der Code wird besser lesbar, da der Programmierer klar überblicken kann, was an welcher Stelle im Code bezüglich der Konfiguration und Initialisierung von Objekten geschieht. Somit erhöht sich auch das Verständnis für das Systemverhalten. Der Administrator hat keine Berührung mehr mit dem Source- Code. Konfigurationen werden in der GUI vorgenommen, in der die zu konfigurierenden Eigenschaften übersichtlich gebündelt und hierarchisch angeordnet sind. Der Administrator behandelt das System also nur aus seiner Administratorsicht, mit der internen Sicht muss er sich nicht mehr auseinander setzen. Die Introspektion ermöglicht es dem Programmierer ausführliche Dokumentationen der zu konfigurierenden Eigenschaften direkt im Code zu erstellen. In der getDescription()- Methode kann die Beschreibung der zu konfigurierenden Daten angegeben werden. Diese ausführlichen Beschreibungen der Attribute, die in der GUI direkt dargestellt werden, helfen auch dem Administrator bei seiner Arbeit, da er genau über die zu konfigurierenden Attribute informiert wird. Somit wird die Arbeit beider Parteien erleichtert: Der Programmierer weiß, wo und wie er konkret die zu konfigurierenden Attribute dokumentieren soll und der Administrator bekommt die Dokumentation bequem angezeigt. Durch die Option, einmal erstellte Konfigurationen in XML- Dateien abspeichern zu können, ist es dem Administrator möglich, bestehende Konfigurationen wieder zu verwenden und somit den Aufwand zur Systemkonfiguration zu reduzieren. Die Anwendung der Introspektion erhöht die Konsistenz der Konfigurationsdaten, da keine zu konfigurierenden Attribute vergessen werden können. Der Code korrespondiert direkt mit den Konfigurationsdaten, die durch die GUI eingelesen und im Code verarbeitet werden. Es besteht eine direkte Verbindung, anders als bei den zum Code völlig separaten Properties- Dateien der Java Properties. Die Introspektion stellt einen skalierbaren Ansatz dar. Schon für kleinere Systeme lohnt sich die Anwendung dieses Verfahrens. Bei großen Systemen kommen die Anwendungsvorteile vollends zur Entfaltung. 18 4.4. Nachteile Das Muster, nach dem der Initialisierungsworkflow abläuft, bietet neben den oben angeführten Vorteilen der Übersichtlichkeit und des erhöhten Systemverständnisses allerdings auch den Nachteil, dass der Programmierer an ein starres Schema gebunden ist, was er bei seiner Programmierarbeit umsetzen muss. Es ist nicht möglich, von diesem Schema abzuweichen, was vereinzelt zu Problemen führen kann. In der GUI werden die Systemdaten vor dem Start der Applikation konfiguriert und stehen dem System zur Ausführungszeit dann zur Verfügung. Konfigurationsdaten, die während der Laufzeit erstellt werden, die so genannten Benutzerdaten, können in der GUI nicht berücksichtigt werden. 4.5. Zusammenfassung Wie in obigen Abschnitt bereits angesprochen, eignet sich die Introspektion nur zur Verwaltung von Systemdaten. Kurzfristige, zur Laufzeit anfallende Benutzerdaten können mit diesem Verfahren nicht gemanagt werden. Für die Systemdaten allerdings bietet die Introspektion einen äußert intelligenten und komfortablen Ansatz, dessen Vorteile absolut für sich sprechen. 5. Resümee Es ist wichtig, sich klar zu werden darüber, welche Art von Konfigurationsdaten man verwalten möchte. Denn je nachdem ergeben sich andere Prämissen, die die Wahl des Verfahrens bestimmen. Der Java Properties- Ansatz eignet sich für System- wie für Benutzerdaten, wobei in beiden Fällen die Nachteile dieses Ansatzes schwer ins Gewicht fallen. Allein die Einfachheit mag für diesen Ansatz sprechen. Der Java Preferences Ansatz eignet sich nach genauerer Betrachtung nur für die Verwaltung von Benutzerdaten. Möchte man mit diesem Verfahren Systemdaten verwalten, muss man sich mit einem schwierigen Handling aufgrund von Unübersichtlichkeit und fehlender Dokumentation herumschlagen. Die Introspektion stellt sich genau entgegengesetzt dar. Dieses Verfahren eignet sich nur für die Verwaltung von Systemdaten, für die Verwaltung von Benutzerdaten werden keinerlei Mechanismen angeboten. Allerdings beschränkt sich die Introspektion nicht nur auf die bloße Verwaltung der Systemdaten, sondern bietet einen viel umfassenderen Ansatz, dessen Implementierung sich vorteilhaft auf das gesamte System auswirkt, wie es bei den Vorteilen ausführlich erläutert wurde. Die Entscheidung bei der Entwicklung eines Software- Systems, welcher Ansatz oder welche Kombination von Ansätzen zur Verwaltung der Konfigurationsdaten gewählt werden soll, muss also in Abhängigkeit davon getroffen werden, welche Art von Konfigurationsdaten man verwalten möchte. Je nachdem bieten sich unterschiedliche Optionen an. Es liegt dann an den Entwicklern, eine für das geplante System und die zu erfüllende Aufgabe zufrieden stellende Lösung auszuwählen. 19 Literaturverzeichnis [java_01] Artikel über Java Preferences von Ray Djajadinataz, Oktober 2001, http://java.sun.com/developer/technicalArticles/releases/preferences/ [java_02] JDK 1.4 Tutorial, The Preferences API Chapter 10 von Greg Travis, veröffentlicht durch Manning Publications Company, Mai 2002 http://java.sun.com/developer/Books/javaprogramming/jdk14/javapch10.PDF [java_03] Artikel: „Using the Preferences API and Interfaces and Constants“, July 2003 http://java.sun.com/developer/JDCTechTips/2003/tt0715.html [intro_01] http://www.sign-lang.uni-hamburg.de/Projekte/PLex/PLex/lemmata/ILemma/Introspe.htm 20