Erstellung eines Inhaltsverzeichnisses bei der E-Mail-Verarbeitung
Bei der Konvertierung von E-Mails mit jadice server nach PDF besteht gelegentlich die Anforderung, dass zusätzlich nachvollzogen werden muss, welche Anhänge an dieser Mail anhängen.
Im entstehenden PDF bringt jadice server automatisch ein Inhaltsverzeichnis auf, doch wie kann ein solches auch in der Client-Anwendung erstellt werden? Dies kann mit einfachen Mitteln erreicht werden.
Zunächst wird wie im Benutzerhandbuch beschrieben ein normaler Workflow zur Konvertierung von EML nach PDF aufgebaut:
private static void runMailConversion(InputStream mail, OutputStream target) throws Exception { // e-mail conversion as described in jadice server developer manual final Job job = new JMSJobFactory(new ActiveMQConnectionFactory("tcp://jadice-server:61616"), "JS.REQUEST").createJob(); final StreamInputNode inputNode = new StreamInputNode(); final ScriptNode scriptNode = new ScriptNode(); scriptNode.setScript(new URI("resource:email-conversion/EmailConversion.groovy")); final PDFMergeNode pdfMergeNode = new PDFMergeNode(); final StreamOutputNode outputNode = new StreamOutputNode(); // Workflow configuration and submit workflow job.attach(inputNode .appendSuccessor(new MessageRFC822Node()) .appendSuccessor(scriptNode) .appendSuccessor(pdfMergeNode) .appendSuccessor(outputNode)); job.addJobListener(new TraceListener()); job.submit(); // Submit mail stream inputNode.addStream(mail); inputNode.complete(); for (Stream s : outputNode.getStreamBundle()) { Util.copyAndClose(s.getInputStream(), target); } }
Der "Trick" besteht nun darin, dass man wissen muss, dass jadice server über einen JobListener
Ihre Anwendung nicht nur über Zustandswechsel des Job
s informiert, sondern auch, wenn während der Verarbeitung dynamisch neue Node
s erzeugt werden, die Methode subPipelineCreated() darüber informiert.
Während der Konvertierung eine E-Mail wird auf Serverseite im groovy-Script EmailConversion.groovy
der MailBodyCreatorNode
aufgerufen, der für die Erstellung der PDFs mit dem Inhalt des E-Mailbodies zuständig ist. Dieser Knoten hat als Parameter bereits die Objekt-Struktur AttachmentDirectory
, die zur Erstellung des Inhaltsverzeichnisses im PDF verwendet wird.
Über einen eigene JobListener-Implementierung kann diese Objekt-Struktur auch in der Client-Anwendung abgegriffen werden:
/** * Thread-safe receiver of a {@link AttachmentDirectory} */ public class AttachmentDirectoryReceiver extends JobListenerAdapter { private final CountDownLatch latch = new CountDownLatch(1); private final ScriptNode outerScriptNode; private AttachmentDirectory attachmentDirectory; public AttachmentDirectoryReceiver(ScriptNode scriptNode) { outerScriptNode = scriptNode; } @Override public void stateChanged(Job job, State oldState, State newState) { // Count down the latch if job fails. AttechmentDirectory will never be received if (newState == State.FAILED || newState == State.ABORTED) { latch.countDown(); } } @Override public void subPipelineCreated(Job job, Node parent, Set<? extends Node> createdNodes) { // Only care about the ScriptNode created here, // not be those ones created for mails attached within the mail if (!parent.getUUID().equals(outerScriptNode.getUUID())) { return; } for (Node node : createdNodes) { if (node instanceof MailBodyCreatorNode) { this.attachmentDirectory = ((MailBodyCreatorNode) node).getAttachmentDirectory(); latch.countDown(); return; } } } public AttachmentDirectory getAttachmentDirectory() throws InterruptedException { latch.await(); return attachmentDirectory; } }
Zwei Dinge sind in dieser Klasse besonders erwähnenswert:
- Durch den
CountDownLatch
wird sichergestellt, dass ein Zugriff auf die MethodegetAttachmentDirectory()
solange blockiert, bis dieses entweder vorliegt oder der Job fehlschlägt und klar ist, dass dieses nicht erzeugt werden kann - Während der Verarbeitung von Mails an denen Mails angehängt sind, werden dynamisch weitere
ScriptNode
s erzeugt, die wiederum mehrfachMailBodyCreatorNode
s und somitAttachmentsDirectory
s erzeugen. Um sicherzustellen, dass nur dasjenigeAttachmentDirectory
verwendet wird, dass für die "äußere" E-Mail gilt, wird in der MethodesubPipelineCreated()
überprüft, ob der neu instantiierte Knoten nicht von einem dynamisch erzeugenScriptNode
aus erstellt wurde.
Der obere Aufruf von jadice server kann nun mit folgendem Listener ergänzt werden:
(...) final AttachmentDirectoryReceiver attachmentDirectoryReceiver = new AttachmentDirectoryReceiver(scriptNode); job.addJobListener(attachmentDirectoryReceiver); job.submit(); (...) // Am Ende der Methode attachmentDirectoryReceiver.getAttachmentDirectory();
Dieses AttachmentDirectory kann sehr einfach rekursiv durchlaufen werden:
private static void prettyPrint(AttachmentDirectory dir, int level) { for (AttachmentDirectory subDir : dir.getSubDirectories()) { final String string = String.format("%"+(level+1)+"s %s (%s)", "", subDir.getFilename(), subDir.getType()); System.out.println(string); prettyPrint(subDir, level + 1); } }