com.blackducksoftware.tools.commonframework.standard.email.CFEmailNotifier.java Source code

Java tutorial

Introduction

Here is the source code for com.blackducksoftware.tools.commonframework.standard.email.CFEmailNotifier.java

Source

/*******************************************************************************
 * Copyright (C) 2016 Black Duck Software, Inc.
 * http://www.blackducksoftware.com/
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 com.blackducksoftware.tools.commonframework.standard.email;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.xml.security.exceptions.Base64DecodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.blackducksoftware.tools.commonframework.core.config.ConfigurationManager;
import com.blackducksoftware.tools.commonframework.core.config.EmailBean;
import com.blackducksoftware.tools.commonframework.core.exception.CommonFrameworkException;
import com.blackducksoftware.tools.commonframework.standard.common.ProjectPojo;

/**
 * Generic Email Notifier implementation for the Common Framework,
 *
 * Usage: Instantiate class with configuration manager Invoke configuration for
 * your template file and get a content map Send email with populated content
 * map
 *
 * @author Ari Kamen
 * @date June 15th, 2015
 *
 *
 */
public class CFEmailNotifier implements EmailNotifier {
    private final Logger log = LoggerFactory.getLogger(this.getClass().getName());

    private String smtpProtocol;

    private String smtpHost;

    private int smtpPort;

    private boolean smtpUseAuth;

    private String smtpUsername;

    private String smtpPassword;

    private String smtpFrom;

    private String smtpTo;

    private boolean ready = false;

    // Location of the user, one per location
    private String templateFileLocation;

    // This internal map is of all the variables defined in the template.
    // The user should retrieve this, set their values there and pass it back.
    private EmailContentMap emailContentMap;

    public CFEmailNotifier(ConfigurationManager config) throws Base64DecodingException {
        init(config);
    }

    /**
     * Populates the internal value map containing only those variables that
     * have specifically been provided in the template.
     *
     * @param projectInfo
     * @param templateString
     *            **Absolute Path** of the template file.
     * @throws CommonFrameworkException
     */
    @Override
    public EmailContentMap configureContentMap(String templateFileLocation) throws CommonFrameworkException {
        emailContentMap = new EmailContentMap();
        this.templateFileLocation = templateFileLocation;
        try {

            String templateBody = getBodyTemplate();

            // Run through our template and find all the variables
            // We are looking for all Velocity templates defined as: ${var}
            String regExPattern = "\\$\\{(\\s*?.*?)*?\\}";
            Pattern pattern = Pattern.compile(regExPattern);
            Matcher matcher = pattern.matcher(templateBody);

            // Find all matches
            while (matcher.find()) {
                // Get the matching string
                String match = matcher.group();
                String strippedDownVariable = StringUtils.substringBetween(match, "${", "}");
                log.debug("Found template match: " + strippedDownVariable);
                strippedDownVariable = strippedDownVariable.trim();
                emailContentMap.put(strippedDownVariable, null);
            }

        } catch (Exception e) {
            ready = false;
            log.error("Problem parsing template: " + e.getMessage());
        }

        return emailContentMap;

    }

    private void init(ConfigurationManager config) {
        EmailBean emailConfig = config.getEmailConfiguration();
        if (!emailConfig.isEmailConfigured()) {
            log.warn("Email configuration fields not set, no email configured.");
            return; // User didn't configure email, so no email notifications
        }
        // These are the basic required fields
        smtpHost = emailConfig.getSmtpAddress();
        smtpFrom = emailConfig.getSmtpFrom();
        smtpTo = emailConfig.getSmtpTo();

        smtpPassword = null;
        smtpUsername = null;
        smtpUseAuth = emailConfig.isUseAuth();
        // only obtain username and password if useAuth set to true
        if (smtpUseAuth) {
            smtpUsername = emailConfig.getAuthUserName();
            smtpPassword = emailConfig.getAuthPassword();
        }

        smtpPort = emailConfig.getSmtpPort();
        smtpProtocol = emailConfig.getEmailProtocol();

        ready = true;
    }

    /**
     * Informs the user whether the Emailer is ready to be used!
     *
     * @return
     */
    public boolean isConfigured() {
        return ready;
    }

    /**
     * Sends the email.
     *
     * @param projectInfo
     *            - Protex project
     * @param content
     *            - Content map retrieved from configuration step
     * @param rules
     *            - Optional rules that govern whether an email should be sent
     * @return
     * @throws CommonFrameworkException
     */
    @Override
    public EmailTemplate sendEmail(ProjectPojo projectInfo, EmailContentMap content, List<EmailTriggerRule> rules)
            throws CommonFrameworkException {
        if (!ready) {
            return null;
        }

        boolean areRulesTriggered = checkRules(rules);

        if (areRulesTriggered) {
            EmailTemplate emailTemplate = generateEmail(projectInfo, content);

            send(emailTemplate);
            log.info("Email sent complete to user: " + smtpTo);

            return emailTemplate;
        } else {
            log.info("Trigger rules were created, but none triggered - no email[s] sent.");
            return null;
        }

    }

    private boolean checkRules(List<EmailTriggerRule> userProvidedRules) {
        boolean ruleValidator = false;
        if (userProvidedRules == null || userProvidedRules.size() == 0) {
            log.debug("Rules are empty, defaulting to true for all rules");
            ruleValidator = true;
        } else {
            for (EmailTriggerRule rule : userProvidedRules) {
                if (rule.isRuleTriggered()) {
                    ruleValidator = true;
                    log.info("Email rule triggered, for rule name: " + rule.getRuleName());
                }
            }
        }

        return ruleValidator;
    }

    private EmailTemplate generateEmail(ProjectPojo projectInfo, EmailContentMap map) {
        Reader reader = null;

        try {
            reader = new InputStreamReader(new FileInputStream(templateFileLocation));
        } catch (FileNotFoundException e1) {
            log.error(e1.getMessage());
        }

        VelocityContext velContext = initializeVelocityContext(projectInfo, map);

        EmailTemplate emailTemplate = evaluateVelocityContext(velContext, reader);

        try {
            reader.close();
        } catch (IOException e) {
            log.error("Error closing reader", e);
        }

        return emailTemplate;
    }

    /**
     * Deprecating. There should not be a whole new method just for this One
     * method should be able to handle ALL template files.
     */
    @Override
    @Deprecated
    public void sendErrorNotification(Exception e, String message, String templateFile, String projectName) {
        if (!ready) {
            return;
        }
        String errorNotificationsString = null;
        try {
            errorNotificationsString = getBodyTemplate();
        } catch (Exception e1) {
            log.error("Unable to convert template to string: " + e1.getMessage());
            return;
        }
        if (errorNotificationsString == null) {
            log.warn("Unable to initialize email notifications." + templateFile + " was not readable.");
            return; // Won't try to do email notifications without both of these
        }

        Reader reader = null;

        reader = new InputStreamReader(IOUtils.toInputStream(errorNotificationsString));

        VelocityContext velContext = initializeVelocityContext(e, message, projectName);

        EmailTemplate notification = evaluateVelocityContext(velContext, reader);

        send(notification);
    }

    // initializes variables for the project completion notification
    private VelocityContext initializeVelocityContext(ProjectPojo projectInfo, EmailContentMap contentMap) {
        VelocityContext velContext = new VelocityContext();

        // Preload non dynamic values
        contentMap.put("smtpFrom", smtpFrom);
        contentMap.put("smtpTo", smtpTo);
        if (projectInfo.getProjectName() != null) {
            contentMap.put("projectName", projectInfo.getProjectName());
        }

        // Load all the values

        Set<String> ks = contentMap.keySet();

        for (String key : ks) {
            String value = contentMap.get(key);
            velContext.put(key, value);

        }

        return velContext;
    }

    // initializes variables for the error notification
    private VelocityContext initializeVelocityContext(Exception e, String message, String projectName) {
        VelocityContext velContext = new VelocityContext();

        velContext.put("projectName", projectName);
        velContext.put("errorMessage", message);
        velContext.put("stackTrace", getStackTraceAsString(e));

        return velContext;

    }

    private String getStackTraceAsString(Throwable aThrowable) {
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        aThrowable.printStackTrace(printWriter);
        return result.toString();
    }

    // substitutes the values for the variables that appear in the template file
    private EmailTemplate evaluateVelocityContext(VelocityContext velContext, Reader reader) {
        EmailTemplate notification = null;
        StringWriter writer = new StringWriter();

        try {
            boolean evaluted = Velocity.evaluate(velContext, writer, "", reader);
            if (!evaluted) {
                log.warn("Velocity unable to evaluate template");
            }
        } catch (Exception e1) {
            log.error("Failure to evaluate the Velocity expressions onto template file", e1);
            return null;
        }

        try {
            notification = EmailTemplateDigester
                    .getEmailTemplate(new ByteArrayInputStream(writer.toString().getBytes()));
        } catch (IOException e1) {
            log.error("Failure to apply digester on email message", e1);
            return null;
        } catch (SAXException e1) {
            log.error("Failure to apply digester on email message", e1);
            return null;
        }

        return notification;
    }

    /**
     * This is the actual java mail execution.
     *
     * @param notification
     */
    private void send(EmailTemplate notification) {

        String from = notification.getFrom();
        String to = notification.getTo();
        String subject = notification.getSubject();
        String style = notification.getStyle();

        // body of the email is set and all substitutions of variables are made
        String body = notification.getBody();

        // Get system properties
        Properties props = System.getProperties();

        // Setup mail server

        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtps.port", smtpPort);
        props.put("mail.smtps.auth", smtpUseAuth ? "true" : "false");

        // if (smtpUseAuth) {
        //
        // props.setProperty("mail.smtp.auth", smtpUseAuth + "");
        // props.setProperty("mail.username", smtpUsername);
        // props.setProperty("mail.password", smtpPassword);
        //
        // }

        // Get the default Session object.
        Session session = Session.getDefaultInstance(props);

        // Create a default MimeMessage object.
        MimeMessage message = new MimeMessage(session);

        try {
            // Set the RFC 822 "From" header field using the
            // value of the InternetAddress.getLocalAddress method.
            message.setFrom(new InternetAddress(from));
            // Add the given addresses to the specified recipient type.
            message.addRecipients(Message.RecipientType.TO, InternetAddress.parse(to));

            // Set the "Subject" header field.
            message.setSubject(subject);

            // Sets the given String as this part's content,
            // with a MIME type of "text/html".
            message.setContent(style + body, "text/html");

            if (smtpUseAuth) {
                // Send message
                Transport tr = session.getTransport(smtpProtocol);
                tr.connect(smtpHost, smtpPort, smtpUsername, smtpPassword);

                message.saveChanges();
                tr.sendMessage(message, message.getAllRecipients());
                tr.close();
            } else {
                Transport.send(message);
            }
        } catch (AddressException e) {
            log.error("Email Exception: Cannot connect to email host: " + smtpHost, e);
        } catch (MessagingException e) {
            log.error("Email Exception: Cannot send email message", e);
        }

    }

    /**
     * Reads the user provided template file and converts to a string
     *
     * @return
     * @throws Exception
     */
    private String getBodyTemplate() throws Exception {

        String templateBody = null;
        try (FileInputStream fis = new FileInputStream(new File(templateFileLocation))) {
            templateBody = IOUtils.toString(fis);
        } catch (IOException e) {
            throw new Exception("Error converting template file to string, cannot send emails", e);
        }

        return templateBody;
    }

    /**
     * Get the EmailContentMap that has been configured.
     *
     * @return
     * @throws CommonFrameworkException
     */
    public EmailContentMap getEmailContentMap() throws CommonFrameworkException {
        if (emailContentMap == null) {
            throw new CommonFrameworkException("No content map configured!");
        }
        return emailContentMap;
    }
}