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:

Konvertierung EML nach PDF
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 Jobs informiert, sondern auch, wenn während der Verarbeitung dynamisch neue Nodes 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:

Eigener JobListener, um AttachmentDirectory abzugreifen
/**
  * 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:

  1. Durch den CountDownLatch wird sichergestellt, dass ein Zugriff auf die Methode getAttachmentDirectory() solange blockiert, bis dieses entweder vorliegt oder der Job fehlschlägt und klar ist, dass dieses nicht erzeugt werden kann
  2. Während der Verarbeitung von Mails an denen Mails angehängt sind, werden dynamisch weitere ScriptNodes erzeugt, die wiederum mehrfach MailBodyCreatorNodes und somit AttachmentsDirectorys erzeugen. Um sicherzustellen, dass nur dasjenige AttachmentDirectory verwendet wird, dass für die "äußere" E-Mail gilt, wird in der Methode subPipelineCreated() überprüft, ob der neu instantiierte Knoten nicht von einem dynamisch erzeugen ScriptNode aus erstellt wurde.

Der obere Aufruf von jadice server kann nun mit folgendem Listener ergänzt werden:

Änderung, um AttachmentDirectoryReceiver zu verwenden
(...)
  
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:

Rekursive Verarbeitung des AttachmentDirectories
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);
  }
}


Die vollständige Demo-Klasse finden Sie in Github unter https://gist.github.com/geissebn/6317468