Dokumente
jadice web toolkit Dokumentenmodell
Das Dokumentenmodell wurde von der jadice document platform 5 übernommen und leicht erweitert. Hinzugekommen sind sogenannte PageSegmentHandles innerhalb der PageSegments, über die der Server die einzelnen PageSegments wiederherstellen kann (FailOver). Dies bietet zusätzlich die Möglichkeit von stateless Servern, auch wenn es sinnvoll ist, die Benutzer immer auf den gleichen Server zu leiten, um deren Cache auszunutzen und das Dokument nicht immer neu holen und parsen zu müssen.
Zum jadice document platform 5 Dokumentenmodell siehe die dortige Dokumentation
Dokumente laden
Das Laden eines Dokuments wird im jadice web toolkit durch die Client-Anwendung initiiert. Folgend finden Sie eine grobe Beschreibung des Ablaufs beim Laden eines Dokuments:
Ladeablauf
-
Zunächst erstellt der Client die Source-Objekte, die geladen werden sollen und teilt diese dann dem Reader mit. Ein Dokument kann hierbei auch aus mehreren Sources bestehen.
-
Wurden alle Sources dem Reader übergeben, kann der Ladevorgang gestartet werden. Dies geschieht über den Aufruf der Methode Reader. complete. Die Dokument-Struktur (im Prinzip eine Reihe von Source Objekten) werden nun an den Server übermittelt.
-
Der Server sucht zu jeder Source über die DocumentDataProviderRegistry jeweils passende DocumentDataProvider Implementationen, welche die Sources verarbeiten können.
-
Die DocumentDataProvider Implementationen werden nun nacheinander aufgerufen und sind für das Laden der Seiten zuständig. Die Informationen, welche Daten genau geladen werden sollen, ist in den Source-Objekten spezifiziert, welche die DocumentDataProvider Implementationen als Parameter erhalten.
-
Um ein späteres Wiederherstellen von Dokumenten zu ermöglichen, wird für eine Source auch ein PageSegmentHandle mit Hilfe des DocumentDataProviders erzeugt. Dieses PageSegmentHandle wird anschließend automatisch einer geladenen Seite zugewiesen.
-
Das Dokument mit den
PageSegment
s wird nun dem Client übergeben. Der Reader benachrichtigt dieAsyncCallback
; Implementation, welche beim Aufruf der Reader. complete -Methode übergeben wurde.
Bei der Übergabe an die AsyncCallback
; Implementation muss das
Dokument noch nicht vollständig auf dem Client angekommen sein. Wenn
möglich, werden die Seiten sukzessiv an den Client übermittelt, um
möglichst frühzeitig die erste Seite eines Dokuments darstellen zu
können. Der aktuelle Zustands eines Dokuments kann jederzeit über
die Methode Document
.getState überprüft werden.
Ladeablauf mit URIs
Das jadice web toolkit bringt bereits Implementierungen von Source und PageSegmentHandle für einen Ladevorgang mit URIs mit. Als Integrator muss dann lediglich ein entsprechender DocumentDataProvider bzw. konkret ein UriBasedDocumentDataProvider für das jeweilige Schema implementiert werden.
LazyLoading von zusammengesetzten Dokumenten
Mit dem jadice-Dokumentenmodell ist es möglich, ein logisches Dokument zu definieren, das aus mehreren physikalischen Dokumenten besteht. Lädt man ein solches Composite-Dokument nun, werden normalerweise alle Teildokumente geladen unabhängig davon, welche Teildokumente wirklich benötigt werden.
Um die Ladezeiten solcher Dokumente zu optimieren, ist es möglich, die verschiedenen Teildokumente jeweils erst dann zu laden, wenn diese benötigt werden. Lädt man ein solches Composite-Dokument, werden standardmäßig alle Teildokumente geladen. Mit der LazyLoading-Funktionalität kann das Laden auf die Teildokumente beschränkt werden, die gerade benötigt werden (z.B. zur Anzeige). Bei Funktionen, die das gesamte Dokument betreffen (z.B. die Textsuche oder der Export), werden alle restlichen Teildokumente nachgeladen.
Die LazyLoading-Funktionalität wird dadurch realisiert, dass zunächst
ein LazyStreamPageSegment
als Platzhalter für das eigentliche
PageSegment eingefügt wird. Erst wenn dieser Platzhalter gerendert
werden soll, werden die eigentlichen Inhalte geladen und gerendert.
Dazu muss durch den Integrator in der Implementierung des
DocumentDataProvider sichergestellt werden, dass in der
read()
-Methode ein Dokument erzeugt wird, dessen Seiten
LazyStreamPageSegment
s enthalten. Bei Erzeugung der
LazyStreamPageSegment
s wird dabei definiert, woher der eigentliche
Inhalt später gelesen wird. Weiterhin muss durch die Implementierung des
PageSegmentHandle und der PageSegmentSource sichergestellt sein,
dass auch bei Recovery einer nicht mehr im Cache vorgehaltenen Seite die
zur Wiederherstellung benötigte Information im PageSegmentHandle
vorgehalten wird.
Wird auf einem LazyStreamPageSegment
dann zu einem späteren Zeitpunkt
das Rendering ausgelöst, wird der eigentliche Inhalt dieses
PageSegment
s gelesen und gerendert. Die Quelle des eigentlichen
Inhalts muss bereits bei Erzeugung des LazyStreamPageSegment
definiert
werden.
Derzeit ist es im jadice web toolkit nicht vorgesehen, die Eigenschaften
von Seiten - beispielsweise deren Dimension - nachträglich im Client zu
aktualisieren. Deswegen muss die Größe der PageSegments bereits bei
Erzeugung der LazyStreamPageSegment
gesetzt werden. Weiterhin wird
aufgrund der lazy geladenen Eigenschaften der PageSegments die
Verwendung auflösungsabhängiger Annotationen nicht unterstüzt.
Die Verwendung des Lazy-Load-Mechanismus ist im
showcase
des jadice web toolkit beispielhaft implementiert. Dort wird im
LazyClassPathDocumentDataProvider
basierend auf LazyClassPathSource
und einem dazugehörigen LazyClassPathHandle
die Erzeugung eines
Dokuments mit LazyStreamPageSegment
s als Platzhaltern gezeigt.
Wiederherstellung von Dokumenten
Die recover Methode wird aufgerufen, wenn im Servercache keine Dokumentstrukturinformation zu einem bereits geladenen Dokument gefunden werden kann.
Es gibt verschiedene Szenarien, die zu einem Fehlen des Dokumentes im Cache führen können:
-
Ein Benutzer öffnet ein Dokument und der Server wird, während dieses Dokument geöffnet ist, neu gestartet
-
Ein Benutzer öffnet ein Dokument. Anschließend fällt der zugehörige Server aus, und alle weiteren Anfragen des Benutzers werden durch LoadBalancing auf andere Server geleitet.
-
Ein Benutzer öffnet ein Dokument und dieses bleibt längere Zeit im Viewer geöffnet. In der Zwischenzeit werden viele weitere Dokumente von anderen Benutzern zur Anzeige gebracht. Durch den sich selbst organisierenden Servercache wird das schon länger serverseitig nicht angesprochene, aber immer noch geöffnete, Dokument aus dem Servercache entfernt.
Führt der Benutzer nun im besagten Dokument eine Aktion aus, wie z.B. das Scrollen zu einer anderen Seite, werden diese Dokumentinformationen, welche sich im Cache befanden, aber für den Rendervorgang der zugehörigen Seite benötigt. In diesem Fall wird die recover()-Methode des DocumentDataProviders aufgerufen.
Die Implementierung sieht im einfachen Fall analog zur read-Methode aus. Falls aber z.B. das Archivdokument im Rahmen der read-Methode serverseitig im Filesystem abgelegt wird, könnte die Implementierung der recover-Methode ohne einen weiteren Archivzugriff auskommen und versuchen, das Archivdokument direkt aus dem Filesystem einzulesen. Erst falls es dort nicht gefunden werden kann, muss ein erneuter Archivzugriff erfolgen.
Hierbei muss jedoch beachtet werden, dass in der recover()-Methode die PageSegmentHandles zur Verfügung stehen, nicht aber die Source. In den meisten Fällen können die Source-Informationen einfach auch in die Handles geschrieben werden, jedoch ist hier zu beachten, dass diese Handles in jedem PageSegment abgelegt werden und somit bei jeder Anfrage an den Server, die ein solches PageSegment betrifft, mit übertragen werden. Dies ist beispielsweise beim Rendern eines Tiles der Fall. In Spezialfällen kann es vorkommen, dass die Sourcen sehr groß werden, beispielsweise wenn direkt ein Datenstrom darin abgelegt wurde. Dann sollte dieser im DocumentDataProvider gesichert werden und nur eine Referenz darauf in das Handle geschrieben werden. Dies verringert die Größe der zwischen Client und Server übertragenen PageSegmentHandles, sodass die Netzwerkinfrastruktur entlastet wird.
Zusammengesetzte Dokumente
Zusammengesetzte Dokumente können nur serverseitig zusammengesetzt werden, damit die Recovery funktioniert. Ein clientseitiges Zusammensetzen ist aktuell nicht möglich. Das serverseitige Zusammensetzen kann mittels der Klassen CompositeDocumentDataProvider, CompositeSource und CompositeHandle erfolgen. Der nachfolgende Code zeigt die Verwendung des CompositeDocumentDataProviders. In diesem Beispiel werden drei Teildokumente zu einem zusammengesetzen Dokument zusammengefasst. Das zusammengesetzte Dokument beinhaltet hintereinander die drei Teildokumente. Mittels dieser Technik ist es möglich, beim Ladevorgang Seiten in einem Dokument aus verschiedenen Quellen zusammenzustellen. Es ist zu beachten, dass die append-Metode nur einmal aufgerufen werden darf, damit die Recovery gewährleistet ist. Hierzu sind die Teildokumente zunächst zu einem CompositeSource-Objekt zusammenzusetzen.
import com.levigo.jadice.web.client.reader.Reader;
import com.levigo.jadice.web.demo.common.server.dataprovider.CompositeDocumentDataProvider;
import com.levigo.jadice.web.demo.common.shared.service.sources.CompositeSource;
private AsyncCallbackk<Document> callback;
// create resources on client
private final static String[][] FILES_SERVER_COMPOSITE = new String[][]{
{
"Teildokument 1 (PDF)", "/Teildokument 1.pdf", "pdf"
}, {
"Teildokument 2 (TIFF)", "/Teildokument 2.tif", "image"
}, {
"Teildokument 3 (PDF)", "/Teildokument 3.pdf", "pdf"
},
};
List<String> sources = new ArrayList<>();
// add in reverse order
for (int i = FILES_SERVER_COMPOSITE.length - 1; i >= 0; i--) {
String[] file = FILES_SERVER_COMPOSITE[i];
sources.add(file[1]);
}
CompositeSource compositeSource = new CompositeSource(sources, "CompositeDoc (Server)");
Reader reader = new Reader();
reader.append(compositeSource); // important: only call append method once
// client finished creating CompositeSource and sends the list of documents to assemble to server
reader.complete(new ReaderEventTranslator(callback);
//server loads documents
compositeDocumentDataProvider.read(reader, resources);
Laden von hOCR-Daten
Die Verarbeitung und Verwendung von hOCR-Daten wird im jadice web toolkit unterstützt. Hierbei kann sich an der Dokumentation der jadice document platform 5, sowie dem Showcase orientiert werden.
Ausblenden von Seiten über FilteredDocuments
Mit Hilfe von FilteredDocuments ist möglich, Dokumentseiten auszublenden, um die Anzeige dieser Seiten zu unterdrücken. Dies kann für den Haupt-Viewer und die Thumbnail-Ansicht unabhängig voneinander geschehen. Eine ausführliche Beschreibung verschiedener Use Cases und deren Implementierung findet sich in der jadice knowledge base und verschiedenen Showcases.
Die APIs im Detail
Reader
Der Reader im Client besitzt die Methode append. append fügt einen neuen Abschnitt an die aktuelle Position ein. Wichtig ist, dass die append-Methode nur einmal aufgerufen werden darf, andernfalls entstehen Probleme bei der Recovery. Siehe Beispiel zu zusammengesetzten Dokumenten unter Zusammengesetzte Dokumente
Die Methode add wurde deprecated. Um ein Dokument auf ein anderes zu legen, kann stattdessen die serverseitige Implementierung genutzt werden. Vergleiche ClassPathWithAnnoDocumentDataProvider.read. Es ist wichtig, den targetIndex nach dem Lesen des Dokuments und vor dem Einlesen der Annotationen zurückzusetzen. Das Zurücksetzen erfolgt auf den Wert, den er vor dem Lesen des Dokuments innehatte.
DocumentDataProvider
Der DocumentDataProvider ist für den Zugriff auf die tatsächlichen Dokumentdaten und den Ladevorgang dieser verantwortlich.
Jeder DocumentDataProvider wird mit einer Source und einer PageSegmentHandle Klasse assoziiert. Diese Assoziation erfolgt über eine Registrierung des DocumentDataProvider bei der DocumentDataProviderRegistry.
Bei serverseitig zusammengesetzten Dokumenten ist es essentiell, dass in der Implementierung des DocumentDataProviders der Ziel-Seitenindex über Reader. setTargetIndex vor dem Lesen jedes Streams korrekt gesetzt wird, da sonst die Dokumente bzw. deren Annotationen falsch bzw. unvollständig zusammengesetzt werden.
UriBasedDocumentDataProvider
Ein spezieller DocumentDataProvider der für ein spezielles URI-Schema verantwortlich ist.
Bei der Verwendung eines eigenen URI-Schemas für das Laden von Dokumenten ist keine Implementierung von Source
und
PageSegmentHandle
erforderlich. Dieser Weg ist sehr viel komfortabler als der Weg über Source
und
PageSegmentHandle
, da hier weniger Code zu schreiben ist.
Das Angular-Frontend unterstützt lediglich das Laden von Dokumenten über URIs, nicht über die Kombination aus
Source
und PageSegmentHandle
.
Beispiel: für das Laden eines Dokuments (mit der ID 1YQ4XR08
) aus einem P8-Archiv würde der Lesevorgang folgendermaßen
aussehen:
Angular-Frontend
<jwt-multi-mode-viewer #viewerComponent
[source]="{uri: 'p8://1YQ4XR08'}"/>
GWT-Frontend
Reader r = new Reader();
UriSource source = new UriSource("p8://1YQ4XR08", null);
r.read(source, new ReaderEventTranslator(new AsyncCallback<Document>() {...}
Backend
@Component
public class P8SchemeDataProvider implements UriBasedDocumentDataProvider {
private final String[] schemes = new String[] { "p8" };
public P8SchemeDataProvider() {
}
@Override
public void read(Reader reader, UriSource source) throws JadiceException, IOException {
}
@Override
public void recover(Reader reader, UriHandle handle) throws RecoverFailedException, JadiceException {
}
@Override
public boolean pertainsTo(String scheme) {
return P8SchemeDataProvider.super.pertainsTo(scheme);
}
@Override
public List<String> getSchemes() {
return Arrays.asList(schemes);
}
}
DocumentDataProviderRegistry
Um einen DocumentDataProvider für das jadice web toolkit zur Verfügung zu stellen, muss dieser bei der DocumentDataProviderRegistry registriert werden. Diese Registrierung wird über den WebtoolkitServerContext zur Verfügung gestellt.
Automatische Registrierung von DocumentDataProvidern
DocumentDataProvider-Implementierungen
werden automatisch registriert, sofern diese mit @Component
annotiert wurden.
Siehe auch
Automatische Registrierung von DocumentDataProvidern.
Übertragung user-spezifischer Dokumenten- und Seitenproperties
Userspezifische Properties vom Typ String, Integer und Boolean können zum einen vom Server an den Browser und zum anderen vom Browser an den Server übertragen werden. Hierbei wird zwischen dokumenten- und seitenbezogenen Properties unterschieden. Dokumentenbezogene Properties sind einem kompletten Dokument zugeordnet. Falls dies für einen bestimmten Anwendungsfall nicht feingranular genug sein sollte, können (zusätzlich) einer einzelnen Seite Properties hinzugefügt werden.
Der Transport der Properties erfolgt über eine Map
, die im einen Fall
dem zugehörigen Dokument und im anderen Fall der zugehörigen Seite
zugeordnet ist. Das Setzen von Properties wird über die Methode
com.levigo.jadice.document.Document#getProperties#put
bzw.
com.levigo.jadice.document.Page#getProperties#put
realisiert. Analog
dazu werden die Properties auf der Gegenseite über
com.levigo.jadice.document.Document#getProperties#get
bzw.
com.levigo.jadice.document.Page#getProperties#get
ausgelesen.
Das Hinzufügen und Ändern von Properties wird in Form von
PropertyChangeEvents
propagiert. Durch die Nutzung von
DocumentListenern
kann auf diese Fälle flexibel reagiert werden.