All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.commons.mail.MailTemplate Maven / Gradle / Ivy

/*
 * Copyright 1997-2010 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.cq.commons.mail;

import com.day.cq.commons.jcr.JcrConstants;

import com.day.cq.commons.mail.impl.HtmlParserAccessor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.sling.commons.html.HtmlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;

/**
 * The MessageTemplate class provides email text templating functionality. Templates are
 * nt:file nodes in the repository representing a text file.
 * 

* The text file contains the complete email. Email headers are defined as the first lines of the file in the format * Header-Name: Header-Value, one header per line. Headers supporting multiple values can thus have several * header lines. The supported headers are the standard email headers. *

* After the last header line put an empty line and start the email body afterwards. *

* Within all of the text file, replacement variables can be used in the form of ${variable}, e.g. * ${payload.title}. The available variables are defined by the variable resolver given in the {@link * #getEmail(org.apache.commons.lang.text.StrLookup, Class)} method. *

* The getEmail method returns the chosen (type argument) email implementation, as long as the * type extends {@link org.apache.commons.mail.Email} and has a publically accessible default constructor. Out of the * box the three email implementations provided by the Apache Commons Email library can be used: {@link * org.apache.commons.mail.SimpleEmail}, {@link org.apache.commons.mail.HtmlEmail} and {@link * org.apache.commons.mail.MultiPartEmail}. * */ public class MailTemplate { private static final String HEADER_TO = "To"; private static final String HEADER_CC = "CC"; private static final String HEADER_BCC = "BCC"; private static final String HEADER_REPLYTO = "Reply-To"; private static final String HEADER_FROM = "From"; private static final String HEADER_SUBJECT = "Subject"; private static final String HEADER_BOUNCETO = "Bounce-To"; private static final String[] PRIMARY_HEADERS = new String[]{ HEADER_TO, HEADER_CC, HEADER_BCC, HEADER_REPLYTO, HEADER_FROM, HEADER_SUBJECT, HEADER_BOUNCETO }; private static final String DEFAULT_CHARSET = "utf-8"; private static final Logger log = LoggerFactory.getLogger(MailTemplate.class); private String message; private String charset; /** * Constructs a new MailTemplate with the template text given as the inputStream and the * encoding (may be null). * * @param inputStream The template text. * @param encoding The encoding of the input stream (may be null). If empty, UTF-8 will be used. * * @throws IOException If an error occurs handling the input stream. */ public MailTemplate(final InputStream inputStream, final String encoding) throws IOException { if (null == inputStream) { throw new IllegalArgumentException("input stream may not be null"); } charset = StringUtils.defaultIfEmpty(encoding, DEFAULT_CHARSET); final InputStreamReader reader = new InputStreamReader(inputStream, charset); final StringWriter writer = new StringWriter(); IOUtils.copy(reader, writer); message = writer.toString(); } /** * Create an {@link org.apache.commons.mail.Email} based on the template text and replacing variables in the * template text using the given lookup implementation. Emails are constructed and returned with the * given type. If the type is {@link org.apache.commons.mail.HtmlEmail} (or a subclass), * and the template content appears to be HTML, it is used as the HTML part of the email message. The text part of * the email message is constructed from doing a basic HTML to plain text conversion. * * @param lookup The {@link org.apache.commons.lang.text.StrLookup} implementation to use for variable lookup. * @param type The class defining the email type. * @param The email type. * * @return An email based on the template text and the variable resolver. * * @throws IOException If an error occurs handling the text template. * @throws MessagingException If an error occurs during building the email message. * @throws EmailException If an error occurs during building the email. */ @SuppressWarnings("rawtypes") public T getEmail(final StrLookup lookup, final Class type) throws IOException, MessagingException, EmailException { if (null == lookup) { throw new IllegalArgumentException("lookup may not be null"); } final StrSubstitutor substitutor = new StrSubstitutor(lookup); final String source = substitutor.replace(message); final ByteArrayInputStream in = new ByteArrayInputStream( source.getBytes(charset)); final InternetHeaders headers = new InternetHeaders(in); T email = null; try { final Constructor constructor = type.getConstructor(); email = constructor.newInstance(); email.setCharset(charset); // read the remainder of the original message and use as the // email's body. any header's at the beginning of the // stream will have already been read, so anything remaining // will be the email message content. final StringWriter writer = new StringWriter(); IOUtils.copy(in, writer, charset); final String msg = writer.toString(); HtmlParser parser = HtmlParserAccessor.HTML_PARSER_INSTANCE; if (email instanceof HtmlEmail && parser != null && isHtmlMessage(msg)) { final HtmlEmail htmlEmail = (HtmlEmail) email; try { final PlainTextExtractor plainTextExtractor = new PlainTextExtractor(); parser.parse(new ByteArrayInputStream(msg.getBytes(charset)), charset, plainTextExtractor); htmlEmail.setTextMsg(plainTextExtractor.toString()); htmlEmail.setHtmlMsg(msg); } catch (SAXException e) { email.setMsg(msg); } } else { email.setMsg(msg); } // add primary headers (sending relevant) final Enumeration primaryHeaders = headers.getMatchingHeaders(PRIMARY_HEADERS); while (primaryHeaders.hasMoreElements()) { final Header header = (Header) primaryHeaders.nextElement(); final String name = header.getName(); final String value = header.getValue(); if (null != value) { if (HEADER_TO.equalsIgnoreCase(name)) { email.addTo(value); } else if (HEADER_CC.equalsIgnoreCase(name)) { email.addCc(value); } else if (HEADER_BCC.equalsIgnoreCase(name)) { email.addBcc(value); } else if (HEADER_REPLYTO.equalsIgnoreCase(name)) { email.addReplyTo(value); } else if (HEADER_FROM.equalsIgnoreCase(name)) { email.setFrom(value); } else if (HEADER_SUBJECT.equalsIgnoreCase(name)) { email.setSubject(value); } else if (HEADER_BOUNCETO.equalsIgnoreCase(name)) { email.setBounceAddress(value); } } else { log.warn("got empty primary header [{}].", name); } } // add secondary headers final Enumeration secondaryHeaders = headers.getNonMatchingHeaders(PRIMARY_HEADERS); while (secondaryHeaders.hasMoreElements()) { final Header header = (Header) secondaryHeaders.nextElement(); final String name = header.getName(); final String value = header.getValue(); if (null != value) { email.addHeader(name, value); } else { log.warn("got empty secondary header [{}].", name); } } } catch (NoSuchMethodException e) { // ignore } catch (InvocationTargetException e) { // ignore } catch (InstantiationException e) { // ignore } catch (IllegalAccessException e) { // ignore } return email; } /** * Convenience method to create a new {@link com.day.cq.commons.mail.MailTemplate} based on the path * identifying the location of the email template text in the repository. * * @param path The location of the email template text in the repository. Must point to an nt:file node. * @param session The session used for accessing the repository. * * @return A mail template or null if there was an error creating the template. */ public static MailTemplate create(final String path, final Session session) { if (StringUtils.isBlank(path)) { throw new IllegalArgumentException("path may not be null or empty"); } if (null == session) { throw new IllegalArgumentException("session may not be null"); } InputStream is = null; try { if (session.itemExists(path)) { final Node node = session.getNode(path); if (JcrConstants.NT_FILE.equals(node.getPrimaryNodeType().getName())) { final Node content = node.getNode(JcrConstants.JCR_CONTENT); final String encoding = (content.hasProperty(JcrConstants.JCR_ENCODING)) ? content.getProperty(JcrConstants.JCR_ENCODING).getString() : "utf-8"; is = content.getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); log.debug("loaded template [{}].", path); return new MailTemplate(is, encoding); } else { throw new IllegalArgumentException("provided path does not point to a nt:file node"); } } } catch (RepositoryException e) { log.error("error creating message template: ", e); } catch (IOException e) { log.error("error creating message template: ", e); } finally { IOUtils.closeQuietly(is); } return null; } /* * This is perhaps an inelegant way to determine this, but Sling's Commons HTML parser is too lenient and will * create false positives. */ private static boolean isHtmlMessage(final String msg) { return msg.toLowerCase().indexOf("html") > -1; } private static class PlainTextExtractor implements ContentHandler { private boolean append = false; private StringBuilder buffer = new StringBuilder(); private String linkText; @Override public void setDocumentLocator(Locator locator) { } @Override public void startDocument() throws SAXException { } @Override public void endDocument() throws SAXException { } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { } @Override public void endPrefixMapping(String prefix) throws SAXException { } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { if ("body".equals(localName)) { append = true; } else if ("li".equals(localName)) { buffer.append("\n * "); } else if ("dt".equals(localName)) { buffer.append(" "); } else if ("p".equals(localName) || "tr".equals(localName) || localName.matches("h[1-5]")) { buffer.append("\n"); } else if ("a".equals(localName)) { final String href = atts.getValue("href"); if (href != null) { linkText = String.format(" <%s>", href); } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if ("body".equals(localName)) { append = false; } else if ("br".equals(localName) || "p".equals(localName) || "tr".equals(localName) || localName.matches("h[1-5]")) { buffer.append("\n"); } else if ("a".equals(localName) && linkText != null) { buffer.append(linkText); linkText = null; } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (append) { buffer.append(ch, start, length); } } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { } @Override public void processingInstruction(String target, String data) throws SAXException { } @Override public void skippedEntity(String name) throws SAXException { } @Override public String toString() { return buffer.toString().trim(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy