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.
Der Job
wird zunächst clientseitig erstellt, um ihm anschließend 1…n
Nodes anzuhängen.
Konfigurationsmöglichkeiten des Jobs sind:
-
JobListener
-Implementierung, siehe Abschnitt „Erstellung eines JobListeners“ -
TimeLimit
und ähnlicheLimit
s, siehe Abschnitt „Konfiguration von Limits“ -
Workflow (= azyklischer Graph, der durch miteinander verkettete
Node
s definiert wird) -
Typ: Mit diesem Bezeichner werden
Job
s von der gleichen Art ausgezeichnet. Serverseitig wird diese Angabe u.a. zu statistischen Zwecken verwendet.
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; } }
Mit dem JobListener
können Zustände des Job
s 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
Um den Ressourcen-Verbrauch eines Job
s oder dessen Knoten zu beschränken, ist es
sinnvoll, Limit
s darauf anzuwenden. Folgende Limit
s sind hierbei möglich:
Tabelle 6.1. Verfügbare Limit
s
Typ des Limits | Beschreibung | Kontext | |
---|---|---|---|
Job |
Node |
||
TimeLimit |
Maximale Verarbeitungszeit |
☑ | ☑ |
Maximale Anzahl an |
☐ | ☑ | |
Maximale Größe der |
☐ | ☑ | |
Maximale Anzahl an Seiten eines generierten Dokuments |
☐ | ☑[a] | |
Maximale Anzahl an Knoten, die ein |
☑ | ☒ | |
[a] Für Knoten, die Dokumente generieren, die einen Seiten-Begriff kennen; vgl. javadoc |
Tabelle 6.2. Legende zu Tabelle 6.1, „Verfügbare Limit
s“
☑ | 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 Limit
s kann ausgewählt werden, was passieren soll, wenn es
überschritten wird:
Beispiel 6.5. Beispielhafte Verwendung von Limit
s
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:
-
Limit
s mit derLimit.WhenExceedAction
WARN werden in jedem Fall vererbt. -
Limit
s mit derLimit.WhenExceedAction
ABORT werden nicht an Knoten vererbt, auf die bereits ein Limit der selben Klasse mit derLimit.WhenExceedAction
ABORT angewendet wurde, auch wenn dieses weniger restriktiv ist. -
Werden
Limit
s der selben Klasse mitLimit.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.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()) { // Reading the meta data final StreamDescriptor descr = stream.getDescriptor(); final String mimeType = descr.getMimeType(); } }
|
The method getStreamBundle() is blocking until jadice server has finished the job. For working in an asynchronous way, a StreamListener can be implemented |
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 NotificationNode
s
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; } }
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) … } }
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)); // (...) }
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 StreamDescriptor
en 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:
Tabelle 6.3. Legende
Dateiformat | Nodeklasse | Bemerkung |
---|---|---|
ZIP | UnZIPNode |
|
RAR |
UnRARNode |
|
GZIP |
UnGZIPNode |
|
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) … } }
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 JobListener
s, so können
Sie über die Methode subPipelineCreated
erfahren, welche
weiteren Node
s 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.
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“).
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 ScriptNode
s
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 ScriptNode
s 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 |
---|---|
|
Es wird eine Warnung in das Log geschrieben. |
|
Es wird ein Error in das Log geschrieben (Standardwert). |
|
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 |
---|---|
Das Bild wurde aufgrund der Einstellung
|
|
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 |
---|---|
|
Standardverhalten, bei dem zu breite und zu hohe Bilder auf mehrere Seiten aufgeteilt werden. |
|
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. |
|
Verlagerung aller Bilder in den Anhang. |
|
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. |
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) … } }
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(); … } }
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:
Nicht ausgefiltert werden:
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:
Nicht ausgefiltert wird:
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:
Nicht ausgefiltert werden:
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.