Metadaten im jadice Dokumentenmodell

Während die Properites eines 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 (zum Beispiel RDF, XMP) manche aber auch eher spezialisiert sind (zum Beispiel 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 beziehungsweise die Verwendung von Metadaten, die aus Dateiformaten beziehungsweise 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 beziehungsweise der Transport von integrationsspezifischen Metadaten, also zum Beispiel von Indexdaten eines Archivsystems.

    Folgende Klassen unterstützen das Verwalten integrationsspezifischer Metadaten:

Metadaten nutzen: der 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 beziehungsweise die Ausgabe in ein anderes Medium.

Die einfachste aber äußerst nützliche Form der Konvertierung der Metadaten stellt die Umwandlung 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, zum Beispiel auf die Konsole, muss kein hoher Aufwand betrieben 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, das heißt ein org.w3c.dom.Document, überführen.

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

Der MetadataNode bietet zunächst diejenigen Funktionen, die man von ihm erwartet: Zugriff auf Basisinformationen (getName(), getText(), getCDATA()), Attribute (getAttribute(...), 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 unter minimalem Aufwand mittels 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, zum Beispiel 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 zum Beispiel mit

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

Das XMP-Dokument ist jedoch Teil des Metadaten-Baumes, das heißt Abfragen könnten auch unmittelbar Elemente aus diesem adressieren. Auch die Daten aus eventuell 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? Bitte sehr:

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 BasicDocument: während das Document lediglich ein MetadataProvider ist (da nicht davon auszugehen ist, dass alle Document-Implementierungen sinnvoll schreibbare Metadaten bereitstellen), 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 zum Beispiel 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, zum Beispiel InputStreams, Files, Reader und so weiter, 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);
[jadice document platform Version 5.5.12.1: Dokumentation für Entwickler. Veröffentlicht: 2021-08-17]