Metadaten im jadice Dokumentenmodell

jhWährend die Properites eines von PropertiesProvider für eher technische orientierte Informationen gedacht sind und darüber hinaus beliebige Objekttypen aufnehmen können, haben wir, auch im Sinne einer klareren Trennung der Zuständigkeiten, dem Document eine zweite Schnittstelle spendiert, die für Informationen beschreibenden Charakters, also Metadaten, zuständig ist. Für Metadaten existieren am Markt bereits diverse Standards und Datenformate, die teilweise sehr generisch (z.B. RDF, XMP) manche aber auch eher spezialisiert sind (z.B. EXIF). Da die jadice document platform mit den verschiendensten Dokumentformaten umgehen muss, kommt für uns nur eine Repräsentation in Frage, die all diese Formate aufnehmen und mit möglichst geringem Verlust transportieren kann. Da sich insbesondere die XML-basierten Metadaten-Formate einer großen Verbreitung erfreuen, bietet es sich natürlich an, die jadice-interne Repräsentation der Metadaten ebenfalls auf XML basieren zu lassen. Leider sind die unter Java standardmäßig verfügbaren APIs und Repräsentationen für XML nicht sehr benutzerfreundlich. Für einen Ausweg aus diesem Dilemma haben wir uns von dem OpenSource-Projekt xmltool inspirieren lassen: eine DOM-basierende Repräsentation der Daten in Kombination mit einer benutzerfreundlichen Fassade. Ein Dank geht hier an den Erfinder von xmltool - Mathieu Carbou - für viele gute Ideen.

Metadaten im jadice Dokumentenmodell

Für die Verwendung von Metadaten im jadice Dokumentenmodell lassen sich zwei prinzipiell verschiedene Anwendungsfälle unterscheiden:

  • Der Zugriff auf bzw. die Verwendung von Metadaten, die aus Dateiformaten bzw. Datenströmen stammen und von jadice selbst bereit gestellt werden.
    Jadice stellt Metadaten aus gelesenen Datenströmen über die Klassen
    • PageSegment (für Metadaten, die einzelne Seiten beschreiben) sowie
    • PageSegmentSource (für Metadaten, die einen gesamten Datenstrom beschreiben) bereit. Es existieren noch eine Reihe weiterer Klassen, die Metadaten bereit stellen, diese fallen jedoch nicht unter das derzeit veröffentliche API.
  • Das Hinzufügen bzw. der Transport von integrationsspezifischen Metadaten, also z.B. von Indexdaten eines Archivsystems. Folgende Klassen unterstützen das Verwalten integrationsspezifischer Metadaten:
    • Document
    • Page
    • Reader

Metadaten nutzen: de MetadataProvider

Klassen, die lesenden Zugriff auf Metadaten bieten, implementieren die Schnittstelle MetadataProvider. Ebenso wie der PropertiesProvider ist diese Schnittstelle sehr spartanisch gehalten. Sie bietet lediglich die Methode Metadata getMetadata(). Das von dieser Methode gelieferte Metadata-Objekt entspricht grob einem org.w3c.dom.Document, hat jedoch eine sehr viel aufgeräumtere Schnittstelle. Sie bietet drei wesentliche Funktionsgruppen:

  • den Zugriff auf den Inhalt des Dokumentes, also den Wurzelknoten
  • die Validierung des Inhaltes gegen ein Schema sowie
  • die Konvertierung des Inhaltes in ein anderes Format bzw. die Ausgabe in ein anderes Medium.

Die einfachste aber äußerst nützliche Form der Konvertierung der Metadaten stellt die Konvertierung in eine Zeichenkette dar: im Gegensatz zu vielen anderen XML-Repräsentationen liefert ein simpler toString()-Aufruf auf einem Metadata-Objekt die Metadaten in "schön" formatierter XML-Form zurück. Für eine Ausgabe der Metadaten z.B. auf die Konsole braucht deshalb kein großes Aufhebens gemacht werden - ein System.out.println(document.getMetadata()) genügt. Mit weiteren Formen der Konvertierung lassen sich Metadaten in OutputStreams oder Writer schreiben, als javax.xml.transform.Result weiterverarbeiten oder in DOM-Form, d.h. ein org.w3c.dom.Document, überführen.

Soweit, so nütztlich, die weitaus interessanteren Tricks hat jedoch der MetadataNode (der Wurzelknoten wird von Metadata.getRoot() zurückgegeben) auf Lager:

Metadaten durch brennende Ringe springen lassen: der MetadataNode

Der MetadataNode bietet zunächst diejenigen Funktionen, die man von ihm erwartet: Zugriff auf Basisinformationen (getName(), getText(), getCDATA()), Attribute (get/findAttribute(...)) und Kindknoten (getChild*(...)). Auch der Zugriff auf die darunter liegende DOM-Repräsentation wird gewährt mittels getElement() - auch wenn man diesen Wunsch sicher selten verspüren wird. Richtig mächtig wird der MetadataNode aber erst dadurch, dass er in der Lage ist, Aufgaben nicht nur durch einfache Navigation des DOM-Baumes zu erledigen, sondern viele Funktionen mit minimalem Aufwand mit XPath-Ausdrücken genutzt werden können. Aber lassen wir doch einfach einige Beispiele sprechen:

// wir laden zunächst ein TIFF-Bild
final Reader reader = new Reader();
reader.read(new File("example.tif"));

// hiervon holen wir uns die erste Seite und darin das Standard-PageSegment
final PageSegment ps = reader.getDocument().getPage(0).getPageSegment(DocumentLayer.DEFAULT);

// lassen wir die Metadaten sprechen
final Metadata md = ps.getMetadata();
System.out.println(md.toString()); // simples Ausgeben auf die Konsole

In der Konsole erscheint das Ergebnis, das wie folgt beginnt:

<tiff>
    <ifd group="TIFF6">
        <field code="256" count="1" name="ImageWidth" type="SHORT">
            <number>1168</number>
        </field>
[...]

Soweit so einfach. Wir würden nun gerne wissen, welche Beschreibung der Erzeuger des TIFFs für uns hinterlegt hat:

final MetadataNode r = md.getRoot();
r.evalToNumber("ifd[@group='TIFF6']/field[@name='ImageDescription']/string")

Wir hätten den XPath-Ausdruck auch noch weiter vereinfachen können, z.B. durch Weglassen des /string oder des 'ifd'-Pfadteils, so sehen Sie aber, wie die vollständige und absolute Adressierung eines Elementes aussieht. Sollten Sie einmal in die Verlegenheit kommen, wissen zu müssen, wie an welcher Position in der Datei sich der fünfte Strip eines gestripeten TIFFs befindet - hier ist die Antwort:

// Ja, XPath adressiert seine Arrays 1-basiert!
r.evalToNumber("ifd[@group='TIFF6']/field[@name='StripOffsets']/number[5]")

Die Metadaten für TIFF-Dateien umfassen nicht nur die Basisinformationen aus dem TIFF6-Standard, sondern auch eingebettete EXIF- und XMP-Metadaten. Das eingebettete XMP-Dokument (sofern vorhanden) erhalten Sie z.B. mit

r.getChildren("ifd[@group='TIFF6']/field[@name='XMP']").get(0)

Das XMP-Dokument ist jedoch Teil des Metadaten-Baumes, d.h. Abfragen könnten auch unmittelbar Elemente aus diesem adressieren. Auch die Daten aus evtl. eingebetteten ICC-Profilen werden über die Metadaten bereitgestellt. Der Name des Geräteherstellers (Device Manifacturer Description - dmnd) des Geräts, für das das Farbprofil gilt, gefällig? Bittesehr:

r.evalToString("ifd[@group='TIFF6']/field[@name='ICCProfile']/icc-profile/tag[@signature='dmnd']/ascii")

Metadaten erzeugen und hinzufügen: der MutableMetadataProvider

Bisher gingen wir davon aus, dass die Metadaten durch jadice bereit gestellt wurden. Allerdings ist die API, wie oben bereits erwähnt, auch für die Aufnahme integrationsspezifischer Daten gedacht. Für die Erzeugung von Metadaten steht ein separates Inferface bereit, das MetadataProvider erweitert und von allen Klassen, für die die Aufnahme von Metadaten vorgesehen ist, implementiert wird: MutableMetadataProvider. An dieser Stelle trennen sich die Wege des Document und des SimpleDocument: während das Document lediglich ein MetadataProvider ist, da nicht gesagt ist, dass alle Document-Implementierungen sinnvoll schreibbare Metadaten bereit stellen, ist die Standardimplementierung von Document bereit dafür. Schauen wir uns einfach ein kleines Beispiel dazu an:

/*
 * Erzeugen eines BasicDocument. Wir benutzen hier bewusst nicht den Typ Document für die
 * Variable doc, da nur das BasicDocument ein MutableMetadataProvider ist.
 */
final BasicDocument doc = new BasicDocument();

// Wurzelknoten der Metadaten holen
final MutableMetadataNode mmr = doc.getMutableMetadataRoot();

/*
 * Wir hängen unter den Wurzelknoten einen Datensatz, der z.B. im Archiv als Indexdatensatz
 * hinterlegt sein könnte. Der MutableMetadataNode erlaubt mit seinem "Fluent Interface" eine -
 * für Java-Verhältnisse - recht kompakte Formulierung dieser Aufgabe.
 */
mmr.addNode("index-data") // liefert den Knoten "index-data" zurück!
.addAttribute("id", "123456789")
// nun legen wir einige Knoten, jeweils mit Attribute und Textinhalt an. addText(...) springt
// automatisch zum Elternknoten zurück.
.addNode("Versicherungsnehmer").addAttribute("type", "string").addText("Max Mustermann")
.addNode("Versicherungsnummer").addAttribute("type", "number").addText("876492148913")
.addNode("Vertragsart").addAttribute("type", "string").addText("KFZ-Haftpflicht")
.addNode("Eingangsdatum").addAttribute("type", "date").addText("2010-02-11")
.addNode("Schluesselworte").addAttribute("type", "list-of-strings")
.addNode("entry").addText("Schaden")
.addNode("entry").addText("Gutachten")
.addNode("entry").addText("Kasko");

// Metadaten ausgeben
System.out.println(doc.getMetadata());

Die Ausgabe ist automatisch "schön" formatiert:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document>
<index-data id="123456789">
<Versicherungsnehmer type="string">Max Mustermann</Versicherungsnehmer>
<Versicherungsnummer type="number">876492148913</Versicherungsnummer>
<Vertragsart type="string">KFZ-Haftpflicht</Vertragsart>
<Eingangsdatum type="date">2010-02-11</Eingangsdatum>
<Schluesselworte type="list-of-strings">
<entry>Schaden</entry>
<entry>Gutachten</entry>
<entry>Kasko</entry>
</Schluesselworte>
</index-data>
</document>

Wenn die Metadaten bereits als XML vorliegen, können wir diese direkt einbinden:

/*
 * Der MetadataBuilder übernimmt die Erzeugung eines Metadata-Baums. Er nimmt noch viele
* andere Formen des Input, z.B. InputStreams, Files, Reader, usw., entgegen.
 */
final MutableMetadataNode md = MetadataBuilder.from(
"<wetter>"
+ "  <temp einheit=\"celsius\">9</temp>"
+ "  <niederschlag menge=\"2l/qm\" risiko=\"94%\"/>"
+ "</wetter>",
false);

/*
 * Das XML enthält zusätzlichen "whitespace", der das Pretty-Printing stört. Wir räumen
 * diesen deshalb zunächst auf:
 */
md.getDocument().trimWhitespace();

// nun müssen wir den Knoten nur noch einhängen:
doc.getMutableMetadataRoot().addNode(md);