org.jresponder.message.MessageRefImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jresponder.message.MessageRefImpl.java

Source

/**
 * =========================================================================
 *     __ ____   ____  __  ____    ___   __  __ ____    ____ ____ 
 *     || || \\ ||    (( \ || \\  // \\  ||\ || || \\  ||    || \\
 *     || ||_// ||==   \\  ||_// ((   )) ||\\|| ||  )) ||==  ||_//
 *  |__|| || \\ ||___ \_)) ||     \\_//  || \|| ||_//  ||___ || \\
 * =========================================================================
 *
 * Copyright 2012 Brad Peabody
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * =========================================================================
 */
package org.jresponder.message;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import org.joda.time.format.ISOPeriodFormat;
import org.joda.time.format.PeriodFormatter;
import org.jresponder.domain.Subscriber;
import org.jresponder.domain.Subscription;
import org.jresponder.engine.SendConfig;
import org.jresponder.util.TextRenderUtil;
import org.jresponder.util.TextUtil;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.MimeMessageHelper;

/**
 * Default MessageRef implementation - reads messages from a file on disk,
 * renders contents using Velocity
 * 
 * @author bradpeabody
 *
 */
public class MessageRefImpl implements MessageRef {

    /* ====================================================================== */
    /* Logger boiler plate                                                    */
    /* ====================================================================== */
    private static Logger l = null;

    private Logger logger() {
        if (l == null)
            l = LoggerFactory.getLogger(this.getClass());
        return l;
    }
    /* ====================================================================== */

    private String name;
    private File file;
    private String fileContents;
    private long fileContentsTimestamp = 0;
    private Document document;
    private Map<String, String> propMap;

    /**
     * Default constructor - make sure to call setFile() and then refresh()
     */
    public MessageRefImpl() {

    }

    /**
     * Constructor which calls setFile() and refresh() for you
     * @param aFile
     * @throws InvalidMessageException
     */
    public MessageRefImpl(File aFile) throws InvalidMessageException {
        setFile(aFile);
        refresh();
    }

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

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
        // id is set to file name without .html extension
        name = file.getName().replaceAll("[.]html$", "");
    }

    public String getFileContents() {
        return fileContents;
    }

    @Override
    public synchronized void refresh() throws InvalidMessageException {

        try {

            logger().debug("MessageRef - Starting refresh for: {}", file.getCanonicalPath());

            // set timestamp
            fileContentsTimestamp = file.lastModified();

            StringBuilder myStringBuilder = new StringBuilder();
            char[] buf = new char[4096];
            BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            int len;
            while ((len = r.read(buf)) > 0) {
                myStringBuilder.append(buf, 0, len);
            }
            r.close();

            fileContents = myStringBuilder.toString();

            document = Jsoup.parse(fileContents, "UTF-8");

            propMap = new HashMap<String, String>();

            Elements myMetaTagElements = document.select("meta");
            if (myMetaTagElements == null || myMetaTagElements.isEmpty()) {
                throw new InvalidMessageException("No meta tags found in file: " + file.getCanonicalPath());
            }

            for (Element myPropElement : myMetaTagElements) {
                String myName = myPropElement.attr("name");
                String myValue = myPropElement.attr("content");
                propMap.put(myName, myValue);
            }

            // bodies are not read at all until message generation time

        } catch (IOException e) {
            throw new InvalidMessageException(e);
        }

        // debug dump
        if (logger().isDebugEnabled()) {
            for (String myKey : propMap.keySet()) {
                logger().debug("  property -- {}: {}", myKey, (propMap.get(myKey)));
            }
        }
    }

    /**
     * Get a property by string name
     */
    @Override
    public String getProp(String aName) {
        return propMap.get(aName);
    }

    /**
     * Type-safe version of getProp
     */
    @Override
    public String getProp(MessageRefProp aName) {
        return getProp(aName.toString());
    }

    /**
     * Get all property names
     */
    @Override
    public List<String> getPropNames() {
        return new ArrayList<String>(propMap.keySet());
    }

    /**
     * Gets the JR_WAIT_AFTER_LAST_MESSAGE property and parses it as an
     * ISO8601 duration and returns the number of milliseconds it represents.
     * Returns null if not set.
     * @throws IllegalArgumentException if the value is in an invalid format
     * @return
     */
    @Override
    public Long getWaitAfterLastMessage() {

        // try to parse time from message
        String myWaitAfterLastMessageString = this.getProp(MessageRefProp.JR_WAIT_AFTER_LAST_MESSAGE);
        if (myWaitAfterLastMessageString == null) {
            logger().debug("No JR_WAIT_AFTER_LAST_MESSAGE property found on message (message={})", getName());
            return null;
        }

        PeriodFormatter myPeriodFormatter = ISOPeriodFormat.standard();
        try {
            long myDuration = myPeriodFormatter.parsePeriod(myWaitAfterLastMessageString).toStandardDuration()
                    .getMillis();
            return myDuration;
        } catch (IllegalArgumentException e) {
            logger().error(
                    "Unable to parse JR_WAIT_AFTER_LAST_MESSAGE value for (message={}), value was: \"{}\"  (this is a problem you need to fix!!! e.g. for one day, use \"P1D\")",
                    new Object[] { getName(), myWaitAfterLastMessageString });
            throw new IllegalArgumentException(
                    "Error parsing JR_WAIT_AFTER_LAST_MESSAGE value: " + myWaitAfterLastMessageString, e);
        }

    }

    /**
     * Render a message in the context of a particular subscriber
     * and subscription.
     */
    @Override
    public boolean populateMessage(MimeMessage aMimeMessage, SendConfig aSendConfig, Subscriber aSubscriber,
            Subscription aSubscription) {

        try {

            // prepare context
            Map<String, Object> myRenderContext = new HashMap<String, Object>();
            myRenderContext.put("subscriber", aSubscriber);
            myRenderContext.put("subscription", aSubscription);
            myRenderContext.put("config", aSendConfig);
            myRenderContext.put("message", this);

            // render the whole file
            String myRenderedFileContents = TextRenderUtil.getInstance().render(fileContents, myRenderContext);

            // now parse again with Jsoup
            Document myDocument = Jsoup.parse(myRenderedFileContents);

            String myHtmlBody = "";
            String myTextBody = "";

            // html body
            Elements myBodyElements = myDocument.select("#htmlbody");
            if (!myBodyElements.isEmpty()) {
                myHtmlBody = myBodyElements.html();
            }

            // text body
            Elements myJrTextBodyElements = myDocument.select("#textbody");
            if (!myJrTextBodyElements.isEmpty()) {
                myTextBody = TextUtil.getInstance().getWholeText(myJrTextBodyElements.first());
            }

            // now build the actual message
            MimeMessage myMimeMessage = aMimeMessage;
            // wrap it in a MimeMessageHelper - since some things are easier with that
            MimeMessageHelper myMimeMessageHelper = new MimeMessageHelper(myMimeMessage);

            // set headers

            // subject
            myMimeMessageHelper.setSubject(TextRenderUtil.getInstance()
                    .render((String) propMap.get(MessageRefProp.JR_SUBJECT.toString()), myRenderContext));

            // TODO: implement DKIM, figure out subetha

            String mySenderEmailPattern = aSendConfig.getSenderEmailPattern();
            String mySenderEmail = TextRenderUtil.getInstance().render(mySenderEmailPattern, myRenderContext);
            myMimeMessage.setSender(new InternetAddress(mySenderEmail));

            myMimeMessageHelper.setTo(aSubscriber.getEmail());

            // from
            myMimeMessageHelper.setFrom(
                    TextRenderUtil.getInstance()
                            .render((String) propMap.get(MessageRefProp.JR_FROM_EMAIL.toString()), myRenderContext),
                    TextRenderUtil.getInstance()
                            .render((String) propMap.get(MessageRefProp.JR_FROM_NAME.toString()), myRenderContext));

            // see how to set body

            // if we have both text and html, then do multipart
            if (myTextBody.trim().length() > 0 && myHtmlBody.trim().length() > 0) {

                // create wrapper multipart/alternative part
                MimeMultipart ma = new MimeMultipart("alternative");
                myMimeMessage.setContent(ma);
                // create the plain text
                BodyPart plainText = new MimeBodyPart();
                plainText.setText(myTextBody);
                ma.addBodyPart(plainText);
                // create the html part
                BodyPart html = new MimeBodyPart();
                html.setContent(myHtmlBody, "text/html");
                ma.addBodyPart(html);
            }

            // if only HTML, then just use that
            else if (myHtmlBody.trim().length() > 0) {
                myMimeMessageHelper.setText(myHtmlBody, true);
            }

            // if only text, then just use that
            else if (myTextBody.trim().length() > 0) {
                myMimeMessageHelper.setText(myTextBody, false);
            }

            // if neither text nor HTML, then the message is being skipped,
            // so we just return null
            else {
                return false;
            }

            return true;

        } catch (MessagingException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public synchronized boolean conditionalRefresh() throws InvalidMessageException {

        if (file.lastModified() != fileContentsTimestamp) {
            refresh();
            return true;
        }
        return false;

    }

}