\chapter{Framework}
\label{ch:framework}

In den vorangegangenen Kapiteln wurde ein grober Entwurf für die Funktionalität von WikiWord entwickelt (\rr{ch:entwurf}) und die zu bewältigenden Aufgaben sowie die zu berücksichtigenden Rahmenbedingungen spezifiziert (\rr{ch:anforderungen}). Dieses Kapitel gibt nun einen Überblick über die zu diesem Zweck entwickelten Softwarekomponenten (siehe \rrr[Anhang]{ch:quellcode} für Zugang zum Quellcode). Eine Beschreibung der Kommandozeilenschnittstelle für WikiWord findet sich in \rrr[Anhang]{ch:cli-spec}.

\section{Import-Architektur}
\label{sec:import-achitektur}
\label{sec:frameworkklassen}

\later{Frameworkklassen (UML)}

Dieser Abschnitt gibt eine Übersicht über die wichtigsten Klassen und Komponenten von WikiWord. 
%\rrr[Abb.]{abb:frameworkklassen} zeigt die wesentlichen Zusammenhänge, die im Folgenden näher beschrieben werden sollen. 
Im Zentrum der Betrachtung steht dabei \cli{ImportConcepts}, das Programm zur Extraktion der Thesaurusdaten aus dem Wiki-Text.

Die Programme, die die Kommandozeilenschnittstelle von WikiWord bilden, sowie die Parameter und Optionen für ihren Aufruf sind in \rrr[Anhang]{ch:cli-spec} beschrieben. Weitere Möglichkeiten zur Konfiguration bieten die sogenannten \lingo{Tweak-Werte}, siehe \rr{sec:tweaks}. Eine Auflistung der relevanten Klassen und Packages ist in \rr{ch:quellcode} gegeben.

Die Basis für alle Programme der Kommandozeilenschnittstelle von WikiWord ist die Klasse \javaid{CliApp}: Sie stellt Basisfunktionalität unter anderem zur Verarbeitung von Kommandozeilenparametern und zur Ausgabe von Log-Nachrichten bereit. Alle in \rr{sec:entrypoints} erwähnten Import-Programme mit Ausnahme von \javaid{ExportSkos}, bauen auf der von \javaid{CliApp} abgeleiteten Klasse \javaid{ImportApp} auf: dieses bietet Unterstützung für die Verwendung des \nom{Agenda}-Systems zur Ablaufkontrolle (siehe \rrr[Anhang]{sec:agenda}) sowie für die Erzeugung eines geeigneten Datenzugriffsobjektes für die Interaktion mit der Datenbank (siehe \rr{sec:DAO}).

Eine genauere Betrachtung verdient die Klasse \javaid{ImportDump}, auf der auch \javaid{ImportConcepts} basiert, welches der Einstiegspunkt für einen Großteil der Extraktionslogik ist. \javaid{ImportDump} ist von \javaid{ImportApp} abgeleitet und bildet das Framework für eine \lingo{Consumer-Producer}-Architektur für den Import von Daten: Eine Implementation des Interfaces \javaid{ImportDriver} liest "`Seiten"' aus einer Datenquelle und übergibt sie, eine nach der anderen, an eine Implementation des Interfaces \javaid{WikiWordImporter}, die die Daten dann weiter verarbeitet. Die Klasse \javaid{AbstractImporter} bildet die Basis für Implementationen dieses Interfaces und bietet Funktionen insbesondere für die Ablaufsteuerung über das Agenda-Objekt sowie für die Umsetzung allgemeiner Kommandozeilenparameter.

\later{Dataflow (Pseudo-)UML}

Im Normalfall sollen die Wiki-Seiten aus einem Wikipedia-Dump gelesen werden \q{MW:XML}. Dies wird von der Klasse \javaid{DumpImportDriver} umgesetzt (einer Implementation des Interfaces \javaid{ImportDriver}), die ihrerseits auf die MWDumper-Bibliothek von Wikimedia zurückgreift \q{MW:DUMPER}: Sie verarbeitet die XML-Struktur und ruft für jede enthaltene Wiki-Seite die Methode \javaid{handlePage} auf dem \javaid{WikiWordImporter} auf. Dabei wird die XML-Datei automatisch dekomprimiert, sollte dies erforderlich ein\footnote{Unterstützt werden die weit verbreiteten Formate GZip und BZip2.}.

Für die Analyse des Wiki-Textes und den Import der so gewonnenen Ressourcen in das lokale Datenmodell verwendet das Programm \javaid{ImportConcepts} die Klasse \javaid{ConceptImporter} als Implementation von \javaid{WikiWordImporter}. Diese überführt den rohen Wiki-Text zunächst unter Verwendung von \javaid{WikiTextAnalyzer} in das Ressourcenmodell (siehe \rr{entwurf:analyse}, \rr{transform:analyse}, \rr{model:ressourcenmodell}), interpretiert die Eigenschaften der Ressource (\rr{entwurf:import}, \rr{transform:import}) und schreibt das Ergebnis dann in das lokale Datenmodell, ein Datenzugriffsobjekt des Typs \javaid{LocalConceptStoreBuilder} (siehe  und \rr{sec:DAO-Builder}). %Dieser Datenfluss ist in \rrr[Abb.]{abb:datenfluss} dargestellt.

Die Interaktion von \javaid{DumpImportDriver}, \javaid{ConceptImporter}, \javaid{WikiTextAnalyzer} und \javaid{LocalConceptStoreBuilder} wird im nächsten Abschnitt genauer erläutert.

\section{Ablauf und Parallelisierung  (Pipeline)}
\label{sec:pipeline}\label{sec:parallelisierung}
\begin{figure}[htbp]
  \includegraphics[width=\textwidth]{import-sequenz}
  \caption{Parallelisierung: die Import-Pipeline}
  \label{abb:parallelisierung}
\end{figure}

Die Aufgabe von \javaid{ImportConcepts} lässt sich in zwei Abschnitte gliedern: den eigentlichen Datenimport, also das Lesen, Interpretieren und Speichern der Daten, und die Nachbereitung, also die Konsolidierung der Daten. Der Datenimport ist selbst in mehrere Schritte gegliedert, von denen einige parallel ausgeführt werden können --- diese Parallelisierung soll hier näher beschrieben werden.

Die am Datenimport beteiligten Komponenten sind \javaid{DumpImportDriver}, \javaid{ConceptImporter}\javaid{WikiTextAnalyzer} und \javaid{LocalConceptStoreBuilder} \bzw \javaid{DatabaseLocalConceptStoreBuilder}. Diese Komponenten arbeiten zum Teil parallel (siehe \rrr[Abb.]{abb:parallelisierung}) und sind wie folgt gekoppelt:

Im Haupt-Thread des \javaid{ImportConcepts}-Programms wird die \javaid{runImport}-Methode von \javaid{DumpImportDriver} aufgerufen --- sie ist der Einstiegspunkt für den gesamten Datenimport. \javaid{DumpImportDriver} extrahiert mit Hilfe der MWDumper-Bibliothek den Wiki-Text der einzelnen Seiten aus der XML-Struktur der Dump-Datei. Der Wiki-Text jeder Seite wird dann, zusammen mit der zugehörigen Metainformation wie dem Seitentitel, in eine Warteschlange eingefügt, aus der er, gemäß dem \lingo{Consumer-Producer}-Muster, von einem anderen Thread herausgenommen wird, der ihn dann an die Methode \javaid{handlePage} von \javaid{ConceptImporter} übergibt. Die Warteschlange ist auf \var{n} Einträge beschränkt: wenn sich schon \var{n} unbearbeitete Einträge in der Warteschlange befinden, wenn der Producer (\javaid{DumpImportDriver}) einen Eintrag hinzufügen will, so wird dieser blockiert, bis der Consumer (\javaid{ConceptImporter}) einen Eintrag aus der Warteschlange entnommen hat. Die Länge der Warteschlange ist per Default 8 und kann über den Tweak-Wert \javaid{dumpdriver.pageImportQueue} konfiguriert werden --- die Länge ist aber relativ unerheblich, da die Warteschlange in der Regel voll  und der Producer-Thread blockiert ist. Die Warteschlange dient also nicht als Puffer, sondern lediglich der Entkopplung der beiden Aktivitäten, die eine parallele Ausführung erlaubt; eine Entkopplung nach dem \lingo{Rendezvous}-Muster wäre hier ebenfalls ausreichend. Wird \javaid{dumpdriver.pageImportQueue} auf 0 gesetzt, so wird der Import "`durchgekoppelt"' und keine Warteschlange verwendet --- in diesem Fall ruft der \javaid{DumpImportDriver} direkt die Methode \javaid{handlePage} von \javaid{ConceptImporter} auf.

Wie oben beschrieben, wird der \javaid{ConceptImporter} in einem eigenen Thread betrieben und aus einer Warteschlange bedient. Für jeden Eintrag aus der Warteschlange wird die Methode \javaid{handlePage} mit dem Wiki-Text und den zugehörigen Metadaten wie dem Seitentitel aufgerufen. Diese Methode verwendet nun zunächst den \javaid{WikiTextAnalyzer}, um den Wiki-Text in das Ressourcenmodell, implementiert durch \javaid{WikiTextAnalyzer.WikiPage} (siehe \rr{model:ressourcenmodell}), zu überführen, das heißt, den Text zu parsen und die relevanten Features (\rr{entwurf:ressourcenmodell}) wie Wiki-Links, verwendete Vorlagen etc. zu extrahieren. Die so bestimmten Eigenschaften werden dann interpretiert und entsprechende Informationen werden zur Speicherung an die Datenzugriffsschicht übergeben (siehe \rr{transform:import} und \rr{sec:DAO-Builder}).

\label{p:db-flush} Die datenbankbasierte Implementation der Datenzugriffsschicht (DAO), \javaid{DatabaseLocalConceptStoreBuilder}, verwendet intern für jede Datenbanktabelle einen Puffer, in dem neue Einträge zwischengespeichert werden. Ist ein Puffer voll, so wird er in eine Warteschlange gelegt und ein neuer Puffer für die betreffende Tabelle verwendet. Ein separater Thread arbeitet im Hintergrund die Puffer aus der Warteschlange ab, indem er einen nach dem anderen entnimmt und an die Datenbank weiterleitet. Das entspricht einer asynchronen \lingo{Flush}-Funktion für die Puffer. Bei einem expliziten Aufruf der Methode \javaid{flush} werden die Puffer aller Tabellen in die Warteschlange des \nospell{Flush-Threads} gelegt und dann gewartet, bis alle Daten in die Datenbank geschrieben wurden. 

Die Länge der Warteschlange für die asynchronen \lingo{Flush}-Funktion ist per Default 4 und kann über den Tweak-Wert \javaid{dbstore.backgroundFlushQueue} konfiguriert werden --- die Länge ist aber relativ unerheblich, da die Warteschlange in der Regel leer sein sollte, da nur recht selten Einträge in ihr abgelegt werden. Sollte die Warteschlange häufig voll sein und damit den Arbeitsthread blockieren, sollte die Größe der Einfügepuffer erhöht werden --- das kann über den Tweak-Wert \javaid{dbstore.insertionBufferFactor} geschehen. Dabei ist allerdings zu beachten, dass die Größe jedes einzelnen Puffers durch die MySQL-Konfigurationsvariable \sqlid{max_allowed_packet} begrenzt ist --- gegebenenfalls muss auch diese erhöht werden. Die Warteschlange dient also vornehmlich der Entkopplung der langwierigen Flush-Operation, so dass diese parallel zur Analyse des Wiki-Textes ausgeführt werden kann. Wird \javaid{dbstore.backgroundFlushQueue} auf 0 gesetzt, so wird die Flush-Operation "`durchgekoppelt"' und keine Warteschlange verwendet --- in diesem Fall muss der Arbeitsthread warten, während volle Einfügepuffer in die Datenbank übertragen werden. Werden die Tweak-Werte \javaid{dbstore.useEntityBuffer} und \javaid{dbstore.useRelationBuffer} auf \javaid{false} gesetzt, so wird für das Einfügen in die Datenbank gar kein Puffer benutzt, jeder Eintrag wird sofort übertragen. Dieser Modus ist allerdings sehr langsam und nur für die Fehlersuche sinnvoll.
