Die Implementierung des Speichers wird, wie die der anderen Komponenten, über eine Konfigurationsdatei parametrisiert. Die möglichen Paramter und ihre Bedeutung sind in Tabelle 5.4 zusammengefaßt.
|
Empfängt der Speicher ein PUT Kommando der Form
PUT <metadoc> <methodname> <methodparamters>,
so extrahiert er zunächst die bereits unter Kapitel 2.4.2 beschriebenen Metadaten und speichert sie unter Benutzung des System-Pakets der Programmbibliothek in der Datenbank ab. Bei diesem Vorgang wird auch eine eindeutige Dokumenten-ID für das neue Dokument erzeugt. Dann wird die Dokumentenmethode mit dem Namen <methodname> und den Parametern <methodparameters> auf dem Dokument aktiviert. Im allgemeinen wird es sich hierbei um eine Methode zur Speicherung der Dokumentendaten handeln. Ohne Aufruf einer solchen Methode werden nur die Metainformationen des Dokuments im Speicher abgelegt. Als Ergebnis wird die Dokumenten-ID (als Zähler implementiert) zurückgeliefert.
Handelt es sich bei dem empfangenen Kommando um eine Informationsanforderung (Kommando: INFO) über die in der Ablaufumgebung verfügbaren Pakete der Programmbibliothek, werden alle im Verzeichnis LIB_PATH vorhandenen Pakete durchsucht, die Klassen- und Methodeninformationen extrahiert und als Ergebnis (ASCII) zurückgeliefert.
Wird dem Speicher ein Kommando CMD_EXIT vom Broker gesendet, so verarbeitet der Speicher den momentan laufenden Auftrag und beendet sich.
Ein ACTIVATE Kommando bewirkt den Aufruf einer Dokumentenmethode auf einem bestimmten Dokument. Ein ACTIVATE Kommando hat folgende Gestalt:
ACTIVATE <docid> <methodname> <methodparameters>
Zunächst wird geprüft, ob ein Dokument mit der entsprechenden <docid> und einer Methode namens <methodname> gespeichert ist.
Die Methode wird dann in einer temporären Datei abgelegt. Diese Datei, die Dokumenten-ID und die Parameter werden an die Ablaufumgebung übergeben. Der Rückgabewert der Ablaufumgebung repräsentiert das Ergebnis der Methodenausführung, er wird als Antwort zurückgeschickt.
Vereinfacht heißt das, daß aus Python heraus mit der Datenbank kommuniziert werden muß. Zu diesem Zweck wurde Python um ein Modul zur Anbindung an die PostgreSQL-Datenbank erweitert. Das PostgreSQL-Datenbankmanagementsystem wird über eine TCP/IP-Verbindung angesprochen. Über diese Verbindung (Connectionhandle) werden SQL-Anweisungen an die Datenbank gesendet. Als Ergebnis erhält man ein sogenanntes „Resulthandle“. Dieses Resulthandle wird dazu verwendet, auf die Ergebnisse und Statusmeldungen der ausgeführten SQL-Anweisung zuzugreifen. Eine besondere Rolle spielen binäre Objekte (Bilder, Tondateien, u.s.w.), um diese Informationen in Postgres ablegen zu können, bedarf es spezieller Routinen.
Die Kommunikation mit der Datenbank unter Benutzung von „Connectionhandles“ und „Resulthandles“ ist der von Microsoft definierten „Open Database Connectivitiy“-Schnittstelle (ODBC) sehr ähnlich ([SSC95]).
Das realisierte Modul stellt drei Klassen zur Verfügung. Eine Klasse (pgconn) stellt alle Funktionalitäten bereit, die zum Verbindungsauf- und abbau, sowie zum Versenden von SQL-Statements notwendig sind. Eine weitere Klasse (pgres) unterstützt die Auswertung von Resulthandles zur Verwertung der Ergebnisse. Eine dritte Klasse (pgblob) schließlich realisiert den Zugriff auf Binärobjekte.
Die C-Bibliothek operiert im wesentlichen auf drei verschiedenen Datenstrukturen: Eine für die Verbindung zum Datenbankserver (Connectionhandle), eine für die Verarbeitung von Abfrageergebnissen (result handle) und eine für den Zugriff auf binäre Datenbankobjekte (blobs). Diese drei Datenstrukturen wurden auf drei Pythonklassen abgebildet und deren Prozeduren auf die Methoden der Klassen. Die Implementierung dieser Klassen erfolgte in der Programmiersprache C ([KR88]). Die Vorgehensweise soll im folgenden skizziert werden.
Das Arbeiten mit Klassen und deren Instanzen (Objekten) erfolgt unter Python dreistufig.
Ein Stück Pythoncode, das diese drei Schritte beschreibt ist in Abbildung 5.5 dargestellt.
Diese drei Schritte können auf die C-Implementierung der Pythonerweiterung abgebildet werden. Um die Vorgehensweise von Python beim Modulgebrauch zu erläutern, sollen die Schritte den entsprechenden C-Abläufen gegenübergestellt werden.
Um ein Modul und damit die darin enthaltenen Klassen in einem Pythonprogramm verfügbar zu machen, wird eine import-Anweisung ausgeführt. Die import-Anweisung bewirkt das Ausführen der Modulinitialisierungsroutine, diese liest eine im Modul zu definierende Klassentabelle ein, die ihrerseits auf die jeweiligen Klasseninitialisierungsroutinen verweist. Dieser Zusammenhang ist in Abbildung 5.6 dargestellt.
Wird ein Objekt der geladenen Klasse instanziiert, so wird die jeweilige Klasseninitialisierungsroutine aufgerufen, diese führt den Klassenkonstruktor aus und liefert als Ergebnis ein Objekt der Klasse zurück.
Der Aufruf des Konstruktors bewirkt, daß ein neues Objekt erzeugt wird, alle dazu notwendigen Informationen werden in einer Objektbeschreibungstabelle festgelegt. Der Ablauf einer Instanziierung ist in Abbildung 5.7 verdeutlicht.
Eine wichtige Methode jeder Klasse ist die get_attr Routine. Sie wird benötigt, um die Methodenaufrufe aufzulösen und die eigentlichen Methoden auszuführen. Der Vorgang eines Methodenaufrufs ist in Abbildung 5.8 skizziert.
Die Methode get_attr ermittelt unter Zuhilfenahme der Objektmethodentabelle die auszuführende Methode und ruft diese auf. Der Vorteil dieser Vorgehensweise ist die Möglichkeit der einfachen Erweiterbarkeit und der übersichtlichen Programmierung.
Der vollständige Quellcode zu diesem Pythonmodul ist in Anhang ?? abgedruckt.
Den hinzugekommenen Klassen kommen folgende Aufgaben zu:
Die Klassen sind über die eindeutige Dokumenten-ID miteinander verknüpft. Da die Klassen jeweils die Daten aller gespeicherten Dokumente aufbewahren und die gespeicherten Datenmenge pro Klasse dadurch sehr schnell wächst, war es notwendig den Datenzugriff zu beschleunigen.
Hierzu wurden Hash-Indizes auf das Verknüpfungsfeld (Dokumenten-ID) aller Klassen gesetzt. Um den Zugriff auf die Strukturinformationen ebenfalls effizient zu gestalten, wurden die Felder, die Bruder- und Sohnverweise, sowie die fortlaufende Elementnummer aufnehmen ebenfalls indiziert.
Die Übergabe der Methodenargumente erfolgt über die Shell-Schnittstelle. Eine Dokumentenmethode muß diese Argumente selbständig einlesen, um sie verarbeiten zu können. Der letzte Parameter wird immer dann benötigt, wenn durch die Methode ein neues Dokument im Speichersystem abgelegt werden soll.
Die Methode erzeugt eine temporäre Datei. Diese Datei wird aus drei Abschnitten zusammengesetzt: Dem Dateiheader, dem Initialisierungsabschnitt und dem eigentlichen mobilen Code. Der Dateiheader legt anhand der in der Konfigurationsdatei gesetzten Variablen EXECPYTHON den zu benutzenden Interpreter fest. Auf diese Art ist es möglich, einen Interpreter mit verminderter Funktionalität einzusetzen, um Sicherheitsrisiken zu vermindern. Der Header beinhaltet noch einige Kommentarzeilen, die lediglich der Fehlersuche und dem Verständnis dienen sollen.
Im Initialisierungsabschnitt werden einige Variablen gesetzt, die von verschiedenen Methoden der Programmbibliothek benutzt werden. Dazu gehören die Variable für die Dokumenten-ID (DOCID) und die Variable für die Dokumentendatei (CURRENT_INFILE), falls dieser Wert als Parameter übergeben wurde. Der letzte Abschnitt beinhaltet schließlich den mobilen Code.
Wurde die temporäre Datei zusammengesetzt, so wird sie ausgeführt. Jegliche Ausgaben der Methode werden wiederum zwischengespeichert. Dieser Teil des Quellcodes ist in Abbildung 5.10 zu sehen.
Terminiert die Methode ohne Fehler, wird als Ergebnis eine Zeile mit dem Inhalt:
RESULT <docid>
gefolgt von der Standardausgabe der Methode zurückgegeben. Beendet sich die Methode mit einer Fehlermeldung, wird als Ergebnis die Standardfehlerausgabe der Methode zurückgegeben, diesem steht folgende Zeile voran:
ERROR <docid>
Eventuell nicht terminierende Endlosschleifen wurden nicht berücksichtigt und werden nicht entdeckt. Die Ablaufumgebung stellt also nicht sicher, daß eine ausgeführte Methode auch terminiert.