Die Anwendungsszenarien und Konfigurationsmöglichkeiten für den jadice server sind ebenso zahlreich wie vielfältig. Deshalb sollen hier nur die gängigsten Szenarien aufgegriffen werden, die als Ausgangspunkt für eigene Implementierungen dienen können.

In den meisten Fällen sind diese nach dem Schema

aufgebaut. Dies ist der Einfachheit der Beispiele geschuldet. In realen Anwendungen werden die Ergebnisse üblicherweise nicht direkt nach der Verarbeitung zurückgesendet, sondern in kaskadierten Schritten verarbeitet. Auch die Quelle und das Ziel der Datenströme sind nicht zwingenderweise der Client, sondern können beispielsweise ein zentraler Datei-, Mail-, Archivserver o. ä. sein.

Der Job wird zunächst clientseitig erstellt, um ihm anschließend 1…n Nodes anzuhängen.

Konfigurationsmöglichkeiten des Jobs sind:



Die Funktion createServerJob() aus Beispiel 6.1, „Job erstellen (mit ActiveMQ als Messagebroker)“ wird als Basis für die kommenden Beispiel verwendet.

Mit dem JobListener können Zustände des Jobs und serverseitige Fehlermeldungen verarbeitet werden.

Beispiel 6.3. Beispiel einer JobListener-Implementation

public class MyJobListener implements JobListener {
  public void stateChanged(Job job, State oldState, State newState) {
    dump("stateChanged", job, oldState, newState, null, null);
  }

  public void executionFailed(Job job, Node node, String messageId, String reason, Throwable cause) {
    dump("executionFailed", job, node, messageId, reason, cause);
  }

  public void errorOccurred(Job job, Node node, String messageId, String message, Throwable cause) {
    dump("errorOccurred", job, node, messageId, message, cause);
  }

  public void warningOccurred(Job job, Node node, String messageId, String message, Throwable cause) {
    dump("warningOccurred", job, node, messageId, message, cause);
  }

  public void subPipelineCreated(Job job, Node parent, Set<? extends Node> createdNodes) {
    dump("subPipelineCreated", job, parent);
  }

  private void dump(String ctx, Job job, Object... args) {
    System.err.println("Context:   " + ctx);
    System.err.println("Job:       " + job.toString());
    if (args == null)
      return;
    for (Object arg : args)
      System.err.println("           " + arg.toString());
  }
}


jadice server liegen zwei Implementierungen dieses Interfaces bei, die in der Integration eingesetzt werden können:

TraceListener

Leitet Fehlermeldung via Apache Commons Logging an das Client-Log weiter

JobListenerAdapter

Leere Standard-Implementierung des JobListener-Interfaces. Davon abgeleitete Klassen müssen nur die gewünschten Methoden überschreiben

Um den Ressourcen-Verbrauch eines Jobs oder dessen Knoten zu beschränken, ist es sinnvoll, Limits darauf anzuwenden. Folgende Limits sind hierbei möglich:

Tabelle 6.1. Verfügbare Limits

Typ des LimitsBeschreibungKontext
JobNode
TimeLimit

Maximale Verarbeitungszeit

StreamCountLimit

Maximale Anzahl an Streams, die ein Knoten liefert

StreamSizeLimit

Maximale Größe der Streams, die ein Knoten liefert

PageCountLimit

Maximale Anzahl an Seiten eines generierten Dokuments

[a]

NodeCountLimit

Maximale Anzahl an Knoten, die ein Job haben darf. Berücksichtigt auch Knoten, die dynamisch vom Server erzeugt werden.

[a] Für Knoten, die Dokumente generieren, die einen Seiten-Begriff kennen; vgl. javadoc


Tabelle 6.2. Legende zu Tabelle 6.1, „Verfügbare Limits“

wird direkt an dieser Stelle berücksichtigt
wird nicht an dieser Stelle berücksichtigt
wird nicht berücksichtigt, aber an die Knoten vererbt (s.u.)


Bei der Definition eines Limits kann ausgewählt werden, was passieren soll, wenn es überschritten wird:

Beispiel 6.4. Beispielhafte Verwendung von Limits

TimeLimit tl = new TimeLimit(60, TimeUnit.SECONDS);
tl.setExceedAction(WhenExceedAction.ABORT); // default

NodeCountLimit ncl = new NodeCountLimit(20);
ncl.setExceedAction(WhenExceedAction.WARN);


Bei der Limit.WhenExceedAction ABORT bricht der komplette Job ab, bei der Limit.WhenExceedAction WARN wird dem Client eine Warning gemeldet.

Da bei der clientseitigen Workflow-Definition möglicherweise nicht alle Knoten bekannt sind oder es nicht sinnvoll ist, jeden einzelnen Knoten mit Limits zu belegen, können diese auch zentral auf den Job angewendet werden. Diese werden an die einzelnen Knoten vererbt. Dabei gelten folgenden Vererbungsregeln:

  1. Limits mit der Limit.WhenExceedAction WARN werden in jedem Fall vererbt.

  2. Limits mit der Limit.WhenExceedAction ABORT werden nicht an Knoten vererbt, auf die bereits ein Limit der selben Klasse mit der Limit.WhenExceedAction ABORT angewendet wurde, auch wenn dieses weniger restriktiv ist.

  3. Werden Limits der selben Klasse mit Limit.WhenExceedAction ABORT sowohl clientseitig als auch über die Security-Schnittstelle gesetzt, haben die restriktiveren Vorrang. Vgl. Abschnitt „Restriktionen“

Der jadice server bietet mächtige Module zur Erkennung unbekannter Dateiformate. Diese werden in den Modulen zur automatischen Konvertierung unbekannter Dateien bzw. E-Mails eingesetzt (siehe Abschnitte „Konvertierung unbekannter Eingabedaten in ein einheitliches Format (PDF)“ und „Konvertierung von E-Mails nach PDF“).

Darüber hinaus ist es auch möglich, diese Module durch den StreamAnalysisNode anzusprechen und für eigene Zwecke zu verwenden.

Beispiel 6.5. Verwendung des StreamAnalysisNode

Job job = createServerJob();
job.setType("run stream analysis");

// Nodes erstellen:
// 1. Dateneingabe-Node
StreamInputNode siNode = new StreamInputNode();
// 2. Analyseknoten
StreamAnalysisNode saNode = new StreamAnalysisNode();
// 3. Ausgabe-Node
StreamOutputNode soNode = new StreamOutputNode();

// Workflow erstellen
job.attach(siNode.appendSuccessor(saNode).appendSuccessor(soNode));

// Job ausführen und Dokumentdatenstrom senden
job.submit();
siNode.addStream(…);
siNode.complete();

// Auf Antwort vom Server warten //@Callout Die Methode getStreamBundle() blockiert, bis der Server die Abarbeitung beendet hat. Eine asynchrone Verarbeitung ist durch die Verwendung einer JobListener-Implementierung zu realisieren
for (Stream stream : soNode.getStreamBundle()) {
  // Lesen der beschreibenden Daten
  final StreamDescriptor descr = stream.getDescriptor();
  final String mimeType = descr.getMimeType();
}

Der JadiceDocumentInfoNode analysiert ein Dokument. Dazu wird das Dokument mit der jadice document platform geladen und Metadaten extrahiert. Mit diesen Informationen wird der StreamDescriptor angereichtert und an den nächsten Knoten als IDocumentInfo weitergegeben. Das jeweils untersuchte Format muss dabei von der jadice document platform 5 unterstützt werden.

Als einfachstes Beispiel kann diese Information mit Hilfe der &NotifacNode direkt an den Client übergegeben und dort auf die Konsole ausgegeben werden.

Beispiel 6.6. Verwendung des JadiceDocumentInfoNode

// Server-Job erstellen
Job job = createServerJob();
job.setType("retrieve document info");

// InfoNode erstellen
JadiceDocumentInfoNode infoNode = new JadiceDocumentInfoNode();

// Listener erstellen und an NotificatioNode anhängen
DocumentInfoListener documentInfoListener = new DocumentInfoListener();
NotificationNode notifyNode = new NotificationNode();
notifyNode.addNotificationResultListener(documentInfoListener);

// Workflow zusammenstellen
StreamInputNode siNode = new StreamInputNode();
siNode.appendSuccessor(infoNode);
infoNode.appendSuccessor(notifyNode);
// Datenstrom am Ende der Analyse verwerfen
notifyNode.appendSuccessor(new NullNode());

// 1. Node dem Job übergeben...
job.attach(siNode);
// ...und Job starten
job.submit();

// Nach dem Start des Jobs den Datenstrom dem Inputnode übergeben
siNode.addStream(…);
// Dateneingabe beenden
siNode.complete();
// Auf Abarbeitung durch Server warten (siehe oben)
documentInfoListener.waitForDocumentInfo();
// DokumentInfo holen und Daten ausgeben
IDocumentInfo documentInfo = documentInfoListener.getDocumentInfo();
System.out.println("Anzahl Seiten  : " + documentInfo.getPageCount());
// Beispielhaft: Daten der ersten Seite
System.out.println("Format         : " + documentInfo.getFormat(0));
System.out.println("Größe (Pixel) : " + documentInfo.getSize(0).getWidth() + "x"
    + documentInfo.getSize(0).getHeight());
System.out.println("Auflösung (dpi): " + documentInfo.getVerticalResolution(0) + "x"
    + documentInfo.getHorizontalResolution(0));

Beispiel 6.7. In Beispiel 6.6, „Verwendung des JadiceDocumentInfoNode verwendeter NotificationNode.NotificationListener

public class DocumentInfoListener implements NotificationListener {
  /**
   * Dokumentinfo, wird vom JadiceDocumentInfoNode erzeugt und an den StreamDescriptor gehängt
   */
  private IDocumentInfo documentInfo;

  /**
   * Latch um Thread zu blockieren, bis {@link #documentInfo} verfügbar ist. Hinweis:
   * Fehlerbehandlung, wenn Job abbricht / keine Ergebnis geliefert wird, im Beispiel nicht
   * umgesetzt
   */
  private CountDownLatch latch = new CountDownLatch(1);

  @Override
  public void notificationRecieved(StreamDescriptor streamDescriptor) {
    final Serializable prop = streamDescriptor.getProperties().get(JadiceDocumentInfoNode.PROPERTY_NAME);
    if (prop != null && prop instanceof IDocumentInfo) {
      documentInfo = (IDocumentInfo) prop;
      latch.countDown();
    }
  }

  public void waitForDocumentInfo() throws InterruptedException {
    // Blockieren bis IDocumentInfo erhalten wurde
    latch.await();
  }

  public IDocumentInfo getDocumentInfo() {
    return documentInfo;
  }
}

Mit dem PDFMergeNode ist es möglich, mehrere PDF-Dokumente zu einem einzigen zusammenzufassen.

Beispiel 6.8. Verwendung des PDFMergeNode

Job job = createServerJob();
job.setType("merge pdfs");

// Nodes erstellen:
// 1. Dateneingabe-Node
StreamInputNode siNode = new StreamInputNode();
// 2. Zusammenfassen von Eingabedaten (1...n zu 1)
PDFMergeNode pmNode = new PDFMergeNode();
// 3. Ausgabe-Node
StreamOutputNode soNode = new StreamOutputNode();

// Workflow erstellen
job.attach(siNode.appendSuccessor(pmNode).appendSuccessor(soNode));
// Job ausführen
job.submit();

// PDF-Dokument-Datenstrom senden
siNode.addStream(…);
siNode.addStream(…);
// ... evtl. weitere Datenströme

// Dateneingabe beenden
siNode.complete();

// Auf Antwort vom Server warten
for (Stream stream : soNode.getStreamBundle()) {
  // Lesen der Daten
  InputStream is = stream.getInputStream();
}


Um Dokumente und deren Annotationen mit Standardprogrammen anzeigen zu können, müssen diese als „normale“ Objekte im Ausgangsformat verankert werden.

Dies kann ebenfalls mit dem ReshapeNode realisiert werden. Die notwendige Assoziation zwischen dem Dokument- und den Annotationsdatenströmen zeigt das folgende Beispiel:

Beispiel 6.10. Dauerhafte Verankerung von Annotationen

/**
 * Beispiel-Interface zur Bündelung von Dokument und Annotationen
 */
interface DocumentAndAnnotations {
  InputStream getContent();

  List<InputStream> getAnnotations();
}

public void convert(DocumentAndAnnotations doc) throws JMSException, JobException, IOException {
  Job job = createServerJob();
  job.setType("imprint annotations");

  // Nodes erstellen
  StreamInputNode inputNode = new StreamInputNode();
  ReshapeNode reshapeNode = new ReshapeNode();
  StreamOutputNode outputNode = new StreamOutputNode();

  // Konfiguration des Ergebnis-Typs (z.B. PDF)
  reshapeNode.setTargetMimeType("application/pdf");

  // Annotations-Datenströme mit Content assoziieren
  reshapeNode.setOutputMode(OutputMode.ASSOCIATED_STREAM);

  // Workflow zusammenstellen
  job.attach(inputNode.appendSuccessor(reshapeNode).appendSuccessor(outputNode));
  job.submit();

  // Sende Dokument-Inhalt
  // (hier mit explizitem Mime-Type)
  final StreamDescriptor contentSD = new StreamDescriptor("application/pdf");
  inputNode.addStream(doc.getContent(), contentSD);

  // Verarbeitung der Annotationen:
  for (InputStream annoStream : doc.getAnnotations()) {
    StreamDescriptor annoSD = new StreamDescriptor();
    // Assoziation zw. Dokument und Annotation:
    annoSD.setParent(contentSD);
    // Setzen des Annotationstyps (z.B. Filenet P8):
    annoSD.setMimeType(ReshapeNode.AnnotationMimeTypes.FILENET_P8);
    // Senden des Annotationsdatenstroms
    inputNode.addStream(annoStream, annoSD);
  }
  inputNode.complete();
  // Nicht gezeigt: Verarbeitung des Ergebnisses
}


In dieser Konfiguration sind zwei Einstellungen besonders relevant:

Dokument- und Annotationsdatenströme müssen über die StreamDescriptor-Hierarchie miteinander verknüpft werden. Dazu muss der StreamDescriptor des Dokuments als Parent der StreamDescriptoren der Annotationen gesetzt werden.

Für die verfügbaren Mime-Types von Annotationen gibt es in der Klasse ReshapeNode vordefinierte Konstanten, die zwingend gesetzt werden müssen. Weitere Informationen über die Annotationsformate und deren Eigenschaften können Sie dem Annotationshandbuch der jadice document platform 5 entnehmen.

Vertrauliche Dokumentinhalte

Bitte beachten Sie, dass keine Analyse des Dokumentinhalts stattfindet. Inhalte, die von Annotationen verdeckt werden, können je nach Datenformat weiterhin im Zieldatenstrom vorhanden sein. Außerdem ist es möglich, dass im zweiten Fall unerwünschte (Meta-)Daten im Dokument verbleiben können, die einer Vertraulichkeit unterliegen.

Um die Netzwerklast zu verringern, werden Dateien häufig komprimiert. Diese können vor der Weiterverarbeitung durch den jadice server entpackt werden. Dies geschieht je nach Dateiformat in unterschiedlichen Nodeklassen:


Wie dies für den UnZIPNode aussieht, zeigt folgendes Codebeispiel:

Beispiel 6.11. Verwendung des UnZIPNode

Job job = createServerJob();
job.setType("unpack zip");

// Nodes erstellen:
// 1. Dateneingabe-Node
StreamInputNode siNode = new StreamInputNode();
// 2. Entpacken von ZIP-Archiven
UnZIPNode unzipNode = new UnZIPNode();
// 3. Ausgabe-Node
StreamOutputNode soNode = new StreamOutputNode();

// Workflow erstellen
job.attach(siNode.appendSuccessor(unzipNode).appendSuccessor(soNode));

// Job ausführen
job.submit();
// Dokument-Datenstrom senden
siNode.addStream(…);
// Dateneingabe beenden
siNode.complete();

// Auf Antwort vom Server warten
for (Stream stream : soNode.getStreamBundle()) {
  // Lesen der Daten (1 Stream je Datei im Archiv)
  System.out.println("filename: " + stream.getDescriptor().getFileName());
  InputStream is = stream.getInputStream();
}


Eine Vereinheitlichung von Dokumenten ist besonders im Bereich der Langzeitarchivierung von Nutzen. Der Zugriff auf die Datenquelle, die automatische Analyse von Daten, eine zielvorgabenorientierte, dynamische Weiterverarbeitung und eine abschließende Archivierung ins Archiv bringt folgende Vorteile:

Die aufrufende Anwendung braucht keinerlei Kenntnis über Quelldateien und Formate. Es besteht keine Gefährdung durch bösartige Daten oder Dokumente. Darüber hinaus ist eine Minimierung des Netzwerktransfers die Folge. Durch seine Struktur ermöglicht es der jadice server, zu jeder Zeit das Konvertierungsergebnis flexibel zu steuern.


Ergänzen Sie diesem Job um eine eigene Implementierung eines JobListeners, so können Sie über die Methode subPipelineCreated erfahren, welche weiteren Nodes dynamisch von jadice server erzeugt wurden.

Das verwendete Regelwerk finden Sie im Verzeichnis /server-config/dynamic-pipeline-rules. Die XML-basierten Regelwerke können auf einen Bedürfnisse angepasst werden. Dazu hilft das ebenfalls in diesem Ordner zu findenden XML Schema.

Bei der E-Mailkonvertierung wird die E-Mail direkt vom Mailserver geholt. Hierzu müssen die entsprechenden Zugriffsdaten angegeben werden.

Der Vorgang ist ähnlich der dynamischen Konvertierung (siehe Abschnitt „Konvertierung unbekannter Eingabedaten in ein einheitliches Format (PDF)“). Die E-Mail wird analysiert, eventuelle Anhänge wie z. B. Office-Dokumente, Bilder usw. werden alle konvertiert, in einer Übersicht zusammengefasst und an den E-Mail-Text angehängt.

Archivdateien werden dabei ausgepackt und deren Inhalt in den Konvertierungsvorgang eingebunden.


Sollen E-Mails nicht mit dem JavamailInputNode über einen IMAP- oder POP3-Account abgerufen, sondern z. B. als eml-Datei eingelesen werden, muss zusätzlich der MessageRFC822Node zwischengeschaltet werden, der die Abtrennung von E-Mail-Header und -Body vornimmt:


E-Mails, die im MS Outlook-Format (msg-Dateien) vorliegen, können durch den TNEFNode ohne Aufruf von MS Outlook in ein von jadice server unterstütztes Format und über den diesen Weg konvertiert werden:


In der oben gezeigten Konfiguration wird standardmäßig zu jedem Dateianhang eine Trennseite generiert, die die Metadaten des jeweiligen Anhangs enthält. Sind keine Trennseiten gewünscht, können diese mit der folgenden Konfiguration des ScriptNodes für alle Dateianhänge deaktiviert werden:

scNode.getParameters().put("showAttachmentSeparators", false);

Eine weitere Konfigurationsmöglichkeit betrifft formatierte E-Mails. Wurden diese sowohl im HTML- als auch Plaintext-Format gesendet, wird standardmäßig der HTML-Teil konvertiert. Soll stattdessen der Plaintext-Teil konvertiert werden, ist folgende Konfiguration des ScriptNodes vorzunehmen:

scNode.getParameters().put("preferPlainTextBody", true);

Davon unabhängig ist es möglich, denjenigen Teil, der normalerweise nicht konvertiert wird, als zusätzliches Attachment an die E-Mail anzuhängen. Somit kann die konvertierte E-Mail sowohl im HTML- als auch im Plaintext-Format dargestellt werden. Die nötige Konfiguration ist:

scNode.getParameters().put("showAllAlternativeBody", true);

Um zu verhindern, dass jadice server Bilder und andere in E-Mails referenzierte Dateien von unbekannten Quellen nachlädt, kann dies über folgende Einstellung verhindert werden:

scNode.getParameters().put("allowExternalHTTPResolution", false);

Die Behandlung von Attachments, deren Format nicht erkannt wurde oder nicht für die Konvertierung durch jadice server vorgesehen ist, kann über den Parameter unhandledAttachmentAction gesteuert werden:

scNode.getParameters().put("unhandledAttachmentAction", "failure");

Folgende Werte werden hierbei akzeptiert:

WertBedeutung

warning

Es wird eine Warnung in das Log geschrieben.

error

Es wird ein Error in das Log geschrieben (Standardwert).

failure

Der zugehörige Job bricht mit einem Fehler ab.

Zur Kenntlichmachung von Bilddateien, die in einer E-Mail referenziert sind, aber nicht konvertiert wurden, werden anstelle dieser folgende Platzhalter eingefügt:

WertBedeutung
Als Platzhalter wird ein mit einem roten X markiertes Icon gezeigt

Das Bild wurde aufgrund der Einstellung allowExternalHTTPResolution nicht geladen (s. o.)

Als Platzhalter wird ein mit einem roten Minuszeichen markiertes Icon gezeigt

Die Bilddatei konnte nicht geladen werden.

Durch den ExternalProcessCallNode ist die Ansteuerung externer Programme sehr einfach möglich. Dabei kümmert sich der jadice server automatisch darum, dass ein- und ausgehende Datenströme automatisch zu temporären Dateien umgewandelt und diese nach der Verarbeitung durch das externe Programm gelöscht werden.

Einzige Voraussetzung ist, dass das Programm auf dem Server über Kommandozeile angesprochen werden kann.

Beispiel 6.17. Verwendung des ExternalProcessCallNode

Job job = createServerJob();
job.setType("external my converter");

// Nodes erstellen:
// 1. Dateneingabe-Node
StreamInputNode siNode = new StreamInputNode();
// 2. Externer Prozess
ExternalProcessCallNode epcNode = new ExternalProcessCallNode();
// Konfiguration:
// Programmname (Backslashes müssen escaped werden!)
epcNode.setProgramName("C:\\Programme\\MyConverter\\MyConverter.exe");
// Kommandozeilen-Parameter
// ${infile} und ${outfile:pdf} ersetzt jadice server
epcNode.setArguments("-s -a ${infile} /convert=${outfile:pdf}");
// 3. Ausgabe-Node
StreamOutputNode soNode = new StreamOutputNode();

// Workflow erstellen
job.attach(siNode.appendSuccessor(epcNode).appendSuccessor(soNode));

// Job abschicken und Eingabedatenstrom übergeben
job.submit();
StreamDescriptor sd = new StreamDescriptor();
// jadice server speichert die Datei bei Uebergabe an das externe Programm
// unter diesem Namen ab.
sd.setFileName("myfile.dat");
siNode.addStream(new BundledStream(…, sd));
siNode.complete();

// Auf Antwort vom Server warten
for (Stream stream : soNode.getStreamBundle()) {
  // Lesen der Daten
  InputStream is = stream.getInputStream();
}

[jadice server Version 5.0.5.0: Dokumentation für Entwickler und Administratoren. Veröffentlicht: 2014-06-16]
loading table of contents...