wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Fun with {{Mustache}}

This article is part of a mini series. Read them all:

  1. Fun with {{Mustache}} and Notes Forms
  2. CKEditor and Mustache become friends
  3. Mustache and CKEditor - Round two
  4. Creating nginx configurations for Domino SSL
  5. {{Mustache}} Helper for Domino and XPages
  6. Mail Merge with XPages
  7. Command line mail merge

Command line mail merge

Hero image for Command line mail merge
As shown before mail merging with Notes and {{mustache}} is simple and easy to code. To be able to run merging on the command line, I modified the bean and removed the XPages dependency and added a static main function. Now you can run it off the command line easily. It requires 5 parameters:
  1. the database name
  2. the CSV file
  3. a file with a HTML message including {{mustache}} tags
  4. a file with a Text message including {{mustache}} tags
  5. The senders eMail
There isn't much error handling in that code, so be careful.

package com.notessensei;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.MIMEEntity;
import lotus.domino.MIMEHeader;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
import lotus.domino.Stream;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

public class MergeManager implements Serializable {

    public static void main(String[] args) throws NotesException, IOException {
        // We need 4 parameters: nsfToUse, addresses, html message, text message
        // eMail field in addresses MUST be eMail
        if (args.length < 5) {
            System.out.println("Usage: java MergeManager maifile.nsf, addresses.csv, htmlmessage.txt, textmessage.txt sender");
            System.exit(1);
        }

        String dbName = args[0];
        String adrfilename = args[1];
        String htmlfilename = args[2];
        String txtfilename = args[3];
        String sender = args[4];

        MergeManager mergeManager = new MergeManager();

        // Load the content into the objects
        mergeManager.setRawAddresses(new Scanner(new File(adrfilename)).useDelimiter("\\Z").next());
        mergeManager.setHtmlMessage(new Scanner(new File(htmlfilename)).useDelimiter("\\Z").next());
        mergeManager.setTextMessage(new Scanner(new File(txtfilename)).useDelimiter("\\Z").next());
        mergeManager.setFrom(sender);

        // Spin up Notes
        NotesThread.sinitThread();
        Session s = NotesFactory.createSession();
        Database userMail = s.getDatabase("", dbName);
        // Set mailbox to null if you only want to generate the mails
        // but not send them
        Database mailbox = s.getDatabase("", "mail.box");

        mergeManager.process(s, userMail, mailbox);

        System.out.println(mergeManager.getStatus());

        // Shut down
        userMail.recycle();
        mailbox.recycle();
        NotesThread.stermThread();
    }
    private static final long                      serialVersionUID = 1L;
    private String                                 subject;
    private String                                 from;
    private String                                 rawAddresses;
    private final Map<String, Map<String, String>> rcpData          = new TreeMap<String, Map<String, String>>();
    private String                                 htmlMessage;
    private String                                 textMessage;
    private String                                 eMailField       = "eMail";
    private final ArrayList<String>                fields           = new ArrayList<String>();
    private final MustacheFactory                  factory          = new DefaultMustacheFactory();

    private final StringBuilder                    statusMessages   = new StringBuilder();

    private void addOneDataLine(Map<String, Map<String, String>> allRCP, ArrayList<String> fields, String raw) {
        Map<String, String> oneLine = new HashMap<String, String>();
        String key = raw; // Backup pla
        try {
            String[] dataItems = raw.split(",");
            for (int i = 0; i < dataItems.length; i++) {
                oneLine.put(fields.get(i), dataItems[i]);
                if (fields.get(i).equalsIgnoreCase(this.eMailField)) {
                    key = dataItems[i];
                }
            }
            allRCP.put(key, oneLine);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void getFields(ArrayList<String> list, String raw) {
        String[] fieldNames = raw.split(",");
        list.clear();
        for (int i = 0; i < fieldNames.length; i++) {
            list.add(fieldNames[i]);
        }
        return;
    }

    public String getStatus() {
        return this.statusMessages.toString();
    }

    private void populateMail(Session s, Document mail, String htmlBody, String textBody, String receipient) throws NotesException {
        s.setConvertMime(false);

        final MIMEEntity emailRoot = mail.createMIMEEntity("Body");

        // Primary Header
        String fromSender = this.from;
        MIMEHeader emailHeader = emailRoot.createHeader("Return-Path");
        emailHeader.setHeaderVal(fromSender);
        emailHeader = emailRoot.createHeader("From");
        emailHeader.setHeaderVal(fromSender);
        emailHeader = emailRoot.createHeader("Sender");
        emailHeader.setHeaderVal(fromSender);

        emailHeader = emailRoot.createHeader("Recipients");
        emailHeader.setHeaderVal(receipient);

        emailHeader = emailRoot.createHeader("To");
        emailHeader.setHeaderVal(receipient);

        emailHeader = emailRoot.createHeader("Subject");
        emailHeader.setHeaderVal(this.subject);

        // Text and HTML Body
        MIMEEntity emailRootChild = emailRoot.createChildEntity();
        final String boundary = System.currentTimeMillis() + "-" + String.valueOf(this.hashCode());
        emailHeader = emailRootChild.createHeader("Content-Type");
        emailHeader.setHeaderVal("multipart/alternative; boundary=\"" + boundary + "\"");

        MIMEEntity emailChild = emailRootChild.createChildEntity();

        Stream stream = s.createStream();
        stream.writeText(textBody);
        emailChild.setContentFromText(stream, "text/plain; charset=\"UTF-8\"", MIMEEntity.ENC_NONE);
        stream.close();

        emailChild = emailRootChild.createChildEntity();
        stream = s.createStream();
        stream.writeText(htmlBody);
        emailChild.setContentFromText(stream, "text/html; charset=\"UTF-8\"", MIMEEntity.ENC_NONE);
        stream.close();
        stream.recycle();
        stream = null;
        s.setConvertMime(true);
    }

    public void process(Session s, Database database, Database mailbox) {
        Mustache mHTML = this.factory.compile(new StringReader(this.htmlMessage), "eMailHTML");
        Mustache mText = this.factory.compile(new StringReader(this.textMessage), "eMailPlain");
        for (Map<String, String> oneRecord : this.rcpData.values()) {
            this.sendOneMessage(s, database, mailbox, mHTML, mText, oneRecord);
        }
    }

    private void processRawAdresses(String raw) {
        ByteArrayInputStream in = new ByteArrayInputStream(raw.getBytes());
        boolean firstLine = true;
        Scanner scanner = new Scanner(in);

        while (scanner.hasNextLine()) {
            if (firstLine) {
                this.rcpData.clear();
                this.getFields(this.fields, scanner.nextLine());
                firstLine = false;
            } else {
                this.addOneDataLine(this.rcpData, this.fields, scanner.nextLine());
            }
        }
        this.statusMessages.append(String.format("%x Records loaded and ready\n", this.rcpData.size()));
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sendOneMessage(Session s, Database database, Database mailbox, Mustache mHTML, Mustache mText,
                Map<String, String> oneRecord) {

        // Text as HTML and as plain Text
        StringWriter htmlBody = new StringWriter();
        StringWriter textBody = new StringWriter();
        mHTML.execute(htmlBody, oneRecord);
        mText.execute(textBody, oneRecord);
        try {
            Document mail = database.createDocument();
            String rcpt = oneRecord.get(this.eMailField);
            mail.replaceItemValue("Form", "Memo");
            this.populateMail(s, mail, htmlBody.toString(), textBody.toString(), rcpt);
            mail.save();
            if (mailbox != null) {
                mail.copyToDatabase(mailbox);
            }
            mail.recycle();
            this.statusMessages.append("Mail send to ");
            this.statusMessages.append(rcpt);
            this.statusMessages.append("\n");
        } catch (Exception <e) {
            this.statusMessages.append(e.getMessage());
            this.statusMessages.append("\n");
        }
    }

    public void setEmailField(String mailField) {
        this.eMailField = mailField;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public void setHtmlMessage(String htmlMessage) {
        this.htmlMessage = htmlMessage;
    }

    public void setRawAddresses(String rawAddresses) {
        if (rawAddresses != null && !rawAddresses.equals(this.rawAddresses)) {
            this.processRawAdresses(rawAddresses);
            this.rawAddresses = rawAddresses;
        }
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public void setTextMessage(String textMessage) {
        this.textMessage = textMessage;
    }

}

As usual YMMV

Posted by on 13 December 2014 1 comment categories: IBM Notes

Add your comment…

Comments

  1. posted by Stephan H. Wissel on Tuesday 30 December 2014 AD:

    Of course nobody would manually call the command line (**we wouldn't**). However in a batch file that might come handy.