Im Artikel Das jadice Document & Co. haben wir die Grundlagen des neuen jadice Documents kennen gelernt. Diesmal steigen wir etwas tiefer ein und betrachten, welche Tricks das Document und die anderen Klassen des Dokumentmodells noch auf Lager haben.
Die Schnittstelle PropertiesProvider
Das Document-Interface erweitert die Schnittstelle PropertiesProvider. Diese Schittstelle findet sich noch in einer ganzen Reihe weiterer Klassen und Interfaces der document platform. In allen Fällen dient sie dem gleichen Zweck, nämlich es zu erlauben, integrationsspezifische Zusatzdaten an diese Objekte anzuhängen. PropertiesProvider stellen zu diesem Zweck eine Map zur Verfügung, die Strings als Schlüssel verlangt, jedoch beliebige Objekte als Werte enthalten kann. Diese Funktionalität gab es bereits in jadice 4 in Form der Methoden putUserProperty(key,value)/getUserProperty(key), allerdings haben wir in der neuen Version einige wesentliche Änderungen vorgenommen:
- Statt das API der PropertiesProvider implementierenden Klasse mit einer Vielzahl von Methoden zu überfrachten, sind wir wieder den Weg der Komposition gegangen. PropertiesProvider definiert exakt eine Methode, nämlich Map<String, Object> getProperties().
- Die Schnittstelle wird nun einheitlich an verschiedenen Stellen in unserem API verwendet, statt mehrfach mit ähnlicher aber teilweise leicht abweichender Funktionalität implementiert zu sein.
- Es besteht eine klare Aufgabentrennung zwischen Informationsbereitstellung (entsprechend der ehemaligen InfoProvider-Funktionalität) und der Haltung eher technisch orientierter Zusatzdaten. PropertiesProvider ist rein für letzteres zuständig, was aber natürlich nicht heisst, dass Informationen eines PropertiesProvider einem Benutzer nicht in bestimmten Situationen präsentiert werden können.
Die Beschreibung der in einem PropertiesProvider geführten Daten als 'integrationsspezifische Zusatzdaten' bedeutet allerdings nicht, dass die document platform nicht selbst Zusatzdaten an PropertiesProvider anhängt. Vielmehr nutzen wir diese auch selbst, und zwar in Fällen, in denen eine Anwendung zu speziell ist, als dass eine API-Erweiterung gerechtfertigt wäre. Generell sollten Sie allerdings keine Annahmen über die verwendeten Keys machen. Selbst wenn wir für die Implementierung einer Funktionalität Properties eines PropertiesProvider zur Speicherung benutzen, werden wir immer ein API zur Verfügung stellen, das dieses Implementierungsdetail verbirgt. Darüber hinaus verwenden wir als Keys generell Zeichenfolgen, die mit "com.levigo." beginnen. Sie haben also ohne Weiteres die Möglichkeit, Kollisionen zu vermeiden und können leicht erkennen, welches jadice-eigene Properties sind. Wir halten es für eine gute Idee, für die Keys eine Namenskonvention ähnlich der von Java-Packages zu verwenden, dies ist aber keineswegs verpflichtend.
Als Inspiration hier ein Beispiel, wie sie Properties verwenden könnten:
// Erzeugen eines Dokumentes
final Document doc = new BasicDocument();
// Laden des Dokumentes...
final File sourceFile = new File("MeineBeispieldatei.txt");
// ...
// Anreichern mit Properties
doc.getProperties().put("org.example.SourceFile", sourceFile);
doc.getProperties().put("org.example.LoadTime", new Date());
doc.getProperties().put("org.example.Weather", Weather.Cloudy);
// ... später
System.out.println("Eigenschaften des Dokuments");
for (final Map.Entry<String, Object> e : doc.getProperties().entrySet())
System.out.println(" " + e.getKey() + " -> " + e.getValue());
Neben dem Document sind derzeit noch folgende weitere Klassen bzw. Interfaces PropertiesProvider:
- Page
- PageSegment
- PageSegmentSource
- Annotation
- Reader
sowie darüber hinaus noch einige mehr, die wir zu einem späteren Zeitpunkt noch vorstellen werden.
Der Lebenszyklus von Properties
Properties sind in der Regel an den Lebenszyklus des Objektes gebunden, an dem sie angefügt wurden. Dies bedeutet einerseits, dass Properties automatisch per garbage collection entsorgt werden, sobald das übergeordnete Objekt entsorgt wird und der Eigenschaftswert nicht noch anderweitig referenziert wird. Andererseits müssen Sie aber natürlich beachten, dass Eigenschaftswerte über hard references gehalten werden. Sie sollten also bei "teuren" Objekten ggf. darauf achten, diese auch wieder zu entfernen (mit remove(key)), da von ihnen belegter Speicher und andere Ressourcen sonst erst freigegeben werden, wenn das übergeordnete Objekt entsorgt wird.
Benachrichtigungen über Änderungen von Properties
Manche Klassen, darunter die Standardimplementierungen BasicDocument und BasicPage versenden Benachrichtigungen bei Änderungen an ihren Properties. Um diese Benachrichtigungen zu empfangen, können auf dem Document DocumentListener bzw. auf der Page PageListener registriert werden. Beide Interfaces erben die Funktionalität der altbekannten Schnittstelle java.beans.PropertyChangeListener. Da sowohl das Document als auch die Page über die Änderungen an den Properties hinaus für Änderungen ihren Objekteigenschaften ensprechende PropertyChangeEvents versenden, werden wird den Keys der Properties im PropertyChangeEvent die Zeichenfolge "properties." vorangestellt. Ich denke, an dieser Stelle ist ein Beispiel angebracht:
Art der Änderung | PropertyChangeEvent | ||
---|---|---|---|
Name | OldValue | NewValue | |
Hinzufügen der Property SourceFile | property.SourceFile | null | File("someFile.txt") |
Ändern der Property SourceFile | property.SourceFile | File("someFile.txt") | File("someOtherFile.txt") |
Entfernen der Property SourceFile | property.SourceFile | File("someOtherFile.txt") | null |
Ändern des Document-State setState(CLOSED) | state | BasicState.READY | BasicState.CLOSED |
PropertiesProvider und Nebenläufigkeit
Die von jadice bereit gestellten Implementierungen von ProperiesProvider benutzen sämtlich ConcurrentHashMaps zur Bereitstellung der ProperiesProvider-Funktionalität. Sie sind daher Thread-sicher. Dies ist jedoch keine Zusage der PropertiesProvider-Schnittstelle. Im Zweifelsfalle ist es daher besser, die Dokumentation der fraglichen Implementierung hierzu zu konsultieren.