com.rodaxsoft.mailgun.message.tools.MailgunSender.java Source code

Java tutorial

Introduction

Here is the source code for com.rodaxsoft.mailgun.message.tools.MailgunSender.java

Source

/**
 * MailgunSender.java
 * MailgunTool
 *
 * Created by John Boyer on Sep 25, 2014.
 * Copyright 2014 Rodax Software, Inc. All rights reserved.
    
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 
 */
package com.rodaxsoft.mailgun.message.tools;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.FileBasedConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.beanutils.ConfigurationDynaBean;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang3.exception.ContextedException;
import org.apache.commons.lang3.exception.ContextedRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.rodaxsoft.mailgun.EmailRequest;
import com.rodaxsoft.mailgun.ListInfo;
import com.rodaxsoft.mailgun.MailgunAccount;
import com.rodaxsoft.mailgun.MailgunManager;

/**
 * MailgunSender tool class
 * @author John Boyer
 * @version 2016-07-23
 */
public final class MailgunSender {
    /**
     * Campaign ID option
     */
    private static final String CAMPAIGN_ID_OPT = "c";
    /**
     * Default from email property key
     */
    private static final String DEFAULT_FROM_EMAIL_PROP_KEY = "mailgun.sender.default.from.email";
    /**
     * Email address option
     */
    private static final String EMAIL_ADDRESS_OPT = "e";
    /**
     * Email subject option
     */
    private static final String EMAIL_SUBJECT_OPT = "s";
    /**
     * From email address option
     */
    private static final String FROM_EMAIL_ADDRESS_OPT = "f";
    /**
     * HTML option
     */
    private static final String HTML_OPT = "h";
    /**
     * Logging object
     */
    private static final Log LOG = LogFactory.getLog(MailgunSender.class);
    /**
     * Mailing list option
     */
    private static final String MAILING_LIST_OPT = "m";
    /**
     * CLI options
     */
    private static final Options OPTIONS = new Options();
    /**
     * Plain text option
     */
    private static final String PLAIN_TXT_OPT = "p";
    /**
     * Recipients file option
     */
    private static final String RECIPIENTS_FILE_OPT = "R";
    private static final String SCHEDULED_OPT = "S";
    /**
     * Reply to email address option
     */
    private static final String REPLY_TO_EMAIL_ADDRESS_OPT = "r";
    /**
     * Default email from email address
     */
    private static String sDefaultFromEmail;

    /**
     * Test mode option
     */
    private static final String TEST_MODE_OPT = "t";

    static {

        Option option;
        OptionGroup optionGroup = new OptionGroup();

        //Mailing list address [REQ]
        option = Option.builder(MAILING_LIST_OPT).longOpt("mailing-list-address").desc("Mailing list address")
                .hasArg().argName("List").build();

        optionGroup.addOption(option);

        //Email address [REQ]
        option = Option.builder(EMAIL_ADDRESS_OPT).longOpt("email-address").desc("Recipient's email address")
                .hasArg().argName("Email").build();

        optionGroup.addOption(option);

        //Recipients file [REQ]
        option = Option.builder(RECIPIENTS_FILE_OPT).longOpt("recipients-file").desc("Recipients list file")
                .hasArg().argName("File").build();

        optionGroup.addOption(option);

        optionGroup.setRequired(true);

        OPTIONS.addOptionGroup(optionGroup);

        //From email address [OPTIONAL]
        option = Option.builder(FROM_EMAIL_ADDRESS_OPT).longOpt("from-email-address")
                .desc("From email address [default can be set in mailgun-sender.properties]").hasArg()
                .argName("Email").build();

        OPTIONS.addOption(option);

        //Email subject [REQ]
        option = Option.builder(EMAIL_SUBJECT_OPT).required().longOpt("subject").desc("Email subject title")
                .hasArg().argName("Subject").build();

        OPTIONS.addOption(option);

        //Reply-to email address
        option = Option.builder(REPLY_TO_EMAIL_ADDRESS_OPT).longOpt("reply-to-email-address")
                .desc("Reply-to email address").hasArg().argName("Email").build();

        OPTIONS.addOption(option);

        //Schedule delivery time
        option = Option.builder(SCHEDULED_OPT).longOpt("schedule-delivery-time")
                .desc("Scheduled message delivery time ").hasArg().argName("RFC 2822 Date").build();

        OPTIONS.addOption(option);

        //Campaign
        option = Option.builder(CAMPAIGN_ID_OPT).longOpt("campaign-id").desc("Campaign identifier").hasArg()
                .argName("ID").build();

        OPTIONS.addOption(option);

        //Test mode
        option = Option.builder(TEST_MODE_OPT).longOpt("test-mode-flag").desc("Test mode flag [default: false]")
                .hasArg().argName("Flag").type(Boolean.class).build();

        OPTIONS.addOption(option);

        //Plain Text file [REQ]
        option = Option.builder(PLAIN_TXT_OPT).required().longOpt("plain-text-file").desc("Plain text file content")
                .hasArg().argName("File").build();

        OPTIONS.addOption(option);

        //HTML file
        option = Option.builder(HTML_OPT).required().longOpt("html-file").desc("HTML file content").hasArg()
                .argName("File").build();

        OPTIONS.addOption(option);

    }

    /**
     * Handles option error and throws and <code>ContextedRuntimeException</code>.
     * @param cmd Command line arguments
     * @param msg Error message string
     * @throws ContextedRuntimeException to handle the option value error
     */
    private static void handleOmittedOptionError(CommandLine cmd, final String msg)
            throws ContextedRuntimeException {
        LOG.error(msg);
        Iterator<Option> it = cmd.iterator();
        List<String> options = new ArrayList<String>();
        while (it.hasNext()) {
            final String desc = it.next().toString();
            options.add(desc);
            LOG.debug(desc);
        }

        throw new ContextedRuntimeException(msg).addContextValue("options", options);
    }

    private static void initializeMailgun(CommandLine cmd) throws ContextedException, ConfigurationException {

        //Configure Mailgun account info
        FileBasedConfigurationBuilder<FileBasedConfiguration> builder;
        builder = new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class);
        Parameters params = new Parameters();
        builder.configure(params.properties().setFileName("config/mailgun.properties"));
        Configuration config = builder.getConfiguration();

        DynaBean bean = new ConfigurationDynaBean(config);
        MailgunAccount acct = new MailgunAccount(bean);
        //      Register the MailgunAccount object
        MailgunManager.register(acct);

        LOG.info("Configured mailgun.properties");

        //Set the default from email address
        setDefaultFromEmail(cmd);

    }

    /**
     * Main method
     * @param args Command line arguments
     * @throws ConfigurationException if a configuration error occurs
     */
    public static void main(String[] args) {

        //Parse the command line
        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = null;
        try {
            cmd = parser.parse(OPTIONS, args);
            initializeMailgun(cmd);
        } catch (ParseException | ContextedException | ConfigurationException e) {
            System.err.println(e.getMessage());
            System.err.println();
            printHelp();
            System.exit(-1);
        }

        //Read the plain text and HTML content files
        String text = null;
        String html = null;
        try {
            text = readFileToString(cmd.getOptionValue(PLAIN_TXT_OPT));
            html = readFileToString(cmd.getOptionValue(HTML_OPT));
        } catch (IOException e) {
            LOG.error("Error reading file", e);
            System.exit(-1);
        }

        if (!cmd.hasOption(RECIPIENTS_FILE_OPT)) {
            sendMessageToEmailAddress(cmd, text, html);

        } else {
            sendMessageToRecipientsInFile(cmd, text, html);
        }
    }

    /**
     * Sets the default from email address or <code>sDefaultFromEmail</code> from the 
     * properties file if no <code>-f</code> option exists.
     * @param cmd Command line arguments
     */
    private static void setDefaultFromEmail(CommandLine cmd) throws ContextedException {
        if (!cmd.hasOption(FROM_EMAIL_ADDRESS_OPT)) {

            Parameters params = new Parameters();
            FileBasedConfigurationBuilder<FileBasedConfiguration> builder;
            final Class<PropertiesConfiguration> propConfigClass = PropertiesConfiguration.class;
            builder = new FileBasedConfigurationBuilder<FileBasedConfiguration>(propConfigClass);
            builder.configure(params.properties().setFileName("config/mailgun-sender.properties"));

            try {
                FileBasedConfiguration config = builder.getConfiguration();
                sDefaultFromEmail = config.getString(DEFAULT_FROM_EMAIL_PROP_KEY);
                if (null == sDefaultFromEmail) {
                    throw new ContextedException("Missing " + DEFAULT_FROM_EMAIL_PROP_KEY + " key")
                            .addContextValue(DEFAULT_FROM_EMAIL_PROP_KEY, sDefaultFromEmail).addContextValue("help",
                                    "Must set from email address with option -f or in the `mailgun-sender.properties` file");
                }

                LOG.info("Configured mailgun-sender.properties");

            } catch (ConfigurationException e) {
                throw new ContextedException("Error loading `mailgun-sender.properties`", e);
            }
        }
    }

    /**
     * Makes an email request object
     * @param cmd Command line args
     * @param text Plain text content
     * @param html HTML content
     * @param toAddress Recipient address
     * @return
     */
    private static EmailRequest makeEmailRequest(CommandLine cmd, String text, String html, String toAddress) {
        EmailRequest request;
        request = new EmailRequest().setTo(toAddress)
                .setFrom(cmd.getOptionValue(FROM_EMAIL_ADDRESS_OPT, sDefaultFromEmail))
                .setSubject(cmd.getOptionValue(EMAIL_SUBJECT_OPT)).setTextBody(text).setHTMLBody(html);

        if (cmd.hasOption(SCHEDULED_OPT)) {
            request.setDeliveryTime(cmd.getOptionValue(SCHEDULED_OPT));
        }

        return request;
    }

    /**
     * Prints usage
     */
    private static void printHelp() {
        HelpFormatter formatter = new HelpFormatter();
        String header = "\nChoose options:\n";
        String footer = "\nThis tool is used for sending messages to mailing lists or email addresses.";
        String className = MailgunSender.class.getSimpleName();
        formatter.printHelp(className, header, OPTIONS, footer, true);
    }

    /**
     * Reads file to string
     * @param fileName The file to read
     * @return The file content as a string
     * @throws IOException if an I/O error occurs
     */
    private static String readFileToString(String fileName) throws IOException {
        File file = new File(fileName);
        Charset set = Charset.defaultCharset();
        return FileUtils.readFileToString(file, set);
    }

    /**
     * Sends the message
     * @param cmd Command line args
     * @param er Email request object
     */
    private static void sendMessage(CommandLine cmd, EmailRequest er) {
        //Set optional campaign
        if (cmd.hasOption(CAMPAIGN_ID_OPT)) {
            er = er.setCampaign(cmd.getOptionValue(CAMPAIGN_ID_OPT));
        }

        //Set optional test mode
        if (cmd.hasOption(TEST_MODE_OPT)) {
            final String value = cmd.getOptionValue(TEST_MODE_OPT);
            Boolean mode = new Boolean(value);
            er.setTestMode(mode);
        }

        //Set reply-to email address
        if (cmd.hasOption(REPLY_TO_EMAIL_ADDRESS_OPT)) {
            er = er.setReplyTo(cmd.getOptionValue(REPLY_TO_EMAIL_ADDRESS_OPT));
        }

        try {
            LOG.info("Sending message...");
            boolean success = MailgunManager.sendMessage(er);

            if (success) {
                LOG.info("Message sent successfully");
            } else {
                LOG.error("Failed to send message");
            }
        } catch (ContextedException e) {
            LOG.error(e.getMessage());
            LOG.debug("Mailgun Message Send Error", e);
        }
    }

    /**
     * Sends a message to a mailing list (<code>-m</code> option) or 
     * single email address (<code>-e</code> option).
     * @param cmd Command line arguments
     * @param text Plain text email content
     * @param html HTML email content
     */
    private static void sendMessageToEmailAddress(CommandLine cmd, String text, String html) {

        if (cmd.hasOption(MAILING_LIST_OPT) || cmd.hasOption(EMAIL_ADDRESS_OPT)) {
            final boolean isMailingList = cmd.hasOption(MAILING_LIST_OPT);
            final String toAddress = isMailingList ? cmd.getOptionValue(MAILING_LIST_OPT)
                    : cmd.getOptionValue(EMAIL_ADDRESS_OPT);
            if (isMailingList) {

                try {
                    validateMailingListAddress(toAddress);
                } catch (ContextedException e) {
                    LOG.error(e.getMessage());
                    LOG.debug("Mailgun Mailing List Error", e);
                    System.exit(-1);
                }
            }
            //Build the email request object
            final EmailRequest er = makeEmailRequest(cmd, text, html, toAddress);
            LOG.trace(er);

            sendMessage(cmd, er);
        } else {
            final String msg = "Option value must be an email address or mailing list.";
            handleOmittedOptionError(cmd, msg);
        }
    }

    /**
     * Sends message to the recipients specified by the <code>-R</code> option.
     * @param cmd Command line arguments
     * @param text Plain text email content
     * @param html HTML email content
     * @throws ContextedRuntimeException if the recipients option or -R is omitted.
     */
    private static void sendMessageToRecipientsInFile(CommandLine cmd, String text, String html) {

        if (cmd.hasOption(RECIPIENTS_FILE_OPT)) {
            LineIterator it = null;
            try {
                it = FileUtils.lineIterator(new File(cmd.getOptionValue(RECIPIENTS_FILE_OPT)), "UTF-8");

                while (it.hasNext()) {
                    final String to = it.nextLine();
                    //Build the email request object
                    final EmailRequest er = makeEmailRequest(cmd, text, html, to);
                    LOG.trace(er);

                    sendMessage(cmd, er);
                }

            } catch (IOException e) {
                LOG.error("Error occurre while sending from recipients file", e);

            } finally {
                LineIterator.closeQuietly(it);
            }
        } else {

            final String msg = "Option must be a recipients file";
            handleOmittedOptionError(cmd, msg);
        }
    }

    /**
     * Validates mailing list address
     * @param address The address to validate
     * @throws ContextedException if it's invalid or a processing error occurs
     */
    private static void validateMailingListAddress(final String address) throws ContextedException {

        Collection<ListInfo> lists = MailgunManager.getMailingLists();

        Predicate<ListInfo> predicate = new Predicate<ListInfo>() {
            @Override
            public boolean evaluate(ListInfo info) {
                return info.getAddress().equals(address);
            }
        };

        if (null == IterableUtils.find(lists, predicate)) {
            throw new ContextedException("Invalid mailing address").addContextValue("address", address);
        }
    }

}