Java tutorial
/* * NorthRidge Software, LLC - Copyright (c) 2015. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.nridge.core.app.mail; import com.nridge.core.app.mgr.AppMgr; import com.nridge.core.base.field.Field; import com.nridge.core.base.field.data.*; import com.nridge.core.base.io.console.DataBagConsole; import com.nridge.core.base.io.console.DataTableConsole; import com.nridge.core.base.std.NSException; import com.nridge.core.base.std.StrUtl; import freemarker.template.Configuration; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * The MailManager is responsible capturing messages and * sending them to email recipients based on configuration * settings. The class offers a flexible template approach * to message generation via Freemarker Templates. * * @see <a href="http://javamail.kenai.com/nonav/javadocs/com/sun/mail/smtp/package-summary.html">Mail Reference</a> * @see <a href="http://crunchify.com/java-mailapi-example-send-an-email-via-gmail-smtp/">GMail SMTP (TLS Authentication)</a> * @see <a href="http://freemarker.org/">Freemarker Template</a> */ public class MailManager { private AppMgr mAppMgr; private DataTable mTable; private Session mMailSession; private Configuration mConfiguration; private String mCfgPropertyPrefix = Mail.CFG_PROPERTY_PREFIX; public MailManager(AppMgr anAppMgr) { mAppMgr = anAppMgr; mTable = new DataTable(schemaServiceMailBag()); } public MailManager(AppMgr anAppMgr, DataBag aBag) { mAppMgr = anAppMgr; mTable = new DataTable(aBag); } public MailManager(AppMgr anAppMgr, String aPropertyPrefix) { mAppMgr = anAppMgr; mCfgPropertyPrefix = aPropertyPrefix; mTable = new DataTable(schemaServiceMailBag()); } /** * Returns the configuration property prefix string. * * @return Property prefix string. */ public String getCfgPropertyPrefix() { return mCfgPropertyPrefix; } /** * Assigns the configuration property prefix to the document data source. * * @param aPropertyPrefix Property prefix. */ public void setCfgPropertyPrefix(String aPropertyPrefix) { mCfgPropertyPrefix = aPropertyPrefix; } /** * Convenience method that returns the value of an application * manager configuration property using the concatenation of * the property prefix and suffix values. * * @param aSuffix Property name suffix. * @return Matching property value. */ public String getCfgString(String aSuffix) { String propertyName; if (StringUtils.startsWith(aSuffix, ".")) propertyName = mCfgPropertyPrefix + aSuffix; else propertyName = mCfgPropertyPrefix + "." + aSuffix; return mAppMgr.getString(propertyName); } /** * Convenience method that returns the value of an application * manager configuration property using the concatenation of * the property prefix and suffix values. If the property is * not found, then the default value parameter will be returned. * * @param aSuffix Property name suffix. * @param aDefaultValue Default value. * * @return Matching property value or the default value. */ public String getCfgString(String aSuffix, String aDefaultValue) { String propertyName; if (StringUtils.startsWith(aSuffix, ".")) propertyName = mCfgPropertyPrefix + aSuffix; else propertyName = mCfgPropertyPrefix + "." + aSuffix; return mAppMgr.getString(propertyName, aDefaultValue); } /** * Returns a typed value for the property name identified * or the default value (if unmatched). * * @param aSuffix Property name suffix. * @param aDefaultValue Default value to return if property * name is not matched. * * @return Value of the property. */ public int getCfgInteger(String aSuffix, int aDefaultValue) { String propertyName; if (StringUtils.startsWith(aSuffix, ".")) propertyName = mCfgPropertyPrefix + aSuffix; else propertyName = mCfgPropertyPrefix + "." + aSuffix; return mAppMgr.getInt(propertyName, aDefaultValue); } /** * Returns <i>true</i> if the application manager configuration * property value evaluates to <i>true</i>. * * @param aSuffix Property name suffix. * * @return <i>true</i> or <i>false</i> */ public boolean isCfgStringTrue(String aSuffix) { String propertyValue = getCfgString(aSuffix); return StrUtl.stringToBoolean(propertyValue); } /** * Performs a property lookup for the from address. * * @return Email address from property file. * * @throws NSException Property is undefined. */ public String lookupFromAddress() throws NSException { String propertyName = "address_from"; String mailAddressFrom = getCfgString(propertyName); if (StringUtils.isEmpty(mailAddressFrom)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); throw new NSException(msgStr); } return mailAddressFrom; } /** * Convenience method that assigns the recipient email address to an array * list. * * @param anEmailAddress Single email address. * * @return Array list of email address strings. */ public ArrayList<String> createRecipientList(String anEmailAddress) { ArrayList<String> recipientList = new ArrayList<String>(); recipientList.add(anEmailAddress); return recipientList; } /** * Convenience method that assigns the recipient email addresses * defined in the application property file to an array list. * * @return Array list of email address strings. */ public ArrayList<String> createRecipientList() throws NSException { ArrayList<String> recipientList = new ArrayList<String>(); String propertyName = "address_to"; if (mAppMgr.isPropertyMultiValue(getCfgString(propertyName))) { String[] addressToArray = mAppMgr.getStringArray(getCfgString(propertyName)); for (String mailAddressTo : addressToArray) recipientList.add(mailAddressTo); } else { String mailAddressTo = getCfgString(propertyName); if (StringUtils.isEmpty(mailAddressTo)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); throw new NSException(msgStr); } } return recipientList; } /** * Convenience method that assigns a single attachment to an array * list. * * @param anAttachmentPathFileName Attachment path file name. * * @return Array list of attachment path/file names. */ public ArrayList<String> createAttachmentList(String anAttachmentPathFileName) { ArrayList<String> attachmentPathFileList = new ArrayList<String>(); attachmentPathFileList.add(anAttachmentPathFileName); return attachmentPathFileList; } /** * This method will create a mail bag with one field for a * message description. * * @return Data bag instance. */ public DataBag schemaMailBag() { DataBag dataBag = new DataBag("Application Mail Manager"); DataTextField dataTextField = new DataTextField("msg_description", "Message Description"); dataBag.add(dataTextField); return dataBag; } /** * This method will create a mail bag of fields suitable for * capturing a table of messages that could describe the result * of a batch operation like a connector service. * * @return Data bag instance. */ public DataBag schemaServiceMailBag() { DataBag dataBag = new DataBag("Application Service Mail Manager"); DataDateTimeField dataDateTimeField = new DataDateTimeField("msg_ts", "Message Timestamp"); dataDateTimeField.setDefaultValue(Field.VALUE_DATETIME_TODAY); dataBag.add(dataDateTimeField); DataTextField dataTextField = new DataTextField("msg_operation", "Message Operation"); dataTextField.enableFeature(Field.FEATURE_IS_REQUIRED); dataBag.add(dataTextField); dataTextField = new DataTextField("msg_status", "Message Status"); dataTextField.setDefaultValue(Mail.STATUS_SUCCESS); dataBag.add(dataTextField); dataTextField = new DataTextField("msg_description", "Message Description"); dataTextField.setDefaultValue(Mail.MESSAGE_NONE); dataBag.add(dataTextField); dataTextField = new DataTextField("msg_detail", "Message Detail"); dataTextField.setDefaultValue(Mail.MESSAGE_NONE); dataBag.add(dataTextField); dataBag.resetValuesWithDefaults(); return dataBag; } private void initialize() throws IOException, NSException { Logger appLogger = mAppMgr.getLogger(this, "initialize"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); if (mMailSession == null) { String propertyName = "account_name"; String mailAccountName = getCfgString(propertyName); if (StringUtils.isEmpty(mailAccountName)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } propertyName = "account_password"; String mailAccountPassword = getCfgString(propertyName); if (StringUtils.isEmpty(mailAccountPassword)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } MailAuthenticator mailAuthenticator = new MailAuthenticator(mailAccountName, mailAccountPassword); propertyName = "smtp_host"; String smtpHostName = getCfgString(propertyName); if (StringUtils.isEmpty(smtpHostName)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } propertyName = "smtp_port"; String smtpPortNumber = getCfgString(propertyName); if (StringUtils.isEmpty(smtpHostName)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } Properties systemProperties = new Properties(); systemProperties.setProperty("mail.smtp.submitter", mailAccountName); systemProperties.setProperty("mail.smtp.host", smtpHostName); systemProperties.setProperty("mail.smtp.port", smtpPortNumber); if (isCfgStringTrue("authn_enabled")) { systemProperties.setProperty("mail.smtp.auth", "true"); systemProperties.setProperty("mail.smtp.starttls.enable", "true"); mMailSession = Session.getInstance(systemProperties, mailAuthenticator); } else mMailSession = Session.getInstance(systemProperties); mConfiguration = new Configuration(Configuration.VERSION_2_3_21); String cfgPathName = mAppMgr.getString(mAppMgr.APP_PROPERTY_CFG_PATH); File cfgPathFile = new File(cfgPathName); try { mConfiguration.setDirectoryForTemplateLoading(cfgPathFile); } catch (IOException e) { appLogger.error(cfgPathName, e); } mConfiguration.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_21)); } appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); } private String createMessage(DataBag aBag) throws IOException, NSException { Logger appLogger = mAppMgr.getLogger(this, "createMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); initialize(); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); DataBagConsole dataBagConsole = new DataBagConsole(aBag); dataBagConsole.setUseTitleFlag(true); dataBagConsole.writeBag(printWriter, aBag.getTitle()); printWriter.close(); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); return stringWriter.toString(); } private String createMessage(DataBag aBag, String aTemplateFileName) throws IOException, NSException { Logger appLogger = mAppMgr.getLogger(this, "createMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); initialize(); Map<String, Object> dataModel = new HashMap<String, Object>(); for (DataField dataField : aBag.getFields()) dataModel.put(dataField.getName(), dataField.getValue()); StringWriter stringWriter = new StringWriter(); try { Template fmTemplate = mConfiguration.getTemplate(aTemplateFileName); fmTemplate.process(dataModel, stringWriter); } catch (Exception e) { String msgStr = String.format("%s: %s", aTemplateFileName, e.getMessage()); appLogger.error(msgStr, e); throw new NSException(msgStr); } appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); return stringWriter.toString(); } /** * Returns the number of messages previously stored in the internally * managed table. * * @return Count of messages. */ public int messageCount() { return mTable.rowCount(); } /** * Empties the internally managed table of any messages. */ public void reset() { synchronized (this) { mTable.emptyRows(); } } /** * Adds the fields contained within the data bag to the internally * managed table. Refer to <code>schemaMailBag</code> and * <code>schemaServiceMailBag</code> methods. * * @param aBag Data bag instance. * * @return <i>true</i> if the message is valid and added. <i>false</i> * otherwise. */ public boolean addMessage(DataBag aBag) { boolean isValid; Logger appLogger = mAppMgr.getLogger(this, "addMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); if ((aBag == null) || (!aBag.isValid())) isValid = false; else { synchronized (this) { mTable.addRow(aBag); } isValid = true; } appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); return isValid; } /** * Adds the fields contained within the data bag to the internally * managed table. Refer to <code>schemaServiceMailBag</code> * method. * * @param anOperation Application defined operation string. * @param aStatus Status message of operation. * @param aDescription Description of the operation. * @param aDetail Details around the operation. * * @return <i>true</i> if the message is valid and added. <i>false</i> * otherwise. */ public boolean addMessage(String anOperation, String aStatus, String aDescription, String aDetail) { Logger appLogger = mAppMgr.getLogger(this, "addMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); DataBag dataBag = schemaServiceMailBag(); dataBag.setValueByName("msg_operation", anOperation); dataBag.setValueByName("msg_status", aStatus); dataBag.setValueByName("msg_description", aDescription); dataBag.setValueByName("msg_detail", aDetail); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); return addMessage(dataBag); } /** * If the property "delivery_enabled" is <i>true</i>, then this * method will deliver the subject and message via an email * transport (e.g. SMTP). * * @param aSubject Message subject. * @param aMessage Message content. * * @throws IOException I/O related error condition. * @throws NSException Missing configuration properties. * @throws MessagingException Message subsystem error condition. */ public void sendMessage(String aSubject, String aMessage) throws IOException, NSException, MessagingException { InternetAddress internetAddressFrom, internetAddressTo; Logger appLogger = mAppMgr.getLogger(this, "sendMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); if (isCfgStringTrue("delivery_enabled")) { if ((StringUtils.isNotEmpty(aSubject)) && (StringUtils.isNotEmpty(aMessage))) { initialize(); String propertyName = "address_to"; Message mimeMessage = new MimeMessage(mMailSession); if (mAppMgr.isPropertyMultiValue(getCfgString(propertyName))) { String[] addressToArray = mAppMgr.getStringArray(getCfgString(propertyName)); for (String mailAddressTo : addressToArray) { internetAddressTo = new InternetAddress(mailAddressTo); mimeMessage.addRecipient(MimeMessage.RecipientType.TO, internetAddressTo); } } else { String mailAddressTo = getCfgString(propertyName); if (StringUtils.isEmpty(mailAddressTo)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } internetAddressTo = new InternetAddress(mailAddressTo); mimeMessage.addRecipient(MimeMessage.RecipientType.TO, internetAddressTo); } propertyName = "address_from"; String mailAddressFrom = getCfgString(propertyName); if (StringUtils.isEmpty(mailAddressFrom)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } internetAddressFrom = new InternetAddress(mailAddressFrom); mimeMessage.addFrom(new InternetAddress[] { internetAddressFrom }); mimeMessage.setSubject(aSubject); mimeMessage.setContent(aMessage, "text/plain"); appLogger.debug(String.format("Mail Message (%s): %s", aSubject, aMessage)); Transport.send(mimeMessage); } else throw new NSException("Subject and message are required parameters."); } else appLogger.warn("Email delivery is not enabled - no message will be sent."); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); } /** * If the property "delivery_enabled" is <i>true</i>, then this * method will deliver the subject and messages stored in the * internal table via an email transport (e.g. SMTP). * * @param aSubject Message subject. * * @throws IOException I/O related error condition. * @throws NSException Missing configuration properties. * @throws MessagingException Message subsystem error condition. */ public void sendMessageTable(String aSubject) throws IOException, NSException, MessagingException { Logger appLogger = mAppMgr.getLogger(this, "sendMessageTable"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); int rowCount = mTable.rowCount(); if (rowCount > 0) { String propertyName = "template_service_file"; String mailTemplatePathFileName = getCfgString(propertyName); if (StringUtils.isEmpty(mailTemplatePathFileName)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } DataBag dataBag = new DataBag("Mail Message Bag"); dataBag.add(new DataTextField("msg_table", "Message Table")); synchronized (this) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); DataTableConsole dataTableConsole = new DataTableConsole(mTable); dataTableConsole.write(printWriter, StringUtils.EMPTY); printWriter.close(); dataBag.setValueByName("msg_table", stringWriter.toString()); } String messageBody = createMessage(dataBag, mailTemplatePathFileName); sendMessage(aSubject, messageBody); } else throw new NSException("The message table is empty - nothing to send."); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); } /** * If the property "delivery_enabled" is <i>true</i>, then this * method will deliver the subject and data bag via an email * transport (e.g. SMTP). * * @param aSubject Message subject. * @param aBag Data bag instance of fields. * * @throws IOException I/O related error condition. * @throws NSException Missing configuration properties. * @throws MessagingException Message subsystem error condition. */ public void sendMessageBag(String aSubject, DataBag aBag) throws IOException, NSException, MessagingException { Logger appLogger = mAppMgr.getLogger(this, "sendMessageBag"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); String propertyName = "template_service_file"; String mailTemplatePathFileName = getCfgString(propertyName); if (StringUtils.isEmpty(mailTemplatePathFileName)) { String msgStr = String.format("Mail Manager property '%s' is undefined.", mCfgPropertyPrefix + "." + propertyName); appLogger.error(msgStr); throw new NSException(msgStr); } String messageBody = createMessage(aBag, mailTemplatePathFileName); sendMessage(aSubject, messageBody); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); } /** * If the property "delivery_enabled" is <i>true</i>, then this method * will generate an email message that includes subject, message and * attachments to the recipient list. You can use the convenience * methods <i>lookupFromAddress()</i>, <i>createRecipientList()</i> * and <i>createAttachmentList()</i> for parameter building assistance. * * @param aFromAddress Source email address. * @param aRecipientList List of recipient email addresses. * @param aSubject Subject of the email message. * @param aMessage Messsage. * @param anAttachmentFiles List of file attachments or <i>null</i> for none. * * @see <a href="https://www.tutorialspoint.com/javamail_api/javamail_api_send_email_with_attachment.htm">JavaMail API Attachments</a> * @see <a href="https://stackoverflow.com/questions/6756162/how-do-i-send-mail-with-both-plain-text-as-well-as-html-text-so-that-each-mail-r">JavaMail API MIME Types</a> * * @throws IOException I/O related error condition. * @throws NSException Missing configuration properties. * @throws MessagingException Message subsystem error condition. */ public void sendMessage(String aFromAddress, ArrayList<String> aRecipientList, String aSubject, String aMessage, ArrayList<String> anAttachmentFiles) throws IOException, NSException, MessagingException { InternetAddress internetAddressFrom, internetAddressTo; Logger appLogger = mAppMgr.getLogger(this, "sendMessage"); appLogger.trace(mAppMgr.LOGMSG_TRACE_ENTER); if (isCfgStringTrue("delivery_enabled")) { if ((StringUtils.isNotEmpty(aFromAddress)) && (aRecipientList.size() > 0) && (StringUtils.isNotEmpty(aSubject)) && (StringUtils.isNotEmpty(aMessage))) { initialize(); Message mimeMessage = new MimeMessage(mMailSession); internetAddressFrom = new InternetAddress(aFromAddress); mimeMessage.addFrom(new InternetAddress[] { internetAddressFrom }); for (String mailAddressTo : aRecipientList) { internetAddressTo = new InternetAddress(mailAddressTo); mimeMessage.addRecipient(MimeMessage.RecipientType.TO, internetAddressTo); } mimeMessage.setSubject(aSubject); // The following logic create a multi-part message and adds the attachment to it. BodyPart messageBodyPart = new MimeBodyPart(); messageBodyPart.setText(aMessage); // messageBodyPart.setContent(aMessage, "text/html"); Multipart multipart = new MimeMultipart(); multipart.addBodyPart(messageBodyPart); if ((anAttachmentFiles != null) && (anAttachmentFiles.size() > 0)) { for (String pathFileName : anAttachmentFiles) { File attachmentFile = new File(pathFileName); if (attachmentFile.exists()) { messageBodyPart = new MimeBodyPart(); DataSource fileDataSource = new FileDataSource(pathFileName); messageBodyPart.setDataHandler(new DataHandler(fileDataSource)); messageBodyPart.setFileName(attachmentFile.getName()); multipart.addBodyPart(messageBodyPart); } } appLogger.debug(String.format("Mail Message (%s): %s - with attachments", aSubject, aMessage)); } else appLogger.debug(String.format("Mail Message (%s): %s", aSubject, aMessage)); mimeMessage.setContent(multipart); Transport.send(mimeMessage); } else throw new NSException("Valid from, recipient, subject and message are required parameters."); } else appLogger.warn("Email delivery is not enabled - no message will be sent."); appLogger.trace(mAppMgr.LOGMSG_TRACE_DEPART); } }