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.
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) sowiePageSegmentSource
(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
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);