Anwendungsszenarien samt Code-Beispielen

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

  • Datei vom Client empfangen

  • Serverseitige Verarbeitung

  • Ergebnis zum Client zurücksenden

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.

Erstellen eines Server-Jobs

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

Konfigurationsmöglichkeiten des Jobs sind:

Um einen Job erstellen zu können, muss zunächst eine JMSJobFactory erzeugt und initialisiert werden. Diese stellt die Verbindung zum Messagingsystem her und ist für die weitere Kommunikation zwischen der Client-Anwendung und jadice server verantwortlich. Die Funktion createServerJob() in Beispiel 6.1, „JobFactory initialisieren und Job erstellen (mit ActiveMQ als Messagebroker)“ wird als Basis für die kommenden Beispiele verwendet:

Beispiel 6.1. JobFactory initialisieren und Job erstellen (mit ActiveMQ als Messagebroker)

public Job createServerJob() throws JobException {
  if (jobFactory == null) {
    // Create a job factory with the parameter "brokerUrl" and the default JMS queue name
    jobFactory = new JMSJobFactory(
        new ActiveMQConnectionFactory("tcp://<Broker_IP>:<Broker-Port>"),
        JMSJobFactory.DEFAULT_QUEUE_NAME);
    
    // Provide connection credentials (optionally)
    jobFactory.setCredentials(new Credentials("my-jms-user", "my-jms-password"));
    
    // Connect to the messaging system
    jobFactory.connect();
  }
  
  // Create a job for jadice server
  Job job = jobFactory.createJob();
  return job;
}


Wurde die JobFactory korrekt initialisiert, ist sie dafür zuständig, alle nachfolgenden Jobs zur Konvertierung zu erzeugen.

Beispiel 6.2. Job konfigurieren und ausführen

// Create a job
try (Job job = createServerJob()) {
  // Apply a timeout limit:
  job.apply(new TimeLimit(60, TimeUnit.SECONDS));
  // Declare the job type
  job.setType("first-example");
  // Attach a JobListener (see below)
  job.addJobListener(…);
  // Assemble the workflow  (see below)
  job.attach(…);
  // Perform the job
  job.submit();
}


Sind alle über diese JobFactory erstellten Jobs beendet worden und sollen keine weiteren Jobs mehr erstellt werden, so muss die Verbindung zum Messagingsystem getrennt werden. Andernfalls werden Verbindungen nicht freigegeben und es kann zu einem Ressourcen-Leck kommen.

Beispiel 6.3. Beenden der JobFactory am Ende ihres Lebenszyklus

public void disconnect() {
  if (jobFactory != null) {
    jobFactory.close();
    jobFactory = null;
  }
}


Erstellung eines JobListeners

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

Beispiel 6.4. 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

Konfiguration von Limits

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 Limits Beschreibung Kontext
Job Node
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.5. Beispielhafte Verwendung von Limits

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

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 Warnung 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“

Identifikation unbekannter Eingabedaten

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.6. Verwendung des StreamAnalysisNode

try (Job job = createServerJob()) {
  job.setType("run stream analysis");
  
  // Instantiate nodes:
  //  1. data input node
  StreamInputNode siNode = new StreamInputNode();
  // 2. analysis node
  StreamAnalysisNode saNode = new StreamAnalysisNode();
  // 3. output node
  StreamOutputNode soNode = new StreamOutputNode();
  
  // Assemble the workflow
  job.attach(siNode.appendSuccessor(saNode).appendSuccessor(soNode));
  
  // Perform the job and send data
  job.submit();
  siNode.addStream(…);
  siNode.complete();
  
  // Wait for server reply
  for (Stream stream : soNode.getStreamBundle()) {                             (1)
    // Reading the meta data
    final StreamDescriptor descr = stream.getDescriptor();
    final String mimeType = descr.getMimeType();
  }
}

1

The method getStreamBundle() is blocking until jadice server has finished the job. For working in an asynchronous way, a StreamListener can be implemented


Extraktion von Dokument-Informationen

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 des NotificationNodes direkt an den Client übergegeben und dort auf die Konsole ausgegeben werden.

Beispiel 6.7. Verwendung des JadiceDocumentInfoNode

try (Job job = createServerJob()) {
    job.setType("retrieve document info");
    
    // Instantiate info node
    JadiceDocumentInfoNode infoNode = new JadiceDocumentInfoNode();

    // Create a listener and attach it to a NotificatioNode
    DocumentInfoListener documentInfoListener = new DocumentInfoListener();
    NotificationNode notifyNode = new NotificationNode();
    notifyNode.addNotificationResultListener(documentInfoListener);

    // Assemble the workflow
    StreamInputNode siNode = new StreamInputNode();
    siNode.appendSuccessor(infoNode);
    infoNode.appendSuccessor(notifyNode);
    // Discard the data at the end of the analysis:
    notifyNode.appendSuccessor(new NullNode());

    // Perform the job
    job.attach(siNode);
    job.submit();

    // Submit the data to jadice server and end transmission
    siNode.addStream(…);
    siNode.complete();

    // Wait for server reply (see above)
    documentInfoListener.waitForDocumentInfo();
    // Retrieve and dump document info:
    IDocumentInfo documentInfo = documentInfoListener.getDocumentInfo();
    System.out.println("Number of pages  : " + documentInfo.getPageCount());
    // As example here: Details of the first page
    System.out.println("format          : " + documentInfo.getFormat(0));
    System.out.println("size (pixels)   : " + documentInfo.getSize(0).getWidth() + "x"
        + documentInfo.getSize(0).getHeight());
    System.out.println("resolution (dpi): " + documentInfo.getVerticalResolution(0) + "x"
        + documentInfo.getHorizontalResolution(0));
  }

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

public class DocumentInfoListener implements NotificationListener {
  /**
   * DocumentInfo will be generated by the JadiceDocumentInfoNode and attached to the StreamDescriptor
   */
  private IDocumentInfo documentInfo;

  /**
   * Latch in order to block the current thread until {@link #documentInfo} is available.
   * NOTE: This example does not perform any error handling if the job aborts or no result is available!
   */
  private CountDownLatch latch = new CountDownLatch(1);

  @Override
  public void notificationReceived(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 {
    // Block until documentInfo is available
    latch.await();
  }

  public IDocumentInfo getDocumentInfo() {
    return documentInfo;
  }
}

Zusammenfassen mehrerer PDF-Dokumente

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

Beispiel 6.9. Verwendung des PDFMergeNode

try (Job job = createServerJob()) {
  job.setType("merge pdfs");
  
  // Instantiate nodes:
  //  1. data input node
  StreamInputNode siNode = new StreamInputNode();
  //  2. merge input data (1...n streams to a single stream)
  PDFMergeNode pmNode = new PDFMergeNode();
  //  3. output node
  StreamOutputNode soNode = new StreamOutputNode();
  
  // Assemble the workflow and perform the job
  job.attach(siNode.appendSuccessor(pmNode).appendSuccessor(soNode));
  job.submit();
  
  // Send PDF documents
  siNode.addStream(…);
  siNode.addStream(…);
  // ... possible further PDF documents
  
  // Signalise the end of input data
  siNode.complete();
  
  // Wait for server reply
  for (Stream stream : soNode.getStreamBundle()) {
    // Reading the data
    InputStream is = stream.getInputStream();
    // Work with this data (not shown)
    …        
  }
}


Konvertierung nach TIFF

Die meisten Konvertierungsvorgänge (z. B. über LibreOffice) erzeugen PDF. Es ist jedoch möglich, durch das Einfügen des ReshapeNode das Ergebnis weiter nach TIFF zu konvertieren.

Im folgenden Beispiel wird der Workflow aus Beispiel 6.9, „Verwendung des PDFMergeNode verändert; anstelle des PDFMergeNode wird eine Konvertierung nach TIFF mit anschließender Aggretation angehängt:

Beispiel 6.10. Konvertierung nach Tiff

// (...)
  ReshapeNode reshapeNode = new ReshapeNode();
  reshapeNode.setTargetMimeType("image/tiff");
  // Join all incoming data to one resulting stream
  reshapeNode.setOutputMode(ReshapeNode.OutputMode.JOINED);
  // Assemble the workflow and include the TIFF converter node
  job.attach(siNode.
    appendSuccessor(reshapeNode).
    appendSuccessor(soNode));
  // (...)
}

Dauerhafte Verankerung von Annotationen

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.11. Dauerhafte Verankerung von Annotationen

/**
 * stub interface in order to bundle a document and its annotations
 */
interface DocumentAndAnnotations {
  InputStream getContent();
  List<InputStream> getAnnotations();
}

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

    // Instantiate nodes:
    StreamInputNode inputNode = new StreamInputNode();
    ReshapeNode reshapeNode = new ReshapeNode();
    StreamOutputNode outputNode = new StreamOutputNode();

    // Define the target MIME type (e.g. PDF)
    reshapeNode.setTargetMimeType("application/pdf");

    // Associate the annotations streams with the content
    reshapeNode.setOutputMode(ReshapeNode.OutputMode.ASSOCIATED_STREAM);

    // Assemble the workflow and perform the job
    job.attach(inputNode.appendSuccessor(reshapeNode).appendSuccessor(outputNode));
    job.submit();

    // Sending document content (with explicitly declared MIME type here)
    final StreamDescriptor contentSD = new StreamDescriptor("application/pdf");
    inputNode.addStream(doc.getContent(), contentSD);

    // Process annotations:
    for (InputStream annoStream : doc.getAnnotations()) {
      StreamDescriptor annoSD = new StreamDescriptor();
      // Associate document and annotation:
      annoSD.setParent(contentSD);
      // Declare the annotations' MIME type (e.g. Filenet P8):
      annoSD.setMimeType(ReshapeNode.AnnotationMimeTypes.FILENET_P8);
      // Send annotation stream
      inputNode.addStream(annoStream, annoSD);
    }
    // Signalise the end of input data
    inputNode.complete();
    
    // Handle the job result  (not shown)
    …    
}


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.

Entpacken von Archivdateien

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:

Tabelle 6.3. Legende

Dateiformat Nodeklasse Bemerkung
ZIP UnZIPNode  

RAR

UnRARNode  

GZIP

UnGZIPNode

.tar.gz-Dateien müssen zuerst durch den UnGZIPNode, anschließend durch den UnTARNode

TAR

UnTARNode


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

Beispiel 6.12. Verwendung des UnZIPNode

try (Job job = createServerJob()) {
    job.setType("unpack zip");
    
    // Instantiate nodes:
    //  1. data input node
    StreamInputNode siNode = new StreamInputNode();
    //  2. unpacking of ZIP archives
    UnZIPNode unzipNode = new UnZIPNode();
    //  3. output node
    StreamOutputNode soNode = new StreamOutputNode();
    
    // Assemble the workflow
    job.attach(siNode.appendSuccessor(unzipNode).appendSuccessor(soNode));
    
    // Perform the job
    job.submit();
    // Send data
    siNode.addStream(…);
    // Signalise the end of input data
    siNode.complete();

    // Wait for server reply
    for (Stream stream : soNode.getStreamBundle()) {
      // Reading the data: (1 stream per file in the archive)
      System.out.println("file name: " + stream.getDescriptor().getFileName());
      InputStream is = stream.getInputStream();
      // Work with this data (not shown)
      …        
    }
  }


Konvertierung unbekannter Eingabedaten in ein einheitliches Format (PDF)

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.

Beispiel 6.13. Konvertierung beliebiger Datenströme nach PDF

try (Job job = createServerJob()) {
    job.setType("convert to pdf");
    
    // Instantiate nodes:
    //  1. data input node
    StreamInputNode siNode = new StreamInputNode();
    //  2. node for dynamic data converserion
    DynamicPipelineNode dpNode = new DynamicPipelineNode();
    dpNode.setRuleset(new URI("resource:/dynamic-pipeline-rules/default.xml"));
    //  3. merge input data (1...n streams to a single stream)
    PDFMergeNode pmNode = new PDFMergeNode();
    //  4. output node
    StreamOutputNode soNode = new StreamOutputNode();
    
    // Assemble the workflow
    job.attach(siNode.appendSuccessor(dpNode).appendSuccessor(pmNode).appendSuccessor(soNode));

    // Perform the job and send data
    job.submit();
    siNode.addStream(…);
    siNode.complete();

    // Wait for server reply
    for (Stream stream : soNode.getStreamBundle()) {
      // Work with the result
      InputStream is = stream.getInputStream();
      …        
    }
  }

Ergänzen Sie diesen 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 an eigene Anforderungen angepasst werden. Dazu hilft das ebenfalls in diesem Ordner zu findenden XML-Schema.

Konvertierung von Office-Dokumenten nach PDF

Beispiel 6.14. Ansteuerung von LibreOffice

try(Job job = createServerJob()) {
    job.setType("libreoffice to pdf");  
    
    // Instantiate nodes:
    //  1. data input node
    StreamInputNode siNode = new StreamInputNode();
    //  2. Conversion via LibreOffice
    LibreOfficeConversionNode loNode = new LibreOfficeConversionNode();
    // 3. merge input data (1...n streams to a single stream)
    PDFMergeNode pmNode = new PDFMergeNode();
    // 4. output node
    StreamOutputNode soNode = new StreamOutputNode();
    
    // Assemble the workflow
    job.attach(siNode.appendSuccessor(loNode).appendSuccessor(pmNode).appendSuccessor(soNode));
    
    // Perform the job and send document data
    job.submit();
    siNode.addStream(…);
    siNode.complete();

    // Wait for server reply
    for (Stream stream : soNode.getStreamBundle()) {
      // Reading the data
      InputStream is = stream.getInputStream();
      // Work with this data (not shown)
      …        
    }
  }

Anmerkung

Der Klassenpfad muss zur Ansteuerung von LibreOffice wie in Abschnitt „Konfiguration LibreOffice“ beschrieben gesetzt werden.

Anmerkung

Dokumente im Word2007-Format (Dateiendung docx) müssen vor der Konvertierung mit LibreOffice durch den StreamAnalysisNode vorverarbeitet werden (vgl. Abschnitt „Identifikation unbekannter Eingabedaten“).

Konvertierung von E-Mails nach PDF

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.

Beispiel 6.15. E-Mail-Konvertierung mit direktem Abruf vom Server

try (Job job = createServerJob()) {
    job.setType("mail to pdf");

    // Instantiate nodes:
    //  1. input node that retrieves the mail from a mail server
    JavamailInputNode jiNode = new JavamailInputNode();
    //   Configuration of the mail server
    jiNode.setStoreProtocol("<protocol>"); // POP3 or IMAP
    jiNode.setHostName("<server>");
    jiNode.setUsername("<user>");
    jiNode.setPassword("<password>");
    jiNode.setFolderName("<e-mail folder>");
    jiNode.setImapMessageUID(…);

    //  2. Perform the email conversion
    ScriptNode scNode = new ScriptNode();
    scNode.setScript(new URI("resource:email-conversion/EmailConversion.groovy"));
    // 3. merge data (1...n streams to a single stream)
    PDFMergeNode pmNode = new PDFMergeNode();
    // 4. output node
    StreamOutputNode soNode = new StreamOutputNode();

    // Assemble the workflow and perform the job
    job.attach(jiNode.appendSuccessor(scNode).appendSuccessor(pmNode).appendSuccessor(soNode));
    job.submit();

    // Wait for server reply
    for (Stream stream : soNode.getStreamBundle()) {
      // Work with the result
      InputStream is = stream.getInputStream();
      …        
    }
  }


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:

Beispiel 6.16. E-Mail-Konvertierung einer eml-Datei

Job job = createServerJob();
// Instantiate nodes:
//  1. input node the receives the mail from the client
StreamInputNode siNode = new StreamInputNode();
//  2. Separate of mail header and mail body
MessageRFC822Node msgNode = new MessageRFC822Node();
//  3. Perform the email conversion
ScriptNode scNode = new ScriptNode();
scNode.setScript(new URI("resource:email-conversion/EmailConversion.groovy"));
// Further procedure as above


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:

Beispiel 6.17. E-Mail-Konvertierung einer msg-Datei

Job job = createServerJob();
// Instantiate nodes:
//  1. input node the receives the mail from the client
StreamInputNode siNode = new StreamInputNode();
//  2. Pre-processing of MSG files
TNEFNode tnefNode = new TNEFNode();
tnefNode.setInputFormat(InputFormat.MSG);
//  3. Perform the email conversion
ScriptNode scNode = new ScriptNode();
scNode.setScript(new URI("resource:email-conversion/EmailConversion.groovy"));
// Further procedure as above


Anmerkung

Bitte beachten Sie, dass der Mailbody in msg-Dateien üblicherweise als Rich Text (rtf) vorliegt und daher in der Standardkonfiguration über LibreOffice nach PDF konvertiert wird.

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);

Mit der folgenden Einstellen kann verhindert werden, dass jadice server Bilder und andere in E-Mails referenzierte Dateien von unbekannten Quellen nachlädt:

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:

Wert Bedeutung

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:

Wert Bedeutung
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.

Standardmäßig werden Bilder im HTML Markup einer E-Mail, die für das aktuell ausgewählte PDF Seitenformat zu breit oder zu hoch sind, auf mehrere Seiten aufgeteilt.

Anmerkung

Grundsätzlich ist bei komplexer HTML-Formatierung zu beachten, dass diese nicht originär für ein Medium gedacht ist, das seitenweise arbeitet und deshalb oft nur verlustbehaftet konvertiert und insbesondere auf Seiten umgebrochen werden muss. Darüber hinaus lässt sich HTML auch nicht immer auf eine gewisse Breite "zusammenzwängen", weshalb ggf. auf größere Seitenbreiten ausgewichen werden muss. Sind zum Beispiel Tabellen oder komplexe Layouts im HTML Markup vorhanden, so ist es nicht möglich verlustfrei eine Skalierung dieser Inhalte auf ein bestimmtes Seitenformat zu erreichen.

Um dennoch das PDF-Seitenformat, im Falle eines zu breiten oder zu hohen Bildes im HTML-Markup, möglichst einzuhalten, können nun für die MailBodyCreatorNode, durch den Aufruf von setHtmlProcessingMode(), folgende Konfigurations-Optionen gewählt werden:

Tabelle 6.4. E-Mail-Konvertierung, Skalieren von Bilder im HTML-Markup auf PDF-Seitenformat

Konfigurationswert Beschreibung

DEFAULT

Standardverhalten, bei dem zu breite und zu hohe Bilder auf mehrere Seiten aufgeteilt werden.

PLACEHOLDER_ATTACHMENTS_BY_SIZE

Größenabhängige Verlagerung von Bildern in den Anhang. Diese Einstellung bewirkt, dass Bilder, die zu groß sind, in den Anhang des Resultats der E-Mail-Konvertierung verschoben werden.

PLACEHOLDER_ATTACHMENTS_ALL

Verlagerung aller Bilder in den Anhang.

FIT_BODY_IMAGES

Durch diese Einstellung werden Bilder, soweit möglich, auf das aktuelle PDF-Seitenformat angepasst. Dies kann nicht in allen Fällen garantiert werden, da bei komplexer HTML-Formatierung sonst ein Informationsverlust entstehen kann.


Auflösungsreduktion von Bildern in PDF-Dokumenten

Die Dateigröße eines PDF-Dokuments kann durch darin eingebettete Bilder stark zunehmen. Mit dem PDFImageOptimizationNode ist es möglich, ein bereits existierendes PDF-Dokument dahingehend zu manipulieren, dass die Auflösung der eingebetteten Bilder auf einen DPI-Schwellenwert reduziert wird, um eine Reduktion der Dateigröße des Dokuments zu erreichen. Hierbei wird so vorgegangen, dass für jedes enthaltene Bild geprüft wird, ob dessen Auflösung den als Parameter übergebbaren Schwellenwert übersteigt. Ist dies der Fall, wird es durch ein JPEG-Bild ersetzt, dessen Auflösung dem Schwellenwert entspricht. Die bei der JPEG-Generierung zu verwendende Bildqualität (es handelt sich um einen Prozentwert) kann ebenfalls als Parameter mitgegeben werden.

Bei der Berechnung der Auflösung eines Bildes spielt die Größe der Seite, auf der sich das Bild befindet, eine wichtige Rolle. Im Normalfall werden alle Seiten eines PDF-Dokuments die gleiche Größe haben (z. B. A4). Da aber in PDF vorgesehen ist, dass für jede Seite eine individuelle Größe gesetzt werden kann, kann es durchaus vorkommen, dass ein PDF-Dokument unterschiedlich große Seiten enthält. Aufgrund dieses Sachverhalts erlaubt der PDFImageOptimizationNode das Setzen einer Zielseitengröße (target page size). Wird dieser optionale Parameter nicht gesetzt, bezieht sich die berechnete Auflösung auf die im PDF-Dokument hinterlegte Seitengröße. Das Setzen der Zielseitengröße ist z. B. dann sinnvoll, wenn es vor allem auf die Bildqualität im ausgedruckten Dokument ankommt. Durch das Setzen der Zielseitengröße kann dann möglicherweise eine wesentlich stärkere Reduktion der Dateigröße erreicht werden.

Beispiel 6.18. Verwendung des PDFImageOptimizationNode

try (Job job = createServerJob()) {
  job.setType("optimize images");
  
  // Instantiate nodes:
  //  1. data input node
  StreamInputNode siNode = new StreamInputNode();
  //  2. optimize embedded images
  PDFImageOptimizationNode imgOptimizationNode = new PDFImageOptimizationNode();

  //  3. set the image resolution threshold to 150 DPI (default: 300)
  imgOptimizationNode.setMaxResolution(150);

  //  4. set the JPEG image quality to 80 percent (default: 75)
  imgOptimizationNode.setJPEGQuality(0.8f);

  //  5. set the page size of the output device (optional)
  imgOptimizationNode.setTargetPageSize(PDFImageOptimizationNode.PageSize.A4);

  //  6. output node
  StreamOutputNode soNode = new StreamOutputNode();
  
  // Assemble the workflow and perform the job
  job.attach(siNode.appendSuccessor(imgOptimizationNode).appendSuccessor(soNode));
  job.submit();
  
  // Send PDF document
  siNode.addStream(…);
  
  // Signalise the end of input data
  siNode.complete();
  
  // Wait for server reply
  for (Stream stream : soNode.getStreamBundle()) {
    // Reading the data
    InputStream is = stream.getInputStream();
    // Work with this data (not shown)
    …        
  }
}

Ansteuerung externer Programme

Durch den ExternalProcessCallNode ist die Ansteuerung externer Programme sehr einfach möglich. Dabei kümmert sich der jadice server 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.19. Verwendung des ExternalProcessCallNode

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

    // Instantiate nodes:
    //  1. data input node
    StreamInputNode siNode = new StreamInputNode();
    //  2. start an external process
    ExternalProcessCallNode epcNode = new ExternalProcessCallNode();
    //    Configuration:
    //      - Program name (back slashes must be escaped!)
    epcNode.setProgramName("C:\\Programme\\MyConverter\\MyConverter.exe");
    //      - Command line parameters (jadice server will substitute ${infile} / ${outfile:pdf})
    epcNode.setArguments("-s -a ${infile} /convert=${outfile:pdf}");
    //  3. output node
    StreamOutputNode soNode = new StreamOutputNode();

    // Assemble the workflow
    job.attach(siNode.appendSuccessor(epcNode).appendSuccessor(soNode));

    // Submit job and send data
    job.submit();
    StreamDescriptor sd = new StreamDescriptor();
    // jadice server will use the name when it stores this file and passes it to the external program
    sd.setFileName("myfile.dat");
    siNode.addStream(new BundledStream(…, sd));
    siNode.complete();

    // Wait for server reply
    for (Stream stream : soNode.getStreamBundle()) {
      // Work with this data (not shown)
      InputStream is = stream.getInputStream();
      …  
    }
  }

Ausfiltern von Dateien bei der Extraktion von Archiv-Dateiformaten (ZIP, RAR, 7ZIP, TAR)

Eingehende Mails und Archiv-Dateiformate, die konvertiert werden sollen, enthalten zum Teil aus fachlicher Sicht irrelevante Dateiformate. Um diese Dateien auszufiltern, besteht die Möglichkeit, regelbasiert eine Filterung der Datenströme anhand des Dateinamen vorzunehmen.

Dabei können diese Regeln in einer Konfigurationsdatei auf Serverseite definiert werden. Beispielhaft ist hier ein Auszug möglicher Regeldefinitionen aufgeführt. Die Regeldatei muss in UTF-8-Kodierung vorliegen.

Tabelle 6.5. Beispiele für Regeln zur Ausfilterung von Dateien aus Archiv-Dateiformaten.

Beispiel Beschreibung

**/CVS/*

Trifft auf alle Dateien in "CVS" Ordnern zu.

Folgende Dateien werden hiermit beispielsweise ausgefiltert:

  1. org/apache/CVS/Eintrag1

  2. org/apache/CVS/EintragN

  3. org/apache/jakarta/tools/ant/CVS/Eintrag1

  4. org/apache/jakarta/tools/ant/CVS/EintragN

Nicht ausgefiltert werden:

  1. org/apache/CVS/foo/bar/Eintraege

Hierbei treffen die Unterordner von CVS "foo/" und "bar/" nicht auf den Regelausdruck zu.

org/apache/jakarta/**

Trifft auf alle Dateien im Datei-Pfad "org/apache/jakarta" zu.

Folgende Dateien werden hiermit beispielsweise ausgefiltert:

  1. org/apache/jakarta/tools/ant/docs/index.html

  2. org/apache/jakarta/test.xml

Nicht ausgefiltert wird:

  1. org/apache/xyz.java

Da hier der Unterordner "jakarta" des Ordners "org/apache" fehlt.

org/apache/**/CVS/*

Trifft auf alle Dateien in "CVS" Ordnern zu, die in Unterordnern des Ordners "org/apache" liegen.

Folgende Dateien werden hiermit beispielsweise ausgefiltert:

  1. org/apache/CVS/Eintrag1

  2. org/apache/CVS/EintragN

  3. org/apache/jakarta/tools/ant/CVS/Eintrag1

  4. org/apache/jakarta/tools/ant/CVS/EintragN

Nicht ausgefiltert werden:

  1. org/apache/CVS/foo/bar/Eintraege

Hier treffen wieder die Unterordner "foo/" und "bar/" nicht auf den Regelausdruck zu.

**/test/**

Trifft auf alle Dateien in "test" Ordnern zu, inklusive Dateien in deren Unterordnern.


Mehr Informationen zum Thema und die Quelle dieser Regelbeschreibungen finden Sie unter https://ant.apache.org/manual/dirtasks.html#patterns.

Um Regelsätze bei einem Extraktionsvorgang der Archiv-Datenformate anzuwenden, ist eine entsprechende Konfiguration der zugehörigen Worker notwendig. Diese Funktionalität steht für folgende Worker zur Verfügung: UnZIPWorker, UnRARWorker, UnSevenZIPWorker sowie UnTARWorker. Um eine Regeldatei einem Worker zuzuordnen, fügen Sie eine Konfiguration für den entsprechden Worker in die Datei server-config/application/workers.xml mit ein.

Beispiel 6.20. Beispiel einer Worker-Konfiguration mit Ausfilterung von Dateien aus Archiv-Dateiformaten (workers.xml)

<bean id="unzipFilterRulesBean" class="com.levigo.jadice.server.archive.worker.filter.AntPatternArchiveEntryFilter">
    <!-- Die Regeldatei unzipFilterRules.txt muss für diese Beispielkonfiguration auf Serverseite im Ordner <jadice-server>/server-config/custom/ vorliegen -->
    <property name="antPatternFilterRulesURI" value="resource://custom/unzipFilterRules.txt" />
</bean>

<workers:worker class="com.levigo.jadice.server.archive.worker.UnZIPWorker">
    <property name="filters">
         <util:list>
             <bean class="com.levigo.jadice.server.archive.worker.filter.OSXFilter" />
             <ref bean="unzipFilterRulesBean"/>
         </util:list>
    </property>
</workers:worker>
			  

Der Namespace xmlns:util="http://www.springframework.org/schema/util" und die SchemaLocations http://www.springframework.org/schema/util sowie http://www.springframework.org/schema/util/spring-util-2.5.xsd müssen in der Deklaration der workers.xml mit eingebunden werden.

[jadice server Version 5.8.7.0: Dokumentation für Entwickler und Administratoren. Veröffentlicht: 2021-04-15]