mitm.application.djigzo.james.mailets.SendMailTemplate.java Source code

Java tutorial

Introduction

Here is the source code for mitm.application.djigzo.james.mailets.SendMailTemplate.java

Source

/*
 * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.application.djigzo.james.mailets;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.UUID;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

import mitm.application.djigzo.james.MailAddressUtils;
import mitm.application.djigzo.james.MailetUtils;
import mitm.application.djigzo.james.SystemMailAttributes;
import mitm.application.djigzo.service.SystemServices;
import mitm.common.mail.MailUtils;
import mitm.common.template.TemplateBuilder;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrBuilder;
import org.apache.james.core.MailImpl;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModelException;

/**
 * SendMailTemplate mailet sends an email using a template (a Freemarker template) as the base for the email to all recipients.
 *
 * The following template parameters are supported:
 * 
 * subject    : Set to subject of source mail (Address)
 * from       : Set to from of source mail (only the first from is used) (Address)
 * replyTo    : Set to replyTo of source mail (only the first replyTo is used) (Address)
 * to         : Set to 'to' of the source mail (collection of Address)
 * cc         : Set to 'cc' of the source mail (collection of Address)
 * sender     : Set to the sender of the mail (MailAddress)
 * recipients : Set to recipients of the source mail (collection of MailAddress)
 * body       : Set to null
 * date       : Current date (Date)
 * mail       : The source mail (Mail)
 * 
 * Supported mailet parameters:
 * 
 * template                : Path to the Freemarker template file. The file must exist (no default, must be specified)
 * processor               : The next processor for the new mail (default: root)
 * passThrough             : If false the source message will be removed (ghost'ed)
 * passThroughProcessor    : If passThrough is true the pass through message next state will be set to passThroughProcessor
 * catchRuntimeExceptions  : If true all RunTimeExceptions are caught (default: true)
 * catchErrors             : If true all Errors are caught (default: true)
 * 
 * @author Martijn Brinkers
 *
 */
public class SendMailTemplate extends AbstractDjigzoMailet {
    private final static Logger logger = LoggerFactory.getLogger(SendMailTemplate.class);

    /*
     * General SendMailTemplate Freemarker template parameter names
     */
    protected static final String SUBJECT_TEMPLATE_PARAM = "subject";
    protected static final String FROM_TEMPLATE_PARAM = "from";
    protected static final String REPLYTO_TEMPLATE_PARAM = "replyTo";
    protected static final String TO_TEMPLATE_PARAM = "to";
    protected static final String CC_TEMPLATE_PARAM = "cc";
    protected static final String SENDER_TEMPLATE_PARAM = "sender";
    protected static final String RECIPIENTS_TEMPLATE_PARAM = "recipients";
    protected static final String BODY_TEMPLATE_PARAM = "body";
    protected static final String DATE_TEMPLATE_PARAM = "date";
    protected static final String MAIL_TEMPLATE_PARAM = "mail";
    protected static final String BOUNDARY_TEMPLATE_PARAM = "boundary";

    /*
     * For building Freemarker templates
     */
    private TemplateBuilder templateBuilder;

    /*
     * The Freemarker template for the message.
     */
    private String templateFile;

    /*
     * The processor for the notification message.
     */
    private String processor = Mail.DEFAULT;

    /*
     * Should the SMTP extensions attributes be removed from the Mail after cloning.
     */
    private boolean removeSMTPExtensions;

    /*
     * If true, missing recipients are not considered to be an error
     */
    private boolean ignoreMissingRecipients;

    /*
     * If true the source message will continue to be used. If false the source message will
     * be ghost'ed.
     */
    private boolean passThrough = true;

    /*
     * If passThrough is true the pass through message next state will be set 
     * to passThroughProcessor. If null next state will not be changed.
     */
    private String passThroughProcessor;

    /*
     * Thrown when some internal method needs to report that there are no recipients.  
     */
    protected static class MissingRecipientsException extends MessagingException {
        private static final long serialVersionUID = 765760228227617458L;

        public MissingRecipientsException(String message) {
            super(message);
        }
    }

    /*
     * The mailet initialization parameters used by this mailet.
     */
    protected enum Parameter {
        TEMPLATE("template"), PROCESSOR("processor"), REMOVE_SMTP_EXTENSIONS(
                "removeSMTPExtensions"), IGNORE_MISSING_RECIPIENTS("ignoreMissingRecipients"), PASS_THROUGH(
                        "passThrough"), PASS_THROUGH_PROCESSOR("passThroughProcessor");

        private String name;

        private Parameter(String name) {
            this.name = name;
        }

        public static Parameter fromName(String name) {
            for (Parameter parameter : Parameter.values()) {
                if (parameter.name.equals(name)) {
                    return parameter;
                }
            }

            return null;
        }

        public String getName() {
            return name;
        }
    };

    @Override
    protected Logger getLogger() {
        return logger;
    }

    private void loadTemplate() {
        templateFile = getInitParameter(Parameter.TEMPLATE.name);

        if (templateFile == null) {
            throw new IllegalArgumentException("template must be specified.");
        }

        /*
        * Validate the template (if it can be found)
        */
        try {
            templateBuilder.getTemplate(templateFile);
        } catch (IOException e) {
            throw new IllegalArgumentException("Error loading the template.", e);
        }
    }

    protected boolean getInitRemoveSMTPExtensions() {
        return getBooleanInitParameter(Parameter.REMOVE_SMTP_EXTENSIONS.name, false);
    }

    protected boolean getIgnoreMissingRecipients() {
        return getBooleanInitParameter(Parameter.IGNORE_MISSING_RECIPIENTS.name, false);
    }

    @Override
    public void initMailet() throws MessagingException {
        getLogger().info("Initializing mailet: " + getMailetName());

        templateBuilder = SystemServices.getTemplateBuilder();

        loadTemplate();

        String param = getInitParameter(Parameter.PROCESSOR.name);

        if (param != null) {
            processor = param;
        }

        removeSMTPExtensions = getInitRemoveSMTPExtensions();

        ignoreMissingRecipients = getIgnoreMissingRecipients();

        param = getInitParameter(Parameter.PASS_THROUGH.name);

        if (param != null) {
            Boolean bool = BooleanUtils.toBooleanObject(param);

            if (bool == null) {
                throw new IllegalArgumentException(param + " is not a valid boolean.");
            }

            passThrough = bool;
        }

        passThroughProcessor = getInitParameter(Parameter.PASS_THROUGH_PROCESSOR.name);

        StrBuilder sb = new StrBuilder();

        sb.append("template: ");
        sb.append(templateFile);
        sb.appendSeparator("; ");
        sb.appendSeparator("; ");
        sb.append("processor: ");
        sb.append(processor);
        sb.appendSeparator("; ");
        sb.append("removeSMTPExtensions: ");
        sb.append(removeSMTPExtensions);
        sb.appendSeparator("; ");
        sb.append("ignoreMissingRecipients: ");
        sb.append(ignoreMissingRecipients);
        sb.appendSeparator("; ");
        sb.append("passThrough");
        sb.append(passThrough);
        sb.appendSeparator("; ");
        sb.append("passThroughProcessor");
        sb.append(passThroughProcessor);

        getLogger().info(sb.toString());
    }

    protected Address getFrom(Mail mail) throws MessagingException {
        Address from = null;

        try {
            Address[] froms = mail.getMessage().getFrom();

            from = (froms != null && froms.length > 0) ? froms[0] : null;
        } catch (MessagingException e) {
            logger.warn("From address is not a valid email address.");
        }

        return from;
    }

    protected Collection<MailAddress> getRecipients(Mail mail)
            throws MessagingException, MissingRecipientsException {
        return MailAddressUtils.getRecipients(mail);
    }

    protected Address getReplyTo(Mail mail) throws MessagingException {
        Address replyTo = null;

        try {
            Address[] replies = mail.getMessage().getReplyTo();

            replyTo = (replies != null && replies.length > 0) ? replies[0] : null;
        } catch (MessagingException e) {
            logger.warn("Reply-To address is not a valid email address");
        }

        return replyTo;
    }

    protected MailAddress getSender(Mail mail) throws MessagingException {
        return mail.getSender();
    }

    protected String getSubject(Mail mail) throws MessagingException {
        return mail.getMessage().getSubject();
    }

    protected Collection<Address> getTo(Mail mail) throws MessagingException {
        Collection<Address> to = new LinkedList<Address>();

        try {
            Address[] addresses = mail.getMessage().getRecipients(RecipientType.TO);

            if (addresses != null) {
                for (Address address : addresses) {
                    to.add(address);
                }
            }
        } catch (MessagingException e) {
            logger.warn("To is not a valid email address.");
        }

        return to;
    }

    protected Collection<Address> getCC(Mail mail) throws MessagingException {
        Collection<Address> cc = new LinkedList<Address>();

        try {
            Address[] addresses = mail.getMessage().getRecipients(RecipientType.CC);

            if (addresses != null) {
                for (Address address : addresses) {
                    cc.add(address);
                }
            }
        } catch (MessagingException e) {
            logger.warn("CC is not a valid email address.");
        }

        return cc;
    }

    protected String getBody(Mail mail) throws MessagingException {
        return null;
    }

    protected Date getDate(Mail mail) {
        return new Date();
    }

    /*
     * Creates a new unique message boundary which will be used for multipart messages.
     */
    private String createBoundary() {
        String boundary = "=-" + UUID.randomUUID().toString();

        return boundary;
    }

    /*
     * Duplicates the source mail
     */
    protected MailImpl duplicateMail(Mail source) throws MessagingException {
        /*
         * We need to create a new Mail object this way to make sure that all attributes are cloned.
         */
        return new MailImpl(source, MailetUtils.createUniqueMailName());
    }

    private void sendMail(Mail mail, MimeMessage notificationMessage, MailAddress sender,
            Collection<MailAddress> recipients) throws MessagingException, MissingRecipientsException {
        MailImpl newMail = duplicateMail(mail);

        if (removeSMTPExtensions) {
            SystemMailAttributes.removeSMTPExtensions(newMail);
        }

        try {
            newMail.setSender(sender);
            newMail.setRecipients(recipients);
            newMail.setMessage(notificationMessage);
            newMail.setState(processor);

            getMailetContext().sendMail(newMail);
        } finally {
            newMail.dispose();
        }
    }

    protected void sendMail(Mail mail)
            throws MessagingException, MissingRecipientsException, TemplateException, IOException {
        MimeMessage message = createMessage(mail, new SimpleHash());

        MailAddress sender = getSender(mail);
        Collection<MailAddress> recipients = getRecipients(mail);

        sendMail(mail, message, sender, recipients);
    }

    protected MimeMessage createMessage(Mail mail, SimpleHash root)
            throws MessagingException, MissingRecipientsException, TemplateException, IOException {
        String subject = getSubject(mail);
        Address from = getFrom(mail);
        Address replyTo = getReplyTo(mail);
        Collection<Address> to = getTo(mail);
        Collection<Address> cc = getCC(mail);
        MailAddress sender = getSender(mail);
        Collection<MailAddress> recipients = getRecipients(mail);
        String body = getBody(mail);
        Date date = getDate(mail);

        return createMessage(mail, subject, from, replyTo, to, cc, sender, recipients, body, date, root);
    }

    protected Template getTemplate(Mail mail, SimpleHash root) throws IOException, MessagingException {
        return templateBuilder.getTemplate(templateFile);
    }

    /*
     * Sets the root parameter if a value for key is not already available
     */
    private void setFreemarkerParam(SimpleHash root, String key, Object value) throws TemplateModelException {
        if (root.get(key) == null) {
            root.put(key, value);
        }
    }

    private MimeMessage createMessage(Mail mail, String subject, Address from, Address replyTo,
            Collection<Address> to, Collection<Address> cc, MailAddress sender, Collection<MailAddress> recipients,
            String body, Date date, SimpleHash root)
            throws MessagingException, MissingRecipientsException, TemplateException, IOException {
        setFreemarkerParam(root, SUBJECT_TEMPLATE_PARAM, subject);
        setFreemarkerParam(root, FROM_TEMPLATE_PARAM, from);
        setFreemarkerParam(root, REPLYTO_TEMPLATE_PARAM, replyTo);
        setFreemarkerParam(root, TO_TEMPLATE_PARAM, to);
        setFreemarkerParam(root, CC_TEMPLATE_PARAM, cc);
        setFreemarkerParam(root, SENDER_TEMPLATE_PARAM, sender);
        setFreemarkerParam(root, RECIPIENTS_TEMPLATE_PARAM, recipients);
        setFreemarkerParam(root, BODY_TEMPLATE_PARAM, body);
        setFreemarkerParam(root, DATE_TEMPLATE_PARAM, date);
        setFreemarkerParam(root, MAIL_TEMPLATE_PARAM, mail);
        setFreemarkerParam(root, BOUNDARY_TEMPLATE_PARAM, createBoundary());

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        Writer writer = new OutputStreamWriter(bos);

        getTemplate(mail, root).process(root, writer);

        MimeMessage templatedMessage = MailUtils.byteArrayToMessage(bos.toByteArray());

        /*
         * Make sure the message has a message-ID
         */
        templatedMessage.saveChanges();

        return templatedMessage;
    }

    protected void internalServiceMail(Mail mail)
            throws MessagingException, MissingRecipientsException, TemplateException, IOException {
        sendMail(mail);
    }

    @Override
    public void serviceMail(Mail mail) {
        try {
            try {
                internalServiceMail(mail);
            } catch (MissingRecipientsException e) {
                if (!ignoreMissingRecipients) {
                    throw e;
                }

                getLogger().debug("Ignoring MissingRecipientsException.", e);
            }

            if (!passThrough) {
                mail.setState(Mail.GHOST);
            } else if (StringUtils.isNotEmpty(passThroughProcessor)) {
                mail.setState(passThroughProcessor);
            }
        } catch (MissingRecipientsException e) {
            getLogger().error("Missing recipients.", e);
        } catch (MessagingException e) {
            getLogger().error("Unhandled exception.", e);
        } catch (TemplateException e) {
            getLogger().error("Error processing template.", e);
        } catch (IOException e) {
            getLogger().error("Error processing template.", e);
        }
    }

    /**
     * Returns the template builder service.
     */
    protected TemplateBuilder getTemplateBuilder() {
        return templateBuilder;
    }
}