Übergreifende Funktionalitäten

Dokumente

jadice® web toolkit Dokumentenmodell

Das Dokumentenmodell wurde von der jadice® document platform 5 übernommen und leicht erweitert. Hinzugekommen sind so genannte 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

  1. 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.

  2. 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.

  3. Der Server sucht zu jeder Source über die DocumentDataProviderRegistry jeweils passende DocumentDataProvider Implementationen, welche die Sources verarbeiten können.

  4. 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.

  5. 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.

  6. Das Dokument mit den PageSegments wird nun dem Client übergeben. Der Reader benachrichtigt die AsyncCallback Implementation, welche beim Aufruf der Reader. complete -Methode übergeben wurde.

    Wichtig: Zustand des erhaltenen Dokuments

    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.

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 LazyStreamPageSegments enthalten. Bei Erzeugung der LazyStreamPageSegments 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 PageSegments 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 LazyStreamPageSegments 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

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.

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.

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

Seit Version 5.5 besteht die Möglichkeit, DocumentDataProvider-Implementierungen automatisch registrieren zu lassen. Sofern weder das „Java-EE-Integrationsmodul“ noch das „Spring Boot-Integrationsmodul“ zum Einsatz kommt, wird hierfür der JRE-ServiceLoader verwendet. Damit werden alle Implementierungen, die in der Datei META-INF/services/com.levigo.jadice.web.server.DocumentDataProvider eingetragen sind, automatisch gefunden und registriert.

Bei Verwendung des Java-EE-Integrationsmoduls basiert die automatische Registrierung auf CDI (siehe „Automatische Registrierung von DocumentDataProvidern per CDI“).

Bei Verwendung des Spring-Integrationsmoduls basiert die automatische Registrierung auf der proprietären Dependency Injection des Frameworks (siehe „Automatische Registrierung von DocumentDataProvidern“).

Dokumente schließen

Um ein Document com.levigo.jadice.document.Document korrekt zu schließen und die auf dem Server reservierten Resourcen freizugeben, wurde analog der jadice® document platform die Methode com.levigo.jadice.document.Document#close implementiert.

Folgendes Codebeispiel, welches auch im Showcase gezeigt wird, demonstriert diese Funktionalität

			import com.levigo.jadice.document.Document;
			import com.levigo.jadice.web.client.PageView;
			import com.levigo.jadice.web.client.Viewer;

			// field initialization skipped for demonstration purposes
			private Document document;
			private final Viewer viewer;

			protected void closeDocument() {
				document.close();
				viewer.getPageView().cleanup();
			}
			

Übertragung userspezifischer 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.

Annotationen

Überblick

Grundsätzliches zu Annotationen kann der Dokumentation der jadice® document platform 5 entnommen werden.

Das jadice® web toolkit unterstützt die folgenden Annotationsformate:

  • FileNet Image Services

  • IBM FileNet P8

  • IBM Content Manager 7

  • IBM Content Manager 8

  • Jadice Format (XML-basiertes, Jadice eigenes Annotationsformat)

Dabei unterstützt das jadice® web toolkit die folgenden Annotationstypen:

  • LineAnnotation

    Darstellung einer einfachen Linie. Start-/Endpunkt, Linienfarbe und Liniendicke können definiert werden.

  • ArrowAnnotation

    Darstellung eines Pfeils, ist von der LineAnnotation abgeleitet. Pfeilspitze, Winkel und Länge (in Pixel oder prozentual zur Gesamtlänge) des Pfeilkopfes können definiert werden.

  • PathAnnotation

    Darstellung eines Vektorobjekts. Vektorpfad (offen, geschlossen, gefüllt), Linien- und Füllfarbe sowie Liniendicke können definiert werden.

  • RectangleAnnotation

    Darstellung eines Rechtecks. Größe, Füllung (gefüllt/nicht gefüllt), Linien- und Füllfarbe sowie Liniendicke können definiert werden.

  • EllipseAnnotation

    Darstellung einer Ellipse, von der RectangleAnnotation abgeleitet.

  • TextAnnotation

    Darstellung von Text, von der RectangleAnnotation abgeleitet. Schriftart, -grösse und Stil sowie Textfarbe können definiert werden.

  • HistoryAnnotation (aktuell nur für IBM Content Manager 7 unterstützt, aktuell kein Parallelbetrieb mit IBM-Clients unterstützt)

    Revisionssichere Darstellung von Text, von der TextAnnotation abgeleitet.

  • StampAnnotation

    Darstellung von Text, ähnlich eines Stempels. Rotierbar, von der TextAnnotation abgeleitet. Rotation kann definiert werden.

  • CalloutAnnotation

    Legendenannotation: Verankerter, abknickbarer Pfeil, an dessen Ende ein Textblock hängt. Dieser Typ wird nur vom Jadice Format unterstützt.

  • JWTImageAnnotation

    Bildannotation: Darstellung eines Bildes, welches serverseitig zur Verfügung gestellt wird. Von der RectangleAnnotation abgeleitet. Dieser Typ wird nur vom Jadice Format unterstützt.

  • TextHighlightAnnotation

    TextHighlightAnnotation: Durch das Selektieren eines Textabschnitts innerhalb eines Dokuments wird dieser Bereich farblich markiert. Die TextHighlightAnnotation sorgt dafür, dass die Markierung dauerhaft erhalten bleibt. Sobald die Annotation erstellt ist kann sie nicht mehr verschoben werden. Dieser Typ wird nur vom Jadice Format unterstützt und ist zudem nicht in die AnnotationProfileAwareToolbar integriert.

Aktuell werden keine StampImageAnnnotations unterstützt. Diese sind proprietär für IBM Content Manager 8. Falls ein Dokument eine solche Annotation enthält, wird die Annotation mit einer entsprechenden Logmeldung ignoriert.

Für die oben aufgeführten Annotationen bietet das jadice® web toolkit passende Editoren, über die ein Benutzer im Frontend neue Annotationen erstellen und bestehende Annotationen bearbeiten kann. Dies beinhaltet unter anderem die Eingabe und Formatierung beliebiger Texte für Text- und Stempelannotationen und die Formatierung der Farben und Formen von RectangleAnnotations.

Typische Anwendungsfälle

  • Markierung wichtiger Stellen im Dokument

    Der Sachbearbeiter erstellt eine ArrowAnnotation auf der zu markierenden Stelle. Der Sachbearbeiter markiert die Annotation und stellt die Pfeilfarbe im Annotationseditor auf "rot" ein.

  • Revisionssichere Dokumentation der Bearbeitungsergebnisse

    Ein Dokument wird über den Posteingang an einen Sachbearbeiter geroutet. Der Sachbearbeiter analysiert das Dokument und erstellt anschließend eine HistoryAnnotation auf der ersten Seite. Der Sachbearbeiter dokumentiert die gewonnenen Erkenntnisse als Text in der HistoryAnnotation. Die Annotation protokolliert zusätzlich den Benutzernamen und das aktuelle Datum. Der eingegebene Text kann anschließend nicht mehr gelöscht werden. In der weiteren Sachbearbeitung werden zusätzliche Freitexte von anderen Sachberabeitern aufgebracht, die History Annotation zeigt die gesamte Historie chronologisch geordnet nach dem Datum der Annotationsmodifikation und mit dem Namen des zugehörigen Sachbearbeiters.

  • Verdecken sensibler Dokumentinhalte beim Druck

    Der Sachbearbeiter erstellt auf der sensiblen Stelle eine schwarze RectangleAnnotation und verdeckt dadurch die sensiblen Daten mit einem schwarzen Balken. Der Sachbearbeiter druckt das Dokument, der sensible Teil ist nicht lesbar.

  • Verdecken sensibler Dokumentinhalte bei der weiteren Sachbearbeitung

    Der Sachbearbeiter erstellt auf der sensiblen Stelle eine schwarze RectangleAnnotation. Über die Vergabe entsprechender Berechtigungen wird das Rendering dieser Annotation immer serverseitig durchgeführt. Dies bedeutet, dass die Annotation serverseitig in die entsprechende Kachel eingebrannt und der sensible Teil des Dokuments damit niemals zum Benutzer übetragen wird. Details zur Konfiguration der notwendigen Berechtigungen finden sich im Kapitel „Berechtigungen“.

Wichtig

Bei der Nutzung von Annotationen zum Verdecken sensibler Dokumentinhalte gilt es folgendes zu beachten: Sofern es sich um ein textbasiertes Dokument handelt (z.B. ein PDF), findet eine Textsuche auch Text, der von der Annotation verdeckt ist. Gleiches gilt für die Textselektion - auch diese erlaubt das Selektieren von Textpassagen, die von einer "eingebrannten" Annotation überdeckt werden.

Es wird daher empfohlen, das TextSelectionTool und die Textsuche (RolloutSearch bzw. ActivateRolloutSearchCommand, ToolbarSearch bzw. ActivateToolbarSearch oder AdvancedSearch) für die betroffenen Dokumente zu deaktivieren.

Rendering von Textannotationen auf Client und Server mit Standard 14 Fonts

Das jadice® web toolkit verfügt über eine automatische Konfiguration sowie ein vordefiniertes Default-Mapping von logischen Schriftarten auf physikalische Schriften. Damit wird sichergestellt, dass sowohl auf dem Client als auch auf dem Server dieselben physikalischen Schriftarten zum Rendern von Textannotationen verwendet werden, wenn in den Textannotationen logische Fontbezeichnungen angegeben sind. Dies ist wichtig, da sonst Zeilenhöhen und Laufweiten deutlich voneinander abweichen können.

Zum Hintergrund: Annotationen werden im jadice® web toolkit teils client-, teils serverseitig gerendert. Bei der Anzeige im Viewer werden Annotationen in der Regel clientseitig gerendert. Nur wenn sie über eingeschränkte Berechtigungen und eine entsprechende Render-Strategie gezielt konfiguriert werden, werden sie bei der Dokumentanzeige serverseitig gerendert und in die Dokumentkacheln mit eingebrannt (siehe "Schwärzen von Akten" im Abschnitt „Berechtigungen“). Beim PDF Export werden Annotationen grundsätzlich serverseitig gerendert.

Im jadice® web toolkit werden als Default-Schriftarten für textbasierte Annotationen die Standard 14 Fonts (manchmal auch als "base 14 fonts" bezeichnet) eingesetzt. Die Standard 14 Fonts stellen der Anwendung Open-Source-Fonts für die logischen Schriftfamilien "serif", "sans-serif" und "monospaced" zur Verfügung (derzeit Arimo, Cousine und Tinos, jeweils in den Varianten "regular", "bold", "italic" und "bold+italic").

Für den Einsatz im Browser werden diese Schriften vom jadice® web toolkit Server über ein Servlet zur Verfügung gestellt und vom Client beim Aufruf der Webseite geladen.

Wichtig

Die Standard 14 Fonts sind in der jadice® web toolkit Auslieferung enthalten, können aber auch unter folgender URL heruntergeladen werden: Open-Source-Fonts auf github. Es ist keine Installation der Standard 14 Fonts auf dem jadice® web toolkit Server erforderlich, sie müssen lediglich in die Anwendung eingebunden werden.

Im Normalfall wird das FontDownloadServlet zum Bereitstellen der Fonts auf dem Client automatisch über eine Annotation eingebunden. Sollte dies nicht funktionieren, muss der Deployment Deskriptor web.xml um folgenden Eintrag ergänzt werden:


<servlet>
	<servlet-name>FontDownload</servlet-name>
	<servlet-class>com.levigo.jadice.web.server.FontDownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>FontDownload</servlet-name>
	<url-pattern>/fonts/fontmanager</url-pattern>
</servlet-mapping>		

				

Zudem muss die in der Distribution im Verzeichnis dist/webapp enthaltene Datei webfonts.js in das Hauptverzeichnis der Webapplikation gelegt werden.

Ausblick: Im Moment sind die Mappings von serif, sans-serif und monospaced nach Tinos, Arimo und Cousine fest verdrahtet. Für die Zukunft wird eine flexiblere Konfigurierbarkeit angestrebt. Aufgrund fehlender Fontmetriken in der HTML5- und JavaScript-Welt kann derzeit noch keine vollständige Gleichheit beim Rendering zwischen Server und Client gewährleistet werden. Stetige Verbesserungen sind in Entwicklung.

Serien-Erstellung von Annotationen

In einigen Usecases müssen Annotationen desselben Typs mehrfach hintereinander aufgebracht werden.

In diesen Fällen kann der Erstellungsmodus über die Annotationstoolbar eingerastet werden. Dadurch wird das Aufbringen beliebig vieler Annotationen desselben Typs mit jeweils nur einem Mausklick ermöglicht.

Standardmäßig wird der Erstellungsmodus über einen Doppelklick auf das jeweilige Annotationsicon in der Annotationstoolbar eingerastet. Jeder Linksklick auf eine Seite führt dann zum Erstellen einer neuen Annotation des ausgewählten Typs. Über einen Rechtsklick oder das Drücken der Escape-Taste wird der Modus beendet.

Die Implementierung eines kundenspezifischen AnnotationToolbarInitializers ermöglicht das Überschreiben des Defaultverhaltens. Damit kann der Batch-Erstellungsmodus global oder auch für einzelne Annotationstypen ein- oder ausgeschaltet werden. Es wird empfohlen eigene Implementierungen von der Klasse AbstractAnnotationToolbarInitializer abzuleiten.

Details zur Integration und die zugehörigen Codebeispiele finden sich in den Showcases unter "Annotations -> Change CreateMode of CreateAnnotationCommand".

Ausblenden von Annotationen auf Basis Annotationstyp

Für Review-Usecases kann es sinnvoll sein, ein Dokument ohne Annotationen anzuzeigen. Damit versperren aufgebrachte Annotationen die Sicht auf den Dokument-Content nicht mehr.

Das jadice® web toolkit bietet daher die Möglichkeit, alle Annotationen eines oder mehrerer Annotationstypen auf dem gesamten Dokument auszublenden. Hierbei wird für jeden im Annotationsprofil definierten Annotationstypen z.B. im Kontextmenü ein Menüeintrag in der Benutzeroberfläche angezeigt. Über diesen kann jeder einzelne Annotationstyp ausgeblendet werden. Zusätzlich gibt es die Möglichkeit, alle Annotationstypen aus- bzw. wieder einzublenden. Im Standard wird die Liste der ausgeblendeten Annotationstypen beim Laden eines neuen Dokuments zurückgesetzt.

Falls nach dem Ausblenden von Annotationstypen ein Druck (PDF, TIFF, Postscript) vom Benutzer angestoßen wird, sind die ausgeblendeten Annotationen in diesem ebenfalls nicht enthalten.

Serverseitig gerenderte, zum Maskieren kritischer Dokumentpassagen eingesetzte Annotationen sind immer sichtbar, auch wenn die zugehörigen Annotationstypen ausgeblendet sind. Dies gilt sowohl für die Anzeige als auch den Ausdruck (Export) des Dokuments. Diese Annotationen sind zwar, auch wenn sie ausblendet sind, auf dem Dokument sichtbar, jedoch nicht mehr selektierbar. Auch die Toolbar wird dann nicht mehr angezeigt.

Die Thumbnails sind von der Funktionalität unabhängig - die Thumbnailansicht zeigt immer alle Annotationen.

Wird ein Annotationstyp ausgeblendet, so wird die Neuerstellung für diese Annotationen unterbunden. In diesem Fall werden dann die zugehörigen Buttons der Annotationstoolbar ausgegraut.

Details zur Integration und die zugehörigen Codebeispiele finden sich in den Showcases unter "Annotations -> Hide Annotation Types" und auch in der Enterprise-Demo.

Einblenden von Tooltips

In einigen Anwendungsfällen kann es sinnvoll sein, dem Benutzer eine Möglichkeit zu bieten, schnell eine Auswahl an Annotations-Metainformationen ein- und wieder auszublenden. Damit können relevante Annotationen schnell von weniger interessanten Annotationen unterschieden und Arbeitsprozesse beschleunigt werden.

Das jadice® web toolkit bietet daher die Möglichkeit, über die kundenspezifische Implementierung eines AnnotationTooltipGenerators eine Reihe von Metainformationen in frei wählbarer Form innerhalb von Tooltips anzuzeigen. Die Tooltips erscheinen beim Hovern mit der Maus über eine Annotation und werden ausgeblendet, sobald der Mauszeiger die Annotation wieder verlässt.

Typischerweise werden Tooltips dabei spezifisch pro Annotationstyp eingeblendet. So könnte beispielsweise für jede Textannotation der Benutzername des letzten Bearbeiters und das letzte Bearbeitungsdatum angezeigt werden. Für Stempelannotationen könnte hingegen lediglich das Erstellungsdatum relevant sein.

Für die Visualisierung mittels Tooltips stehen die folgenden Informationen zur Verfügung:

  • Ersteller

  • Erstellungszeitpunkt (Format unter Berücksichtigung des Locale)

  • Bearbeiter

  • Bearbeitungszeitpunkt (Format unter Berücksichtigung des Locale)

  • Berechtigungen der Annotation

  • Berechtigungen des Dokuments

  • Content der Annotation (bei textbasierten Annotationen)

  • alle über die Annotationsinstanz zugreifbaren Informationen

  • alle über die zugrundeliegende Dokumentinstanz zugreifbaren Informationen

Details zur Integration und die zugehörigen Codebeispiele finden sich in den Showcases unter "Annotations -> Annotation Tooltips" und auch in der Enterprise-Demo.

Löschen aller selektierten Annotationen

In manchen Fällen sollen mehrere Annotationen gemeinsam gelöscht werden. Das sequentielle Löschen ist in diesem Fall zu aufwendig, sodass die selektierten Annotationen mit einer Aktion entfernt werden können sollen.

Für diesen Usecase liefert die Klasse DeleteSelectedAnnotationsCommand die passende Funktionalität. Insbesondere kann das Command mit einem Shortcut verbunden werden, sodass die selektierten Annotationen beispielsweise mit der Taste "Entf" gelöscht werden können.

Ein Codebeispiel hierzu findet sich in der Enterprise-Demo.

Annotationsprofile

Die unterstützten Annotationen, deren Eigenschaften, Vorbelegungen und die zum Zeichnen verwendeten Renderer-Implementierungen werden in den aus der jadice® document platform 5 bekannten Annotations-Profilen definiert (siehe Abschnitt Annotationsformate / Profile in der Dokumentation der jadice® document platform 5 ).

Renderer-Klassen

Die Renderer-Klassen setzen die Eigenschaften der Annotationen in visuelle Objekte um und bestimmen ihre Darstellung im jadice® web toolkit Viewer.

Um eine einheitlichere Darstellung zu erzielen, bietet das jadice® web toolkit für manche Annotationsformate (z.B. FileNet P8- oder Content Manager-Annotationen) die Möglichkeit, eine archivspezifische Parametrisierung der Renderer vorzunehmen oder dedizierte archivspezifische Renderer-Klassen zu verwenden. Die zugehörige Konfiguration wird im Annotationsprofil vorgenommen. Beispielprofile sind im Verzeichnis annotation-configurations der Library webtoolkit-demo-common-*.jar zu finden.

Die folgende Tabelle gibt einen Überblick über die derzeit im jadice® web toolkit verfügbaren Renderer-Implementierungen und Parametrisierungsmöglichkeiten.

Annotationstyp GWT Renderer (jadice® web toolkit) Annotationsformat Parameter Swing Renderer (jadice® document platform)
Arrow com.levigo.jadice.web.client.renderer.internal.annotation.ArrowAnnotationRenderer FN P8 adjustLineWidth FNP8ArrowAnnotationRenderer
CM 7/8 adjustLineWidth CMArrowAnnotationRenderer
sonstige keine ArrowAnnotationRenderer
Ellipse com.levigo.jadice.web.client.renderer.internal.annotation.EllipseAnnotationRenderer FN P8 adjustLineWidth FNP8EllipseAnnotationRenderer
CM 7/8 adjustLineWidth CMEllipseAnnotationRenderer
sonstige keine EllipseAnnotationRenderer
Line com.levigo.jadice.web.client.renderer.internal.annotation.LineAnnotationRenderer FN P8 adjustLineWidth FNP8LineAnnotationRenderer
CM 7/8 adjustLineWidth CMLineAnnotationRenderer
sonstige keine LineAnnotationRenderer
Path com.levigo.jadice.web.client.renderer.internal.annotation.PathAnnotationRenderer FN P8 adjustLineWidth FNP8PathAnnotationRenderer
CM 7/8 adjustLineWidth CMPathAnnotationRenderer
sonstige keine PathAnnotationRenderer
Rectangle com.levigo.jadice.web.client.renderer.internal.annotation.RectangleAnnotationRenderer FN P8 adjustLineWidth FNP8RectangleAnnotationRenderer
CM 7/8 adjustLineWidth CMRectangleAnnotationRenderer
sonstige keine RectangleAnnotationRenderer
Stamp com.levigo.jadice.web.client.renderer.internal.annotation.fnp8.FNP8StampAnnotationRenderer FN P8 adjustLineWidth, useISRendering FNP8StampAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.cm.CMStampAnnotationRenderer CM 7/8 adjustLineWidth, adjustFontSize CMStampAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.StampAnnotationRenderer sonstige keine StampAnnotationRenderer
Text com.levigo.jadice.web.client.renderer.internal.annotation.TextAnnotationRenderer sonstige lineWrap TextAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.cm.CMTextAnnotationRenderer CM 7/8 adjustLineWidth, adjustFontSize CMTextAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.FNP8TextAnnotationRenderer FN P8 useISRendering, adjustLineWidth, lineWrap FNP8TextAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer sonstige infoDateType, infoUserType, infoLayoutConstraint, infoTextStyle, infoTextColor, infoFontFace, infoTextPattern, infoFontSize, showInfoText, lineWrap TextInfoAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.FNP8TextInfoAnnotationRenderer FN P8 infoDateType, infoUserType, infoLayoutConstraint, infoTextStyle, infoTextColor, infoFontFace, infoTextPattern, infoFontSize, showInfoText, lineWrap, adjustLineWidth, useIsRendering FNP8TextInfoAnnotationRenderer
com.levigo.jadice.web.client.renderer.internal.annotation.FNISNoteAnnotationRenderer FN IS iconifiedWidth, iconifiedHeight, lineWrap FNISNoteAnnotationRenderer
HistoryAnnotation com.levigo.jadice.web.client.renderer.internal.annotation.HistoryAnnotationRenderer Jadice lineWrap HistoryAnnotationRenderer
Callout com.levigo.jadice.web.client.renderer.internal.annotation.CalloutAnnotationRenderer Jadice lineWrap CalloutAnnotationRenderer
Image com.levigo.jadice.web.client.renderer.internal.annotation.JWTImageAnnotationRenderer Jadice keine JWTImageAnnotationRenderer
Beliebig com.levigo.jadice.web.client.renderer.internal.annotation.IconRenderer Sonstige / Beliebig iconUrl, iconWidth, iconHeight (alles Pflichtfelder) com.levigo.jadice.annotation.internal.renderer.IconAnnotationRenderer

Konfiguration der Renderer-Klassen
  • adjustLineWidth

    • Der Parameter adjustLineWidth gibt an, ob die Liniendicke einer Annotation abhängig von der Auflösung angepasst werden soll. Hierdurch kann die Anzeige an die im IBM-CM-Viewer und Daeja-Viewer angelichen werden.

      Mögliche Werte: true, false. Default: true.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.ArrowAnnotationRenderer" >
      			<property name="adjustLineWidth">false</property>
      		</renderer>
      							
  • adjustFontSize

    • Der Parameter adjustFontSize gibt an, ob die Schriftgröße einer Annotation abhängig von der Auflösung angepasst werden soll. Hierdurch kann die Anzeige an die im IBM-CM-Viewer angelichen werden.

      Mögliche Werte: true, false. Default: true.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.cm.CMStampAnnotationRenderer" >
      			<property name="adjustFontSize">false</property>
      		</renderer>
      							
  • useISRendering

    • Der Parameter useISRendering gibt an, ob das FileNet IS (IDM Viewer) renderspezifische Verhalten verwendet werden soll oder nicht. Wird dieser Parameter auf false gesetzt, so wird das FNP8 Daeja Viewer Verhalten verwendet.

      Mögliche Werte: true, false. Default: false.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.fnp8.FNP8StampAnnotationRenderer" >
      			<property name="useISRendering">true</property>
      		</renderer>
      							
  • lineWrap

    • Der Parameter lineWrap gibt an, wie das Zeilenumbruchverhalten von Annotationen aussehen soll.

      Mögliche Werte: WORD_CR, CHARACTER_CR, NONE. Default: WORD_CR.

      Für eine Darstellung, die dem IBM CM-Viewer entspricht, muss hier der Parameter NONE eingestellt werden.

      • WORD_CR

        Bricht den Text nach dem letzten vollständig enthaltenen Wort um, falls der Text länger als die Textannotation ist.

      • CHARACTER_CR

        Bricht den Text nach einem beliebigen Zeichen um, falls der Text länger als die Textannotation ist.

      • NONE

        Bricht den Text innerhalb einer Zeile nicht um, falls der Text länger als die Textannotation ist.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextAnnotationRenderer" >
      			<property name="lineWrap">CHARACTER_CR</property>
      		</renderer>
      							
  • infoDateType

    • Der Parameter infoDateType gibt an, welcher Zeitstempel angezeigt werden soll.

      Mögliche Werte: CREATE, MODIFY, CURRENT. Default: MODIFY.

      • CREATE

        Es wird der Zeitstempel angezeigt, wann die Annotation erstellt wurde.

      • MODIFY

        Es wird der Zeitstempel angezeigt, wann die Annotation zuletzt verändert wurde.

      • CURRENT

        Es wird immer der aktuelle Zeitstempel angezeigt. Dieser wird bei jedem Rendervorgang aktualisiert.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoDateType">CREATE</property>
      		</renderer>
      							
  • infoUserType

    • Der Parameter infoUserType gibt an, welcher Benutzer angezeigt werden soll.

      Mögliche Werte: CREATE, MODIFY. Default: MODIFY.

      • CREATE

        Es wird der Benutzer angezeigt, welcher die Annotation angebracht hat.

      • MODIFY

        Es wird der Benutzer angezeigt, welcher die Annotation zuletzt bearbeitet hat.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoUserType">CREATE</property>
      		</renderer>
      							
  • infoLayoutConstraint

    • Der Parameter infoLayoutConstraint gibt an, wo die Infobox angezeigt werden soll.

      Mögliche Werte: TOP, BOTTOM. Default: BOTTOM.

      • TOP

        Die Infobox wird oberhalb der Annotation angezeigt.

      • BOTTOM

        Die Infobox wird unterhalb der Annotation angezeigt.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoLayoutConstraint">TOP</property>
      		</renderer>
      							
  • infoTextStyle

    • Der Parameter infoTextStyle gibt an, wie der Text der Infobox dargestellt werden soll.

      Mögliche Werte: PLAIN, BOLD, ITALIC, BOLD_ITALIC. Default: PLAIN.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoTextStyle">BOLD</property>
      		</renderer>
      							
  • infoTextColor

    • Der Parameter infoTextColor gibt an, welche Farbe für den in der Infobox dargestellten Text verwendet werden soll.

      Mögliche Werte: Alle Farbwerte. Default: BLACK.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoTextColor">RED</property>
      		</renderer>
      							
  • infoFontFace

    • Der Parameter infoFontFace gibt an, welche Schriftart für den in der Infobox dargestellten Text verwendet werden soll.

      Mögliche Werte: alle Schriftarten. Default: monospaced.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoFontFace">serif</property>
      		</renderer>
      							
  • infoTextPattern

    • Der Parameter infoTextPattern gibt das Pattern an, welches für die Ausgabe des Infotextes verwendet werden soll.

      Mögliche Werte: String. Default: {user} {date,MM-dd-yyyy} {time,HH:mm:ss}.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoFontFace">{user} {date,MM-dd-yyyy} {time,HH:mm:ss}</property>
      		</renderer>
      							
  • infoFontSize

    • Der Parameter infoFontSize gibt an, welche Schriftgröße für den in der Infobox dargestellten Text verwendet werden soll.

      Mögliche Werte: Integer-Werte. Default: 12.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="infoFontSize">serif</property>
      		</renderer>
      							
  • showInfoText

    • Der Parameter showInfoText gibt an, ob die Infobox angezeigt werden soll.

      Mögliche Werte: true, false. Default: false.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.TextInfoAnnotationRenderer" >
      			<property name="showInfoText">true</property>
      		</renderer>
      							
  • iconifiedWidth, iconifiedHeight

    • Die Parameter iconifiedWidth und iconifiedHeight geben die Größe (in Pixeln) von ikonifizierten Annotationen an.

      Mögliche Werte: Integer-Werte. Default: 0.

      		<renderer toolkit="gwt"
      			class="com.levigo.jadice.web.client.renderer.internal.annotation.FNISNoteAnnotationRenderer" >
      			<property name="iconifiedWidth">20</property>
      			<property name="iconifiedHeight">20</property>
      		</renderer>
      							
  • iconUrl, iconWidth, iconHeight

    • Bei Verwendung des IconRenderer (oder auch des IconWranglers) geben die Parameter iconWidth und iconHeight die Größe (in Pixeln) des Images an. Diese Felder sind Pflichtfelder und müssen immer angegeben werden. Das Bild wird in der angegebenen Pixelgröße gerendert.

      Mögliche Werte: Integer-Werte größer 0

      Der Parameter iconUrl gibt die URL des Images für den IconRenderer an. Folgende URL-Angaben werden unterstützt:

      Data URLs: Direkte Angabe einer Data-URL (z.B. data:image/png;base64,BASE64_ENCODED_PNG_DATA)

      Relative Web-URL: Angabe einer relativen Web-URL (z.B. /images/MyImage.jpg). Das Image muss auf dem lokalen Server verfügbar sein.

      Absolute Web-URL: Angabe einer absoluten Web-URL (z.B. https://external/MyImage.jpg). Hinweis: Ggfs müssen für die Verwendung von externen URLs entsprechende CORS-Filter konfiguriert werden

      		<renderer toolkit="gwt" class="com.levigo.jadice.web.client.renderer.internal.annotation.IconRenderer">
      			<property name="iconUrl">/images/MyImage.jpg</property>
      			<property name="iconWidth">24</property>
      			<property name="iconHeight">24</property>
      		</renderer>
      							
Wrangler-Klassen

Die Wrangler-Klassen dienen der Interaktion GUI / Annotation. Sie werden zum Anlegen, Verändern und Editieren der Annotation verwendet.

Manche Wrangler lassen sich über das Annotationprofil konfigurieren. Beispielprofile sind im Verzeichnis annotation-configurations der Library webtoolkit-demo-common-*.jar zu finden.

Die folgende Tabelle gibt einen Überblick über die im jadice® web toolkit verfügbaren Wrangler-Implementierungen und Parameterisierungsmöglichkeiten.

Annotationstyp GWT Wrangler (jadice® web toolkit) Annotationsformat Parameter Swing Wrangler (jadice® document platform)
Arrow com.levigo.jadice.web.client.internal.annotation.wrangler.ArrowAnnotationWrangler alle minimumLineLength ArrowAnnotationWrangler
Ellipse com.levigo.jadice.web.client.internal.annotation.wrangler.EllipseAnnotationWrangler alle minimumWidth, minimumHeight EllipseAnnotationWrangler
Highlight, Mask, Rectangle com.levigo.jadice.web.client.internal.annotation.wrangler.DefaultWrangler alle minimumWidth, minimumHeight RectangleAnnotationWrangler
Freehand com.levigo.jadice.web.client.internal.annotation.wrangler.FreehandAnnotationWrangler alle - FreehandAnnotationWrangler
Polygon com.levigo.jadice.web.client.internal.annotation.wrangler.PolygonAnnotationWrangler alle closed PolygonAnnotationWrangler
Iconified Note com.levigo.jadice.web.client.internal.annotation.wrangler.IconifiedTextAnnotationWrangler FN P8, FN IS executeActionAfterCreation, editorWidth, editorHeight, textSelection, cursorPosition IconifiedTextAnnotationWrangler
Line com.levigo.jadice.web.client.internal.annotation.wrangler.LineAnnotationWrangler alle minimumLineLength LineAnnotationWrangler
Stamp com.levigo.jadice.web.client.internal.annotation.wrangler.StampAnnotationWrangler alle executeActionAfterCreation, editorWidth, editorHeight, textSelection, cursorPosition, editorBehaviorOnOutsideClick, lineWrap, forceAnnotationUpright StampAnnotationWrangler
Text com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler alle executeActionAfterCreation, editorWidth, editorHeight, textSelection, cursorPosition, editorBehaviorOnOutsideClick, expandHeight, forceAnnotationUpright, showRotationHandle TextAnnotationWrangler
Callout com.levigo.jadice.web.client.internal.annotation.wrangler.CalloutAnnotationWrangler alle editorWidth, editorHeight, executeActionAfterCreation, editorBehaviorOnOutsideClick, textSelection, cursorPosition, expandHeight -
Image com.levigo.jadice.web.client.internal.annotation.wrangler.JWTImageAnnotationWrangler alle - -
Beliebig com.levigo.jadice.web.client.internal.annotation.wrangler.IconWrangler alle iconWidth, iconHeight (in px; alles Pflichtfelder) -
TextHighlight com.levigo.jadice.annotation.TextHighlightAnnotation jadice (*)isAllowMultiPageSelection TextHighlightAnnotationWrangler, (*)HighlightTextCommand

(*) Die TextHighlightAnnotation besitzt zusätzlich zum Wrangler das HighlightTextCommand, welches das Anlegen der Annotation übernimmt.

Konfiguration der Wrangler-Klassen

Codebeispiele finden sich in den Showcases unter Customized Wranglers.

  • DefaultWrangler (und alle davon abgeleiteten Wrangler)

    • Minimale Breite und Höhe rechteckbasierter Annotationen (Rectangle, Highlight, Mask, Ellipse, Text, Note, Image): minimumWidth, minimumHeight

      Mögliche Werte: Float-Wert in mm. Default: 5.0.

      		<wrangler toolkit="gwt"
      			class="com.levigo.jadice.web.client.internal.annotation.wrangler.DefaultWrangler" >
      			<property name="minimumWidth">10.0</property>
      			<property name="minimumHeight">10.0</property>
      		</wrangler>
      							
    • Anlegemodus (im folgenden Beispiel für den TextAnnotationWrangler gezeigt): creationMode

      Mögliche Werte: DEFAULT, SINGLE_CLICK

      <wrangler toolkit="gwt"	class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      	<property name="creationMode">SINGLE_CLICK</property>
      </wrangler>
      								

      Ohne Angabe bzw. mit Wert DEFAULT: Die Annotation kann während des Anlegens in der Größe beliebig aufgezogen werden. Der Anlegevorgang wird beendet, sobald die Maustaste losgelassen wird. Einschränkung: Das Aufziehen ist bei Stempelannotationen nicht möglich, da diese ihre Größe anhand des Textinhalts bzw. der Bildgrösse selbst bestimmen.

      SINGLE_CLICK: Das Anlegen der Annotation wird beim ersten Mausklick beendet; die Annotation erhält die minimale Grösse (die wiederum im Annotationsprofil über minimumWidth/minimumHeight konfiguriert werden kann). Über einen AnnotationCustomizer lässt sich zusätzlich - abweichend von der Minimalgröße - eine initiale Größe der Annotation einstellen. Diesen Weg kann man nutzen, um eine initiale Größe vorzugeben, die von der Minimalgröße abweicht.

  • LineAnnotationWrangler, ArrowAnnotationWrangler

    • Minimale Länge von Linien- und Pfeilannotationen: minimumLineWidth

      Mögliche Werte: Float-Wert in mm. Default: 5.0.

      <wrangler toolkit="gwt"	class="com.levigo.jadice.web.client.internal.annotation.wrangler.LineAnnotationWrangler" >
      	<property name="minimumLineLength">10.0</property>
      </wrangler>
      							
  • TextAnnotationWrangler, StampAnnotationWrangler, IconifiedTextAnnotationWrangler, CalloutAnnotationWrangler

    Aufgrund der Vererbungshierarchie wirkt sich eine etwaige Konfiguration des TextAnnotationWranglers nicht nur auf Textannotationen, sondern auch auf Stempel- und Notizannotationen aus. Falls unterschiedliche Konfigurationen pro Annotationstyp eingestellt werden sollen, können die jeweiligen Parameter für Stempel- und Notizannotationen überschrieben werden.

    • Automatisches Öffnen des Texteditors: executeActionAfterCreation

      Mögliche Werte: true, false. Default: false.

      <annotation-type name="BaseText" archetype="BaseText" class="com.levigo.jadice.annotation.TextAnnotation">
      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="executeActionAfterCreation">true</property>
      	</wrangler>
      </annotation-type>
      							

      Über den Parameter executeActionAfterCreation kann der TextAnnotationWrangler so konfiguriert werden, dass unmittelbar nach dem Erstellen der Annotation der Texteditor geöffnet wird.

    • Freie Rotation bei der Bearbeitung erlauben

      <fixed name="showRotationHandle" propertyType="java.lang.Boolean">
          <default>true</default>
      </fixed>
                                   

      Hier wird definiert, ob der Anfasser für die freie Rotation angezeigt wird.

      Folgende Einstellungen sind möglich:

      • false

        Der Anfasser für das freie Rotieren der Annotation wird nicht angezeigt (Standardeinstellung)

      • true

        Der Anfasser für das freie Rotieren der Annotation wird angezeigt

      Hinweis für den Umgang mit ContentManager Annotationen

      Generell wird beim Speichern von Annotationen im CM Format die korrekte Konfiguration via CMAnnotationWriterSettings erwartet, siehe hierzu die Dokumentation der jadice® document platform 5. Im Fall von rotierten Annotationen muss .setSaveAdditionalInfo(true) aufgerufen werden, siehe folgendes Code Snippet.

      FormatWriter writer = new CMAnnotationWriter();
      controls.getSettings(CMAnnotationWriterSettings.class).setFormat(CMAnnotationWriterSettings.Format.CM8);
      controls.getSettings(CMAnnotationWriterSettings.class).setSaveAdditionalInfo(true);
                                   
    • Verhalten auf rotierten Seiten: forceAnnotationUpright

      Mögliche Werte: OFF, ON_CREATION. Default: OFF.

      <annotation-type name="BaseText" archetype="BaseText" class="com.levigo.jadice.annotation.TextAnnotation">
      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="forceAnnotationUpright">true</property>
      	</wrangler>
      </annotation-type>
      							

      Über den Parameter forceAnnotationUpright kann der TextAnnotationWrangler so konfiguriert werden, dass beim Erstellen der Annotation die Rotation so angepasst wird, dass bei ON_CREATION die Annotation aufrecht steht.

      Wenn die Einstellung ON_CREATION gewählt wird, muss die Eigenschaft Rotation für Textannotationen im Profil aufgenommen werden. Hierdurch wird sichergestellt, dass die beim Erstellen gesetzte Rotation gespeichert werden kann.

    • Minimale Breite und Höhe des Annotationseditors: editorWidth, editorHeight

      Mögliche Werte: Integer-Wert in px. Default: 50.

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="editorWidth">300</property>
      		<property name="editorHeight">100</property>
      	</wrangler>
      							

      Über die Parameter editorWidth und editorHeight können die Wrangler textbasierter Annotationen so konfiguriert werden, dass sie den Texteditor mit den angegebenen Minimalgrößen öffnen.

    • Initiales Selektionsverhalten des Editors: textSelection

      Mögliche Werte: NONE, ALL. Default: NONE.

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="textSelection">ALL</property>
      	</wrangler>
      							

      Der Parameter textSelection bestimmt, ob im TextEditor der Text selektiert ist.

    • Cursorposition im Editor: cursorPosition

      Mögliche Werte: START, END. Default: START.

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="cursorPosition">END</property>
      	</wrangler>
      							

      Der Parameter cursorPosition bestimmt die Position des Cursors nach dem Öffnen des TextEditors.

    • Verhalten des Editiermodus: editorBehaviorOnOutsideClick

      Mögliche Werte: APPLY_TEXT, DISCARD_TEXT, IGNORE_CLICK. Default: APPLY_TEXT.

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="editorBehaviorEditorOnOutsideClick">IGNORE_CLICK</property>
      	</wrangler>
      							

      Der Parameter behaviorOnOutsideClick bestimmt das Verhalten, wenn außerhalb der Annotation geklickt wird. Bei APPLY_TEXT und DISCARD_TEXT wird der Editier-Modus verlassen und die aktuelle Textänderung der Annotation entweder übernommen bzw. verworfen. Bei IGNORE_CLICK wird der Editier-Modus nicht verlassen.

    • Höhenerweiterung abhängig von der Textlänge: expandHeight

      Mögliche Werte: OFF, ON_TEXT_INPUT, ON_TEXT_INPUT_AND_EDITOR_OPEN. Default: ON_TEXT_INPUT.

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.TextAnnotationWrangler" >
      		<property name="expandHeight">ON_TEXT_INPUT</property>
      	</wrangler>
      							

      Der Parameter expandHeight bestimmt das Verhalten, wenn der Text beim Bearbeiten im Editor zu lang ist und nicht vollständig in der Annotation angezeigt werden kann. Bei ON_TEXT_INPUT wird die Annotation beim Editieren nach unten erweitert, um genug Platz zu schaffen. Bei ON_TEXT_INPUT_AND_EDITOR_OPEN passiert dies bereits beim öffnen des Editors. Bei OFF werden keine Größenerweiterungen vorgenommen.

  • IconWrangler

    • Größe des Images wird auch im Wrangler für die Selektionsgröße benötigt: iconWidth und iconHeight müssen immer angegeben werden.

      Mögliche Werte: Integer-Wert in px > 0

      	<wrangler toolkit="gwt" class="com.levigo.jadice.web.client.internal.annotation.wrangler.IconWrangler">
      		<property name="iconWidth">24</property>
      		<property name="iconHeight">24</property>
      	</wrangler>
      								
Konfiguration des HighlightTextCommand

Wie eingangs des Kapitels bereits erwähnt, gibt es bei der TextHighlightAnnotation zusätzlich zum Wrangler noch das HighlightTextCommand. Hierzug gibt es einen Parameter isAllowMultiPageSelection. Ist dieser Parameter gesetzt, ist es möglich mehrere Seiten zu selektiern, womit pro Seite dann genau eine neue Annotation erstellt wird, sobald das Command ausgeführt wird. Ist im Gegensatz hierzu der Parameter deaktiviert, lässt sich das Command nur genau dann aktivieren, wenn eine Selektion auf einer einzelnen Seite aufgezogen wird.

Konfiguration der Annotationen selbst
  • TextAnnotation, StampAnnotation

    • Nicht editierbare textbasierte Annotationen: editable = false

      <annotation-type name="Stamp" archetype="Stamp" class="com.levigo.jadice.annotation.StampAnnotation">
      	<fixed name="editable" propertyType="java.lang.Boolean">
      		<default>true</default>
      	</fixed>
      	...
      </annotation-type>
      ...
      <annotation-template name="StampDone" extends="Stamp">
      	...
      	<!-- configure stamp annotation derived from this template to be read-only -->
      	<default name="editable">false</default>
      	...
      </annotation-template>
      								

      Über den Parameter editable kann der Annotationseditor auf Basis Annotationstyp als read-only konfiguriert werden. In diesem Fall öffnet sich der Annotationseditor für alle Annotationen des zugehörigen Annotationstyps unabhängig von etwaigen Berechtigungen immer im Read-Only-Modus.

  • StampAnnotation

    • Nicht drehbare Stempelannotationen: allowRotation = false

      <annotation-type name="Stamp" archetype="Stamp" class="com.levigo.jadice.annotation.StampAnnotation">
      	<fixed name="allowRotation" propertyType="java.lang.Boolean">
      		<default>true</default>
      	</fixed>
      	...
      </annotation-type>
      ...
      <annotation-template name="StampDone" extends="Stamp">
      	...
      	<!-- do not allow rotation of these stamp annotations -->
      	<default name="allowRotation">false</default>
      	...
      </annotation-template>
      								

      Über den Parameter allowRotation kann konfiguriert werden, ob der Anwender Stempelannotationen generell bzw. einzelne Ausprägungen (Templates) von Stempelannotationen drehen darf.

  • ArrowAnnotation

    Der ArrowAnnotationWrangler stellt einen Sonderfall dar, da er indirekt über die Annotation selbst konfiguriert wird. Gibt die Konfiguration einer ArrowAnnotation einen Bereich (range) für headAngle oder headLength mit Minimal- und Maximal-Werten vor, so werden diese vom ArrowAnnotationWrangler berücksichtigt.

    <annotation-type name="Arrow" archetype="Arrow"
    	extends="Line" class="com.levigo.jadice.annotation.ArrowAnnotation">
    	...
    	<range name="headAngle" propertyType="java.lang.Integer">
    		<labels>
    			<label locale="en">Head angle</label>
    			<label locale="de">Winkel</label>
    		</labels>
    		<default>20</default>
    		<maximum>89</maximum>
    		<minimum>1</minimum>
    	</range>
    	<range name="headLength" propertyType="java.lang.Integer">
    		<default>25</default>
    		<labels>
    			<label locale="en">Arrow tip</label>
    			<label locale="de">Pfeilspitze</label>
    		</labels>
    		...
    		<maximum>100</maximum>
    		<minimum>1</minimum>
    	</range>   
    	<fixed name="headLengthInPercent" propertyType="java.lang.Boolean">
    		<default>false</default>
    	</fixed>
    	...
    </annotation-type>
    							
  • CalloutAnnotation

    Auch bei CalloutAnnotations werden Profil-Einstellungen der Annotation von ihren Wranglern ausgewertet und berücksichtigt. Anders als bei ArrowAnnotations werden bei CalloutAnnotations (anstelle von headAngle und headLength) die Eigenschaften arrowWidth und arrowHeight konfiguriert (Einheit hier: Jadice Document Units).

    Analog zur Konfiguration von TextAnnotations lassen sich auch die Eigenschaften der Textbox über das Annotationsprofil konfigurieren (strokeColor, fillColor, lineWidth, fontFace, fontSize, textColor,... ).

    <annotation-type name="Callout" archetype="Callout"
    	class="com.levigo.jadice.annotation.CalloutAnnotation">
    	...
    	<range name="arrowWidth" propertyType="java.lang.Integer">
    		<default>1000</default>
    		<minimum>0</minimum>
    		<maximum>10000</maximum>
    	</range>
    		
    	<range name="arrowHeight" propertyType="java.lang.Integer">
    		<default>1000</default>
    		<minimum>0</minimum>
    		<maximum>10000</maximum>
    	</range> 
    
    	...
    </annotation-type>
    							
  • JWTImageAnnotation

    Dieser Annotationstyp stellt eine Besonderheit dar, da er aus zwei Teilen besteht:

    • Der Annotation selber.

      • Diese hält lediglich eine Referenz auf ein Bild in Form einer ID.

    • Einem AnnotationImageProvider

      • Dieser ist dafür verantwortlich, die Bild-ID in ein Bild aufzulösen, um es dem client- oder serverseitigen Renderer zur Verfügung zu stellen.

      • Wird serverseitig in der AnnotationProfileRegistry auf ein bestimmtes Annotationsprofil registriert.

        • Immer wenn eine Bildannotation gerendert wird, wird abhängig des zugehörigen Annotationsprofils der Provider gewählt und das Bild angefragt.

    Die Bild-ID kann entweder statisch in dem Annotationsprofil gesetzt werden oder programmatisch per AnnotationCustomizer auf Clientseite. Durch letztere Variante lassen sich dynamisch Bild-IDs setzen, um beispielsweise abhängig von der Benutzersitzung unterschiedliche Bilder anzufragen.

    Definition der Bild-ID über das Annotationsprofil:

    <annotation-profile name="exampleProfile">
    ...
        <annotation-type name="Image" archetype="Image"
        	class="com.levigo.jadice.annotation.JWTImageAnnotation">
        	...
            <fixed name="imageID" propertyType="java.lang.String">
                <default>exampleImageID</default>
            </fixed>
        
        	...
        </annotation-type>
    ...
    </annotation-profile>
    						

    Programmatische Definition der Bild-ID auf Clientseite

    Annotations.getAnnotationCustomizers().add(new AnnotationCustomizer() {
        @Override
        public void customize(Annotation annotation) {
            if (annotation instanceof JWTImageAnnotation) {
                final String name = annotation.getType().getName();
                
                if (name.equals("Image"))
                    ((JWTImageAnnotation) annotation).setImageID("exampleImageID");
            }
        }
    });
    						

    Definition eines entsprechenden AnnotationImageProviders

    annotationProfileRegistry.registerAnnotationImageProvider("exampleProfile", new AnnotationImageProvider() {
        @Override
        public SeekableInputStream provideAnnotationImage(String annotationImageID) {
            InputStream is = null;
            if (annotationImageID.equals("exampleImageID"))
                is = loadRessource("example.png");
    
            if (null == is)
                throw new IOException("Couldn't find/load image for id \"" + annotationImageID + "\"");
    
            return new MemoryInputStream(is);
        }
    });
    
                               

    Analog zur Konfiguration von RectangleAnnotations lassen sich auch die Eigenschaften des Rahmens über das Annotationsprofil konfigurieren (linePainted, strokeColor, lineWidth).

     <annotation-type name="Image" archetype="Image" 
        class="com.levigo.jadice.annotation.JWTImageAnnotation">
    	...
        <fixed name="linePainted" propertyType="java.lang.Boolean">
            <default>true</default>
        </fixed>
    		
        <fixed name="lineWidth" propertyType="java.lang.Float">
            <default>1.0</default>
        </fixed>
        
        <fixed name="strokeColor" propertyType="java.awt.Color">
            <default>#000000</default>
        </fixed>
    	...
    </annotation-type>
    							
Initializer

Annotation Initializer werden benötigt, um die Annotation um zusätzliche Informationen (wie z.B. der Auflösung der Seite, auf der sich die Annotation befindet) anzureichern. Initializer sind nur für FN P8 und CM Annotationen erforderlich.

Annotationstyp GWT Initializer (jadice® web toolkit) Annotationsformat Swing Initializer (jadice® document platform)
Alle com.levigo.jadice.web.client.internal.annotation.initializer.FNP8AnnotationInitializer FN P8 FNP8AnnotationInitializer
Alle com.levigo.jadice.web.client.internal.annotation.initializer.CMAnnotationInitializer CM7 / 8 com.levigo.jadice.annotation.internal.renderer.cm.CMAnnotationInitializer

Auf Clientseite wird aktuell nur der FNP8AnnotationInitializer sowie der CMAnnotationInitializer unterstützt. Die Konfiguration sieht beispielsweise wie folgt aus:

	<annotation-type name="BaseLine" archetype="BaseLine"
		class="com.levigo.jadice.annotation.LineAnnotation">
			...
		<initializer toolkit="swing"
			class="com.levigo.jadice.annotation.internal.renderer.fnp8.FNP8AnnotationInitializer" />
		<initializer toolkit="gwt"
			class="com.levigo.jadice.web.client.internal.annotation.initializer.FNP8AnnotationInitializer" />
			...
	</annotation-type>

Bitte beachten Sie, dass diese Initializer im Annotationsprofil für alle Annotationstypen konfiguriert werden müssen, um eine korrekte Darstellung der Annotationen zu gewährleisten. Weitere Beispiele finden sich in den mitgelieferten CM- und P8-Annotationsprofilen in den Demos.

Annotation API

Das jadice® web toolkit stellt eine API zur Verfügung, um Annotationen programmatisch zu erstellen. Hiermit lassen sich kundenspezifische Annotationen ohne Nutzerinteraktion anlegen.

Vor der Erstellung kann jede Annotation mit kundenspezifischen Standard-Einstellungen konfiguriert werden. Textannotationen können dadurch beispielsweise einen frei wählbaren Vorlagetext erhalten oder bestimmte Annotations-Metadaten im Text anzeigen.

Nach der Erstellung kann jede Annotation über den Annotationseditor vom Benutzer bearbeitet werden.

Um eine Annotation über die API anzulegen, wird ein eigenes, kundenspezifisches Command implementiert, das vom abstrakten Basiscommand CreateAnnotationViaApiCommand erbt. Innerhalb des Commands wird unter anderem konfiguriert, auf welche Seite die neue Annotation angebracht werden soll und welcher Annotationstyp verwendet wird. Auch feingranulare Einstellungen wie spezifische Hintergrundfarben, Schriftarten, etc. können darüber vorgenommen werden.

Verwandte Kapitel:

Details zur Integration und die zugehörigen Codebeispiele finden sich in den Showcases unter "Creating Annotations via API".

Beeinflussung des Annotationsrendering über eine AnnotationRenderStrategy

Annotationen werden grundsätzlich clientseitig gerendert. Sollen die Annotationen eine serverseitige Maskierung von Dokumentinhalten sicherstellen, so kann über die AnnotationRenderStrategy SERVER_SIDE_MASKING und die Permissions DENY.CHANGE und DENY.REMOVE ein serverseitiges Rendering der Annotationen erzwungen werden.

Das Setzen der Render Strategy funktioniert mittels PredefinedAnnotationRenderStrategy.SERVER_SIDE_MASKING.applyToDocument(Document) bzw. PredefinedAnnotationRenderStrategy.CLIENT.applyToDocument(Document) (Default) und kann im DocumentDataProvider eingestellt werden. Hierzu gibt es auch einen neuen Artikel in der jadice Knowledge-Base, siehe Annotationsrendering im JWT auf Client- oder Serverseite.

Für das Berechtigungskonzept im Allgemeinen beachten Sie bitte das Kapitel „Berechtigungskonzept“

Ausrichtung von Annotationen an Grids

In manchen Anwendungsfällen sollen Annotationen nicht an beliebigen Stellen innerhalb eines Dokuments positioniert werden können, sondern an definierten Stellen einrasten. Um dieses Szenario zu unterstützen, kann einem Dokument clientseitig ein rechteckiges Gitternetz (Grid) hinzugefügt werden, an dem sich die Annotationen beim Erstellen, Verschieben und Resizen ausrichten. Für jeden Annotationstyp kann dabei eingestellt werden, ob dieser an den Ecken oder im Zentrum der Grid-Zellen einrastet. Grids können über das GridTool visualisiert werden.

Die folgende Abbildung zeigt ein via GridTool visualiertes Grid (mit Außengrenzen) und drei daran ausgerichtete Annotationen:

Pfeilannotation (zentral ausgerichtet), Rechteck- und Polygonannotation (an den Ecken ausgerichtet)

Um ein Grid für die Ausrichtung zu verwenden, muss zunächst eine Instanz der Klasse BoundedGrid erzeugt werden. Über den Konstruktor werden zum einen Breite und Höhe der zugehörigen Zellen gesteuert, zum anderen kann darüber auch eine Außengrenze des Grids definiert werden, was die Verwendung eines Rands zwischen der zugehörigen Seite und dem Grid ermöglicht.

Im zweiten Schritt muss die erzeugte Grid-Instanz dem aktuellen Dokument hinzugefügt werden. Dies erfolgt über die Klasse Grids.

Im Standard rasten alle Annotationen an den Ecken der Grid-Zellen ein. Falls für einen Annotationstyp im Zentrum eingerastet werden soll, kann dies über die Klasse SnapToMappings konfiguriert werden.

Die Ausrichtung am Grid erfolgt aussschließlich bei der Bearbeitung von Annotationen. Insbesondere werden bestehende Annotation im Rahmen des Ladevorgangs an ihrer ursprünglichen Position dargestellt. Erst mit der Bearbeitung (Verschieben oder Resizen) werden Annotationen am Grid ausgerichtet.

Details zur Integration und ein zugehöriges Codebeispiel finden sich in den Showcases unter "Annotations -> Grid Positioning".

Achtung

Beim Verschieben einer am Grid ausgerichteten Mehrfachselektion, in der sowohl Annotationen enthalten sind, die an den Ecken, als auch Annotationen, die am Zentrum der Grid-Zellen ausgerichtet werden, verlieren die am Zentrum ausgerichteten Annotationen ihre Ausrichtung am Grid. Hintergrund ist, dass die Selektion immer als Ganzes verschoben wird.

Beim Resizen einer Mehrfachselektion verlieren die in der Selektion enthaltenen Annotationen ihre Ausrichtung am Grid.

Um sicherzustellen, dass am Grid ausgerichtete Annotationen nach dem Verschieben und Resizen am Grid ausgerichtet bleiben, muss die Mehrfachselektion über das AnnotationTool ausgeschaltet werden. Details siehe „AnnotationTool“.

Beim Resizen von Freihand- und Polygonannotationen geht die Ausrichtung am Grid verloren. Um dies zu verhindern muss für diese Annotationstypen das fixed Property allowResize im Annotationsprofil auf den Wert false gesetzt werden.

Berechtigungskonzept

Das jadice® web toolkit adaptiert das Berechtigungskonzept der jadice® document platform. Berechtigungen können analog der jadice® document platform serverseitig auf Dokumente oder Annotationen angebracht werden. Beim Transport der Dokumentdaten und Annotationen auf den Client werden die Berechtigungen mit übertragen, so dass die Tools auf Clientseite entsprechend reagieren können.

Binden von Berechtigungen an Objekte

Berechtigungen können an folgende Objekte gebunden sein:

  • Document : Die Berechtigungen gelten global für das gesamte Dokument.

    Beispiel: Die Berechtigung DocumentAnnotationPermission.DENY.ADD verbietet dem Benutzer das Anbringen von Annotationen auf dem Dokument.

  • Annotation : Die Berechtigungen gelten nur für einzelne Instanzen von Annotationen.

    Beispiel: Die Berechtigung IndividualAnnotationPermission.DENY.CHANGE verbietet dem Benutzer das Verändern einer einzelnen Annotation.

Besonderheit bei Annotationsberechtigungen: Verbietet eine dokumentbezogene Berechtigung eine Aktion, so wird die individuelle Berechtigung erst gar nicht ausgewertet. Erlaubt hingegen die dokumentbezogene Berechtigung eine Aktion, so wird im Anschluss noch die individuelle Berechtigung ausgewertet. Nur wenn diese nicht gesetzt ist oder die Aktion zulässt, so ist die Aktion auch tatsächlich erlaubt.

Berechtigungen

Dokumentberechtigungen

  • DocumentPermission.DENY.ADD_PAGES / ALLOW.ADD_PAGES

  • DocumentPermission.DENY.REMOVE_PAGES / ALLOW.REMOVE_PAGES

  • DocumentPermission.DENY.REORDER_PAGES / ALLOW.REORDER_PAGES

Dokumentbezogene Annotationsberechtigungen

  • DocumentAnnotationPermission.DENY.ADD / ALLOW.ADD

  • DocumentAnnotationPermission.DENY.CHANGE / ALLOW.CHANGE

  • DocumentAnnotationPermission.DENY.REMOVE / ALLOW.REMOVE

Individuelle Annotationsberechtigungen

  • IndividualAnnotationPermission.DENY.CHANGE / ALLOW.CHANGE

  • IndividualAnnotationPermission.DENY.REMOVE / ALLOW.REMOVE

  • IndividualAnnotationPermission.DENY.SHOW / ALLOW.SHOW

  • IndividualAnnotationPermission.DENY.WRITE / ALLOW.WRITE

Typische Szenarien

DENY.REORDER_PAGES

Nach Setzen der Berechtigung DocumentPermission.DENY.REORDER_PAGES können Seiten mittels des ThumbnailTools nicht mehr umsortiert werden.

Annotation DENY.CHANGE

Nach Setzen der Berechtigung IndividualAnnotationPermission.DENY.CHANGE oder DocumentAnnotationPermission.DENY.CHANGE können Annotationen nicht mehr verschoben werden. Die Wrangler zum Größer- und Kleinerziehen werden nicht mehr angezeigt und die Annotations-Toolbar bietet keine Annotationseditoren zum Ändern der Annotation (z.B. Farben, Textgröße) mehr an.

Annotation DENY.REMOVE

Nach Setzen der Berechtigung IndividualAnnotationPermission.DENY.REMOVE oder DocumentAnnotationPermission.DENY.REMOVE ist der Löschen-Button in der Annotations-Toolbar deaktiviert, so dass die Annotation nicht mehr gelöscht werden kann.

Annotation DENY.SHOW

Nach Setzen der Berechtigung IndividualAnnotationPermission.DENY.SHOW werden die betroffenen Annotationen nicht mehr angezeigt.

Annotation DENY.ADD

Nach Setzen der Berechtigung DocumentAnnotationPermission.DENY.ADD ist das Erstellen neuer Annotationen nicht mehr zugelassen. Die Buttons zum Erstellen neuer Annotationen in der AnnotationProfileAwareToolbar sind deshalb inaktiv und ausgegraut. Zusätzlich wird die Berechtigung im CreateAnnotationCommand überprüft.

Annotation DENY.CHANGE und DENY.REMOVE zum Schwärzen von Akten

Die Berechtigungen DocumentAnnotationPermission.DENY.CHANGE und DocumentAnnotationPermission.DENY.REMOVE kann man sich dahingehend zunutze machen, dass der darunterliegende Dokumentinhalt vollständig vor nicht berechtigten Benutzern versteckt wird.

Bei dieser Berechtigungskombination wird, abhängig von der AnnotationRenderStrategy siehe „Beeinflussung des Annotationsrendering über eine AnnotationRenderStrategy, die Annotation serverseitig auf das Dokument gerendert. Clientseitig werden nur noch die Bounds der Annotation angezeigt. Sensibler Inhalt bleibt also den Benutzern verborgen. Selbst die listige Abfrage der Data-URLs (z.B. über die DevTools von Chrome oder Firebug für Firefox) verhilft dem Benutzer nicht zum Originaldokument - auch die über diese URLs geladenen Kacheln enthalten bei entsprechend eingeschränkten Berechtigungen bereits die Annotationen, die den Dokumentinhalt maskieren. Es besteht auf diesem Weg also keine Möglichkeit mehr, unautorisierte Daten aus den empfangenen Daten zu sniffen.

Wichtig

Die Annotationen müssen serverseitig im DocumentDataProvider zuerst (also vor dem Dokument) über den Reader geladen werden, damit deren serverseitiges Einbrennen auf dem Dokument sichergestellt ist.

Codebeispiel: Serverseitiges Setzen von Berechtigungen

Mit folgender Änderung im ClassPathWithAnnoDocumentDataProvider in der Methode read soll demonstriert werden, wie Berechtigungen serverseitig gesetzt werden können.

Bitte beachten: Serverseitig sind die Implementierungen com.levigo.jadice.annotation.auth.DocumentPermission, com.levigo.jadice.annotation.auth.DocumentAnnotationPermission bzw. com.levigo.jadice.annotation.auth.IndividualAnnotationPermission zu verwenden; clientseitig jedoch deren serialisierbare Entsprechungen com.levigo.jadice.annotation.auth.web.DocumentPermission, com.levigo.jadice.annotation.auth.web.DocumentAnnotationPermission bzw. com.levigo.jadice.annotation.auth.web.IndividualAnnotationPermission.

	...
	import com.levigo.jadice.annotation.auth.DocumentAnnotationPermission;
	import com.levigo.jadice.annotation.auth.IndividualAnnotationPermission;
	...

	private void read(Reader reader, final String resource) throws JadiceException, IOException {

		// Variante 1 - Setzen der Permissions pro Annotation
		DefaultReaderControls controls = new DefaultReaderControls();
		final AnnotationCustomizer annotationCustomizer = new AnnotationCustomizer() {
				
			@Override
			public void customize(Annotation a) {
				a.getPermissions().getPermissions().add(IndividualAnnotationPermission.DENY.REMOVE);
			}
		};
		controls.getSettings(JadiceAnnotationReaderSettings.class).getAnnotationCustomizers().add(annotationCustomizer);
		reader.setReaderControls(controls);

		// Variante 2 - Dokumentweites Setzen von Permissions
		reader.getDocument().getPermissions().getPermissions().add(DocumentAnnotationPermission.DENY.REMOVE);

		// Ab hier kommt der bereits bestehende Code
		int targetIndex = reader.getTargetIndex();
		{
			InputStream in = getClass().getResourceAsStream(resource);
			if (in == null) {
				in = getClass().getClassLoader().getResourceAsStream(resource);
				if (in == null)
					throw new IOException("Cant find Resource on Classpath: " + resource);
			}
			reader.read(in);
		}
		{
			String annoResource = resource + ".xmlanno";
			InputStream in = getClass().getResourceAsStream(annoResource);
			if (in == null) {
				in = getClass().getClassLoader().getResourceAsStream(annoResource);
				if (in == null)
					return;
			}

			Format oldFormat = reader.getFormat();
			JadiceAnnotationFormat format = new JadiceAnnotationFormat();
			reader.setFormat(format);
			reader.setTargetIndex(targetIndex);
			reader.read(in);
			reader.setFormat(oldFormat);
		}
			

Variante 1 zeigt das Aufbringen von Berechtigungen per AnnotationCustomizer; und zwar einzeln pro Annotation (DENY.REMOVE im Beispiel). Dadurch lassen sich gezielt für einzelne Annotationen oder Annotationstypen bestimmte Berechtigungen setzen. In Variante 2 wird eine DocumentAnnotationPermission gesetzt (im Beispiel ebenfalls DENY.REMOVE). Diese ist global für das gesamte Dokument gültig, d.h. in Konsequenz sind alle Annotationen auf dem Client nicht mehr löschbar.

Achtung

Das Setzen der Berechtigungen muss erfolgen, bevor Reader#read aufgerufen wird.

Bookmarks

Überblick

Bookmarks ermöglichen es Anwendern schneller zwischen wichtigen Bereichen innerhalb eines Dokuments zu navigieren. Hierzu werden einzelne Seiten im ersten Schritt mit einem Bookmark versehen. Anschließend kann über spezielle Elemente der grafischen Benutzeroberfläche einfach und schnell zwischen Bookmarks gesprungen werden.

Im Lieferumfang des jadice® web toolkit enthalten sind Buttons und Commands, mittels derer Anwender Bookmarks erstellen und löschen sowie zwischen Bookmarks navigieren können. Als Anwendungsbeispiel dient die Enterprise-Demo, in deren rechter Sidebar die Bookmark-Funktionalität integriert ist. Die folgende Abbildung zeigt den zugehörigen Ausschnitt:

Bookmark-Funktionalität aus der Sidebar der Enterprise Demo

Persistierung

Neben der clientseitigen Erstellung und Verwendung von Bookmarks gibt es die Möglichkeit, clientseitig erstellte Bookmarks zu persistieren, um diese in einem künftigen Bearbeitungsprozess erneut zu verwenden. Um dies zu erreichen, können clientseitig erstellte Bookmarks zunächst an den JWT Server gesendet und anschließend von dort in einem Drittsystem persistiert werden. Wird das zugehörige Dokument in der Zukunft erneut bearbeitet, können die persistierten Bookmarks im Rahmen des Dokument-Ladeprozesses aus dem Drittsystem ausgelesen und dem Dokument auf der Serverseite als Property hinzugefügt werden. Zusammen mit dem Dokument werden diese hierbei an den Client verschickt und stehen dem Anwender nach Abschluss des Ladevorgangs erneut für eine einfache und schnelle Navigation zur Verfügung.

Die Persistierung von Bookmarks lässt sich in 2 Funktionen unterteilen:

  • Speichern von Bookmarks

  • Laden persistierter Bookmarks

Speichern von Bookmarks

Das Speichern von Bookmarks erfolgt in einer kundenspezifischen Implementierung einer ServerOperation. Details zum zugehörigen Konzept, das die serverseitige Ausführung von kundenspezifischem Code ermöglicht, finden sich im Kapitel „Server Operations “. Dabei stellt das jadice® web toolkit sicher, dass alle Bookmarks während der Ausführung einer ServerOperation in der Methode invoke gemäß des folgenden Codefragments ausgelesen werden können:

@Override
public void invoke(Request<BookmarkPersistencyServerOperationParameters> request,
      ResponseChannel<ShowcaseServerOperationMessage> responseChannel) throws Exception {
    Document doc = request.getDocument();
    
    // retrieve the bookmarks from the document
    for (SerializableBookmark b : ((SerializableBookmarkList) doc.getProperties().get(
        SerializableBookmarkList.PROPERTY_KEY_SERIALIZABLE_BOOKMARK_LIST)).getAll()) {
        // place your logic to persist the bookmark here
    }
}
                    
			

Laden persistierter Bookmarks

Das Laden von Bookmarks erfolgt in einer kundenspezifischen Implementierung eines DocumentDataProvider. In der read-Methode wird dabei vor dem Lesevorgang eine Instanz der Klasse SerializableBookmarkList als Document-Property gemäß des folgenden Codefragments erstellt.

@Override
public final void read(Reader reader, final S source) throws JadiceException, IOException {
    // Place your logic to read persisted bookmarks here and create an instance of
    // SerializableBookmark for each of them. For the reason of simplicity we assume 3 persisted
    // bookmarks - on pageIndexes 7, 15 and 31.
    SerializableBookmark b1 = new SerializableBookmark(7);
    SerializableBookmark b2 = new SerializableBookmark(15);
    SerializableBookmark b3 = new SerializableBookmark(31);
    
    // Create a SerializableBookmarkList and add each persisted bookmark to it
    SerializableBookmarkList bms = new SerializableBookmarkList();
    bms.add(b1);
    bms.add(b2);
    bms.add(b3);
    
    // To make sure the bookmarks are transferred to the client the list has to be added as a
    // document property BEFORE calling reader.read()
    reader.getDocument().getProperties().put(SerializableBookmarkList.PROPERTY_KEY_SERIALIZABLE_BOOKMARK_LIST, bms);
    
    // read the document
    ...
    reader.read(in);
}
                    
                

Die zugehörigen Bookmarks werden beim Aufruf von reader.read() zusammen mit dem Dokument an den Client geschickt und stehen für die clientseitige API zur Verfügung, sobald sich das Client-Dokument im Zustand Document.BasicState.READY befindet.

Ein funktionsfähiges Codebeispiel, das als Vorlage für die Persistierung dienen kann, findet sich in den Showcases.

Serverseitige und clientseitige Bookmark-API

Zu beachten ist, dass sich die serverseitige und die clientseitige Bookmark-API unterscheiden. Die serverseitige Bookmark-API besteht aus den beiden Klassen SerializableBookmark und SerializableBookmarkList (siehe Codefragmente im Kapitel „Persistierung“). Diese beiden Klassen dienen ausschließlich als Datencontainer zum Speichern und Laden von Bookmarks.

Die eigentliche Bookmark-Funktionalität wird über die mächtigere clientseitige Bookmark-API zur Verfügung gestellt. Deren Highlevel-Schicht besteht dabei aus den Commands AddBookmarkCommand, NextBookmarkCommand, PreviousBookmarkCommand, RemoveBookmarkCommand, RemoveAllBookmarksCommand und AbstractBookmarkCommand. Darunter verbirgt sich die Lowlevel-Schicht bestehend aus den Klassen BookmarkListFactory und BookmarkFactory für die Erzeugung und den beiden Interfaces BookmarkList und Bookmark für die Verwendung.

Kundenspezifische Bookmarks

Für die Umsetzung spezifischerer Anforderungen, für deren Abbildung die produkteigenen Commands nicht ausreichend sind, können über die Klassen der Lowlevel Bookmark-API kundenspezifische Bookmark-Commands implementiert werden. Als Orientierungshilfe eignen sich dabei die im Produktumfang enthaltenen Commands (siehe „Serverseitige und clientseitige Bookmark-API“).

Ein funktionsfähiges Codebeispiel, das als Vorlage für die Implementierung kundenspezifischer Commands dienen kann, findet sich in den Showcases.

Server Operations

Überblick

Das Server-Operation Konzept ermöglicht es Integratoren, dokumentbezogene Aktionen asynchron auf der Serverseite auszuführen. Anwendungsfälle sind beispielsweise das Speichern von clientseitigen Änderungen an Annotationen, das Drucken via StreamPrinting oder das Auswerten von Attributen der serverseitigen HttpSession.

Funktionsweise

Der Client schickt hierfür einen ServerOperation.Request, der das zugrundeliegende Dokument sowie Konfigurationsparameter für die Steuerung der serverseitigen Ausführung enthält, an den Server. Die Verarbeitung des Requests wird im Anschluss vom Server asynchron durchgeführt und kann daher mit einer beliebigen Anzahl von ServerOperationMessages beantwortet werden. Insbesondere kann der Server bei lang laufenden Operationen den Fortschritt der Verabeitung an den Client zurückmelden. Auf Clientseite muss hierfür eine Implementierung des Callback-Interfaces Observer zur Verfügung gestellt werden, innerhalb der flexibel auf etwaige ankommende ServerOperationMessages reagiert werden kann.

Vor der Verwendung muss die ServerOperation Instanz zunächst mit der zugehörigen ServerOperationParameters Klasse als Schlüssel an der ServerOperationRegistry registriert werden. Dies geschieht typischerweise beim Starten der Webanwendung in einem ServletContextListener. Anschließend kann die ServerOperation zu einem beliebigen Zeitpunkt auf Clientseite aufgerufen werden.

Aus Integratorsicht müssen die Interfaces ServerOperation, ServerOperationParameters und ServerOperationMessage implementiert werden. Beispiele zu Server Operations können im Showcase gefunden werden.

Instanziierung mittels einer ContextualFactory

Um innerhalb einer Server-Operation auf den aktuellen HttpRequest oder die aktuelle HttpSession zuzugreifen, kann die Server-Operation mittels einer ContextualFactory erzeugt werden. Hierfür muss eine Implementierung des Interfaces ContextualFactory bereitgestellt werden. Im Unterschied zur einfachen Verwendung wird anstatt der Server-Operation Instanz eine Instanz der ContextualFacory an der ServerOperationRegistry registriert. Die Instanziierung der ServerOperation erfolgt dann dynamisch zur Laufzeit über die zugehörige Factory-Methode. Da diese Methode auf dem Request-Thread ausgeführt wird, kann dort auf die oben aufgeführten Entitäten zugegriffen werden. Die für die Ausführung relevanten Attribute werden dabei in den Konstruktor der ServerOperation injiziert und stehen dadurch bei der asynchronen Ausführung der ServerOperation auf dem zugehörigen Worker-Thread zur Verfügung.

Ein Codebeispiel, das den Zugriff auf die ID der HttpSession demonstriert, findet sich im Showcase Accessing HttpSession

Weiterführende Informationen

Ein eigenes Kapitel zur Realisierung des Drucks mittels Server Operationen findet sich in „Export mittels einer ServerOperation .

Weitere Codebeispiele, die die Verwendung demonstrieren, finden sich in mehreren Showcases - u.a. in: Simple Usage

Export

In manchen Situationen gibt es die Anforderung das aktuell im Viewer angezeigte Dokument in einer Drittanwendung weiterzuverarbeiten. Für solche Anforderungen bietet das jadice® web toolkit die Möglichkeit ein Dokument serverseitig in die Formate PDF, TIFF und Postscript zu exportieren. Die exportierte Datei kann dann an eine Drittanwendung übergeben werden. Zu beachten dabei ist, dass die Exportfunktion für die Druckdatenaufbereitung entwickelt wurde. Daher liegt der primäre Fokus auf der Gewährleistung eines optisch einwandfreien Exportergebnisses, nicht auf anderen Aspekten, wie beispielsweise der Durchsuchbarkeit der resultierenden Daten.

Unterstützte Exportformate

Das jadice® web toolkit unterstützt direkt die folgenden Formate für die Erstellung von Exports:

Postscript (PS)

Postscript wird häufig für den Druck verwendet. Einige Drucker unterstützen dieses Format ohne weitere Konvertierung.

Portable Document Format (PDF)

Das Portable Document Format (PDF) ist für den konsistenten Austausch von Dokumenten entwickelt worden.

Tagged Image File Format (TIFF)

TIFF ist primär ein Format für Bilddaten.

Die Exporter- API

Um Exports zu erzeugen, stellt das jadice® web toolkit eine entsprechende API zur Verfügung. Diese ermöglicht es, Dokumente, welche im Web-Client zur Anzeige gebracht werden, über den Server in ein gewünschtes Format zu überführen.

Exporter selbst ist ein allgemeines Interface, das eine grundsätzliche API für die Erstellung von Exportdatenströmen bereitstellt.

Eine konkrete Instanz eines Exporter kann über die ExporterFactory erstellt werden. Diese Klasse stellt eine Reihe von Methoden für die Erzeugung von Exporter für verschiedene Formate bereit.

ExporterFactory. createPostscriptExporter

Erstellt einen Exporter für die Ausgabe in das Postscript Format.

Achtung: Um Postscript erzeugen zu können, muss die eingesetzte JVM eine Postscript-Unterstützung bieten. Ist dies nicht der Fall, ist der Rückgabewert der Methode null.

ExporterFactory. createPDFExporter

Erstellt einen Exporter für die Ausgabe in das PDF Format.

Hierbei ist zu beachten, dass der generierte Datenstrom für die Aufbereitung und Erstellung von Dokumenten für den Druck entwickelt wurde. Daher liegt der primäre Fokus auf der Gewährleistung eines optisch einwandfreien Druckergebnisses, nicht auf anderen Aspekten, wie beispielsweise der Durchsuchbarkeit der resultierenden Daten.

ExporterFactory. createPDFExporter(AbstractOpenAction)

Erstellt einen Exporter für die Ausgabe in das PDF Format. Die erzeugte Exporter-Instanz bettet die übergebene "Open Action" in den generierten Datenstrom ein. Wird der Datenstrom mit einem PDF Viewer geöffnet, der Open Actions unterstützt (z.B. Internet Explorer 11, Chrome, Firefox, Edge oder Acrobat Reader), blendet dieser z.B. im Falle einer PrintOpenAction direkt ohne weitere Benutzeraktion seinen Druckdialog ein.

ExporterFactory. createRasterizingPDFExporter

Erstellt einen Exporter für eine gerasterte Ausgabe im PDF Format.

Der generierte Datenstrom besteht rein aus gerasterten Daten, so dass z.B. die Durchsuchbarkeit der resultierenden Daten nicht mehr gegeben ist. Ein typischer Anwendungsfall für diese Art von Export ist das Schwärzen von Dokumentpassagen, um sicherzustellen, dass etwaige Textinhalte unter einer schwärzenden Maskierung nicht mehr zugänglich sind.

ExporterFactory. createTIFFExporter

Erstellt einen Exporter für die Ausgabe in das TIFF Format.

Abhängigkeiten

Um die Export-Funktion nutzen zu können, muss die entsprechende Maven-Dependency eingebunden werden:

    <dependency>
       <groupId>com.levigo.jadice.webtoolkit</groupId>
       <artifactId>webtoolkit-export</artifactId>
    </dependency>
                

Verwendung der Exporter- API

Die Verwendung der Exporter- API folgt im Allgemeinen einem einfachen Ablaufschema.

Ablauf einer Export-Datenstrom-Erzeugung mit der Exporter- API:

Document document = // document reference created elsewhere                    (1)
OutputStream out = new FileOutputStream(new File("outfile.pdf"));              (2)

Exporter exporter = ExporterFactory.createPDFExporter();                       (3)

document.getPages().getReadWriteLock().readLock().lock();                      (4)
try {
    
    printer.getSourcePages().addAll(document.getPages());
    
} finally {
    document.getPages().getReadWriteLock().readLock().unlock();
}

exporter.export(out);                                                          (5)

out.close();                                                                   (6)

1

Das zu druckende Document. Diese Document Instanz kann aus verschiedensten Quellen stammen. Die gebräuchlichste Art ist die Verwendung der Document-Referenz innerhalb einer ServerOperation. Details zu dieser Vorgehensweise sind im Kapitel „Export mittels einer ServerOperation zu finden.

2

Ein java.io.OutputStream , welcher als Ausgabeziel verwendet werden soll. In diesem Beispiel wird der Datenstrom in eine lokale Datei geschrieben. Auf einem ähnlichen Weg könnten Druckdaten über das Netzwerk an einen Printserver verschickt werden.

3

Erstellen eines Exporter. In diesem Beispiel wird ein PDF Exporter erzeugt.

4

Setzen der zu exportierenden Seiten. Hierbei ist es wichtig, die Seitenliste mit einem ReadWriteLock zu sperren, um Modifikationen von anderen Threads an diesem Document vorzubeugen.

5

Ausführen des eigentlichen Exports. Hierbei werden die Exportdaten in den mit out benannten OutputStream geschrieben.

6

Wurde der Export durchgeführt, muss der OutputStream noch geschlossen werden. Wird dies nicht gemacht, kann nicht sichergestellt werden, dass die verwendete java.io.OutputStream -Implementation keine Daten mehr im Hauptspeicher zwischenpuffert.

Export mittels einer ServerOperation

Der empfohlene Weg für das Exportieren eines Dokumentes aus der Webanwendung heraus ist die Verwendung einer ServerOperation. Hierfür bietet das jadice® web toolkit bereits vorbereitete ServerOperation Basisklassen an. Diese haben den Vorteil, dass die Erstellung einer Exporter Instanz bereits vorbereitet ist.

Standardmäßig stehen folgende Basisklassen zur Verfügung:

AbstractPostscriptExportServerOperation

Export eines Dokuments als Postscript-Datenstrom.

AbstractPDFExportServerOperation

Export eines Dokuments als PDF-Datenstrom.

AbstractTIFFExportServerOperation

Export eines Dokuments als TIFF -Datenstrom.

AbstractExportServerOperation

Export eines beliebigen der oben aufgeführten Datenströme.

Implementation einer ServerOperation

Die Erstellung einer konkreten Implementation für diese abstrakten Basisklassen unterscheidet sich hauptsächlich in der Wahl der Basisklasse, die erweitert wird. Exemplarisch wird im Folgenden ein generisches Beispiel vorgestellt, das alle der drei oben aufgeführten Export-Formate erstellen kann. Falls nicht alle der aufgeführten Datenstrom-Formate erzeugt werden müssen, sollte eine Variante für den spezifischen Datenstrom gewählt werden.

ExportServerOperation

public class ExportServerOperation
    extends AbstractExportServerOperation<ExportParameters, ServerOperationMessage, Exporter> {

  private final ExportRepository repository;

  public ExportServerOperation(final ExportRepository repository) {
    this.repository = repository;
  }

  @Override
  protected void doExport(final Request<ExportParameters> request,             (1)
      final ResponseChannel<ServerOperationMessage> responseChannel, final Exporter exporter)
      throws Exception {

    final ExportParameters.Type type = request.getParameters().getType();

    responseChannel.send(new ExportStartedMessage());                          (2)

    final EventList<Page> pages = request.getDocument().getPages();
    // ensuring that there will be not concurrent writing access to the document
    pages.getReadWriteLock().readLock().lock();
    try {
      // attach all pages that will be printed
      exporter.getSourcePages().addAll(pages);                                 (3)
    } finally {
      // allow modifications
      pages.getReadWriteLock().readLock().unlock();
    }

    final File file = File.createTempFile("export-", type.getExtension());
    final FileOutputStream output = new FileOutputStream(file);                (4)

    // export the document into a file
    exporter.export(output);                                                   (5)

    output.close();                                                            (6)

    // register the output file containing the print
    final ExportMeta meta = new ExportMeta(file);
    meta.setMimeType(type.getMimeType());
    final String id = repository.register(meta);
    // send registration id to the client
    // the client may download the file by using this id
    responseChannel.send(new ExportCompletedMessage(id));                      (7)

  }

  @Override
  protected Exporter createExporter(final Request<ExportParameters> request) {

    switch (request.getParameters().getType()){
      case PDF: return ExporterFactory.createPDFExporter();
      case PDF_SHOW_PRINT_DIALOG_ON_OPEN :
      	return ExporterFactory.createPDFExporter(new PrintOpenAction());        (8)
      case POSTSCRIPT: return ExporterFactory.createPostscriptExporter();
      case TIFF: return ExporterFactory.createTIFFExporter();
    }

    throw new IllegalArgumentException("Unknown format type specified");
  }
}

1

Diese Methode muss von der implementierenden Klasse implementiert werden.

2

Um dem Client den Beginn der Verarbeitung zurück zu melden, wird eine ExportStartedMessage übermittelt.

3

Setzen der zu exportierenden Seiten. Hierbei ist es wichtig, die Seitenliste mit einem ReadWriteLock zu sperren, um Modifikationen von anderen Threads an diesem Document vorzubeugen.

4

Ein java.io.OutputStream , welcher als Ausgabeziel verwendet werden soll. Hier könnte beispielsweise ein java.io.OutputStream erstellt werden, welcher Druckdaten über das Netzwerk an einen Printserver verschickt.

5

Ausführen des eigentlichen Exports. Hierbei werden die Daten in den mit output benannten OutputStream geschrieben.

6

Wurde der Exportvorgang durchgeführt, muss der OutputStream noch geschlossen werden. Wird dies nicht gemacht, kann nicht sichergestellt werden, dass die verwendete java.io.OutputStream -Implementation keine Daten mehr im Hauptspeicher zwischenpuffert.

7

Um dem Client eine erfolgreiche Verarbeitung zurück zu melden, wird eine ExportCompletedMessage übermittelt.

8

Einbetten einer Print "Open Action" in das generierte PDF. Wird das PDF in einem Viewer geöffnet, der Open Actions unterstützt, zeigt dieser automatisch nach dem Öffnen des Datenstroms seinen Druckdialog.

Registrieren der ServerOperation

Das Registrieren der im vorherigen Beispiel implementierten ExportServerOperation erfolgt identisch zu allen anderen Arten von ServerOperations.

In den meisten Fällen wird hierfür in einem WebtoolkitServletContextListener die ServerOperation bei der ServerOperationRegistry mit der zugehörigen ServerOperationParameters Klasse als Key registriert.

ApplicationServletContextListener

public class ApplicationServletContextListener extends WebtoolkitServletContextListener {

  @Override
  protected void contextInitialized(ServletContextEvent sce, WebtoolkitServerContext webtoolkitContext) {
  
    final ExportRepository repository = ExportRepository.get(sce.getServletContext());

    final ServerOperationRegistry serverOperationRegistry = webtoolkitContext.getServerOperationRegistry();
    serverOperationRegistry.register(ExportParameters.class, new ExportServerOperation(repository));
  }
}

Ausführen der ServerOperation

Wurde die ServerOperation in der ServerOperationRegistry registriert, kann diese vom Client aus aufgerufen werden. In der Demo Basicviewer wird hierzu ein zugehöriger Button in der Toolbar angelegt.

Triggern des Exports über einen Button:

toolbar.add(new JadiceDefaultButton(ActionFactory.createExportPDFAction(actionRegistry, context)));

Druck

Im jadice® web toolkit wird die Strategie verfolgt, das Drucken von Dokumenten als einen Spezialfall des „Export“ abzubilden. Dies bedeutet, dass das zu druckende Dokument zunächst serverseitig in einen Druckdatenstrom exportiert wird. Im zweiten Schritt kann der erzeugte Druckdatenstrom an ein beliebiges Drucksystem übermittelt werden.

Abhängigkeiten

Um die Druckfunktion nutzen zu können, muss die entsprechende Maven-Dependency eingebunden werden:

<dependency>
   <groupId>com.levigo.jadice.webtoolkit</groupId>
   <artifactId>webtoolkit-export</artifactId>
</dependency>
			

clientseitiges Drucken

Beim clientseitigen Drucken wird der serverseitig exportierte Druckdatenstrom vom Browser des Benutzers heruntergeladen und an einen in der lokalen Umgebung des Benutzers verfügbaren Drucker geschickt. Das jadice® web toolkit unterstützt diese Druckmethode mittels eines speziellen Exports, sodass der Druckvorgang über lediglich zwei Benutzeraktionen abgeschlossen werden kann. Damit wird dem Benutzer aus der Webanwendung heraus das selbe Druckerlebnis wie beim Druck aus einem Rich Client ermöglicht.

Hierfür wird serverseitig ein spezieller PDF Export erzeugt (Details siehe „Die Exporter- API), in den eine sogenannte "Print Open Action" eingebettet ist. Viewer, die die in der PDF Spezifikation beschriebenen Open Actions unterstützen (z.B. Internet Explorer 11, Chrome, Firefox, Edge, Acrobat Reader) zeigen beim Öffnen des Exports dann direkt ohne weitere Benutzeraktion ihren nativen Druckdialog, der Zugriff auf die lokale Druckumgebung besitzt.

In Integrationen wird damit folgendes Vorgehen möglich: Der Benutzer klickt auf den Druckbutton in der Toolbar und initiiert damit den Druckvorgang. Serverseitig wird dadurch der oben beschriebene PDF Export erzeugt. Nachdem der PDF Export bereitsteht wird dieser automatisch in einem neuen Browsertab geöffnet. Der Browser zeigt über die Ausführung der eingebetteten "Print Open Action" seinen nativen Druckdialog. Der Benutzer schließt den Druckvorgang mit einem weiteren Klick ab.

Sowohl die Enterprise Demo als auch die Basic Viewer Demo verwenden diese Druckmethode. Da der zugehörige Democode Teil der Distribution ist, kann dieser als Vorlage für kundenspezifische Implementierungen dienen.

Drucken mit Seitenmodifikationen

In manchen Fällen müssen Dokumente vor dem Druck aufbereitet werden. Ein exemplarischer Anwendungsfall ist die Kennzeichnung eines zu druckenden Dokuments als Kopie, um sichtbar zu machen, dass es sich bei dem Druck nicht um das Originaldokument handelt. Der Inhalt jeder Seite soll dabei verkleinert und dann nach unten verschoben werden. Der sich daraus ergebende zusätzlich Platz im Kopfbereich jeder Seite kann dann verwendet werden, um die gewünschte Kennzeichnung (z.B. als Text) sichtbar zu machen.

Eine ausführliche Beschreibung inklusive Beispielcode findet sich in der jadice Knowledge-Base unter: Drucken mit Seitenmodifikationen im jadice® web toolkit

Connection

Während der Nutzung des jadice® web toolkit durch einen Anwender kommuniziert der im Browser ausgeführte Javascript-Client in unterschiedlichen Situationen mit dem im Application Server ausgeführten Java-Backend.

Diese Kommunikation erfolgt zum einen synchron über das Request-Response-basierte HTTP-Protokoll. Beispielsweise findet das Bootstrapping, also der initiale Download des Javascript-Clients über synchrones HTTP statt. Sobald der Anwender zu Beginn die URL der HTML-Datei aufruft, die das jadice® web toolkit enthält, wird diese HTML-Datei und im Anschluss insbesondere auch der für den genutzten Browser passende Javascript-Client heruntergeladen.

Wie bei Single-Page-Webanwendungen üblich kommunizieren Client und Server daneben aber auch asynchron miteinander, um die gewünschte Usability zu erreichen. Beispielsweise wird beim Laden eines Dokuments durch den Benutzer die ID des Dokuments vom Client an den Server übertragen. Der Server lädt das zur ID passende Dokument daraufhin. Pro erfolgreich geladener Seite antwortet der Server mit Strukturinformationen für diese Seite und lädt parallel dazu die nächste Seite. Der Client kann damit die erste Seite des Dokuments schon anzeigen, während die Folgeseiten noch vom Server geladen werden.

Diese asynchrone Kommunikation wird durch eine eigene Transportschicht realisiert. Im Unterschied zu reinem HTTP, bei dem eine Kommunikation ausschließlich vom Client initiiert werden kann und der Server diese Anfrage ausschließlich mit einer einzelnen Nachricht beantworten kann, ermöglicht es die Transportschicht dem Server eine Anfrage des Clients mit mehreren Nachrichten zu beantworten (u.a. notwendig für den Ladevorgang von Dokumenten, siehe oben) und außerdem auch eine vom Server initiierte Kommunikation. Um diese verbindungsorientierte, asynchrone Kommunikation zu realisieren bietet die Transportschicht unterschiedliche Protokolle an. Abhängig von der kundenindividuellen Umgebung kann die Transportschicht unter Berücksichtigung der Fähigkeiten von Browser und Application Server das dafür passende Protokoll wählen.

Client und Server kommunizieren in folgenden Situationen über die Transportschicht miteinander:

  • Beim Laden eines Dokuments, in dessen Rahmen der Client die ID des zu ladenden Dokuments an den Server schickt und der Server mit Strukturinformationen pro Seite antwortet.

  • Beim Ausführen einer ServerOperation, in dessen Rahmen der Client die Parameter der ServerOperation an den Server schickt und der Server mit mehreren Zwischenergebnissen antwortet.

  • Beim Markieren von Text, in dessen Rahmen der Client die ID der zugehörigen Seite an den Server schickt und der Server mit dem Text antwortet, der sich auf dieser Seite befindet.

  • Beim Ausführen einer Volltextsuche innerhalb eines Dokuments, in dessen Rahmen der Client den zu suchenden Text an den Server schickt und der Server mit den gefundenen Textpassagen und deren Positionen im Dokument antwortet.

  • Beim Download eines Annotationsprofils, in dessen Rahmen der Client den Namen des Annotationsprofils an den Server schickt und der Server mit dem zugehörigen Profil antwortet.

Browserspezifische Beschränkung der offenen Verbindungen

Die Transportschicht hält dauerhaft eine Verbindung zum Server aufrecht, damit dieser zu jeder Zeit Nachrichten an den Client senden kann. Die unterschiedlichen Browser limitieren jedoch die Anzahl der gleichzeitig offenen Verbindungen zu einem Server. Dies gilt nicht nur für einen Tab, sondern Tab-übergreifend. Verwendet man das jadice® web toolkit also in "zu vielen" Tabs gleichzeitig, kommt es irgendwann zu dem Problem, dass keine Anfragen mehr an den Server gesendet werden können. Je nach Browser ist die Beschränkung unterschiedlich (im ungünstigsten Fall nur 6 gleichzeitige Verbindungen).

Protokolle und Problematik

Seit HTML 5 ist WebSocket als Standard für bidirektionale Kommunikation mit geringem Overhead etabliert. Doch leider unterstützen nicht alle Application Server dieses Protokoll. Alternativ dazu bieten sich Server-Sent Events an: In der Richtung Client an Server werden dabei reine HTTP-Anfragen gesendet, in der Gegenrichtung Server-Sent Events. Im Vergleich zu WebSockets hat die Kommunikation darüber einen größeren Overhead, ist aber immer noch relativ effizient. Leider unterstützen die Browser von Microsoft (Internet Explorer und Edge) keine Server-Sent Events. Daher ist eine Fallback-Lösung notwendig, die überall funktioniert: Longpolling mit reinen HTTP-Anfragen und reinen HTTP-Antworten. Dies ist zwar die schlechteste Wahl, da sie den größten Overhead mit sich bringt, ist jedoch gerade für ältere Application Server und/oder Browser das einzig mögliche Protokoll.

Die Wahl des Verfahrens ist folglich nicht trivial: Zum einen unterstützen nicht alle Browser alle Protokolle. Zum anderen kann es sein, dass der Application Server der limitierende Faktor ist, da er z.B. WebSocket nicht unterstützt. Unter Berücksichtigung dieser Einschränkungen soll in jeder Kundensituation über das effizienteste Verfahren mit dem geringsten Overhead kommuniziert werden.

Durch Browser unterstütze Protokolle

  Chrome Firefox Edge Internet Explorer
Longpoll (HTTP basierend) Ja Ja Ja Ja
Server Sent Events Ja, ab Version 6 Ja, ab Version 6 Nein Nein
WebSocket Ja, ab Version 16 Ja, ab Version 11 Ja, ab Version 12 Ja, ab Version 10

Application Server Versionen ab denen WebSockets nach JSR 356 unterstützt wird

Webserver WebSocket JSR 356, ab Version
Oracle WebLogic 12.1.2
Apache Tomcat 7.0.27 (empfohlen größer oder gleich 9.0.33, siehe „Known Issues“)
WebSphere Liberty 8.5.5
WebSphere Traditional 9
Wildfly 10
Jetty 9.2

Automatische Wahl des Verbindungsverfahrens

Die Transportschicht löst dieses Problem, indem es Implementationen für alle drei konfigurierbaren Verfahren bereitstellt und die Wahl des zu verwendenden Verfahrens selbstständig übernimmt. Dabei wählt der Client immer die effizienteste Möglichkeit, die zur Verfügung steht. Das Aushandeln des Verfahrens erfolgt dabei transparent für den Nutzer und kann vorab passend konfiguriert werden. Siehe Verfahren.

Das Downgrade-Verfahren:

  1. Ermittlung der Protokolle, die vom Browser unterstützt werden

  2. Verbinden:

    1. Versuch mit WebSocket zu verbinden, insofern diese Methode enabled ist, ansonsten wird versucht, mit Server Sent Events zu verbinden.

    2. Schlägt die Kommunikation fehl, erfolgt nach einer bestimmten Anzahl Retrys ein Downgrade der Kommunikation auf dieses. Diese Parameter (setMaxReconnectAttempts, setMaxRetryDelay, etc.) lassen sich wie oben genannt frei konfigurieren.

      Schritt 2 passiert so lange, bis das erste funktionierende Verfahren gefunden wurde, in der Reihenfolge:

      • WebSocket basierend → Server-Sent Events basierend → Longpoll basierend

      Ab dem Zeitpunkt, in dem ein funktionierendes Verfahren gefunden wurde, findet die Kommunikation ausschließlich über dieses Verfahren statt.

Wichtig

Es ist zu empfehlen, nicht verwendete Transportmechanismen via API zu deaktiveren, da das Downgrade Verfahren (je nach Konfiguration) eine gewisse Zeit in Anspruch nimmt und erst dann mit der Anwendung gearbeitet werden kann.

Vergleich der Verbindungsverfahren

  Vorteil Nachteil
WebSocket basierend
  • Kommunikation in beide Richtungen ausschließlich über WebSocket.

  • Sehr kleiner Overhead, deutlich geringer als in Longpoll und SSE.

    • Nur zwei Byte Overhead pro Nachricht, statt mehrere Hundert Byte wie in HTTP.

  • Wird von allen modernen Browsern unterstürzt.

  • Asynchron

  • Nicht alle Application Server unterstützen WebSockets.

Server-Sent Events basierend
  • Nachrichten in beide Richtungen ohne Verzögerung.

  • Deutlich kleinerer Overhead als bei Longpoll

    • Clientnachrichten werden zwar immer noch als HTTP-Anfragen an den Server gesendet, Servernachrichten werden jedoch in einem offen gehaltenen HTTP-Requeststream an den Client gesendet, statt in fortlaufenden HTTP-Antworten. Dadurch sinkt der Overhead.

    • Kein ständiges Polling des Clients: Der Server sendet nur dann, wenn er ein Ergebnis hat oder von sich aus senden will.

  • Asynchron

  • Wird nicht von den Microsoft Browsern Edge und Internet Explorer unterstützt.

  • Client Nachrichten an den Server haben immer noch den vollen HTTP-Overhead.

Longpoll basierend

  • Wird von allen Browsern und Application Servern unterstützt.

    • Dient als verlässlicher Fallback für jeden Browser und/oder Application Server

  • Verhältnismäßig großer Overhead

    • Nachrichten des Clients werden als HTTP-Anfrage an den Server gesendet, der Server sendet Nachrichten in den HTTP-Antworten zurück. Jedes Mal fällt der komplette Overhead der HTTP-Anfragen/Antworten an.

    • Polling erfordert kontinuierliches Senden von HTTP-Requests und HTTP-Responses zwischen Client und Server. Client und Server kommunizieren, auch wenn Sie sich nichts zu sagen haben. Die Folge ist eine erhöhte Zahl an Nachrichten.

  • Das Senden von Servernachrichten an den Client benötigt eine Longpollanfrage des Clients. Daher wird jede Servernachricht im schlimmsten Fall um die Zeit zwischen dem Beantworten einer alten Longpollanfrage und dem Empfangen einer neuen verzögert.

    • Daher nicht asynchron, sondern pseudoparallel

Verwendete HTTP-Header

In machen Netzwerkinfrastrukturen kann es vorkommen, dass alle unbekannten HTTP-Header aus Sicherheitsgründen ausgefiltert werden. Das würde dazu führen, dass das jadice® web toolkit nicht mehr korrekt funktioniert. Daher ist es nötig in diesem Fall die folgenden Header freizuschalten:

In jedem Fall wird der folgende HTTP-Header verwendet:

  • X-GWT-Permutation

Lebenszyklus und Zustände einer Connection

Der Lebenszyklus einer Verbindung zwischen Client und Server umfasst mehrere Zustände. Im folgenden Teil werden diese Zustände beispielhaft aufgezeigt, inklusive bestehender Konfigurationsmöglichkeiten und Fehlerbehandlungsmechanismen.

Verbinden, Verbindungsfehler, Verbindungsabbruch oder Verbindungswiederherstellung

Der jadice® web toolkit Client wird im Browser geöffnet. Daraufhin versucht sich der Client zum Backend des jadice® web toolkit zu verbinden.

Der Server ist aufgrund einer kurzfristigen Überlastung des Netzwerks nicht erreichbar. Der Client versucht einige Male mit einer gewissen initialen Verzögerung neu zu verbinden. Bei jedem erneuten Verbindungsversuch wird randomisiert ein wenig länger gewartet um die Verbindungsanfragen besser über die Zeit zu verteilen und das Fluten des Servers mit Anfragen zu verhindern. Dieser Prozess wird Backoff genannt. Um die zunehmend längere, randomisierte Zeitspanne nach oben zu begrenzen, greift er auf eine konfigurierbare Zeitspanne zu, nach der er spätestens einen neuen Verbindungsversuch unternimmt. Wenn der Client erfolglos eine konfigurierbare Maximalanzahl an Verbindungsversuchen unternommen hat, bricht er den Backoff ab.

Alle Service-Anfragen (Laden eines Dokuments, Ausführen von ServerOperations...), die während des Backoffs eingehen, werden zwischengespeichert und nach dem erfolgreichen Wiederherstellen der Verbindung erneut gesendet. Kann die Verbindung während des Backoffs nicht wiederhergestellt werden, so werden die Service-Anfragen abgebrochen und es wird die onError()-Methode des Services aufgerufen.

Schlägt der Backoff fehl, wechselt der Client in den Staus FAILED bzw. versucht bei aktiviertem Longtime Reconnect weiterhin, die Verbindung wiederherzustellen. Während des Longtime Reconnect wird bis zum Schließen des Browsertabs oder bis zum erfolgreichen Verbinden mit dem Server in festen Abständen versucht, eine Verbindung zum Server aufzubauen.

Befindet sich der Client im Staus FAILED oder "Longtime Reconnect", so führt das Auslösen eines neuen Service Calls zu einem unmittelbaren neuen Verbindungsaufbau und der Client wechselt in den Zustand "Reconnect with Backoff".

Die möglichen Zustände der Verbindung und deren Übergange sind in der folgenden Grafik visualisiert.

API und Konfiguration

Die Transportschicht kann sowohl clientseitig als auch serverseitig konfiguriert und angesprochen werden.

Clientseitig

Eine Verbindung wird durch die Instanz einer ServerConnection abgebildet. Diese wird mit dem ServerConnectionBuilder erzeugt:

ServerConnection serverConnection = new ServerConnectionBuilder().build();

Wichtig

Die ServerConnection über den ServerConnectionBuilder zu bauen sollte das erste sein, was im EntryPoint der Anwendung gemacht wird, da verschiedenste Stellen des jadice® web toolkit versuchen, die ServerConnection abzufragen. Ist diese nicht vorhanden kommt es zu NullPointerExceptions.

Dadurch erzeugt der Builder eine Verbindung zur selben URL unter der auch die Web Application des jadice® web toolkit liegt.

Zudem kann über den ServerConnectionBuilder eine Verbindung zu einer beliebigen Zieladresse hergestellt werden, z.B. zu einem Loadbalancer oder einem jadice® web toolkit Server-Backend auf einer anderen Maschine:

ServerConnection serverConnection = new ServerConnectionBuilder("https://www.yourcompany.com/webtoolkit").build();

Der Builder kann dabei auch so konfiguriert werden, dass verschiedene Protokolle im Voraus (de)aktiviert sind:

  • setWebSocketEnabled

  • setServerSentEventsEnabled

  • setLongPollingEnabled

Außerdem kann das Verbindungsverhalten über die folgenden Konfigurationseinstellungen kundenspezifisch angepasst werden:

  • setMaxReconnectAttempts

    • Konfiguriert die maximale Anzahl an möglichen Neuversuchen eine Verbindung aufzubauen. Sollte diese Zahl erreicht sein, so wechselt der Client abhängig davon, ob das longtime reconnect- Verfahren aktiviert wurde, in den Zustand RECONNETCTING_LONGTIME (bei aktiviertem longtime reconnect) beziehungsweise FAILED bei nicht aktiviertem longtime reconnect. Zudem werden die am TransportClient registrierten TransportClient.TransportListener darüber informiert.

    • Standardwert: 10

  • setInitialBackoffValue

    • Setzt die initiale Zeit nach der ein neuer Verbindungsaufbau unternommen werden soll (in Millisekunden)

    • Standardwert: 50 ms

  • setMaxRetryDelay

    • Setzt die Obergrenze, bis zu der hin ein Wiederverbindungsversuch verzögert werden kann (in Millisekunden)

    • Standardwert: 30.000 ms = 30 s

  • setRequestAggregationWindow

    • Setzt das Zeitfenster für Longpoll und Server-Sent Events, innerhalb dessen clientseitig auf Nachrichten gewartet werden soll um sie in einem Batch zu schicken, statt jede einzeln. Das reduziert die Anzahl an Anfragen, die der Client senden muss. Typischerweise liegt dieses Zeitfenster im Millisekundenbereich, sodass der Nutzer keine Verzögerung spürt, aber trotzdem mehrere aufeinander folgende Nachrichten gebündelt an den Server verschickt werden.

    • Standardwert: 100 ms

  • setLongtimeReconnectEnabled

    • Definiert, ob nach dem Erreichen der maximalen Anzahl Versuche mit dem Backoff-Verfahren weiter versucht werden soll, die Serververbindung wiederherzustellen. Wenn dieser Wert auf true gesetzt ist wird in einem definierten Intervall versucht,die Verbindung wiederherzustellen. Die Länge dieses Intervalls kann in setLongTimeIntervalInMillis konfiguriert werden.

    • Standardwert: true

  • setLongtimeReconnectIntervalInMillis

    • Definiert das Intervall in Millisekunden, in dem beim longtime reconnect-Verfahren versucht werden soll, die Serververbindung nach Erreichen der maximalen Anzahl Versuche mit dem Backoff-Verfahren wiederherzustellen. Dieses Intervall wird nur beachtet, wenn das Verfahren mit setLongtimeReconnectEnabled(true) aktiviert wurde.

    • Standardwert: 60.000ms = 60 s

Nachdem eine Verbindung erstellt wurde, kann sie jederzeit statisch abgerufen werden:

ServerConnection.get()

Über die so erhaltene ServerConnection kann man auf den TransportClient zugreifen:

serverConnection.getTransportClient()

Dieser ermöglicht es:

  • Den Verbindungsstatus abzurufen

  • Das verwendete Protokoll zu erfragen

  • Einen TransportClient.TransportListener zu (de)registrieren. Dieser kann

    • über Statusänderungen der Verbindung informieren

    • über Änderungen des verwendeten Protokolls informieren

    • über Fehler der Transportschicht informieren

  • Die Verbindung zu trennen oder aufzubauen

Beispiele hierzu finden sich auch in der Enterprise Demo in der Klasse "EnterpriseDemoMain".

Serverseitig

Da das jadice web toolkit vor der Transportschicht erzeugt wird, benötigt man einen NetworkContext.NetworkInitializedListener der darüber informiert, dass die Transportschicht initialisiert wurde, um sie anzusprechen. Der Listener wird über den NetworkContext (de)registriert:

NetworkContext.addNetworkInitializedListener(...);
NetworkContext.removeNetworkInitializedListener(...);

Nachdem die Transportschicht initialisiert wurde, kann man:

  • sich mit Hilfe von ClientSessionListenern, über verschiedene Client-Events informieren lassen (Erzeugung einer Session, Schließen einer Session, Timeout und Protokolländerungen).

    // Example how to register a listener that notifies when the network layer has been initialized
    NetworkContext.addNetworkInitializedListener(networkContext -> {
      // Example how to register a client session listener that notifies about client events
      networkContext.addClientSessionListener(new ClientSessionListener() {
        @Override
        public void sessionTimedOut(Client client) {
          LOGGER.info("Client timed out: [id=" + client.getId() + ", protocol=" + client.getTransportMethod() + "]");
        }
    
        @Override
        public void sessionStarted(Client client) {
          LOGGER.info(
            "Client session started: [id=" + client.getId() + ", protocol=" + client.getTransportMethod() + "]");
          }
    
        @Override
        public void sessionClosed(Client client) {
          LOGGER.info(
              "A client session was closed: [id=" + client.getId() + ", protocol=" + client.getTransportMethod() + "]");
        }
    
        @Override
        public void methodChanged(Client client, Method oldMethod, Method newMethod) {
          LOGGER.info("Client [id=" + client.getId() + "] changed the communication method from " + oldMethod + " to "
              + newMethod);
        }
      });
    });
  • Die verbundenen Clients ermitteln:

    // Example how to register a listener that notifies when the network layer has been initialized
    NetworkContext.addNetworkInitializedListener(networkContext -> {
       networkContext.getClients();
    };

Wurde die Transportschicht initialisiert, so kann der NetworkContext auch statisch abgefragt werden:

NetworkContext.get();

Konfiguration der serverseitigen Netzwerkparameter

Die Netzwerkparameter sind schon sinnvoll voreingestellt, können deklarativ über Spring Boot (siehe „Konfiguration serverseitiger Einstellungen“) oder aber programmatisch über den ConfigurationManager kundenindividuell angepasst werden:

ConfigurationManager.getServerConfiguration().getNetworkConfiguration();

Hier kann man

  • Bei Verwendung des Longpoll-Protokolls das Zeitfenster konfigurieren, für das Serverantworten gesammelt und als Batch gesendet werden um die Anzahl an Antworten zu reduzieren.

    • Beispiel:

      ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setResponseAggregationWindow(Duration.ofMillis(70));
    • Standardwert: 70 ms

  • Bei Verwendung des Longpoll-Protokolls die Zeitspanne konfigurieren, nach der ein Timeout ausgelöst wird. Diese gibt an, wann eine offene Longpollanfrage geschlossen werden soll, sodass der Client eine neue sendet. Sollte der Server eine Antwort haben, wird dieser Longpoll sofort beantwortet und geschlossen ohne dass ein Timeout eintritt. Um die Nachrichtenzahl niedrig zu halten, sollte dieser Wert nicht zu klein gewählt werden.

    • Beispiel:

                                                  ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setLongPollTimeout(Duration.ofMinutes(5));
                                              

    • Standardwert: 5 min

  • Bei Verwendung des Websocket-Protokolls die Zeitspanne konfigurieren, nach der ein Timeout ausgelöst wird. Diese gibt an, wann eine offene Weboscket-Verbindung geschlossen werden soll, sodass der Client eine neue aufbaut. Um die Nachrichtenzahl niedrig zu halten, sollte dieser Wert nicht zu klein gewählt werden.

    • Beispiel:

                                                  ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setWebSocketTimeout(Duration.ofHours(9));
                                              

    • Standardwert: 9 h

  • Bei Verwendung des Longpoll- oder Server-Sent-Events-Protokolls das Intervall konfigurieren, innerhalb dessen keep-alive-Nachrichten an den Client gesendet werden. Diese Nachrichten führen dazu, dass der initiale HTTP-Request auf Clientseite nicht in einen Timeout läuft.

    • Beispiel:

                                                  ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setKeepAliveInterval(
              Duration.ofSeconds(30));
    • Standardwert: 30 s

  • Die Zeit konfigurieren, nach der für inaktive Clients ein Sessiontimeout eintritt. Sollte sich ein Client über diese Zeit nicht gemeldet haben (z.B. weil das Browserfenster geschlossen wurde), werden alle Netzwerkresourcen aufgeräumt, die mit dem Client verknüpft waren.

    • Beispiel:

      ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setSessionTimeout(Duration.ofSeconds(30));
    • Standardwert: 30 s

Beispiele zu der serverseitigen Konfiguration finden sich in der Enterprise Demo in der Klasse "EnterpriseDemoSystem" oder für die deklarative Konfiguration in der "application.yml" der Simple Spring Demo.

Unterstützung von CORS (Cross-Origin Resource Sharing)

Öffnet man das jadice® web toolkit im Browser, so versucht sich der Client zum Backend des jadice® web toolkit zu verbinden. Falls das Backend unter der selben URL erreichbar ist, von der auch der Javascript-Client geladen wurde, sind keine weiteren Schritte notwendig. Falls sich die URLs jedoch unterscheiden, muss aufgrund der Same-Origin-Policy ein CORS-Filter angelegt werden.

Das folgende Kapitel beschreibt die Schritte, die für den Betrieb des jadice® web toolkit über Domänengrenzen hinweg notwendig sind.

Zunächst muss ein CORS-Filter implementiert werden:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter(
    filterName = "jwtCORSFilter",
    description = "Allows Cross Origin Requests to the JWT",
    displayName = "jadice web tookit CORS Filter",
    urlPatterns = {
        "/*"
    },
    asyncSupported = true)
public class CORSFilter implements Filter {

  @Override
  public void init(final FilterConfig filterConfig) throws ServletException {
  }

  @Override
  public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
      final FilterChain filterChain) throws IOException, ServletException {

    final HttpServletRequest req = (HttpServletRequest) servletRequest;
    final HttpServletResponse resp = (HttpServletResponse) servletResponse;

    // set the allowed origin to http://127.0.0.1:8080.
    resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8080");

    resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
    resp.setHeader("Access-Control-Allow-Credentials", "true");
    resp.setHeader("Access-Control-Allow-Headers",
        "content-type, x-gwt-permutation");

    filterChain.doFilter(servletRequest, servletResponse);
  }

  @Override
  public void destroy() {
  }

  private String getAllowedDomainsRegex() {
    return "individual / customized Regex";
  }
}

Folgende Konfiguration ist hierzu nötig:

  • Access-Control-Allow-Credentials

    Unter der Verwendung von longpoll oder Server-Sent-Events wurde der Aufruf von com.google.gwt.http.client.RequestBuilder#setIncludeCredentials hinzugefügt, um credentials in Cross-Origin Requests zu aktivieren. Deshalb muss nun auch im Filter das flag Access-Control-Allow-Credentials zwingend auf true gesetzt werden.

  • Access-Control-Allow-Origin

    Im obigen Beispiel wird der Eintrag Access-Control-Allow-Origin im Header auf "127.0.0.1:8080" gesetzt. Dies bedeutet, dass nur Anfragen, die von Adresse 127.0.0.1 und Port 8080 kommen erlaubt sind.

Im obigen Beispiel erfolgt die Registrierung des CORS-Filters über die WebFilter Annotation. Alternativ ist dies auch über die web.xml möglich:

        
        <filter>
            <filter-name>CORSFilter</filter-name>
            <filter-class>com.levigo.jadice.web.demo.basicviewer.server.CORSFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>CORSFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>        
        </web-app>
        

Einstiegspunkt des Webtoolkits

Um das Serverbackend des jadice web toolkit anzusprechen, wird eine spezialisierte javax.servlet.ServletContextListener Implementation verwendet. Hierzu kann die Klasse WebtoolkitServletContextListener erweitert werden, die in der Methode contextInitialized() neben des javax.servlet.ServletContextEvents zusätzlich einen WebtoolkitServerContext zur Verfügung stellt:

public class ExampleContextListener extends WebtoolkitServletContextListener {

  protected void contextInitialized(ServletContextEvent sce, WebtoolkitServerContext serverContext) {
    // modify the WebtoolkitServerContext
  }

}				

Detailliertere Beispiele kann man in den verschiedenen Demoimplementationen finden.

Dimensionierung der Hardware und Caching

Überblick

Wichtige Voraussetzung für eine hohe Benutzerzufriedenheit ist die gute Performance des Viewers. Die Performance hängt dabei stark von der zugrundeliegenden Hardwaredimensionierung und der damit einhergehenden Verfügbarkeit zugehöriger Ressourcen ab. Zeitgleich existiert in Projekten die betriebswirtschaftliche Anforderung die Kosten für die eingesetzte Hardware zu minieren. Aus Anwendersicht sollte die Hardware also möglichst großzügig dimensioniert werden, aus betriebswirtschaftlicher Sicht sollte die Dimensionierung möglichst knapp ausfallen. Eine Lösung für diese konkurrierenden Anforderungen stellt dabei die für die vorliegende Kundensituation passende Cachingstrategie dar, weil dadurch existierende Ergebnisse (insbesondere bereits gerenderte Seiten) mit geringem Ressourcenverbrauch bei erneuter Anfrage dem Viewer schnell zur Verfügung gestellt werden können.

Typischer Ressourcenverbrauch

Das jadice® web toolkit besitzt zum einen einen serverseitigen Teil, der das anzuzeigende Dokument aus dem angebundenen Archivsystem abholt und anschließend für jede betrachtete Seite ein Rendering durchführt. Zum anderen besteht die Anwendung aus einer Vielzahl von browserbasierten Clients, über die die tatsächliche Anzeige für die Endanwender erfolgt. Da diese beiden Teile räumlich voneiander getrennt existieren, muss sowohl auf Server- als auch auf Clientseite dimensioniert werden.

Auf Clientseite kommt jeweils ein Browser als Standardanwendung zum Einsatz. Die Anforderungen an die CPU-Leistung fallen dabei typischerweise nicht ins Gewicht. Der Browser lädt die anzuzeigenden Seiten als Bilddateien im PNG-Format vom Serverteil und stellt diese dann dar. Dieses Vorgehen unterscheidet sich nicht von der Bildanzeige einer gewöhnlichen Website. Die heruntergeladenen Bilder können dabei vom Browser in seinem Standardcache gespeichert werden. Falls der Benutzer innerhalb des selben, geöffneten Dokuments scrollt und dabei auf eine bereits besuchte Seite zurückkehrt, wird diese nicht mehr vom Server heruntergeladen, sondern es wird die zugehörige Bilddatei aus dem Cache angezeigt. Damit erhöht sich die Performance und es findet eine Entlastung des Serverteils statt. Die Einstellungen für Göße und Verhalten des Browsercaches finden sich in der Dokumentation des jeweiligen Browserherstellers. Da die Anforderungen an die Größe des Caches stark von der Struktur der anzuzeigenden Dokumente und dem Nutzerverhalten abhängt, kann es keine generelle Empfehlung dafür geben. Eine passende Größe könnte so gewählt sein, dass beim Scrollen innerhalb eines Dokuments keine unnötigen Requests an den Serverteil ausgelöst werden.

Auf Serverseite kommt ein Servletcontainer oder Application Server zum Einsatz. Die Anforderungen an die CPU-Leistung werden dort zum überwiegenden Teil durch das Rendering bestimmt. Dabei wird der eine Seite repräsentierende Datenbereich einer Quelldatei (z.B. PDF, TIFF, AFP, etc.) in eine PNG-Datei gewandelt und dem Client zum Download zur Verfügung gestellt. Da jede von einem Client angezeigte Seite serverseitig gerendert wird, konzentrieren sich die zugehörigen Renderrequests an dieser Stelle. Um ein zügiges Abarbeiten der eintreffenden Renderanfragen zu gewährleisten, sollten serverseitig ausreichend viele, leistungsstarke CPUs vorgesehen werden. Da die Anforderungen an die Rechenkapazität stark von der Struktur der anzuzeigenden Dokumente und dem Nutzerverhalten abhängt, kann es keine generelle Empehlung dafür geben. Die empfohlene Methode für die Ermittlung, ob die serverseitige Rechenkapizät ausreicht, ist ein repräsentativer Performancetest.

Jedes in einem Client anzuzeigende Dokument wird im ersten Schritt serverseitig geladen. Dabei wird das Dokument aus dem angebundenen Archivsystem entnommen und der zugehörige Stream komplett eingelesen. Beim Einlesen entstehen Strukturinformationen, wie z.B. die Anzahl der Seiten des Dokuments, etwaige Annotationen und deren Position innerhalb des Dokuments und viele mehr. Diese Informationen werden über einen anwendungsspezifischen, serverseitigen Cache im Arbeitsspeicher abgelegt. Zusätzlich werden dort auch diverse Zwischenergebnisse im Rahmen eines späteren Renderings abgelegt. Um eine gute Performance der Anwendung zu gewährleisten muss serverseitig ausreichend Arbeitsspeicher für diesen Cache vorgesehen werden. Da die Anforderungen an die Größe des Caches stark von der Struktur der anzuzeigenden Dokumente und dem Nutzerverhalten abhängt, kann es keine generelle Empehlung dafür geben. Die empfohlene Methode für die Ermittlung, ob die Kapazität des serverseitigen Arbeitsspeichers ausreicht, ist ein repräsentativer Performancetest.

Serverseitiges Caching

Das Rendering von Dokumenten erfolgt im jadice® web toolkit auf Serverseite. Die gerenderten Seiteninhalte eines Dokuments werden dabei in Form sogenannter Tiles (Kacheln) in Form von PNG- oder WebP-Bilddateien an den Browser zur Darstellung übermittelt. Bedingt durch die hohe Rechenintensität des Renderingprozesses werden erzeugte Tiles in einem serverseitigen Cache abgelegt. Soll eine bereits gerenderte Seite oder ein Thumbnail z.B. wegen Hin- und Her-scrollen des Anwenders erneut zur Anzeige gebracht werden, wird dies serverseitg erkannt und das bereits existierende Tile aus dem Cache entnommen. Dadurch reduzieren sich Rechenzeit und Prozessorauslastung und Seiteninhalte werden auf dem Client schneller angezeigt.

Konfiguration

Die Konfiguration des serverseitigen Caches erfolgt programmatisch. Die Eigenschaften der standardmäßig verwendeten Cache-Implementierung hingegen werden über die Datei "Jadice.properties" eingestellt. Für gewöhnlich sind hier keine Anpassungen notwendig.

Programmatische Konfiguration

Standardmäßig ist ein passender Cache voreingestellt und das Caching von Tiles aktiviert.

  • Eigene Cache-Implementierung setzen

    Die API ermöglicht das Setzen eines alternativen Caches aus den levigo utils oder sogar das Setzen einer externen eigenen Cache-Implementierung. Letzteres ist z.B. dann nützlich, falls ein Application Server oder eine vergleichbare Komponente bereits besteht, die einen eigenen Cache mitbringt.

    Zur Nutzung des Fremd-Caches ist lediglich ein Adapter zu erstellen, der das Interface com.levigo.util.mm.Cache implementiert, und dem jadice® web toolkit wie folgt zu übergeben:

    com.levigo.util.mm.CacheManager.setDefault(myCacheImplementation)
    							
  • Tile-Caching deaktivieren

    Das serverseitige Caching von Tiles lässt sich ganz einfach abschalten; Dokumentstrukturinformationen und andere Aspekte werden allerdings weiterhin gecached:

    ConfigurationManager.getServerSettings().setTileCachingEnabled(false)
    							
Konfiguration über die "Jadice.properties"

Die Konfigurationsdatei Jadice.properties der jadice® document platform 5 wird als Teil der Bibliothek jadice-document-VERSION.jar ausgeliefert. Sie kann dort extrahiert und bei Bedarf so angepasst werden, dass sich der Cache anders verhält.

  • Maximalzahl von Cacheeinträgen: maxNumberOfCacheEntries

    Mögliche Werte: Integer. Default: 30000.

    Beispiel: Konfiguration der maximalen Anzahl an Objekten auf 30000

    jadice.viewer.cache.maxNumberOfCacheEntries=30000
    

    Die Größe des Caches kann unter anderem über die maximale Anzahl an Elementen, die enthalten sein dürfen, begrenzt werden. Wird diese Grenze überschritten, werden so lange Elemente aus dem Cache entfernt, bis er sich wieder unterhalb der gegebenen Obergrenze befindet.

  • Maximalgröße des Caches in Bytes (HighwaterMark): sizeHighwaterMark

    Mögliche Werte: Integer (in Bytes).

    Beispiel: Konfiguration von maximal 512MB (512*1024*1024=536870912 Byte) Cache-Speicher

    jadice.viewer.cache.sizeHighwaterMark=536870912
    

    Neben der maximalen Anzahl von Elementen kann die Größe des Caches auch (zusätzlich oder ausschließlich) über seinen maximalen Speicherverbrauch definiert werden. Da die Größe in Bytes eine angenäherte Größe ist, kann man hier keine Exaktheit erwarten. Wird sie überschritten, werden so lange Elemente aus dem Cache entfernt, bis man wieder unterhalb dieser Grenze ist. Es ist auch möglich, eine dynamische Grenze für den Speicherverbrauch zu setzen, siehe sizeHighwaterMarkPercent.

  • Maximalgröße des Cache in Bytes in Abhängigkeit des maximalen Heapspaces: sizeHighwaterMarkPercent

    Mögliche Werte: Integer (in Prozent). Default: 10.

    Beispiel: Konfiguration von maximal 25% des maximalen Heapspaces als Cache-Speicher

    jadice.viewer.cache.sizeHighwaterMarkPercent=25
    

    Ist keine sizeHighwaterMark gesetzt, so wird die Maximalgröße des Cache-Speichers anteilig zur Maximalgröße des Heapspaces ermittelt. Der in sizeHighwaterMarkPercent angegebene Wert gibt folglich an, wie viel Prozent des Heapspace dem Cache zustehen sollen. Ist dieser Wert fehlerhaft oder nicht angegeben, wird ein Default von 10% angenommen.

  • Minimale Dauer, wie lange ein Eintrag gehalten werden soll: minimumExpiryAge

    Mögliche Werte: Integer (in Millisekunden).

    Beispiel: Konfiguration des Mindestalters auf 1 Sekunde (1000 Millisekunden)

    jadice.viewer.cache.minimumExpiryAge=1000
    

    Dieser Wert legt fest, wie lange Elemente mindestens im Cache verweilen, bevor sie beim Aufräumen entfernt werden.

  • Prozentualer Anteil an Elementen, die beim Aufräumen verworfen werden sollen: maximumExpiryObjectCountRatio

    Mögliche Werte: Integer (in Prozent).

    Beispiel: Konfiguration des Anteils von Elementen, die jeweils entfernt werden sollen, auf 10%

    jadice.viewer.cache.maximumExpiryObjectCountRatio=10
    

    Dieser Wert gibt an, wie viel Prozent der aktuellen Elemente bei jedem Aufräumvorgang zwingend entfernt werden sollen, um Platz für neue Elemente zu schaffen.

Zusätzliche Konfigurationen in den Jadice.properties

Zusätzlich zu den Einstellmöglichkeiten der jadice® document platform 5 bietet das jadice® web toolkit einige weitere Konfigurationsmöglichkeiten in den jadice.properties. Eine Beispielkonfiguration ist in der Enterprise Demo enthalten.

  • Aufräumen von überalterten Cacheeinträgen: maxAgeExpiryEnabled

    Mögliche Werte: true/false. Default: true.

    Beispiel: Cacheeinträge sollen nicht automatisch nach Erreichen des Maximalalters entfernt werden.

    jadice.viewer.cache.maxAgeExpiryEnabled=false
    

    Diese Einstellung legt fest, ob Cacheeinträge, die das in maxAge definierte Maximalalter überschreiten, aus dem Cache entfernt werden sollen.

  • Maximalalter für Cacheeinträge: maxAge und maxAge.timeUnits

    Mögliche Werte: Integer bzw. String. Default: 60 MINUTES.

    Beispiel: Cacheeinträge sollen maximal 120 Minuten im Cache verbleiben dürfen.

    jadice.viewer.cache.maxAge=120
    jadice.viewer.cache.maxAge.timeUnit=MINUTES
    
    

    Diese Einstellung legt fest, dass Cacheeinträge nach ihrer letzten Verwendung maximal 120 Minuten im Cache verbleiben dürfen. Die Werte für timeUnit müssen den Enum-Werten von java.util.concurrent.TimeUnit entsprechen.

Übertragung gerenderter Kacheln

Architektur

Beim Blättern innerhalb eines Dokuments müssen im Browser die Dokument-Seiten zur Anzeige gebracht werden. Der Browser setzt dabei mehrere rechteckige Kacheln zu einer Seite zusammen und stellt diese dar. Jede Kachel wurde zuvor vom Server gerendert und entspricht einem Bild im PNG-Format.

Der jadice® web toolkit Server stellt die Kacheln über ein Servlet zum Download bereit. Jede Kachel wird vom Browser via HTTP-Download entgegen genommen. Da der Browser die Kacheln wie alle Bilddateien behandelt, werden die Kacheln vom Browser gemäß Browser-Einstellung gecacht. Einstiegspunkte für ein etwaiges Feintuning der Browser-Caches werden in einem gesonderten Abschnitt weiter unten behandelt.

Feintuning der Browser-Caches

Um das Verhalten des Browser-Caches zu steuern, werden entsprechende Instruktionen serverseitig im Header der HTTP Response platziert. Die Klasse TileCacheFilter wird dafür in den Demos verwendet und kann als Vorlage für eine kundenspezifische Browser-Cache-Konfiguration dienen.

Im Allgemeinen sind die Voreinstellungen des Browser-Caches sinnvoll und müssen kundenseitig nicht angepasst werden. Es ist darauf zu achten, dass die Cache-Verzeichnisse nur für den aktuellen Benutzer zugänglich sind; dies ist in der Regel durch die Default-Einstellungen der Browser gegeben.

Die folgende Liste liefert Einstiegspunkte für weitere browserspezifische Optimierungen. Enthalten sind insbesondere Anleitungen zur Visualisierung der jeweiligen browserspezifischen Caches.

  • Internet Explorer 11 legt die Dateien versteckt ab. Eine Anzeige ist via http://www.nirsoft.net/utils/ie_cache_viewer.html möglich. Die Cache-Standardgröße beträgt derzeit 250 MB. Die Cache-Größe kann via Internet Optionen -> Allgemein -> Browserverlauf -> Einstellungen konfiguriert werden.

  • Chrome legt die Dateien unter [C:]\Users\[user-id]\AppData\Local\Google\Chrome\User Data\Default\Cache ab. Die Cache-Standardgröße beträgt derzeit 320 MB. Der Inhalt des Caches kann zur Laufzeit via chrome://net-internals/#httpCache angezeigt werden.

  • Firefox legt die Dateien unter [C:]\Users\[user-id]\AppData\Local\Mozilla\Firefox\Profiles\[profile-id]\cache2\entries ab. Die Cache-Standardgröße beträgt aktuell 350 MB. Die Cache-Größe kann via about:config -> browser.cache.disk.capacity konfiguriert werden. Über die Firefox-Extension CacheViewer kann der Cache-Inhalt tabellarisch visualisiert werden.

  • Edge legt die Dateien unter [C:]\Users\[user-id]\AppData\Local\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\#!001\MicrosoftEdge\Cache\FZ1ABPID ab. Es scheint kein Limit für die Cache-Standardgröße zu geben.

Preloading von Seiten

Um eine flüssige Seitenanzeige auch für unbesuchte Seiten gewährleisten zu können, bietet das jadice® web toolkit die Möglichkeit, Seiten schon vor deren Darstellung zu laden. Navigiert der Benutzer im Anschluss auf eine solche Seite, kann diese direkt aus dem Browser-Cache entnommen und zur Anzeige gebracht werden. Damit entfällt die Latenz durch Netzwerk und Rendering.

Um den Einsatz in möglichst allen Kundenszenarien zu ermöglichen, kann das Preloading-Verhalten flexibel und einfach an der PageView über die Klasse PreloadingPageRange konfiguriert werden. Ausgehend von den aktuell sichtbaren Seiten innerhalb des Viewers wird jeweils ein Seiten-Intervall für den vorangehenden und den nachfolgenden Bereich definiert (Notation: [-n;+m]). Beispielsweise können mit der Konfiguration [-3;+5] die drei vorangehenden und die 5 nachfolgenden Seiten ausgehend von den jeweils sichtbaren Seiten vorgeladen werden. Da zusätzlich auch die erste und die letzte Seite des Dokuments als Intervallgrenze konfigurierbar ist, kann das Preloading-Verhalten an beliebige Einsatzszenarien angepasst werden.

Eine passende Konfiguration des Preloadings hängt in starkem Maße von der Konfiguration des clientseitigen Caches ab. Ist der clientseitige Cache beispielsweise auf 20 Kacheln begrenzt, führt ein Seiten-Intervall von [-50;+50] zum überflüssigen Spülen. Außerdem beeinflusst die Konfiguration auch die anfallende Rechenlast auf Serverseite, da jede vorgeladene Seite dort zunächst gerendert werden muss. Bei einer großen Anzahl parallel arbeitender Benutzer kann der zusätzliche Renderingaufwand signifikant werden. Aus diesem Grund kann es sinnvoll sein, das Feature zunächst eher defensiv zu konfigurieren und die Intervalle iterativ zu vergrößern.

Beispiel: Konfiguration des Preloadings von Seiten


...

final Viewer viewer = new ViewerBuilder().build();
final PageView pageView = viewer.getPageView();

// preload the previous 2 pages and all pages till the end of the document relative to the currently visible pages
// pageView.setPreloadingPageRange(new PreloadingPageRange(2, true));

// preload the previous 2 pages and the next 3 pages relative to the currently visible pages
pageView.setPreloadingPageRange(new PreloadingPageRange(2, 3));

...


				

Clientseitiges Caching von Text

Der Text von Dokumenten wird unter anderem für die Markierungsfunktion clientseitig gespeichert. Das zugehörige Cachingverhalten kann über die PageView konfiguriert werden. Dabei wird festgelegt, ob die Textbestandteile nur für die aktuell sichtbaren bzw. vorgeladenen Seiten gespeichert werden, oder für das gesamte Dokument.

Beispiel: Konfiguration des clientseitigen Cachings für Text


...

final Viewer viewer = new ViewerBuilder().build();
final PageView pageView = viewer.getPageView();

// default - cache text whenever the mouse is hovered over a page and purge cache only when the document is closed
// pageView.setTextPurgeStrategy(PageView.TextPurgeStrategy.PURGE_ON_DOC_CLOSE);

// cache text only for visible and preloaded pages, remove obsolete text on each page change   
pageView.setTextPurgeStrategy(PageView.TextPurgeStrategy.PURGE_ON_PAGE_CHANGE);

...


				

Konfiguration

Server-Konfiguration

Der Server kann über die ServerConfiguration konfiguriert werden. Zugriff darauf erhält man über ConfigurationManager.getServerConfiguration(). Mittels setServerConfiguration(ServerConfiguration serverConfiguration) kann eine eigene ServerConfiguration gesetzt werden, sofern ein eigenes Konfigurationsframework eingebunden werden soll. In der Regel ist dies aber nicht notwendig, da die Einstellungen auch direkt auf der bestehenden ServerConfiguration geändert werden können. Standardmäßig wird eine DefaultServerConfiguration zurückgegeben.

  • Tile Caching: Über die Methoden setTileCachingEnabled(boolean tileCachingEnabled)/isTileCachingEnabled() wird definiert, ob die auf dem Server gerenderten Kacheln dort gecached werden sollen. In der DefaultServerConfiguration ist dieser Wert standardmäßig auf true gesetzt.

  • Kompressionslevel des Tile Renderings bei Http-Requests: Über die Methoden setTileCompressionType(TileCompressionType tileCompressionType)/getTileCompressionType() wird definiert, wie der erzeugte PNG- oder WebP-Datenstrom beim TileRendering in den OutputStream für den Client geschrieben werden soll. Hierbei wird unterschieden zwischen ImageIO ("Best Compression", Default bis jadice® 5.11.0.0), PNGJ in den Ausprägungen "Best Compression" und "Best Speed" und WebP in den Ausprägungen "Lossy" (Default) und "Lossless". Wenn WebP verwendet wird, prüft der Server bei einem Tile-Request, ob der Client WebP anzeigen kann. Ist das nicht der Fall, wird automatisch PNG über ImagIO (höchste Kompatibilität) zurückgeliefert. Die Werte sind über die Enum ServerConfiguration.TileCompressionType definiert.

  • Eine Einstellung, die vom Integrator an der Server-Konfiguration vorbei vorgenommen werden kann, ist die ScreenResolution. Diese wird über die Methode GraphicsEnvironment.setUserDefinedScreenResolution(int resolution) direkt auf dem GraphicsEnvironment der jadice® document platform vorgenommen. Der aktuell eingestellte Wert kann also serverseitig zu jeder Zeit wie beschrieben gesetzt und über GraphicsEnvironment.getScreenResolution() abgefragt werden. Hintergrund dieser Konfiguration ist, dass an einigen Stellen in der jadice® document platform die Auflösung über das GraphicEnvironment und nicht über die BaseRenderSettings abgefragt wird. Auf dem lokalen Rechner gibt dies die Auflösung des Rechners (bei Windows in der Regel 96) zurück. Auf Servern wird in der Regel auf Grund einer HeadlessException jedoch der Wert 72 verwendet. Um dieses Verhalten zu vereinheitlichen, wird der Wert im jadice® web toolkit auf 96 voreingestellt (davon ausgehend, dass die meisten Clients Windows PCs mit genau dieser Auflösung sind).

  • Über ConfigurationManager.getServerConfiguration().getNetworkConfiguration() können Details zur NetworkConfiguration eingestellt werden. Die verschiedenen Konfigurationsparameter sind unter „Serverseitig“ nachzulesen.

  • Thread Pools: jadice® web toolkit verwendet im wesentlichen zwei Thread Pools für Aufgaben wie beispielsweise das asynchrone Rendern von Tiles. Die Größe der Thread Pools richtet sich nach der Anzahl der CPUs im System. Um die Größe der Pools anzupassen, stehen über die ServerConfiguration die Methoden setGeneralPoolCoreSize()/setGeneralPoolMaxSize() und setTileRendererPoolCoreSize()/setTileRendererPoolMaxSize() bereit. Achtung: Eine falsche Konfiguration dieser Werte kann negative Auswirkungen auf das Gesamtsystem haben. Weiterführende Informationen finden Sie in dem Knowledge-Base-Artikel Thread Pools and Concurrency.

Client-Konfiguration

Analog zum Server wird der Client über die ClientConfiguration konfiguriert. Auf die ClientConfiguration kann über ClientConfigurationManager.getClientConfiguration() zugegriffen werden. Über die Methode ClientConfigurationManager.setClientConfiguration(ClientConfiguration clientConfiguration) kann eine eigene Implementierung einer ClientConfiguration gesetzt werden. Dies ist aber in der Regel nicht notwendig, da die Einstellungen auch direkt auf der bestehenden ClientConfiguration geändert werden können. Standardmäßig wird eine DefaultClientConfiguration zurückgegeben.

  • Clientseitige Verwendung von IconFonts: setIsIconFontUsed(boolean). Siehe hierzu „Eigene Icons in Buttons“

  • Anzahl der parallelen Renderanfragen: Bei der Kachelübertragung muss die Anzahl der parallelen Serverrequests begrenzt werden, ansonsten könnte ein einziger Client den Server mit Anfragen überlasten. In der DefaultClientConfiguration wird die Anzahl der parallelen Tileanfragen auf 6 begrenzt. Dieser Wert kann über die Methode setMaxParallelTileRequests(int maxRequestCount) angepasst werden.

  • Darstellungsqualität: Zur Unterstützung hochauflösender Displays, aber auch zur Vermeidung von Unschärfe der Kacheln bei erhöhtem Browserzoom, wird die Device Pixel Ratio des Clients beim Kachelrendering mit einbezogen. Da dies bei hochauflösenden Bildschirmen zu lange Wartezeiten führen kann (da die Kacheln je nach Display dann 2 - 3 mal so groß gerendert werden müssen, wie sie angezeigt werden), kann über die ClientConfiguration eine Quality angegeben werden. Dies ist ein prozentualer Wert zwischen 0 und 100, der angibt, zu wieviel Prozent die Device Pixel Ratio miteinberechnet werden soll. Somit kann ein Kompromiss aus Renderzeit und Kachelschärfe erreicht werden. Per Default ist die Quality 0. Somit wird ohne weitere Konfiguration die Device Pixel Ratio nicht mit einbezogen. Der Wert kann über setQualityEnhancement(int qualityInPercent) gesetzt werden.

  • Soll der minimale bzw. maximale Zoomfaktor begrenzt werden, so geht dies über die Methoden setMinZoomFactor(float minZoom) bzw. setMaxZoomFactor(float maxZoom). Zum Abfragen der Werte werden die Methoden getMinZoomFactor() bzw. getMaxZoomFactor() bereitgestellt. Standardmäßig werden keine Zoombegrenzungen gesetzt.

  • Fokusverhalten des Browsers beim Annotations-Texteditor: Über setFocusTextEditor(boolean) kann das Fokusverhalten des Browsers in Bezug auf den Text Editor von textbasierten Annotationen gesteuert werden. Bei true wird das scrollverhalten des Browsers beim Fokussieren auf die eigenen Scrollbalken übernommen. Bei false wird das Scrollverhalten des Browsers ignoriert und zurückgesetzt. Für Internet Explorer muss true verwendet werden.

Logging

Serverseitiges Logging

Serverseitig wird im jadice® web toolkit das Logging der jadice® document platform 5 eingesetzt, das das Anbinden unterschiedlicher Frameworks erlaubt.

Eine ausführliche Beschreibung kann der Dokumentation der jadice® document platform 5 entnommen werden.

Clientseitiges Logging

Auf der Clientseite wird das Logging auf das von GWT unterstützte java.util.logging umgeleitet. Die Logging-Konfiguration kann in den jeweiligen .gwt.xml Dateien der Module modifiziert werden:

  • Loglevel setzen: <set-property name="gwt.logging.logLevel" value="SEVERE"/>. Hier können die Loglevel von java.util.logging verwendet werden, die das Webtoolkit unterstützt: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE Als Default ist im jadice® web toolkit WARNING gesetzt.

  • Console- und RemoteHandler aktivieren: <set-property name="gwt.logging.consoleHandler" value="ENABLED"/>. consoleHandler und simpleRemoteHandler können über das Attribut ENABLED bzw. DISABLED aktiviert bzw. deaktiviert werden. Per Default ist im jadice® web toolkit der consoleHandler aktiviert und der simpleRemoteHandler deaktiviert.

  • Falls GWT Logging bereits genutzt wird, kann integrationsseitig das im JWT voreingestellte Loglevel durch folgende Reihenfolge in der .gwt.xml Datei überschrieben werden: <inherits name="com.levigo.jadice.web.JadiceWebtoolkit" /> <inherits name="com.google.gwt.logging.Logging" /> <set-property name="gwt.logging.logLevel" value="FINE" />

In der gewünschten Klasse kann nun über Logger logger = LoggerFactory.getLogger(classname.class) ein Logger instanziiert werden. Dieser übernimmt die Konfigurationen der zugehörigen .gwt.xml Datei. Über verschiedene Methoden können dann Messages auf verschiedenen Levels geloggt werden:

Methode java.util.logging Level
logger.trace(String message, Throwable t) FINEST
logger.debug(message, t) CONFIG
logger.info(message, t) INFO
logger.warn(message, t) WARNING
logger.error(message, t) SEVERE

Der Parameter t ist dabei optional.

Weitere Informationen können unter GWT Logging gefunden werden.

Servlet 3.1 Support

Das jadice® web toolkit benötigt serverseitig diverse Servlet- und Filter-Instanzen, die bislang in den Deploymentdeskriptor web.xml der Webapplikation eingetragen werden mussten. Das jadice® web toolkit unterstützt den Servlet 3.1 Standard. Dieser ist Bestandteil der Java EE Version 6, die ingesamt von dem Bestreben gekennzeichnet ist, dem Entwickler das Leben dadurch zu vereinfachen, dass die Notwendigkeit von XML-basierten Konfigurationsdateien auf ein Minimum reduziert wird. Dementsprechend ist vorgesehen, dass Servlet-, Filter- und sonstige im Servletcontainer lebende Objekte durch standardisierte Java-Annotationen (@WebServlet, @WebFilter, etc.) registriert werden können, wobei die Möglichkeit, dies durch einen Eintrag im XML-Deskriptor zu erreichen, weiterhin besteht.

Nachfolgende Tabelle zeigt, welche URL-Patterns in den @WebServlet bzw. @WebFilter Annotationen hinterlegt sind.

Klasse Servlet-/Filtername URL-Pattern
com.levigo.jadice.web.server.FontDownloadServlet jwtFontDownloadServlet /jwt/font/*
com.levigo.jadice.web.conn.server.TileServlet jwtTileDownloadServlet /jwt/tile/*
com.levigo.jadice.web.conn.server.AnnotationImageServlet jwtAnnotationImageDownloadServlet /jwt/resource/*
com.levigo.jadice.web.transport.server.TransportWebtoolkitFilter jwtTransportFilter /jwt/transport/*
com.levigo.jadice.web.server.filter.CacheFilter jwtCacheFilter /*
com.levigo.jadice.web.server.filter.NoCacheFilter jwtNoCacheFilter /*
com.levigo.jadice.web.server.filter.TileCacheFilter jwtTileCacheFilter /*

Asynchrone Kommunikation bei Servlets und Filtern

Einige der Servlets arbeiten asynchron (z.B. das TileServlet sowie die Servlets für Longpoll- und Server-Sent-Event-Kommunikation). Dies hat zur Folge, dass alle Filter, die eine Anfrage an diese Servlets durchläuft, ebenfalls asynchron arbeiten müssen. Für die im jadice® web toolkit enthaltenen Filter ist dies bereits gesetzt; die integrationsspezifischen Filter hingegen müssen entsprechend konfiguriert werden. Die Konfiguration kann gemäß Servlet 3 Standard über Annotationen (asyncSupported = true), über die web.xml oder programmatisch erfolgen. Falls eine web.xml die im JWT enthaltenen annotationsbasierten Servlet- und Filterkonfigurationen überschreibt, ist auch dort an den entsprechenden Stellen asynchroner Support zu konfigurieren.

Wird einer der Filter nicht benötigt, kann er mit dem NoOpFilter überschrieben und damit außer Kraft gesetzt werden. Ist beispielsweise der jwtCacheFilter unerwünscht, so kann dieser mit folgendem Eintrag im Deploymentdeskriptor web.xml deaktiviert werden:

			
<filter>
	<filter-name>jwtCacheFilter</filter-name>
	<filter-class>com.levigo.jadice.web.server.filter.NoOpFilter</filter-class>
</filter>
			
			

Der CacheFilter selbst erlaubt übrigens das Überschreiben der Default-Ablaufzeit von 180 Tagen über die Property cache-expiry-seconds (hier auf 30 Tage reduziert):

			
<filter>
	<filter-name>jwtCacheFilter</filter-name>
	<filter-class>com.levigo.jadice.web.server.filter.CacheFilter</filter-class>
	<init-param>
		<param-name>cache-expiry-seconds</param-name>
		<param-value>2592000</param-value>
	</init-param>    
</filter>
			
			

GWT Development Mode: Workaround

Leider funktioniert im GWT Development Mode (nachfolgend als DevMode bezeichnet) die automatische Registrierung von annotierten Servlet- bzw. Filterklassen nicht. Um dennoch den DevMode nutzen zu können, bringt das jadice® web toolkit einen Workaround in Form der Klasse DevModeStartupListener mit. Dieser Listener sorgt für die programmatische Registrierung der annotierten Servlets und Filter, wenn diese nicht automatisch vom Servletcontainer erkannt werden. Damit das funktioniert, muss er natürlich in den Deploymentdeskriptor web.xml eingetragen werden:

			
<listener>
	<listener-class>com.levigo.jadice.web.server.DevModeStartupListener</listener-class>
</listener>
			
				

Java EE 7/Spring Framework Integration

Heutzutage werden in Java implementierte Unternehmensanwendungen normalerweise entweder auf Basis der Java Enterprise Edition (Java EE) oder des populären Spring Framework entwickelt. Für den Betrieb einer Java-EE-Applikation wird ein vollwertiger Java-EE-konformer Applikationsserver wie WildFly oder WebSphere benötigt. Das Spring Framework kommt dagegen oft zum Einsatz, wenn die Applikation auf einem reinen Servletcontainer wie Tomcat laufen soll. In den letzten Jahren ist zudem das Architekturmuster der Microservices populär geworden, bei dem oft ganz auf einen schwergewichtigen Applikationsserver/Servletcontainer verzichtet wird. Stattdessen wird der Servletcontainer in die Applikation eingebettet, wodurch ein zeitraubender Deploymentschritt dann entfallen kann. Spring Boot stellt eine Erweiterung des Spring Framework dar, die genau dies ermöglicht. Eine typische Spring-Boot-Applikation wird dementsprechend nicht deployt sondern wie eine Java-SE-Applikation gestartet.

Integratoren, die das jadice® web toolkit in eine Java-EE-, Spring- oder Spring-Boot-Applikation integrieren möchten, können eines der beiden ausgelieferten Integrationsmodule nutzen, um Folgendes zu erreichen:

  • Integriertes Threadmanagement

    Ein Java-EE-konformer Applikationsserver sorgt dafür, dass Komponenten, die die Geschäftslogik einer Anwendung implementieren (das sind typischerweise EJBs und CDI-Beans) zur Laufzeit mit verschiedenen technischen Kontexten assoziiert werden, die für technische Aspekte wie Berechtigungskontrolle oder Transaktionssteuerung benötigt werden. Damit diese Dinge funktionieren, ist ein ausgeklügeltes Threadmanagement auf Seiten des Applikationsservers erforderlich, das jedoch mit einer Einschränkung einhergeht: EJBs und CDI-Beans dürfen i.d.R. selbst keine Threads starten, weil dies mit dem Threadmanagement des Applikationsserver unverträglich wäre. Um einer Anwendung dennoch die asynchrone Ausführung von Geschäftslogik zu ermöglichen, wurden im Rahmen von Java EE 7 die Concurrency Utilities for Java EE (JSR 236) auf den Weg gebracht. Die jadice® web toolkit Version 5.5 kann hiervon nun Gebrauch machen, um das asynchrone Laden von Dokumenten (siehe „Dokumente laden“) und die asynchrone Ausführung von Server Operations (siehe „Server Operations “) mit dem Security-Kontext des angemeldeten Benutzers zu assoziieren.

  • Nutzung von Dependency Injection

    Sowohl Java EE 7 als auch das Spring Framework basieren auf einem objektorientierten Entwurfsmuster, das als Dependency Injection bekannt geworden ist. Hierbei geht es darum, Objekte möglichst lose miteinander zu koppeln. Dies wird dadurch erreicht, dass ein Objekt andere Objekte, deren Hilfe es für seine Aufgabe benötigt, von außen injiziert bekommt, anstatt diese selbst zu erzeugen oder sich auf andere Art und Weise eigenverantwortlich zu beschaffen. Als Teil des Java-EE-Programmiermodells ist Dependency Injection durch die CDI-Spezifikation standardisiert worden. Das Spring Framework implementiert dagegen eine proprietäre Form von Dependency Injection.

Java-EE-Integrationsmodul

Die Nutzung das Java-EE-Integrationsmoduls ist denkbar einfach: Es muss lediglich in den Klassenpfad der Applikation gelegt werden - beim Deployment also typischerweise in den Ordner WEB-INF/lib der WAR-Datei kopiert werden. Die Aktivierung erfolgt automatisch, sobald die Applikation auf einem Java-EE-7-konformen Applikationsserver deployt wird.

Threadmanagement

Wenn das EE-Modul aktiv ist, nutzt es einen sog. ManagedExecutorService für das asynchrone Laden von Dokumenten bzw. die Ausführung von Server Operations. Normalerweise wird hierfür der DefaultManagedExecutorService (JNDI-Name: java:comp/DefaultManagedExecutorService) des Applikationsservers verwendet. Es ist aber auch möglich, einen alternativen ManagedExecutorService in den WebtoolkitServerContext injizieren zu lassen. Ein Integrator, der dies tun möchte, muss dazu lediglich eine eigene CDI-Bean beisteuern, die das Interface ManagedExecutorServiceProvider implementiert.

@ApplicationScoped
public class MyManagedExecutorServiceProviderImpl implements ManagedExecutorServiceProvider {

  @Resource(name = "customManagedExecutorService")
  ManagedExecutorService managedExecutorService;

  @Override
  public ManagedExecutorService getManagedExecutorService() {
    return managedExecutorService;
  }
}
			

Die CDI-Bean muss entweder mit @ApplicationScoped oder @Dependent annotiert werden (die Wirkung ist in beiden Fällen die gleiche). Die Defaultimplementierung DefaultManagedExecutorServiceProviderImpl wird hierdurch automatisch überschrieben. Zu beachten ist, dass es nur genau eine alternative CDI-Bean im Klassenpfad der Anwendung geben darf.

Automatische Registrierung von DocumentDataProvidern per CDI

Mit Hilfe des Java-EE-Integrationsmoduls ist es möglich, DocumentDataProvider-Implementierungen mittels CDI automatisch registrieren zu lassen. Hierzu muss die entsprechende Klasse lediglich mit @ApplicationScoped oder @Dependent annotiert werden:

@ApplicationScoped
public class MyDocumentDataProvider implements DocumentDataProvider<MySource, MyPageSegmentHandle> {
   ...
}
			

Spring Boot-Integrationsmodul

Das jadice® web toolkit folgt dem Spring-Boot-Starter-Konzept, so dass das Aufsetzen einer Spring-Boot-Anwendung mit dem jadice® web toolkit sich sehr einfach gestaltet. Der webtoolkit-spring-boot-starter als Starter für das jadice® web toolkit ist Bestandteil der Auslieferung und kann als Maven-Dependency eingebunden werden. Er bringt die erforderlichen Dependencies auf die einzubindenden jadice-web-toolkit- und Spring-Boot-Module mit:

<dependency>
   <groupId>com.levigo.jadice.webtoolkit</groupId>
   <artifactId>webtoolkit-spring-boot-starter</artifactId>
</dependency>
			

Als Beispiel hierfür dient die Demoanwendung webtoolkit-spring-boot-simple; ein einführendes Tutorial existiert unter Getting Started - jadice web toolkit mit Spring Boot.

Der Grundgedanke des webtoolkit-spring-boot-starter liegt darin, die Spring-Paradigmen für das jadice® web toolkit zu unterstützen, so dass die erforderlichen Komponenten deklarativ injiziert bzw. konfiguriert werden können. Dies umfasst DocumentDataProvider, ServerOperations, ContextualFactories sowie Annotationsprofile und Konfigurationswerte - wie nachfolgend ausführlich beschrieben. Die programmatische Registrierung dieser Komponenten, die sonst typischerweise über einen WebtoolkitServletContextListener vorgenommen wird, kann dadurch vollständig entfallen.

Threadmanagement

Das Spring-Modul sorgt dafür, dass bei asynchron ausgeführten Operationen der zuständige Thread mit dem Spring-SecurityContext assoziiert wird. Dies passiert allerdings nur, sofern Spring Security im Klassenpfad gefunden wird. Andernfalls wird ein gewöhnlicher ThreadPoolExecutor aufgerufen.

Spring Boot Application

Eine jadice® web toolkit-Spring-Boot-Anwendung wird durch Hinzufügen der Annotationen @SpringBootApplication und @EnableJWTSpringBootApplication(...) definiert:

  • @SpringBootApplication: zur Aktivierung einer Spring Boot Application, entsprechend Using the @SpringBootApplication Annotation

  • @EnableJWTSpringBootApplication(...): Diese Annotation bringt der webtoolkit-spring-boot-starter mit. Sie sorgt im Hintergrund für ein Autowiring von DocumentDataProvidern, ServerOperations und Annotationsprofilen.

@SpringBootApplication
// Activate jadice web toolkit support specifying the GWT module name for the entry point
@EnableJWTSpringBootApplication("com.levigo.jadice.web.demo.springboot.Application")
public class DemoApplication extends SpringBootServletInitializer {
   public static void main(final String[] args) {
     SpringApplication.run(DemoApplication.class, args);
   }
}
			
Automatische Registrierung von DocumentDataProvidern

DocumentDataProvider-Implementierungen lassen sich mittels Dependency Injection automatisch registrieren. Eine programmatische Registrierung an der DocumentDataProviderRegistry (siehe DocumentDataProviderRegistry) ist dann nicht mehr erforderlich. Hierzu ist die entsprechende Klasse lediglich mit der Spring-Annotation @Component zu versehen:

@Component
public class MyDocumentDataProvider implements DocumentDataProvider<MySource, MyPageSegmentHandle> {
   ...
}
			
Automatische Registrierung von ServerOperations

Auch ServerOperations lassen sich als @Component injizieren. Die manuelle Registrierung bei der ServerOperationRegistry im ServletContextListener lt. „Server Operations “ entfällt dann.

@Component
public class SampleServerOperation implements ServerOperation<SampleServerOperationParameters, SampleServerOperationMessage> {
   ...
}
			
Automatische Registrierung von ContextualFactories

Genauso können auch ContextualFactories als @Component injiziert werden, ohne dass diese programmatisch registriert werden müssen:

@Component
public class MyContextualServerOperationFactory implements ContextualFactory<ServerOperation<MyContextualServerOperationParameters, MyContextualServerOperationMessage>> {
    @Override
    public MyContextualServerOperation create(InvocationContext context) {
        ServletInvocationContext servletInvocationContext = (ServletInvocationContext) context;
        return new MyContextualServerOperation(servletInvocationContext.getSession().getId());
    }
}

@Component
public class MyContextualDocumentDataProviderFactory implements ContextualFactory<DocumentDataProvider<ClassPathSource, ClassPathHandle>> {
    @Override
    public DocumentDataProvider<ClassPathSource, ClassPathHandle> create(InvocationContext context) {
        return new ClassPathDocumentDataProvider();
    }
}
			
Konfiguration serverseitiger Einstellungen

In einer Spring-Boot-Umgebung lassen sich Einstellungen des jadice® web toolkit, die für gewöhnlich programmatisch über die ServerConfiguration vorgenommen werden, deklarativ über eine application.yml bzw. application.properties konfigurieren.

Dabei werden die für das jadice® web toolkit spezifischen Einstellungen mit dem Prefix webtoolkit angesprochen und die Werte über entsprechende Selektoren konfiguriert. Die Namen der Selektoren entsprechen dabei den Namen der Setter der programmatischen Konfiguration.

Im folgenden Beispiel wird eine programmatische Konfiguration ihrem deklarativen Pendant gegenübergestellt:

ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setSessionTimeout(Duration.ofSeconds(20));
ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setResponseAggregationWindow(Duration.ofMillis(20));
ConfigurationManager.getServerConfiguration().setTileCompressionType(TileCompressionType.PNGJ_BEST_COMPRESSION);
ConfigurationManager.getServerConfiguration().getNetworkConfiguration().setKeepAliveInterval(Duration.ofSeconds(30));
			

Konfiguration per application.properties:

webtoolkit.tileCompressionType: PNGJ_BEST_COMPRESSION
webtoolkit.networkConfiguration.sessionTimeout: 30s
webtoolkit.networkConfiguration.responseAggregationWindow: 20ms
webtoolkit.networkConfiguration.keepAliveInterval: 30s

Konfiguration per application.yml:

webtoolkit:
  tileCompressionType: PNGJ_BEST_COMPRESSION

  networkConfiguration:
    sessionTimeout: 30s
    responseAggregationWindow: 20ms
    keepAliveInterval: 30s

Ein weiteres Beispiel hierzu findet sich in der Demo webtoolkit-spring-boot-simple.

Deklaration von Annotationsprofilen

Neben den gerade erwähnten Konfigurationswerten lassen sich auch Annotationsprofile über die Spring-Boot-Konfigurationsdateien spezifizieren. Nachfolgendes Beispiel zeigt die Konfiguration zweier Annotationsprofile in einer Application.yml.

#JWT prefix
webtoolkit:
  
  # Specify annotation profiles 
  annotationProfiles: /jwt-minimal-profile.xml, /jwt-second-profile.xml
  
  # Specify default annotation profile (The value must match annotation-profile name in jwt-minimal-profile.xml)
  defaultAnnotationProfile: JWT-Minimal
			

Migration einer bestehenden Anwendung von Java 8 nach 11 (serversetig)

Im öffentlichen Github Repositoy des jadice® web toolkit Tutorials wird auf einem Demo Branch gezeigt, wie statt der jadice® web toolkit Version 5.8.2.0 die Version 5.9.0.0 eingebunden wird und was darüber hinaus an Änderungen vorgenommen werden muss, um die integrierende Anwendung serverseitig Java 11 kompatibel zu machen.

Name des Branches lautet Demo/JWT-3438_demonstrate_migration_java11

[jadice web toolkit Version 5.10.21.0 : Dokumentation für Entwickler. Veröffentlicht: 2021-04-26]