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

sirius.web.mails.Mails Maven / Gradle / Ivy

There is a newer version: 22.2.3
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.web.mails;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sun.mail.smtp.SMTPMessage;
import com.typesafe.config.Config;
import sirius.kernel.async.CallContext;
import sirius.kernel.async.Operation;
import sirius.kernel.async.Tasks;
import sirius.kernel.commons.Context;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.std.ConfigValue;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Parts;
import sirius.kernel.di.std.Register;
import sirius.kernel.extensions.Extension;
import sirius.kernel.extensions.Extensions;
import sirius.kernel.health.Average;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.HandledException;
import sirius.kernel.health.Log;
import sirius.kernel.health.metrics.MetricProvider;
import sirius.kernel.health.metrics.MetricsCollector;
import sirius.kernel.nls.NLS;
import sirius.web.http.MimeHelper;
import sirius.web.templates.Templates;
import sirius.web.templates.velocity.VelocityContentHandler;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Used to send mails using predefined templates.
 */
@Register(classes = {Mails.class, MetricProvider.class})
public class Mails implements MetricProvider {

    /**
     * Contains the logger mail used by the mailing framework.
     */
    public static final Log LOG = Log.get("mail");

    /**
     * Defines a header which can be used to add a bounce token to an email.
     * 

* This token can be extracted from received bounce mails and handled properly. */ public static final String X_BOUNCETOKEN = "X-Bouncetoken"; private static final String X_MAILER = "X-Mailer"; private static final String MIXED = "mixed"; private static final String TEXT_HTML_CHARSET_UTF_8 = "text/html; charset=\"UTF-8\""; private static final String TEXT_PLAIN_CHARSET_UTF_8 = "text/plain; charset=\"UTF-8\""; private static final String CONTENT_TYPE = "Content-Type"; private static final String MIME_VERSION_1_0 = "1.0"; private static final String MIME_VERSION = "MIME-Version"; private static final String ALTERNATIVE = "alternative"; private static final String MAIL_USER = "mail.user"; private static final String MAIL_SMTP_AUTH = "mail.smtp.auth"; private static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol"; private static final String MAIL_FROM = "mail.from"; private static final String MAIL_SMTP_HOST = "mail.smtp.host"; private static final String SMTP = "smtp"; private static final String MAIL_SMTP_PORT = "mail.smtp.port"; private static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout"; private static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout"; private static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout"; /* * Contains the default timeout used for all socket operations and is set to 60s (=60000ms) */ private static final String MAIL_SOCKET_TIMEOUT = "60000"; @ConfigValue("mail.smtp.host") private String smtpHost; @ConfigValue("mail.smtp.port") private int smtpPort; @ConfigValue("mail.smtp.user") private String smtpUser; @ConfigValue("mail.smtp.password") private String smtpPassword; @ConfigValue("mail.smtp.sender") private String smtpSender; @ConfigValue("mail.smtp.senderName") private String smtpSenderName; private final SMTPConfiguration defaultConfig = new DefaultSMTPConfig(); @ConfigValue("mail.mailer") private String mailer; @Part private Templates templates; @Part private Tasks tasks; @Parts(MailLog.class) private Collection logs; private Average mailsOut = new Average(); @Override public void gather(MetricsCollector collector) { collector.differentialMetric("mails-out", "mails-out", "Mails Sent", mailsOut.getCount(), null); collector.metric("mails-duration", "Send Mail Duration", mailsOut.getAndClearAverage(), "ms"); } /** * Creates a new builder which is used to specify the mail to send. * * @return a new builder used to create an email. */ public MailSender createEmail() { return new MailSender(); } /** * Determines if the given address is a valid eMail address. *

* The name is optional and can be left empty. If address is null or empty, false * will be returned. * * @param address the email address to check * @param name the optional name to also check * @return true if the given address is valid, false otherwise */ public boolean isValidMailAddress(@Nullable String address, @Nullable String name) { if (Strings.isEmpty(address)) { return false; } try { if (Strings.isFilled(name)) { new InternetAddress(address, name).validate(); } else { new InternetAddress(address).validate(); } return true; } catch (Throwable e) { Exceptions.ignore(e); return false; } } /** * Determines if the given email address and the optional name is valid. Throws a * HandledException otherwise. * * @param address the email address to validate * @param name the optional name to validate - can be left empty or null */ public void failForInvalidEmail(@Nullable String address, @Nullable String name) { if (!isValidMailAddress(address, name)) { throw Exceptions.createHandled() .withNLSKey("MailService.invalidAddress") .set("address", Strings.isFilled(name) ? address + " (" + name + ")" : address) .handle(); } } /** * Used as bridge between the given parameters and JavaMail */ protected class DefaultSMTPConfig implements SMTPConfiguration { @Override public String getMailHost() { return smtpHost; } @Override public String getMailPort() { return String.valueOf(smtpPort); } @Override public String getMailUser() { return smtpUser; } @Override public String getMailPassword() { return smtpPassword; } @Override public String getMailSender() { return smtpSender; } @Override public String getMailSenderName() { return smtpSenderName; } @Override public boolean isUseSenderAndEnvelopeFrom() { return true; } } private static class MailAuthenticator extends Authenticator { private SMTPConfiguration config; private MailAuthenticator(SMTPConfiguration config) { this.config = config; } @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(config.getMailUser(), config.getMailPassword()); } } /** * Implements the builder pattern to specify the mail to send. */ public class MailSender { protected boolean simulate; protected String senderEmail; protected String senderName; protected String receiverEmail; protected String receiverName; protected String subject; protected String mailExtension; protected Context context; protected boolean includeHTMLPart = true; protected String text; protected String html; protected List attachments = Lists.newArrayList(); protected String bounceToken; protected String lang; protected Map headers = Maps.newTreeMap(); /** * Sets the language used to perform {@link sirius.kernel.nls.NLS} lookups when rendering templates. * * @param langs an array of languages. The first non empty value is used. * @return the builder itself */ public MailSender setLang(String... langs) { if (langs == null) { return this; } for (String lang : langs) { if (Strings.isFilled(lang)) { this.lang = lang; return this; } } return this; } /** * Sets the email address used as sender of the email. * * @param senderEmail the address used as sender of the email. * @return the builder itself */ public MailSender fromEmail(String senderEmail) { this.senderEmail = senderEmail; return this; } /** * Sets the name used as sender of the mail. * * @param senderName the senders name of the email * @return the builder itself */ public MailSender fromName(String senderName) { this.senderName = senderName; return this; } /** * Specifies the email address to send the mail to. * * @param receiverEmail the target address of the email * @return the builder itself */ public MailSender toEmail(String receiverEmail) { this.receiverEmail = receiverEmail; return this; } /** * Specifies the name of the receiver. * * @param receiverName the name of the receiver * @return the builder itself */ public MailSender toName(String receiverName) { this.receiverName = receiverName; return this; } /** * Specifies the subject line of the mail. * * @param subject the subject line to use. * @return the builder itself */ public MailSender subject(String subject) { this.subject = subject; return this; } /** * Adds an individual header to the SMTP message. * * @param name the name of the header to add * @param value the value of the header to add * @return the builder itself */ public MailSender addHeader(String name, String value) { headers.put(name, value); return this; } /** * Specifies the mail template to use. *

* The template is used to fill the subject like as well as the text and HTML part. The mailExtension * named here has to be defined in the system config in the mails/template section: *

         * {@code
         * mail {
         *  templates {
         *      my-template {
         *          # optional - set "subject" parameter in the given context instead"
         *          subject = "Velocity expression to describe the subject"
         *
         *          # optional - Language dependent subjects
         *          subject_de = "..."
         *          subject_en = "..."
         *
         *          text = "mail/path-to-the-text-template.vm"
         *          # optional
         *          html = "mail/path-to-the-html-template.vm"
         *
         *          # optional: Language dependent templates:
         *          text_de = "..."
         *          html_de = "..."
         *          text_en = "..."
         *          html_en = "..."
         *
         *           # optional (Add headers to the mail)
         *          headers {
         *            name = value
         *          }
         *
         *          # optional
         *          attachments {
         *              test-pdf {
         *                  template = "mail/test.pdf.vm"
         *                  # optional - evaluated by velocity...
         *                  fileName = "test.pdf"
         *                  # optional
         *                  encoding = "UTF-8"
         *                  # optional
         *                  contentType = "text/plain"
         *                  # optional (treat this attachment as alternative to the body part rather than as
         *                  # "real" attachment)
         *                  alternative = true
         *
         *                  # optional (Add headers to the body part)
         *                  headers {
         *                      name = value
         *                  }
         *
         *              }
         *          }
         *      }
         *  }
         * }
         * }
         * 
*

* The given subject line is evaluates by velocity and my therefore either reference variables or use * macros like #nls('nls-key'). The given context can be used to pass variables to the templates. * * @param mailExtension the name of the mail extension to use * @param context the context used to pass in variables used by the templates * @return the builder itself */ public MailSender useMailTemplate(String mailExtension, @Nonnull Context context) { this.mailExtension = mailExtension; this.context = context; return this; } /** * Determines whether the HTML part should be included in the email or not. Some email clients have trouble * rendering HTML therefore it can be suppressed so that only text emails are sent. * * @param includeHTMLPart the flag indicating whether the HTML part should be included (default) or not. * @return the builder itself */ public MailSender includeHTMLPart(boolean includeHTMLPart) { this.includeHTMLPart = includeHTMLPart; return this; } /** * Sets the text content of the email. * * @param text the text content of the email * @return the builder itself */ public MailSender textContent(String text) { this.text = text; return this; } /** * Sets the HTML content of the email. * * @param html the HTML content of the email * @return the builder itself */ public MailSender htmlContent(String html) { this.html = html; return this; } /** * Adds an attachment to the email. * * @param attachment the attachment to add to the email * @return the builder itself */ public MailSender addAttachment(DataSource attachment) { attachments.add(attachment); return this; } /** * Adds an array of attachments to the email. * * @param attachment the attachments to add * @return the builder itself */ public MailSender addAttachments(DataSource... attachment) { if (attachment != null) { attachments.addAll(Arrays.asList(attachment)); } return this; } /** * Adds a list of attachments to the email. * * @param attachments the attachments to add * @return the builder itself */ public MailSender addAttachments(List attachments) { if (attachments != null) { this.attachments.addAll(attachments); } return this; } /** * Sets a bounce token. *

* This bounce toke is hopefully included in a bounce email (generated if a mail cannot be delivered). * This permits better bounce handling. * * @param token the token to identify the mail by a bounde handler. * @return the builder itself */ public MailSender setBounceToken(String token) { this.bounceToken = token; return this; } /** * Sets the simulation flag. *

* A mail which is simulated (simulateOnly is true) will occur * in the mail logs etc. but won't actually be sent. * * @param simulateOnly true if the mail should just be simulated but not actually be sent. * @return the builder itself */ public MailSender simulate(boolean simulateOnly) { this.simulate = simulateOnly; return this; } /** * Sends the mail using the given settings. *

* Once all settings are validated, the mail is send in a separate thread so this method will * return rather quickly. Note that a {@link sirius.kernel.health.HandledException} is thrown in case * of invalid settings (bad mail address etc.). */ public void send() { SMTPConfiguration config = new DefaultSMTPConfig(); String tmpLang = NLS.getCurrentLang(); try { try { if (lang != null) { CallContext.getCurrent().setLang(lang); } fill(); sanitize(); check(); sendMailAsync(config); } finally { CallContext.getCurrent().setLang(tmpLang); } } catch (HandledException e) { throw e; } catch (Throwable e) { throw Exceptions.handle() .withSystemErrorMessage( "Cannot send mail to '%s (%s)' from '%s (%s)' with subject '%s': %s (%s)", receiverEmail, receiverName, senderEmail, senderName, subject) .to(LOG) .error(e) .handle(); } } protected void sendMailAsync(SMTPConfiguration config) { if (Strings.isEmpty(config.getMailHost())) { LOG.WARN("Not going to send an email to '%s' with subject '%s' as no mail server is configured...", receiverEmail, subject); } else { SendMailTask task = new SendMailTask(this, config); tasks.executor("email").fork(task); } } private void check() { try { if (Strings.isFilled(receiverName)) { new InternetAddress(receiverEmail, receiverName).validate(); } else { new InternetAddress(receiverEmail).validate(); } } catch (Throwable e) { throw Exceptions.handle() .to(LOG) .error(e) .withNLSKey("MailService.invalidReceiver") .set("address", Strings.isFilled(receiverName) ? receiverEmail + " (" + receiverName + ")" : receiverEmail) .handle(); } try { if (Strings.isFilled(senderEmail)) { if (Strings.isFilled(senderName)) { new InternetAddress(senderEmail, senderName).validate(); } else { new InternetAddress(senderEmail).validate(); } } } catch (Throwable e) { throw Exceptions.handle() .to(LOG) .error(e) .withNLSKey("MailService.invalidSender") .set("address", Strings.isFilled(senderName) ? senderEmail + " (" + senderName + ")" : senderEmail) .handle(); } } private void sanitize() { if (Strings.isFilled(senderEmail)) { senderEmail = senderEmail.replaceAll("\\s", ""); } if (Strings.isFilled(senderName)) { senderName = senderName.trim(); } if (Strings.isFilled(receiverEmail)) { receiverEmail = receiverEmail.replaceAll("\\s", ""); } if (Strings.isFilled(receiverName)) { receiverName = receiverName.trim(); } if (!includeHTMLPart) { html = null; } } private void fill() { if (Strings.isEmpty(mailExtension)) { return; } Extension ex = findMailExtension(); context.put("template", mailExtension); try { fillSubject(ex); fillTextContent(ex); htmlContent(null); if (ex.get("html").isFilled()) { fillHtmlContent(ex); } if (ex.getConfig("headers") != null) { for (Map.Entry e : ex.getConfig("headers").entrySet()) { headers.put(e.getKey(), NLS.toMachineString(e.getValue().unwrapped())); } } generateAttachments(ex); } catch (HandledException e) { throw e; } catch (Throwable e) { throw Exceptions.handle() .withSystemErrorMessage( "Cannot send mail to '%s (%s)' from '%s (%s)' with subject '%s': %s (%s)", receiverEmail, receiverName, senderEmail, senderName, subject) .to(LOG) .error(e) .handle(); } } private void generateAttachments(Extension ex) { for (Config attachmentConfig : ex.getConfigs("attachments")) { String template = attachmentConfig.getString("template"); try { Templates.Generator attachment = templates.generator(); if (attachmentConfig.hasPath("encoding")) { attachment.encoding(attachmentConfig.getString("encoding")); } attachment.useTemplate(template); attachment.applyContext(context); ByteArrayOutputStream out = new ByteArrayOutputStream(); attachment.generateTo(out); out.flush(); String fileName = attachmentConfig.getString("id"); if (attachmentConfig.hasPath("fileName")) { fileName = templates.generator() .direct(attachmentConfig.getString("fileName"), VelocityContentHandler.VM) .applyContext(context) .generate(); } else { int idx = fileName.lastIndexOf("-"); if (idx >= 0) { fileName = fileName.substring(0, idx) + "." + fileName.substring(idx + 1); } } String mimeType = MimeHelper.guessMimeType(fileName); if (attachmentConfig.hasPath("contentType")) { mimeType = attachmentConfig.getString("contentType"); } boolean asAlternative = false; if (attachmentConfig.hasPath("alternative")) { asAlternative = attachmentConfig.getBoolean("alternative"); } Attachment att = new Attachment(fileName, mimeType, out.toByteArray(), asAlternative); if (attachmentConfig.hasPath("headers")) { for (Map.Entry e : attachmentConfig.getConfig("headers") .entrySet()) { att.addHeader(e.getKey(), NLS.toMachineString(e.getValue().unwrapped())); } } addAttachment(att); } catch (Throwable t) { Exceptions.handle() .to(LOG) .error(t) .withSystemErrorMessage("Cannot generate attachment using template %s (%s) " + "when sending a mail from '%s' to '%s': %s (%s)", mailExtension, template, senderEmail, receiverEmail) .handle(); } } } private void fillTextContent(Extension ex) { textContent(templates.generator() .useTemplate(ex.get("text_" + NLS.getCurrentLang()) .asString(ex.get("text").asString())) .applyContext(context) .generate()); } private void fillSubject(Extension ex) { subject(templates.generator() .direct(ex.get("subject_" + NLS.getCurrentLang()) .asString(ex.get("subject").asString("$subject")), VelocityContentHandler.VM) .applyContext(context) .generate()); } private void fillHtmlContent(Extension ex) { try { htmlContent(templates.generator() .useTemplate(ex.get("html_" + NLS.getCurrentLang()) .asString(ex.get("html").asString())) .applyContext(context) .generate()); } catch (Throwable e) { Exceptions.handle() .to(LOG) .error(e) .withSystemErrorMessage("Cannot generate HTML content using template %s (%s) " + "when sending a mail from '%s' to '%s': %s (%s)", mailExtension, ex.get("html_" + NLS.getCurrentLang()) .asString(ex.get("html").asString()), senderEmail, receiverEmail) .handle(); } } private Extension findMailExtension() { Extension ex = Extensions.getExtension("mail.templates", mailExtension); if (ex == null) { throw Exceptions.handle() .withSystemErrorMessage( "Unknown mail extension: %s. Cannot send mail from: '%s' to '%s'", mailExtension, senderEmail, receiverEmail) .handle(); } return ex; } public MailSender from(String senderEmail, String senderName) { return fromEmail(senderEmail).fromName(senderName); } public MailSender to(String receiverEmail, String receiverName) { return toEmail(receiverEmail).toName(receiverName); } } private class SendMailTask implements Runnable { private MailSender mail; private SMTPConfiguration config; private boolean success = false; private String messageId = null; private String technicalSender; private String technicalSenderName; private SendMailTask(MailSender mail, SMTPConfiguration config) { this.mail = mail; this.config = config; } @Override public void run() { if (mail.receiverEmail != null && mail.receiverEmail.toLowerCase().endsWith(".local")) { LOG.WARN( "Not going to send an email to '%s' with subject '%s' as this is a local address. Going to simulate...", mail.receiverEmail, mail.subject); mail.simulate = true; } determineTechnicalSender(); Operation op = Operation.create("mail", () -> "Sending eMail: " + mail.subject + " to: " + mail.receiverEmail, Duration.ofSeconds(30)); try { if (!mail.simulate) { sendMail(); } else { messageId = "SIMULATED"; success = true; } } finally { Operation.release(op); if (logs.isEmpty()) { if (!success) { LOG.WARN("FAILED to send mail from: '%s' to '%s' with subject: '%s'", Strings.isEmpty(mail.senderEmail) ? technicalSender : mail.senderEmail, mail.receiverEmail, mail.subject); } else { LOG.FINE("Sent mail from: '%s' to '%s' with subject: '%s'", Strings.isEmpty(mail.senderEmail) ? technicalSender : mail.senderEmail, mail.receiverEmail, mail.subject); } } else { for (MailLog log : logs) { try { log.logSentMail(success, messageId, Strings.isEmpty(mail.senderEmail) ? technicalSender : mail.senderEmail, Strings.isEmpty(mail.senderEmail) ? technicalSenderName : mail.senderName, mail.receiverEmail, mail.receiverName, mail.subject, mail.text, mail.html, mail.mailExtension); } catch (Exception e) { Exceptions.handle(LOG, e); } } } } } private void sendMail() { try { LOG.FINE("Sending eMail: " + mail.subject + " to: " + mail.receiverEmail); Session session = getMailSession(config); Transport transport = getSMTPTransport(session, config); try { try { SMTPMessage msg = createMessage(session); transport.sendMessage(msg, msg.getAllRecipients()); messageId = msg.getMessageID(); success = true; } catch (Throwable e) { throw Exceptions.handle() .withSystemErrorMessage( "Cannot send mail to %s from %s with subject '%s': %s (%s)", mail.receiverEmail, mail.senderEmail, mail.subject) .to(LOG) .error(e) .handle(); } } finally { transport.close(); } } catch (HandledException e) { throw e; } catch (Throwable e) { throw Exceptions.handle() .withSystemErrorMessage( "Invalid mail configuration: %s (Host: %s, Port: %s, User: %s, Password used: %s)", e.getMessage(), config.getMailHost(), config.getMailPort(), config.getMailUser(), Strings.isFilled(config.getMailPassword())) .to(LOG) .error(e) .handle(); } } private SMTPMessage createMessage(Session session) throws Exception { SMTPMessage msg = new SMTPMessage(session); msg.setSubject(mail.subject); msg.setRecipients(Message.RecipientType.TO, new InternetAddress[]{new InternetAddress(mail.receiverEmail, mail.receiverName)}); setupSender(msg); if (Strings.isFilled(mail.html) || !mail.attachments.isEmpty()) { MimeMultipart content = createContent(mail.text, mail.html, mail.attachments); msg.setContent(content); msg.setHeader(CONTENT_TYPE, content.getContentType()); } else { if (mail.text != null) { msg.setText(mail.text); } else { msg.setText(""); } } msg.setHeader(MIME_VERSION, MIME_VERSION_1_0); if (Strings.isFilled(mail.bounceToken)) { msg.setHeader(X_BOUNCETOKEN, mail.bounceToken); } msg.setHeader(X_MAILER, mailer); for (Map.Entry e : mail.headers.entrySet()) { if (Strings.isEmpty(e.getValue())) { msg.removeHeader(e.getKey()); } else { msg.setHeader(e.getKey(), e.getValue()); } } msg.setSentDate(new Date()); return msg; } private void setupSender(SMTPMessage msg) throws MessagingException, UnsupportedEncodingException { if (Strings.isFilled(mail.senderEmail)) { if (config.isUseSenderAndEnvelopeFrom()) { msg.setSender(new InternetAddress(technicalSender, technicalSenderName)); } msg.setFrom(new InternetAddress(mail.senderEmail, mail.senderName)); } else { msg.setFrom(new InternetAddress(technicalSender, technicalSenderName)); } if (config.isUseSenderAndEnvelopeFrom()) { msg.setEnvelopeFrom(Strings.isFilled(config.getMailSender()) ? config.getMailSender() : defaultConfig.getMailSender()); } } private void determineTechnicalSender() { technicalSender = config.getMailSender(); technicalSenderName = config.getMailSenderName(); if (Strings.isEmpty(technicalSender)) { technicalSender = defaultConfig.getMailSender(); technicalSenderName = defaultConfig.getMailSenderName(); } } private Session getMailSession(SMTPConfiguration config) { Properties props = new Properties(); props.setProperty(MAIL_SMTP_PORT, Strings.isEmpty(config.getMailPort()) ? "25" : config.getMailPort()); props.setProperty(MAIL_SMTP_HOST, config.getMailHost()); if (Strings.isFilled(config.getMailSender())) { props.setProperty(MAIL_FROM, config.getMailSender()); } // Set a fixed timeout of 60s for all operations - the default timeout is "infinite" props.setProperty(MAIL_SMTP_CONNECTIONTIMEOUT, MAIL_SOCKET_TIMEOUT); props.setProperty(MAIL_SMTP_TIMEOUT, MAIL_SOCKET_TIMEOUT); props.setProperty(MAIL_SMTP_WRITETIMEOUT, MAIL_SOCKET_TIMEOUT); props.setProperty(MAIL_TRANSPORT_PROTOCOL, SMTP); Authenticator auth = new MailAuthenticator(config); if (Strings.isEmpty(config.getMailPassword())) { props.setProperty(MAIL_SMTP_AUTH, Boolean.FALSE.toString()); return Session.getInstance(props); } else { props.setProperty(MAIL_USER, config.getMailUser()); props.setProperty(MAIL_SMTP_AUTH, Boolean.TRUE.toString()); return Session.getInstance(props, auth); } } private MimeMultipart createContent(String textPart, String htmlPart, List attachments) throws Exception { MimeMultipart content = createMainContent(textPart, htmlPart); List mixedAttachments = filterAndAppendAlternativeParts(attachments, content); if (mixedAttachments.isEmpty()) { return content; } return createMixedContent(content, mixedAttachments); } /* * Adds the text and html body parts as ALTERNATIVE */ private MimeMultipart createMainContent(String textPart, String htmlPart) throws MessagingException { MimeMultipart content = new MimeMultipart(ALTERNATIVE); MimeBodyPart text = new MimeBodyPart(); MimeBodyPart html = new MimeBodyPart(); text.setText(textPart, Charsets.UTF_8.name()); text.setHeader(MIME_VERSION, MIME_VERSION_1_0); text.setHeader(CONTENT_TYPE, TEXT_PLAIN_CHARSET_UTF_8); content.addBodyPart(text); if (htmlPart != null) { htmlPart = Strings.replaceUmlautsToHtml(htmlPart); html.setText(htmlPart, Charsets.UTF_8.name()); html.setHeader(MIME_VERSION, MIME_VERSION_1_0); html.setHeader(CONTENT_TYPE, TEXT_HTML_CHARSET_UTF_8); content.addBodyPart(html); } return content; } /* * Filters all ALTERNATIVE attachments and adds them to the content. * returns all "real" attachments which have to be added as mixed body parts */ private List filterAndAppendAlternativeParts(List attachments, MimeMultipart content) throws MessagingException { // by default an "attachment" would be added as mixed body part // however, some attachments like an iCalendar invitation must be added // as alternative body part for the given html and text part // Therefore we split the attachments into these two categories and then generate // the appropriate parts... List mixedAttachments = Lists.newArrayList(); for (DataSource attachment : attachments) { // Filter null values since var-args are tricky... if (attachment != null) { if (attachment instanceof Attachment && ((Attachment) attachment).isAlternative()) { MimeBodyPart part = createBodyPart(attachment); content.addBodyPart(part); } else { mixedAttachments.add(attachment); } } } return mixedAttachments; } /* * Appends all "real" attachments as mixed body parts */ private MimeMultipart createMixedContent(MimeMultipart content, List mixedAttachments) throws MessagingException { // Generate a new root-multipart which contains the mail-content // as alternative-content as well as the attachments. MimeMultipart mixed = new MimeMultipart(MIXED); MimeBodyPart contentPart = new MimeBodyPart(); contentPart.setContent(content); mixed.addBodyPart(contentPart); for (DataSource attachment : mixedAttachments) { MimeBodyPart part = createBodyPart(attachment); mixed.addBodyPart(part); } return mixed; } /* * Creates a body part for the given attachment */ private MimeBodyPart createBodyPart(DataSource attachment) throws MessagingException { MimeBodyPart part = new MimeBodyPart(); part.setFileName(attachment.getName()); part.setDataHandler(new DataHandler(attachment)); if (attachment instanceof Attachment) { for (Map.Entry h : ((Attachment) attachment).getHeaders()) { if (Strings.isEmpty(h.getValue())) { part.removeHeader(h.getKey()); } else { part.setHeader(h.getKey(), h.getValue()); } } } return part; } } protected Transport getSMTPTransport(Session session, SMTPConfiguration config) { try { Transport transport = session.getTransport(SMTP); transport.connect(config.getMailHost(), config.getMailUser(), null); return transport; } catch (Exception e) { throw Exceptions.handle() .withSystemErrorMessage( "Invalid mail configuration: %s (Host: %s, Port: %s, User: %s, Password used: %s)", e.getMessage(), config.getMailHost(), config.getMailPort(), config.getMailUser(), Strings.isFilled(config.getMailPassword())) .to(LOG) .error(e) .handle(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy