5.5 Der Speicher

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.





Parameter Argumente Bedeutung



RECEIVE_PORT <port> Portnummer für eingehende
Aufträge vom Broker



BROKER_ADDRESS <port> Netzadresse des Brokers



LOGFILE <logfilename> Dateiname der LOG-Datei



MAP_MODULE <filtermodule> Pythonmodul, das die
Filterroutinen bereitstellt



MAP_COMMAND <command>, Zuordnung der Kommandos
<filterfunction> zu den Filterroutinen



EXECPYTHON <pythonpath> Pythoninterpreter, zur Ausführung
der Dokumentenmethoden.



DB_SERVER_ADDRESS <host>,<port> Adresse des Datenbank-Servers



DB_NAME <dbname> Name der Datenbank



TMPPATH <tmppath> Verzeichnis für temporäre Dateien



BITSTREAMPATH <bspath> Verzeichnis für als Bitstrom
gespeicherte Informationen



LIB_PATH <libmcpath> Verzeichnis der Programmbibliothek
für mobilen Code (Paketverzeichnis)



FULLTEXT_RESULT <resultfile> temp. Datei für Volltextsuche




Tabelle 5.4: Parameter des Speichers

Wird ein Speicher gestartet, so meldet er sich beim Broker an und teilt diesem die Adresse mit unter der er auf Aufträge wartet.

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.

5.5.1 Anbindung an die Datenbank

Die Speicher brauchen Zugriff auf die Datenbank, um Metadaten abzulegen oder darauf zuzugreifen. Weiterhin müssen Dokumentenmethoden, die in der Ablaufumgebung der Speicher ausgeführt werden, ebenfalls auf die Datenbank Zugriff haben, um Dokumente zu speichern oder abzufragen.

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.

5.5.2 Realisierung der Anbindung

Das DBMS PostgreSQL bietet zur Einbindung in Fremdapplikationen eine C-Bibliothek. Auf der anderen Seite unterstützt Python Mechanismen zur Integration von C-Routinen ([vR95]). Es bot sich daher an, Python um spezielle Klassen zur Kommunkiation mit PostgreSQL zu erweitern. Damit können SQL-Befehle genauso wie Standard-Python-Befehle benutzt werden. Der Zugriff kann aus der Ablaufumgebung, sowie aus dem Speicherprozeß heraus erfolgen.

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.

  1. Importieren einer Klassenbibliothek (Modul)
  2. Instanziieren des Objekts
  3. Methodenaufrufe ausführen

Ein Stück Pythoncode, das diese drei Schritte beschreibt ist in Abbildung 5.5 dargestellt.



# importieren des PostgreSQL <    -->     Python Moduls
import pgpy

# instanziieren eines Objekts der Verbindungsklasse
# DL_PGHOST, DL_PGPORT geben die Netzadresse des DB-Servers an
# DL_DBNAME gibt den Namen der Datenbank an
obj = pgpy.pgconn( DL_PGHOST, DL_PGPORT, DL_DBNAME )

# Aufruf der Methode exec_query, Ausfuehren einer SQL Abfrage
query_result = obj.exec_query("select |~|      * |~|      from |~|      doc_desc;")



Abbildung 5.5: Arbeiten mit Klassen unter Python

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.


PIC
Abbildung 5.6: Import eines Moduls

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.


PIC
Abbildung 5.7: Objektinstanziierung

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.


PIC
Abbildung 5.8: Methodenaufruf

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.

5.5.3 Repräsentation der Dokumente in der Datenbank

Die in Abschnitt 4.6 vorgestellte einfache Klassenhierarchie wurde benutzt und um einige Klassen und Relationen zur Speicherung der Daten aus den Metadokumenten (Kap. 2.4.2) erweitert. Das erweiterte Datenmodell der Implementierung ist in Abbildung 5.9 skizziert.
PIC
Abbildung 5.9: Datenmodell der Implementierung

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.

5.5.4 Ablaufumgebung für Dokumentenmethoden

Die Ablaufumgebung für Dokumentenmethoden wird innerhalb des Speichers durch eine einzige Pythonfunktion (exec_mc) realisiert. Als Parameter werden dieser Funktion die Dokumenten-ID, eine temporäre Datei, die die Dokumentenmethode enthält, die Argumente der Methode und optional eine Datei, die das eigentliche Dokument enthält, übergeben.

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.


def exec_mc(docid,methodtmpfile,params,datafname=""):
    # erzeuge eindeutigen Dateinamen fuer temporaere Datei
    exectmpfile=class_funcs.getuniquename()
    # erzeuge Fileobjekt und oeffne temporaere Datei
    fd1=c_bsio.c_bsio(exectmpfile,"a+    ")
    fd1.open()
    # setze Methode zusammen
    .
    .
    .
    # Methode ausfuehren und Rueckmeldung schicken
    posix.chmod(exectmpfile,0755)
    execstr=exectmpfile+    " |~|       "+    params+    "&1>    "+    TMPPATH+    "output"+    "&2>    "      +    TMPPATH+    "errout"

    if os.system(execstr)==0:
     # Methode fehlerfrei ausgefuehrt
     fdd=open(DL_TMPPATH+    "output","r")
     ret="RESULT |~|       "+    str(docid)+    "\    n"
    else:

     # es ist ein Fehler aufgetreten
     fdd=open(DL_TMPPATH+    "errout","r")
     ret="ERROR |~|       "+    str(docid)+    "\    n"
   
    # hole Methoden Output
    erg=""
    buff=" |~|       "
    while buff!="":
     buff=fdd.readline()
     erg=erg+    buff
    fdd.close()
     
    ret=ret+    erg
    del erg,buff,fdd
    .
    .
    .


Abbildung 5.10: Ausschnitt aus dem Quellcode der Ablaufumgebung

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.