org.pentaho.reporting.platform.plugin.SimpleEmailComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.platform.plugin.SimpleEmailComponent.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software 
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this 
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 
 * or from the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * Copyright 2010-2013 Pentaho Corporation.  All rights reserved.
 */

package org.pentaho.reporting.platform.plugin;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Node;
import org.pentaho.platform.api.engine.IAcceptsRuntimeInputs;
import org.pentaho.platform.api.repository.IContentItem;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.util.messages.LocaleHelper;
import org.pentaho.reporting.platform.plugin.messages.Messages;

/**
 * Creation-Date: 27.09.2009, 00:30:00
 *
 * @author Pedro Alves - WebDetails
 */
public class SimpleEmailComponent implements IAcceptsRuntimeInputs {

    /**
     * The logging for logging messages from this component
     */
    private static final Log log = LogFactory.getLog(SimpleEmailComponent.class);
    private static final String MAILER = "smtpsend"; //$NON-NLS-1$
    private Map<String, Object> inputs;
    private String outputType;

    // Inputs
    public static final String INPUT_TO = "to";
    public static final String INPUT_FROM = "from";
    public static final String INPUT_CC = "cc";
    public static final String INPUT_BCC = "bcc";
    public static final String INPUT_SUBJECT = "subject";
    public static final String INPUT_MESSAGEPLAIN = "message-plain";
    public static final String INPUT_MESSAGEHTML = "message-html";
    public static final String INPUT_MIMEMESSAGE = "mime-message";

    // beans
    private String to;
    private String from;
    private String cc;
    private String bcc;
    private String subject;
    private Object messagePlain;
    private Object messageHtml;
    private IContentItem mimeMessage;
    private String attachmentName;
    private IContentItem attachmentContent;
    private String attachmentName2;
    private IContentItem attachmentContent2;
    private String attachmentName3;

    /*
     * Default constructor
     */
    public SimpleEmailComponent() {
    }

    // ----------------------------------------------------------------------------
    // BEGIN BEAN METHODS
    // ----------------------------------------------------------------------------
    public String getBcc() {
        return bcc;
    }

    public void setBcc(final String bcc) {
        this.bcc = bcc;
    }

    public String getCc() {
        return cc;
    }

    public void setCc(final String cc) {
        this.cc = cc;
    }

    public String getFrom() {
        return from;
    }

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

    public String getSubject() {
        return subject;
    }

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

    public String getTo() {
        return to;
    }

    public void setTo(final String to) {
        this.to = to;
    }

    public Object getMessageHtml() {
        return messageHtml;
    }

    public void setMessageHtml(final Object messageHtml) {
        this.messageHtml = messageHtml;
    }

    public Object getMessagePlain() {
        return messagePlain;
    }

    public void setMessagePlain(final Object messagePlain) {
        this.messagePlain = messagePlain;
    }

    public IContentItem getMimeMessage() {
        return mimeMessage;
    }

    public void setMimeMessage(final IContentItem mimeMessage) {
        this.mimeMessage = mimeMessage;
    }

    public IContentItem getAttachmentContent() {
        return attachmentContent;
    }

    public void setAttachmentContent(final IContentItem attachmentContent) {
        this.attachmentContent = attachmentContent;
    }

    public String getAttachmentName() {
        return attachmentName;
    }

    public void setAttachmentName(final String attachmentName) {
        this.attachmentName = attachmentName;
    }

    public IContentItem getAttachmentContent2() {
        return attachmentContent2;
    }

    public void setAttachmentContent2(final IContentItem attachmentContent2) {
        this.attachmentContent2 = attachmentContent2;
    }

    public IContentItem getAttachmentContent3() {
        return attachmentContent3;
    }

    public void setAttachmentContent3(final IContentItem attachmentContent3) {
        this.attachmentContent3 = attachmentContent3;
    }

    public String getAttachmentName2() {
        return attachmentName2;
    }

    public void setAttachmentName2(final String attachmentName2) {
        this.attachmentName2 = attachmentName2;
    }

    public String getAttachmentName3() {
        return attachmentName3;
    }

    public void setAttachmentName3(final String attachmentName3) {
        this.attachmentName3 = attachmentName3;
    }

    private IContentItem attachmentContent3;

    /**
     * Sets the mime-type for determining which report output type to generate. This should be a mime-type for consistency with streaming output mime-types.
     *
     * @param outputType the desired output type (mime-type) for the report engine to generate
     */
    public void setOutputType(final String outputType) {
        this.outputType = outputType;
    }

    /**
     * Gets the output type, this should be a mime-type for consistency with streaming output mime-types.
     *
     * @return the current output type for the report
     */
    public String getOutputType() {
        return outputType;
    }

    /**
     * This method sets the map of *all* the inputs which are available to this component. This allows us to use action-sequence inputs as parameters for our
     * reports.
     *
     * @param inputs a Map containing inputs
     */
    public void setInputs(final Map<String, Object> inputs) {
        this.inputs = inputs;

        if (inputs.containsKey(INPUT_FROM)) {
            setFrom((String) inputs.get(INPUT_FROM));
        }
        if (inputs.containsKey(INPUT_TO)) {
            setTo((String) inputs.get(INPUT_TO));
        }
        if (inputs.containsKey(INPUT_CC)) {
            setCc((String) inputs.get(INPUT_CC));
        }
        if (inputs.containsKey(INPUT_BCC)) {
            setBcc((String) inputs.get(INPUT_BCC));
        }
        if (inputs.containsKey(INPUT_SUBJECT)) {
            setSubject((String) inputs.get(INPUT_SUBJECT));
        }
        if (inputs.containsKey(INPUT_MESSAGEPLAIN)) {
            setMessagePlain((String) inputs.get(INPUT_MESSAGEPLAIN));
        }
        if (inputs.containsKey(INPUT_MESSAGEHTML)) {
            setMessageHtml((String) inputs.get(INPUT_MESSAGEHTML));
        }
        if (inputs.containsKey(INPUT_MIMEMESSAGE)) {
            setMimeMessage((IContentItem) inputs.get(INPUT_MIMEMESSAGE));
        }
    }

    // ----------------------------------------------------------------------------
    // END BEAN METHODS
    // ----------------------------------------------------------------------------
    protected Object getInput(final String key, final Object defaultValue) {
        if (inputs != null) {
            final Object input = inputs.get(key);
            if (input != null) {
                return input;
            }
        }
        return defaultValue;
    }

    /**
     * This method will determine if the component instance 'is valid.' The validate() is called after all of the bean 'setters' have been called, so we may
     * validate on the actual values, not just the presence of inputs as we were historically accustomed to.
     * <p/>
     * Since we should have a list of all action-sequence inputs, we can determine if we have sufficient inputs to meet the parameter requirements
     * This would include validation of values and ranges of values.
     *
     * @return true if valid
     * @throws Exception
     */
    public boolean validate() throws Exception {

        boolean result = true;

        if (getTo() == null) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailToNotProvided")); //$NON-NLS-1$
            return false;

        } else if (getFrom() == null) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailFromNotProvided")); //$NON-NLS-1$
            return false;

        } else if ((getMessagePlain() == null) && (getMessageHtml() == null) && (getMimeMessage() == null)) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailContentNotProvided")); //$NON-NLS-1$
            result = false;
        }

        return result;

    }

    /**
     * Perform the primary function of this component, this is, to execute. This method will be invoked immediately following a successful validate().
     * <p/>
     * This method has 2 ways of working:
     * <p/>
     * 1. You supply a mimeMessage: That mimeMessage will be sent;
     * Optionally, contents will be added as attachment and the original
     * mimeMessage will be encapsulated under a multipart/mixed
     * <p/>
     * <p/>
     * 2. You supply a messageHtml and/or a messageText. A new mimemessage will be
     * built. If you supply both, a multipart/alternative will be used. After that
     * attachments will be included
     *
     * @return true if successful execution
     * @throws Exception
     */
    public boolean execute() throws Exception {

        try {

            // Get the session object
            final Session session = buildSession();

            // Create the message
            final MimeMessage msg = new MimeMessage(session);

            // From, to, etc.
            applyMessageHeaders(msg);

            // Get main message multipart
            final Multipart multipartBody = getMultipartBody(session);

            // Process attachments
            final Multipart mainMultiPart = processAttachments(multipartBody);
            msg.setContent(mainMultiPart);

            // Send it

            msg.setHeader("X-Mailer", MAILER); //$NON-NLS-1$
            msg.setSentDate(new Date());

            Transport.send(msg);

            return true;

        } catch (SendFailedException e) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailSendFailed")); //$NON-NLS-1$
        } catch (AuthenticationFailedException e) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailAuthenticationFailed")); //$NON-NLS-1$
        }
        return false;

    }

    private Multipart getMultipartBody(final Session session) throws MessagingException, IOException {

        // if we have a mimeMessage, use it. Otherwise, build one with what we have
        // We can have both a messageHtml and messageText. Build according to it

        MimeMultipart parentMultipart = new MimeMultipart();
        MimeBodyPart htmlBodyPart = null, textBodyPart = null;

        if (getMimeMessage() != null) {

            // Rebuild a MimeMessage and use this one

            final MimeBodyPart original = new MimeBodyPart();
            final MimeMessage originalMimeMessage = new MimeMessage(session, getMimeMessage().getInputStream());
            final MimeMultipart relatedMultipart = (MimeMultipart) originalMimeMessage.getContent();

            parentMultipart = relatedMultipart;

            htmlBodyPart = new MimeBodyPart();
            htmlBodyPart.setContent(relatedMultipart);

        }

        // The information we have in the mime-message overrides the getMessageHtml.
        if (getMessageHtml() != null && htmlBodyPart != null) {

            final String content = getInputString(getMessageHtml());

            htmlBodyPart = new MimeBodyPart();
            htmlBodyPart.setContent(content, "text/html; charset=" + LocaleHelper.getSystemEncoding());
            final MimeMultipart htmlMultipart = new MimeMultipart();
            htmlMultipart.addBodyPart(htmlBodyPart);

            parentMultipart = htmlMultipart;
        }

        if (getMessagePlain() != null) {

            final String content = getInputString(getMessagePlain());

            textBodyPart = new MimeBodyPart();
            textBodyPart.setContent(content, "text/plain; charset=" + LocaleHelper.getSystemEncoding());
            final MimeMultipart textMultipart = new MimeMultipart();
            textMultipart.addBodyPart(textBodyPart);

            parentMultipart = textMultipart;
        }

        // We have both text and html? Encapsulate it in a multipart/alternative

        if (htmlBodyPart != null && textBodyPart != null) {

            final MimeMultipart alternative = new MimeMultipart("alternative");
            alternative.addBodyPart(textBodyPart);
            alternative.addBodyPart(htmlBodyPart);

            parentMultipart = alternative;

        }

        return parentMultipart;

    }

    private String getInputString(final Object param) throws IOException {

        if (param instanceof String) {
            return (String) param;
        } else if (param instanceof IContentItem) {

            final InputStream in = ((IContentItem) param).getInputStream();
            // Convert to String
            final ByteArrayOutputStream out = new ByteArrayOutputStream();

            int nextChar;
            while ((nextChar = in.read()) != -1) {
                out.write(nextChar);
            }

            return new String(out.toString(LocaleHelper.getSystemEncoding()));

        }

        throw new IllegalStateException("Input is not a String or ContentItem");

    }

    private Multipart processAttachments(final Multipart multipartBody) throws MessagingException, IOException {

        if (getAttachmentContent() == null) {

            // We don't have a first attachment, won't even search for the others.
            return multipartBody;

        }

        // We have attachments; Creating a multipart-mixed

        final MimeMultipart mixedMultipart = new MimeMultipart("mixed");

        // Add the first part
        final MimeBodyPart bodyPart = new MimeBodyPart();
        bodyPart.setContent(multipartBody);
        mixedMultipart.addBodyPart(bodyPart);

        // Process each of the attachments we have
        processSpecificAttachment(mixedMultipart, getAttachmentContent());
        processSpecificAttachment(mixedMultipart, getAttachmentContent2());
        processSpecificAttachment(mixedMultipart, getAttachmentContent3());

        return mixedMultipart;

    }

    private void processSpecificAttachment(final MimeMultipart mixedMultipart, final IContentItem attachmentContent)
            throws IOException, MessagingException {

        // Add this attachment

        if (attachmentContent != null) {

            final ByteArrayDataSource dataSource = new ByteArrayDataSource(attachmentContent.getInputStream(),
                    attachmentContent.getMimeType());
            final MimeBodyPart attachmentBodyPart = new MimeBodyPart();
            attachmentBodyPart.setDataHandler(new DataHandler(dataSource));
            attachmentBodyPart.setFileName(getAttachmentName());
            mixedMultipart.addBodyPart(attachmentBodyPart);

        }

    }

    private void applyMessageHeaders(final MimeMessage msg) throws Exception {

        msg.setFrom(new InternetAddress(from));
        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));

        if ((cc != null) && (cc.trim().length() > 0)) {
            msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false));
        }
        if ((bcc != null) && (bcc.trim().length() > 0)) {
            msg.setRecipients(Message.RecipientType.BCC, InternetAddress.parse(bcc, false));
        }

        if (subject != null) {
            msg.setSubject(subject, LocaleHelper.getSystemEncoding());
        }

    }

    private Session buildSession() throws Exception {

        final Properties props = new Properties();

        try {
            final Document configDocument = PentahoSystem.getSystemSettings()
                    .getSystemSettingsDocument("smtp-email/email_config.xml"); //$NON-NLS-1$
            final List properties = configDocument.selectNodes("/email-smtp/properties/*"); //$NON-NLS-1$
            final Iterator propertyIterator = properties.iterator();
            while (propertyIterator.hasNext()) {
                final Node propertyNode = (Node) propertyIterator.next();
                final String propertyName = propertyNode.getName();
                final String propertyValue = propertyNode.getText();
                props.put(propertyName, propertyValue);
            }
        } catch (Exception e) {
            log.error(Messages.getInstance().getString("ReportPlugin.emailConfigFileInvalid")); //$NON-NLS-1$
            throw e;
        }

        final boolean authenticate = "true".equals(props.getProperty("mail.smtp.auth")); //$NON-NLS-1$//$NON-NLS-2$

        // Get a Session object

        final Session session;
        if (authenticate) {
            final Authenticator authenticator = new EmailAuthenticator();
            session = Session.getInstance(props, authenticator);
        } else {
            session = Session.getInstance(props);
        }

        // if debugging is not set in the email config file, match the
        // component debug setting
        if (!props.containsKey("mail.debug")) { //$NON-NLS-1$
            session.setDebug(true);
        }

        return session;

    }

    /**
     * This method returns the output-type for the streaming output, it is the same as what is returned by getOutputType() for consistency.
     *
     * @return the mime-type for the streaming output
     */
    public String getMimeType() {
        return outputType;
    }

    private static class EmailAuthenticator extends Authenticator {

        private EmailAuthenticator() {
        }

        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            final String user = PentahoSystem.getSystemSetting("smtp-email/email_config.xml", "mail.userid", null); //$NON-NLS-1$ //$NON-NLS-2$
            final String password = PentahoSystem.getSystemSetting("smtp-email/email_config.xml", "mail.password", //$NON-NLS-1$//$NON-NLS-2$
                    null);
            return new PasswordAuthentication(user, password);
        }
    }
}