diff --git a/notify-email/pom.xml b/notify-email/pom.xml index 633d5f5..23e34f5 100644 --- a/notify-email/pom.xml +++ b/notify-email/pom.xml @@ -23,12 +23,6 @@ notify-core - - org.springframework - spring-context-support - provided - - com.alibaba fastjson diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/DefaultEmailNotifier.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/DefaultEmailNotifier.java index 29d6be7..cbf0109 100644 --- a/notify-email/src/main/java/com/simaek/notify/email/embedded/DefaultEmailNotifier.java +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/DefaultEmailNotifier.java @@ -5,6 +5,9 @@ import com.simaek.notify.*; import com.simaek.notify.email.EmailProvider; import com.simaek.notify.email.EmailTemplate; import com.simaek.notify.email.EmailTemplateParsed; +import com.simaek.notify.email.embedded.mail.javamail.JavaMailSender; +import com.simaek.notify.email.embedded.mail.javamail.JavaMailSenderImpl; +import com.simaek.notify.email.embedded.mail.javamail.MimeMessageHelper; import com.simaek.notify.util.ExpressionUtils; import com.simaek.notify.util.StringUtils; import okhttp3.Call; @@ -16,9 +19,6 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamSource; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.JavaMailSenderImpl; -import org.springframework.mail.javamail.MimeMessageHelper; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailAuthenticationException.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailAuthenticationException.java new file mode 100644 index 0000000..d9aca4c --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailAuthenticationException.java @@ -0,0 +1,39 @@ +package com.simaek.notify.email.embedded.mail; + +/** + * Exception thrown on failed authentication. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + */ +public class MailAuthenticationException extends MailException { + + /** + * Constructor for MailAuthenticationException. + * + * @param msg message + */ + public MailAuthenticationException(String msg) { + super(msg); + } + + /** + * Constructor for MailAuthenticationException. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + */ + public MailAuthenticationException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Constructor for MailAuthenticationException. + * + * @param cause the root cause from the mail API in use + */ + public MailAuthenticationException(Throwable cause) { + super("Authentication failed", cause); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailException.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailException.java new file mode 100644 index 0000000..c6b0696 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailException.java @@ -0,0 +1,32 @@ +package com.simaek.notify.email.embedded.mail; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.lang.Nullable; + +/** + * Base class for all mail exceptions. + * + * @author Dmitriy Kopylenko + */ +public abstract class MailException extends NestedRuntimeException { + + /** + * Constructor for MailException. + * + * @param msg the detail message + */ + public MailException(String msg) { + super(msg); + } + + /** + * Constructor for MailException. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + */ + public MailException(@Nullable String msg, @Nullable Throwable cause) { + super(msg, cause); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailMessage.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailMessage.java new file mode 100644 index 0000000..d3c7596 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailMessage.java @@ -0,0 +1,46 @@ +package com.simaek.notify.email.embedded.mail; + +import com.simaek.notify.email.embedded.mail.javamail.MimeMessageHelper; + +import java.util.Date; + +/** + * This is a common interface for mail messages, allowing a user to set key + * values required in assembling a mail message, without needing to know if + * the underlying message is a simple text message or a more sophisticated + * MIME message. + * + *

Implemented by both SimpleMailMessage and MimeMessageHelper, + * to let message population code interact with a simple message or a + * MIME message through a common interface. + * + * @author Juergen Hoeller + * @see SimpleMailMessage + * @see MimeMessageHelper + * @since 1.1.5 + */ +public interface MailMessage { + + void setFrom(String from) throws MailParseException; + + void setReplyTo(String replyTo) throws MailParseException; + + void setTo(String to) throws MailParseException; + + void setTo(String... to) throws MailParseException; + + void setCc(String cc) throws MailParseException; + + void setCc(String... cc) throws MailParseException; + + void setBcc(String bcc) throws MailParseException; + + void setBcc(String... bcc) throws MailParseException; + + void setSentDate(Date sentDate) throws MailParseException; + + void setSubject(String subject) throws MailParseException; + + void setText(String text) throws MailParseException; + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailParseException.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailParseException.java new file mode 100644 index 0000000..a317708 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailParseException.java @@ -0,0 +1,39 @@ +package com.simaek.notify.email.embedded.mail; + +/** + * Exception thrown if illegal message properties are encountered. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + */ +public class MailParseException extends MailException { + + /** + * Constructor for MailParseException. + * + * @param msg the detail message + */ + public MailParseException(String msg) { + super(msg); + } + + /** + * Constructor for MailParseException. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + */ + public MailParseException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Constructor for MailParseException. + * + * @param cause the root cause from the mail API in use + */ + public MailParseException(Throwable cause) { + super("Could not parse mail", cause); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailPreparationException.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailPreparationException.java new file mode 100644 index 0000000..bca6430 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailPreparationException.java @@ -0,0 +1,35 @@ +package com.simaek.notify.email.embedded.mail; + +/** + * Exception to be thrown by user code if a mail cannot be prepared properly, + * for example when a FreeMarker template cannot be rendered for the mail text. + * + * @author Juergen Hoeller + * @since 1.1 + */ +public class MailPreparationException extends MailException { + + /** + * Constructor for MailPreparationException. + * + * @param msg the detail message + */ + public MailPreparationException(String msg) { + super(msg); + } + + /** + * Constructor for MailPreparationException. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + */ + public MailPreparationException(String msg, Throwable cause) { + super(msg, cause); + } + + public MailPreparationException(Throwable cause) { + super("Could not prepare mail", cause); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSendException.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSendException.java new file mode 100644 index 0000000..5a9818e --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSendException.java @@ -0,0 +1,184 @@ +package com.simaek.notify.email.embedded.mail; + +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Exception thrown when a mail sending error is encountered. + * Can register failed messages with their exceptions. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + */ +public class MailSendException extends MailException { + + private final transient Map failedMessages; + + @Nullable + private final Exception[] messageExceptions; + + + /** + * Constructor for MailSendException. + * + * @param msg the detail message + */ + public MailSendException(String msg) { + this(msg, null); + } + + /** + * Constructor for MailSendException. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + */ + public MailSendException(String msg, @Nullable Throwable cause) { + super(msg, cause); + this.failedMessages = new LinkedHashMap<>(); + this.messageExceptions = null; + } + + /** + * Constructor for registration of failed messages, with the + * messages that failed as keys, and the thrown exceptions as values. + *

The messages should be the same that were originally passed + * to the invoked send method. + * + * @param msg the detail message + * @param cause the root cause from the mail API in use + * @param failedMessages a Map of failed messages as keys and thrown + * exceptions as values + */ + public MailSendException(@Nullable String msg, @Nullable Throwable cause, Map failedMessages) { + super(msg, cause); + this.failedMessages = new LinkedHashMap<>(failedMessages); + this.messageExceptions = failedMessages.values().toArray(new Exception[0]); + } + + /** + * Constructor for registration of failed messages, with the + * messages that failed as keys, and the thrown exceptions as values. + *

The messages should be the same that were originally passed + * to the invoked send method. + * + * @param failedMessages a Map of failed messages as keys and thrown + * exceptions as values + */ + public MailSendException(Map failedMessages) { + this(null, null, failedMessages); + } + + + /** + * Return a Map with the failed messages as keys, and the thrown exceptions + * as values. + *

Note that a general mail server connection failure will not result + * in failed messages being returned here: A message will only be + * contained here if actually sending it was attempted but failed. + *

The messages will be the same that were originally passed to the + * invoked send method, that is, SimpleMailMessages in case of using + * the generic MailSender interface. + *

In case of sending MimeMessage instances via JavaMailSender, + * the messages will be of type MimeMessage. + *

NOTE: This Map will not be available after serialization. + * Use {@link #getMessageExceptions()} in such a scenario, which will + * be available after serialization as well. + * + * @return the Map of failed messages as keys and thrown exceptions as values + * @see SimpleMailMessage + * @see javax.mail.internet.MimeMessage + */ + public final Map getFailedMessages() { + return this.failedMessages; + } + + /** + * Return an array with thrown message exceptions. + *

Note that a general mail server connection failure will not result + * in failed messages being returned here: A message will only be + * contained here if actually sending it was attempted but failed. + * + * @return the array of thrown message exceptions, + * or an empty array if no failed messages + */ + public final Exception[] getMessageExceptions() { + return (this.messageExceptions != null ? this.messageExceptions : new Exception[0]); + } + + + @Override + @Nullable + public String getMessage() { + if (ObjectUtils.isEmpty(this.messageExceptions)) { + return super.getMessage(); + } else { + StringBuilder sb = new StringBuilder(); + String baseMessage = super.getMessage(); + if (baseMessage != null) { + sb.append(baseMessage).append(". "); + } + sb.append("Failed messages: "); + for (int i = 0; i < this.messageExceptions.length; i++) { + Exception subEx = this.messageExceptions[i]; + sb.append(subEx.toString()); + if (i < this.messageExceptions.length - 1) { + sb.append("; "); + } + } + return sb.toString(); + } + } + + @Override + public String toString() { + if (ObjectUtils.isEmpty(this.messageExceptions)) { + return super.toString(); + } else { + StringBuilder sb = new StringBuilder(super.toString()); + sb.append("; message exceptions (").append(this.messageExceptions.length).append(") are:"); + for (int i = 0; i < this.messageExceptions.length; i++) { + Exception subEx = this.messageExceptions[i]; + sb.append('\n').append("Failed message ").append(i + 1).append(": "); + sb.append(subEx); + } + return sb.toString(); + } + } + + @Override + public void printStackTrace(PrintStream ps) { + if (ObjectUtils.isEmpty(this.messageExceptions)) { + super.printStackTrace(ps); + } else { + ps.println(super.toString() + "; message exception details (" + + this.messageExceptions.length + ") are:"); + for (int i = 0; i < this.messageExceptions.length; i++) { + Exception subEx = this.messageExceptions[i]; + ps.println("Failed message " + (i + 1) + ":"); + subEx.printStackTrace(ps); + } + } + } + + @Override + public void printStackTrace(PrintWriter pw) { + if (ObjectUtils.isEmpty(this.messageExceptions)) { + super.printStackTrace(pw); + } else { + pw.println(super.toString() + "; message exception details (" + + this.messageExceptions.length + ") are:"); + for (int i = 0; i < this.messageExceptions.length; i++) { + Exception subEx = this.messageExceptions[i]; + pw.println("Failed message " + (i + 1) + ":"); + subEx.printStackTrace(pw); + } + } + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSender.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSender.java new file mode 100644 index 0000000..183291f --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/MailSender.java @@ -0,0 +1,40 @@ +package com.simaek.notify.email.embedded.mail; + +import com.simaek.notify.email.embedded.mail.javamail.JavaMailSender; + +/** + * This interface defines a strategy for sending simple mails. Can be + * implemented for a variety of mailing systems due to the simple requirements. + * For richer functionality like MIME messages, consider JavaMailSender. + * + *

Allows for easy testing of clients, as it does not depend on JavaMail's + * infrastructure classes: no mocking of JavaMail Session or Transport necessary. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @see JavaMailSender + * @since 10.09.2003 + */ +public interface MailSender { + + /** + * Send the given simple mail message. + * + * @param simpleMessage the message to send + * @throws MailParseException in case of failure when parsing the message + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending the message + */ + void send(SimpleMailMessage simpleMessage) throws MailException; + + /** + * Send the given array of simple mail messages in batch. + * + * @param simpleMessages the messages to send + * @throws MailParseException in case of failure when parsing a message + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending a message + */ + void send(SimpleMailMessage... simpleMessages) throws MailException; + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/SimpleMailMessage.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/SimpleMailMessage.java new file mode 100644 index 0000000..bb8673b --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/SimpleMailMessage.java @@ -0,0 +1,270 @@ +package com.simaek.notify.email.embedded.mail; + +import com.simaek.notify.email.embedded.mail.javamail.JavaMailSender; +import com.simaek.notify.email.embedded.mail.javamail.MimeMailMessage; +import com.simaek.notify.email.embedded.mail.javamail.MimeMessageHelper; +import com.simaek.notify.email.embedded.mail.javamail.MimeMessagePreparator; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.Date; + +/** + * Models a simple mail message, including data such as the from, to, cc, subject, + * and text fields. + * + *

Consider {@code JavaMailSender} and JavaMail {@code MimeMessages} for creating + * more sophisticated messages, for example messages with attachments, special + * character encodings, or personal names that accompany mail addresses. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @see MailSender + * @see JavaMailSender + * @see MimeMessagePreparator + * @see MimeMessageHelper + * @see MimeMailMessage + * @since 10.09.2003 + */ +public class SimpleMailMessage implements MailMessage, Serializable { + + @Nullable + private String from; + + @Nullable + private String replyTo; + + @Nullable + private String[] to; + + @Nullable + private String[] cc; + + @Nullable + private String[] bcc; + + @Nullable + private Date sentDate; + + @Nullable + private String subject; + + @Nullable + private String text; + + + /** + * Create a new {@code SimpleMailMessage}. + */ + public SimpleMailMessage() { + } + + /** + * Copy constructor for creating a new {@code SimpleMailMessage} from the state + * of an existing {@code SimpleMailMessage} instance. + */ + public SimpleMailMessage(SimpleMailMessage original) { + Assert.notNull(original, "'original' message argument must not be null"); + this.from = original.getFrom(); + this.replyTo = original.getReplyTo(); + this.to = copyOrNull(original.getTo()); + this.cc = copyOrNull(original.getCc()); + this.bcc = copyOrNull(original.getBcc()); + this.sentDate = original.getSentDate(); + this.subject = original.getSubject(); + this.text = original.getText(); + } + + + @Override + public void setFrom(String from) { + this.from = from; + } + + @Nullable + public String getFrom() { + return this.from; + } + + @Override + public void setReplyTo(String replyTo) { + this.replyTo = replyTo; + } + + @Nullable + public String getReplyTo() { + return this.replyTo; + } + + @Override + public void setTo(String to) { + this.to = new String[]{to}; + } + + @Override + public void setTo(String... to) { + this.to = to; + } + + @Nullable + public String[] getTo() { + return this.to; + } + + @Override + public void setCc(String cc) { + this.cc = new String[]{cc}; + } + + @Override + public void setCc(String... cc) { + this.cc = cc; + } + + @Nullable + public String[] getCc() { + return this.cc; + } + + @Override + public void setBcc(String bcc) { + this.bcc = new String[]{bcc}; + } + + @Override + public void setBcc(String... bcc) { + this.bcc = bcc; + } + + @Nullable + public String[] getBcc() { + return this.bcc; + } + + @Override + public void setSentDate(Date sentDate) { + this.sentDate = sentDate; + } + + @Nullable + public Date getSentDate() { + return this.sentDate; + } + + @Override + public void setSubject(String subject) { + this.subject = subject; + } + + @Nullable + public String getSubject() { + return this.subject; + } + + @Override + public void setText(String text) { + this.text = text; + } + + @Nullable + public String getText() { + return this.text; + } + + + /** + * Copy the contents of this message to the given target message. + * + * @param target the {@code MailMessage} to copy to + */ + public void copyTo(MailMessage target) { + Assert.notNull(target, "'target' MailMessage must not be null"); + if (getFrom() != null) { + target.setFrom(getFrom()); + } + if (getReplyTo() != null) { + target.setReplyTo(getReplyTo()); + } + if (getTo() != null) { + target.setTo(copy(getTo())); + } + if (getCc() != null) { + target.setCc(copy(getCc())); + } + if (getBcc() != null) { + target.setBcc(copy(getBcc())); + } + if (getSentDate() != null) { + target.setSentDate(getSentDate()); + } + if (getSubject() != null) { + target.setSubject(getSubject()); + } + if (getText() != null) { + target.setText(getText()); + } + } + + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SimpleMailMessage)) { + return false; + } + SimpleMailMessage otherMessage = (SimpleMailMessage) other; + return (ObjectUtils.nullSafeEquals(this.from, otherMessage.from) && + ObjectUtils.nullSafeEquals(this.replyTo, otherMessage.replyTo) && + ObjectUtils.nullSafeEquals(this.to, otherMessage.to) && + ObjectUtils.nullSafeEquals(this.cc, otherMessage.cc) && + ObjectUtils.nullSafeEquals(this.bcc, otherMessage.bcc) && + ObjectUtils.nullSafeEquals(this.sentDate, otherMessage.sentDate) && + ObjectUtils.nullSafeEquals(this.subject, otherMessage.subject) && + ObjectUtils.nullSafeEquals(this.text, otherMessage.text)); + } + + @Override + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(this.from); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.replyTo); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.to); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.cc); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.bcc); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.sentDate); + hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.subject); + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("SimpleMailMessage: "); + sb.append("from=").append(this.from).append("; "); + sb.append("replyTo=").append(this.replyTo).append("; "); + sb.append("to=").append(StringUtils.arrayToCommaDelimitedString(this.to)).append("; "); + sb.append("cc=").append(StringUtils.arrayToCommaDelimitedString(this.cc)).append("; "); + sb.append("bcc=").append(StringUtils.arrayToCommaDelimitedString(this.bcc)).append("; "); + sb.append("sentDate=").append(this.sentDate).append("; "); + sb.append("subject=").append(this.subject).append("; "); + sb.append("text=").append(this.text); + return sb.toString(); + } + + + @Nullable + private static String[] copyOrNull(@Nullable String[] state) { + if (state == null) { + return null; + } + return copy(state); + } + + private static String[] copy(String[] state) { + return state.clone(); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/ConfigurableMimeFileTypeMap.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/ConfigurableMimeFileTypeMap.java new file mode 100644 index 0000000..98cc5a3 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/ConfigurableMimeFileTypeMap.java @@ -0,0 +1,159 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * Spring-configurable {@code FileTypeMap} implementation that will read + * MIME type to file extension mappings from a standard JavaMail MIME type + * mapping file, using a standard {@code MimetypesFileTypeMap} underneath. + * + *

The mapping file should be in the following format, as specified by the + * Java Activation Framework: + * + *

+ * # map text/html to .htm and .html files
+ * text/html  html htm HTML HTM
+ *

+ * Lines starting with {@code #} are treated as comments and are ignored. All + * other lines are treated as mappings. Each mapping line should contain the MIME + * type as the first entry and then each file extension to map to that MIME type + * as subsequent entries. Each entry is separated by spaces or tabs. + * + *

By default, the mappings in the {@code mime.types} file located in the + * same package as this class are used, which cover many common file extensions + * (in contrast to the out-of-the-box mappings in {@code activation.jar}). + * This can be overridden using the {@code mappingLocation} property. + * + *

Additional mappings can be added via the {@code mappings} bean property, + * as lines that follow the {@code mime.types} file format. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @see #setMappingLocation + * @see #setMappings + * @see MimetypesFileTypeMap + * @since 1.2 + */ +public class ConfigurableMimeFileTypeMap extends FileTypeMap { + + /** + * The {@code Resource} to load the mapping file from. + */ + private Resource mappingLocation = new ClassPathResource("mime.types", getClass()); + + /** + * Used to configure additional mappings. + */ + @Nullable + private String[] mappings; + + /** + * The delegate FileTypeMap, compiled from the mappings in the mapping file + * and the entries in the {@code mappings} property. + */ + @Nullable + private FileTypeMap fileTypeMap; + + + /** + * Specify the {@code Resource} from which mappings are loaded. + *

Needs to follow the {@code mime.types} file format, as specified + * by the Java Activation Framework, containing lines such as:
+ * {@code text/html html htm HTML HTM} + */ + public void setMappingLocation(Resource mappingLocation) { + this.mappingLocation = mappingLocation; + } + + /** + * Specify additional MIME type mappings as lines that follow the + * {@code mime.types} file format, as specified by the + * Java Activation Framework. For example:
+ * {@code text/html html htm HTML HTM} + */ + public void setMappings(String... mappings) { + this.mappings = mappings; + } + + + /** + * Return the delegate FileTypeMap, compiled from the mappings in the mapping file + * and the entries in the {@code mappings} property. + * + * @see #setMappingLocation + * @see #setMappings + * @see #createFileTypeMap + */ + protected final FileTypeMap getFileTypeMap() { + if (this.fileTypeMap == null) { + try { + this.fileTypeMap = createFileTypeMap(this.mappingLocation, this.mappings); + } catch (IOException ex) { + throw new IllegalStateException( + "Could not load specified MIME type mapping file: " + this.mappingLocation, ex); + } + } + return this.fileTypeMap; + } + + /** + * Compile a {@link FileTypeMap} from the mappings in the given mapping file + * and the given mapping entries. + *

The default implementation creates an Activation Framework {@link MimetypesFileTypeMap}, + * passing in an InputStream from the mapping resource (if any) and registering + * the mapping lines programmatically. + * + * @param mappingLocation a {@code mime.types} mapping resource (can be {@code null}) + * @param mappings an array of MIME type mapping lines (can be {@code null}) + * @return the compiled FileTypeMap + * @throws IOException if resource access failed + * @see MimetypesFileTypeMap#MimetypesFileTypeMap(InputStream) + * @see MimetypesFileTypeMap#addMimeTypes(String) + */ + protected FileTypeMap createFileTypeMap(@Nullable Resource mappingLocation, @Nullable String[] mappings) throws IOException { + MimetypesFileTypeMap fileTypeMap = null; + if (mappingLocation != null) { + try (InputStream is = mappingLocation.getInputStream()) { + fileTypeMap = new MimetypesFileTypeMap(is); + } + } else { + fileTypeMap = new MimetypesFileTypeMap(); + } + if (mappings != null) { + for (String mapping : mappings) { + fileTypeMap.addMimeTypes(mapping); + } + } + return fileTypeMap; + } + + + /** + * Delegates to the underlying FileTypeMap. + * + * @see #getFileTypeMap() + */ + @Override + public String getContentType(File file) { + return getFileTypeMap().getContentType(file); + } + + /** + * Delegates to the underlying FileTypeMap. + * + * @see #getFileTypeMap() + */ + @Override + public String getContentType(String fileName) { + return getFileTypeMap().getContentType(fileName); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/InternetAddressEditor.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/InternetAddressEditor.java new file mode 100644 index 0000000..21c15fa --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/InternetAddressEditor.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed 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 + * + * https://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.simaek.notify.email.embedded.mail.javamail; + +import org.springframework.util.StringUtils; + +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import java.beans.PropertyEditorSupport; + +/** + * Editor for {@code java.mail.internet.InternetAddress}, + * to directly populate an InternetAddress property. + * + *

Expects the same syntax as InternetAddress's constructor with + * a String argument. Converts empty Strings into null values. + * + * @author Juergen Hoeller + * @since 1.2.3 + * @see InternetAddress + */ +public class InternetAddressEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + try { + setValue(new InternetAddress(text)); + } + catch (AddressException ex) { + throw new IllegalArgumentException("Could not parse mail address: " + ex.getMessage()); + } + } + else { + setValue(null); + } + } + + @Override + public String getAsText() { + InternetAddress value = (InternetAddress) getValue(); + return (value != null ? value.toUnicodeString() : ""); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSender.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSender.java new file mode 100644 index 0000000..b3b26ec --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSender.java @@ -0,0 +1,118 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import com.simaek.notify.email.embedded.mail.*; + +import javax.mail.internet.MimeMessage; +import java.io.InputStream; + +/** + * Extended {@link MailSender} interface for JavaMail, + * supporting MIME messages both as direct arguments and through preparation + * callbacks. Typically used in conjunction with the {@link MimeMessageHelper} + * class for convenient creation of JavaMail {@link MimeMessage MimeMessages}, + * including attachments etc. + * + *

Clients should talk to the mail sender through this interface if they need + * mail functionality beyond {@link SimpleMailMessage}. + * The production implementation is {@link JavaMailSenderImpl}; for testing, + * mocks can be created based on this interface. Clients will typically receive + * the JavaMailSender reference through dependency injection. + * + *

The recommended way of using this interface is the {@link MimeMessagePreparator} + * mechanism, possibly using a {@link MimeMessageHelper} for populating the message. + * See {@link MimeMessageHelper MimeMessageHelper's javadoc} for an example. + * + *

The entire JavaMail {@link javax.mail.Session} management is abstracted + * by the JavaMailSender. Client code should not deal with a Session in any way, + * rather leave the entire JavaMail configuration and resource handling to the + * JavaMailSender implementation. This also increases testability. + * + *

A JavaMailSender client is not as easy to test as a plain + * {@link MailSender} client, but still straightforward + * compared to traditional JavaMail code: Just let {@link #createMimeMessage()} + * return a plain {@link MimeMessage} created with a + * {@code Session.getInstance(new Properties())} call, and check the passed-in + * messages in your mock implementations of the various {@code send} methods. + * + * @author Juergen Hoeller + * @see MimeMessage + * @see javax.mail.Session + * @see JavaMailSenderImpl + * @see MimeMessagePreparator + * @see MimeMessageHelper + * @since 07.10.2003 + */ +public interface JavaMailSender extends MailSender { + + /** + * Create a new JavaMail MimeMessage for the underlying JavaMail Session + * of this sender. Needs to be called to create MimeMessage instances + * that can be prepared by the client and passed to send(MimeMessage). + * + * @return the new MimeMessage instance + * @see #send(MimeMessage) + * @see #send(MimeMessage[]) + */ + MimeMessage createMimeMessage(); + + /** + * Create a new JavaMail MimeMessage for the underlying JavaMail Session + * of this sender, using the given input stream as the message source. + * + * @param contentStream the raw MIME input stream for the message + * @return the new MimeMessage instance + * @throws MailParseException in case of message creation failure + */ + MimeMessage createMimeMessage(InputStream contentStream) throws MailException; + + /** + * Send the given JavaMail MIME message. + * The message needs to have been created with {@link #createMimeMessage()}. + * + * @param mimeMessage message to send + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending the message + * @see #createMimeMessage + */ + void send(MimeMessage mimeMessage) throws MailException; + + /** + * Send the given array of JavaMail MIME messages in batch. + * The messages need to have been created with {@link #createMimeMessage()}. + * + * @param mimeMessages messages to send + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending a message + * @see #createMimeMessage + */ + void send(MimeMessage... mimeMessages) throws MailException; + + /** + * Send the JavaMail MIME message prepared by the given MimeMessagePreparator. + *

Alternative way to prepare MimeMessage instances, instead of + * {@link #createMimeMessage()} and {@link #send(MimeMessage)} calls. + * Takes care of proper exception conversion. + * + * @param mimeMessagePreparator the preparator to use + * @throws MailPreparationException in case of failure when preparing the message + * @throws MailParseException in case of failure when parsing the message + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending the message + */ + void send(MimeMessagePreparator mimeMessagePreparator) throws MailException; + + /** + * Send the JavaMail MIME messages prepared by the given MimeMessagePreparators. + *

Alternative way to prepare MimeMessage instances, instead of + * {@link #createMimeMessage()} and {@link #send(MimeMessage[])} calls. + * Takes care of proper exception conversion. + * + * @param mimeMessagePreparators the preparator to use + * @throws MailPreparationException in case of failure when preparing a message + * @throws MailParseException in case of failure when parsing a message + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending a message + */ + void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException; + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSenderImpl.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSenderImpl.java new file mode 100644 index 0000000..e93ff6f --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/JavaMailSenderImpl.java @@ -0,0 +1,507 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import com.simaek.notify.email.embedded.mail.*; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import javax.activation.FileTypeMap; +import javax.mail.*; +import javax.mail.internet.MimeMessage; +import java.io.InputStream; +import java.util.*; + +/** + * Production implementation of the {@link JavaMailSender} interface, + * supporting both JavaMail {@link MimeMessage MimeMessages} and Spring + * {@link SimpleMailMessage SimpleMailMessages}. Can also be used as a + * plain {@link MailSender} implementation. + * + *

Allows for defining all settings locally as bean properties. + * Alternatively, a pre-configured JavaMail {@link Session} can be + * specified, possibly pulled from an application server's JNDI environment. + * + *

Non-default properties in this object will always override the settings + * in the JavaMail {@code Session}. Note that if overriding all values locally, + * there is no added value in setting a pre-configured {@code Session}. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @see MimeMessage + * @see Session + * @see #setSession + * @see #setJavaMailProperties + * @see #setHost + * @see #setPort + * @see #setUsername + * @see #setPassword + * @since 10.09.2003 + */ +public class JavaMailSenderImpl implements JavaMailSender { + + /** + * The default protocol: 'smtp'. + */ + public static final String DEFAULT_PROTOCOL = "smtp"; + + /** + * The default port: -1. + */ + public static final int DEFAULT_PORT = -1; + + private static final String HEADER_MESSAGE_ID = "Message-ID"; + + + private Properties javaMailProperties = new Properties(); + + @Nullable + private Session session; + + @Nullable + private String protocol; + + @Nullable + private String host; + + private int port = DEFAULT_PORT; + + @Nullable + private String username; + + @Nullable + private String password; + + @Nullable + private String defaultEncoding; + + @Nullable + private FileTypeMap defaultFileTypeMap; + + + /** + * Create a new instance of the {@code JavaMailSenderImpl} class. + *

Initializes the {@link #setDefaultFileTypeMap "defaultFileTypeMap"} + * property with a default {@link ConfigurableMimeFileTypeMap}. + */ + public JavaMailSenderImpl() { + ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap(); +// fileTypeMap.afterPropertiesSet(); + this.defaultFileTypeMap = fileTypeMap; + } + + + /** + * Set JavaMail properties for the {@code Session}. + *

A new {@code Session} will be created with those properties. + * Use either this method or {@link #setSession}, but not both. + *

Non-default properties in this instance will override given + * JavaMail properties. + */ + public void setJavaMailProperties(Properties javaMailProperties) { + this.javaMailProperties = javaMailProperties; + synchronized (this) { + this.session = null; + } + } + + /** + * Allow {code Map} access to the JavaMail properties of this sender, + * with the option to add or override specific entries. + *

Useful for specifying entries directly, for example via + * {code javaMailProperties[mail.smtp.auth]}. + */ + public Properties getJavaMailProperties() { + return this.javaMailProperties; + } + + /** + * Set the JavaMail {@code Session}, possibly pulled from JNDI. + *

Default is a new {@code Session} without defaults, that is + * completely configured via this instance's properties. + *

If using a pre-configured {@code Session}, non-default properties + * in this instance will override the settings in the {@code Session}. + * + * @see #setJavaMailProperties + */ + public synchronized void setSession(Session session) { + Assert.notNull(session, "Session must not be null"); + this.session = session; + } + + /** + * Return the JavaMail {@code Session}, + * lazily initializing it if it hasn't been specified explicitly. + */ + public synchronized Session getSession() { + if (this.session == null) { + this.session = Session.getInstance(this.javaMailProperties); + } + return this.session; + } + + /** + * Set the mail protocol. Default is "smtp". + */ + public void setProtocol(@Nullable String protocol) { + this.protocol = protocol; + } + + /** + * Return the mail protocol. + */ + @Nullable + public String getProtocol() { + return this.protocol; + } + + /** + * Set the mail server host, typically an SMTP host. + *

Default is the default host of the underlying JavaMail Session. + */ + public void setHost(@Nullable String host) { + this.host = host; + } + + /** + * Return the mail server host. + */ + @Nullable + public String getHost() { + return this.host; + } + + /** + * Set the mail server port. + *

Default is {@link #DEFAULT_PORT}, letting JavaMail use the default + * SMTP port (25). + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Return the mail server port. + */ + public int getPort() { + return this.port; + } + + /** + * Set the username for the account at the mail host, if any. + *

Note that the underlying JavaMail {@code Session} has to be + * configured with the property {@code "mail.smtp.auth"} set to + * {@code true}, else the specified username will not be sent to the + * mail server by the JavaMail runtime. If you are not explicitly passing + * in a {@code Session} to use, simply specify this setting via + * {@link #setJavaMailProperties}. + * + * @see #setSession + * @see #setPassword + */ + public void setUsername(@Nullable String username) { + this.username = username; + } + + /** + * Return the username for the account at the mail host. + */ + @Nullable + public String getUsername() { + return this.username; + } + + /** + * Set the password for the account at the mail host, if any. + *

Note that the underlying JavaMail {@code Session} has to be + * configured with the property {@code "mail.smtp.auth"} set to + * {@code true}, else the specified password will not be sent to the + * mail server by the JavaMail runtime. If you are not explicitly passing + * in a {@code Session} to use, simply specify this setting via + * {@link #setJavaMailProperties}. + * + * @see #setSession + * @see #setUsername + */ + public void setPassword(@Nullable String password) { + this.password = password; + } + + /** + * Return the password for the account at the mail host. + */ + @Nullable + public String getPassword() { + return this.password; + } + + /** + * Set the default encoding to use for {@link MimeMessage MimeMessages} + * created by this instance. + *

Such an encoding will be auto-detected by {@link MimeMessageHelper}. + */ + public void setDefaultEncoding(@Nullable String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Return the default encoding for {@link MimeMessage MimeMessages}, + * or {@code null} if none. + */ + @Nullable + public String getDefaultEncoding() { + return this.defaultEncoding; + } + + /** + * Set the default Java Activation {@link FileTypeMap} to use for + * {@link MimeMessage MimeMessages} created by this instance. + *

A {@code FileTypeMap} specified here will be autodetected by + * {@link MimeMessageHelper}, avoiding the need to specify the + * {@code FileTypeMap} for each {@code MimeMessageHelper} instance. + *

For example, you can specify a custom instance of Spring's + * {@link ConfigurableMimeFileTypeMap} here. If not explicitly specified, + * a default {@code ConfigurableMimeFileTypeMap} will be used, containing + * an extended set of MIME type mappings (as defined by the + * {@code mime.types} file contained in the Spring jar). + * + * @see MimeMessageHelper#setFileTypeMap + */ + public void setDefaultFileTypeMap(@Nullable FileTypeMap defaultFileTypeMap) { + this.defaultFileTypeMap = defaultFileTypeMap; + } + + /** + * Return the default Java Activation {@link FileTypeMap} for + * {@link MimeMessage MimeMessages}, or {@code null} if none. + */ + @Nullable + public FileTypeMap getDefaultFileTypeMap() { + return this.defaultFileTypeMap; + } + + + //--------------------------------------------------------------------- + // Implementation of MailSender + //--------------------------------------------------------------------- + + @Override + public void send(SimpleMailMessage simpleMessage) throws MailException { + send(new SimpleMailMessage[]{simpleMessage}); + } + + @Override + public void send(SimpleMailMessage... simpleMessages) throws MailException { + List mimeMessages = new ArrayList<>(simpleMessages.length); + for (SimpleMailMessage simpleMessage : simpleMessages) { + MimeMailMessage message = new MimeMailMessage(createMimeMessage()); + simpleMessage.copyTo(message); + mimeMessages.add(message.getMimeMessage()); + } + doSend(mimeMessages.toArray(new MimeMessage[0]), simpleMessages); + } + + + //--------------------------------------------------------------------- + // Implementation of JavaMailSender + //--------------------------------------------------------------------- + + /** + * This implementation creates a SmartMimeMessage, holding the specified + * default encoding and default FileTypeMap. This special defaults-carrying + * message will be autodetected by {@link MimeMessageHelper}, which will use + * the carried encoding and FileTypeMap unless explicitly overridden. + * + * @see #setDefaultEncoding + * @see #setDefaultFileTypeMap + */ + @Override + public MimeMessage createMimeMessage() { + return new SmartMimeMessage(getSession(), getDefaultEncoding(), getDefaultFileTypeMap()); + } + + @Override + public MimeMessage createMimeMessage(InputStream contentStream) throws MailException { + try { + return new MimeMessage(getSession(), contentStream); + } catch (Exception ex) { + throw new MailParseException("Could not parse raw MIME content", ex); + } + } + + @Override + public void send(MimeMessage mimeMessage) throws MailException { + send(new MimeMessage[]{mimeMessage}); + } + + @Override + public void send(MimeMessage... mimeMessages) throws MailException { + doSend(mimeMessages, null); + } + + @Override + public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { + send(new MimeMessagePreparator[]{mimeMessagePreparator}); + } + + @Override + public void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException { + try { + List mimeMessages = new ArrayList<>(mimeMessagePreparators.length); + for (MimeMessagePreparator preparator : mimeMessagePreparators) { + MimeMessage mimeMessage = createMimeMessage(); + preparator.prepare(mimeMessage); + mimeMessages.add(mimeMessage); + } + send(mimeMessages.toArray(new MimeMessage[0])); + } catch (MailException ex) { + throw ex; + } catch (MessagingException ex) { + throw new MailParseException(ex); + } catch (Exception ex) { + throw new MailPreparationException(ex); + } + } + + /** + * Validate that this instance can connect to the server that it is configured + * for. Throws a {@link MessagingException} if the connection attempt failed. + */ + public void testConnection() throws MessagingException { + Transport transport = null; + try { + transport = connectTransport(); + } finally { + if (transport != null) { + transport.close(); + } + } + } + + /** + * Actually send the given array of MimeMessages via JavaMail. + * + * @param mimeMessages the MimeMessage objects to send + * @param originalMessages corresponding original message objects + * that the MimeMessages have been created from (with same array + * length and indices as the "mimeMessages" array), if any + * @throws MailAuthenticationException in case of authentication failure + * @throws MailSendException in case of failure when sending a message + */ + protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException { + Map failedMessages = new LinkedHashMap<>(); + Transport transport = null; + + try { + for (int i = 0; i < mimeMessages.length; i++) { + + // Check transport connection first... + if (transport == null || !transport.isConnected()) { + if (transport != null) { + try { + transport.close(); + } catch (Exception ex) { + // Ignore - we're reconnecting anyway + } + transport = null; + } + try { + transport = connectTransport(); + } catch (AuthenticationFailedException ex) { + throw new MailAuthenticationException(ex); + } catch (Exception ex) { + // Effectively, all remaining messages failed... + for (int j = i; j < mimeMessages.length; j++) { + Object original = (originalMessages != null ? originalMessages[j] : mimeMessages[j]); + failedMessages.put(original, ex); + } + throw new MailSendException("Mail server connection failed", ex, failedMessages); + } + } + + // Send message via current transport... + MimeMessage mimeMessage = mimeMessages[i]; + try { + if (mimeMessage.getSentDate() == null) { + mimeMessage.setSentDate(new Date()); + } + String messageId = mimeMessage.getMessageID(); + mimeMessage.saveChanges(); + if (messageId != null) { + // Preserve explicitly specified message id... + mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId); + } + Address[] addresses = mimeMessage.getAllRecipients(); + transport.sendMessage(mimeMessage, (addresses != null ? addresses : new Address[0])); + } catch (Exception ex) { + Object original = (originalMessages != null ? originalMessages[i] : mimeMessage); + failedMessages.put(original, ex); + } + } + } finally { + try { + if (transport != null) { + transport.close(); + } + } catch (Exception ex) { + if (!failedMessages.isEmpty()) { + throw new MailSendException("Failed to close server connection after message failures", ex, + failedMessages); + } else { + throw new MailSendException("Failed to close server connection after message sending", ex); + } + } + } + + if (!failedMessages.isEmpty()) { + throw new MailSendException(failedMessages); + } + } + + /** + * Obtain and connect a Transport from the underlying JavaMail Session, + * passing in the specified host, port, username, and password. + * + * @return the connected Transport object + * @throws MessagingException if the connect attempt failed + * @see #getTransport + * @see #getHost() + * @see #getPort() + * @see #getUsername() + * @see #getPassword() + * @since 4.1.2 + */ + protected Transport connectTransport() throws MessagingException { + String username = getUsername(); + String password = getPassword(); + if ("".equals(username)) { // probably from a placeholder + username = null; + if ("".equals(password)) { // in conjunction with "" username, this means no password to use + password = null; + } + } + + Transport transport = getTransport(getSession()); + transport.connect(getHost(), getPort(), username, password); + return transport; + } + + /** + * Obtain a Transport object from the given JavaMail Session, + * using the configured protocol. + *

Can be overridden in subclasses, e.g. to return a mock Transport object. + * + * @see Session#getTransport(String) + * @see #getSession() + * @see #getProtocol() + */ + protected Transport getTransport(Session session) throws NoSuchProviderException { + String protocol = getProtocol(); + if (protocol == null) { + protocol = session.getProperty("mail.transport.protocol"); + if (protocol == null) { + protocol = DEFAULT_PROTOCOL; + } + } + return session.getTransport(protocol); + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMailMessage.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMailMessage.java new file mode 100644 index 0000000..ca3e602 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMailMessage.java @@ -0,0 +1,160 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import com.simaek.notify.email.embedded.mail.MailMessage; +import com.simaek.notify.email.embedded.mail.MailParseException; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.Date; + +/** + * Implementation of the MailMessage interface for a JavaMail MIME message, + * to let message population code interact with a simple message or a MIME + * message through a common interface. + * + *

Uses a MimeMessageHelper underneath. Can either be created with a + * MimeMessageHelper instance or with a JavaMail MimeMessage instance. + * + * @author Juergen Hoeller + * @see MimeMessageHelper + * @see MimeMessage + * @since 1.1.5 + */ +public class MimeMailMessage implements MailMessage { + + private final MimeMessageHelper helper; + + + /** + * Create a new MimeMailMessage based on the given MimeMessageHelper. + * + * @param mimeMessageHelper the MimeMessageHelper + */ + public MimeMailMessage(MimeMessageHelper mimeMessageHelper) { + this.helper = mimeMessageHelper; + } + + /** + * Create a new MimeMailMessage based on the given JavaMail MimeMessage. + * + * @param mimeMessage the JavaMail MimeMessage + */ + public MimeMailMessage(MimeMessage mimeMessage) { + this.helper = new MimeMessageHelper(mimeMessage); + } + + /** + * Return the MimeMessageHelper that this MimeMailMessage is based on. + */ + public final MimeMessageHelper getMimeMessageHelper() { + return this.helper; + } + + /** + * Return the JavaMail MimeMessage that this MimeMailMessage is based on. + */ + public final MimeMessage getMimeMessage() { + return this.helper.getMimeMessage(); + } + + + @Override + public void setFrom(String from) throws MailParseException { + try { + this.helper.setFrom(from); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setReplyTo(String replyTo) throws MailParseException { + try { + this.helper.setReplyTo(replyTo); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setTo(String to) throws MailParseException { + try { + this.helper.setTo(to); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setTo(String... to) throws MailParseException { + try { + this.helper.setTo(to); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setCc(String cc) throws MailParseException { + try { + this.helper.setCc(cc); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setCc(String... cc) throws MailParseException { + try { + this.helper.setCc(cc); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setBcc(String bcc) throws MailParseException { + try { + this.helper.setBcc(bcc); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setBcc(String... bcc) throws MailParseException { + try { + this.helper.setBcc(bcc); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setSentDate(Date sentDate) throws MailParseException { + try { + this.helper.setSentDate(sentDate); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setSubject(String subject) throws MailParseException { + try { + this.helper.setSubject(subject); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + + @Override + public void setText(String text) throws MailParseException { + try { + this.helper.setText(text); + } catch (MessagingException ex) { + throw new MailParseException(ex); + } + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessageHelper.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessageHelper.java new file mode 100644 index 0000000..7db1466 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessageHelper.java @@ -0,0 +1,1144 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import com.simaek.notify.email.embedded.mail.MailMessage; +import com.simaek.notify.email.embedded.mail.SimpleMailMessage; +import org.springframework.core.io.InputStreamSource; +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.FileDataSource; +import javax.activation.FileTypeMap; +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.*; +import java.io.*; +import java.util.Date; + +/** + * Helper class for populating a {@link MimeMessage}. + * + *

Mirrors the simple setters of {@link SimpleMailMessage}, + * directly applying the values to the underlying MimeMessage. Allows for defining + * a character encoding for the entire message, automatically applied by all methods + * of this helper class. + * + *

Offers support for HTML text content, inline elements such as images, and typical + * mail attachments. Also supports personal names that accompany mail addresses. Note that + * advanced settings can still be applied directly to the underlying MimeMessage object! + * + *

Typically used in {@link MimeMessagePreparator} implementations or + * {@link JavaMailSender} client code: simply instantiating it as a MimeMessage wrapper, + * invoking setters on the wrapper, using the underlying MimeMessage for mail sending. + * Also used internally by {@link JavaMailSenderImpl}. + * + *

Sample code for an HTML mail with an inline image and a PDF attachment: + * + *

+ * mailSender.send(new MimeMessagePreparator() {
+ *   public void prepare(MimeMessage mimeMessage) throws MessagingException {
+ *     MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
+ *     message.setFrom("me@mail.com");
+ *     message.setTo("you@mail.com");
+ *     message.setSubject("my subject");
+ *     message.setText("my text <img src='cid:myLogo'>", true);
+ *     message.addInline("myLogo", new ClassPathResource("img/mylogo.gif"));
+ *     message.addAttachment("myDocument.pdf", new ClassPathResource("doc/myDocument.pdf"));
+ *   }
+ * });
+ *

+ * Consider using {@link MimeMailMessage} (which implements the common + * {@link MailMessage} interface, just like + * {@link SimpleMailMessage}) on top of this helper, + * in order to let message population code interact with a simple message + * or a MIME message through a common interface. + * + *

Warning regarding multipart mails: Simple MIME messages that + * just contain HTML text but no inline elements or attachments will work on + * more or less any email client that is capable of HTML rendering. However, + * inline elements and attachments are still a major compatibility issue + * between email clients: It's virtually impossible to get inline elements + * and attachments working across Microsoft Outlook, Lotus Notes and Mac Mail. + * Consider choosing a specific multipart mode for your needs: The javadoc + * on the MULTIPART_MODE constants contains more detailed information. + * + * @author Juergen Hoeller + * @see #setText(String, boolean) + * @see #setText(String, String) + * @see #addInline(String, Resource) + * @see #addAttachment(String, InputStreamSource) + * @see #MULTIPART_MODE_MIXED_RELATED + * @see #MULTIPART_MODE_RELATED + * @see #getMimeMessage() + * @see JavaMailSender + * @since 19.01.2004 + */ +public class MimeMessageHelper { + + /** + * Constant indicating a non-multipart message. + */ + public static final int MULTIPART_MODE_NO = 0; + + /** + * Constant indicating a multipart message with a single root multipart + * element of type "mixed". Texts, inline elements and attachements + * will all get added to that root element. + *

This was Spring 1.0's default behavior. It is known to work properly + * on Outlook. However, other mail clients tend to misinterpret inline + * elements as attachments and/or show attachments inline as well. + */ + public static final int MULTIPART_MODE_MIXED = 1; + + /** + * Constant indicating a multipart message with a single root multipart + * element of type "related". Texts, inline elements and attachements + * will all get added to that root element. + *

This was the default behavior from Spring 1.1 up to 1.2 final. + * This is the "Microsoft multipart mode", as natively sent by Outlook. + * It is known to work properly on Outlook, Outlook Express, Yahoo Mail, and + * to a large degree also on Mac Mail (with an additional attachment listed + * for an inline element, despite the inline element also shown inline). + * Does not work properly on Lotus Notes (attachments won't be shown there). + */ + public static final int MULTIPART_MODE_RELATED = 2; + + /** + * Constant indicating a multipart message with a root multipart element + * "mixed" plus a nested multipart element of type "related". Texts and + * inline elements will get added to the nested "related" element, + * while attachments will get added to the "mixed" root element. + *

This is the default since Spring 1.2.1. This is arguably the most correct + * MIME structure, according to the MIME spec: It is known to work properly + * on Outlook, Outlook Express, Yahoo Mail, and Lotus Notes. Does not work + * properly on Mac Mail. If you target Mac Mail or experience issues with + * specific mails on Outlook, consider using MULTIPART_MODE_RELATED instead. + */ + public static final int MULTIPART_MODE_MIXED_RELATED = 3; + + + private static final String MULTIPART_SUBTYPE_MIXED = "mixed"; + + private static final String MULTIPART_SUBTYPE_RELATED = "related"; + + private static final String MULTIPART_SUBTYPE_ALTERNATIVE = "alternative"; + + private static final String CONTENT_TYPE_ALTERNATIVE = "text/alternative"; + + private static final String CONTENT_TYPE_HTML = "text/html"; + + private static final String CONTENT_TYPE_CHARSET_SUFFIX = ";charset="; + + private static final String HEADER_PRIORITY = "X-Priority"; + + + private final MimeMessage mimeMessage; + + @Nullable + private MimeMultipart rootMimeMultipart; + + @Nullable + private MimeMultipart mimeMultipart; + + @Nullable + private final String encoding; + + private FileTypeMap fileTypeMap; + + private boolean encodeFilenames = false; + + private boolean validateAddresses = false; + + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * assuming a simple text message (no multipart content, + * i.e. no alternative texts and no inline elements or attachments). + *

The character encoding for the message will be taken from + * the passed-in MimeMessage object, if carried there. Else, + * JavaMail's default encoding will be used. + * + * @param mimeMessage the mime message to work on + * @see #MimeMessageHelper(MimeMessage, boolean) + * @see #getDefaultEncoding(MimeMessage) + * @see JavaMailSenderImpl#setDefaultEncoding + */ + public MimeMessageHelper(MimeMessage mimeMessage) { + this(mimeMessage, null); + } + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * assuming a simple text message (no multipart content, + * i.e. no alternative texts and no inline elements or attachments). + * + * @param mimeMessage the mime message to work on + * @param encoding the character encoding to use for the message + * @see #MimeMessageHelper(MimeMessage, boolean) + */ + public MimeMessageHelper(MimeMessage mimeMessage, @Nullable String encoding) { + this.mimeMessage = mimeMessage; + this.encoding = (encoding != null ? encoding : getDefaultEncoding(mimeMessage)); + this.fileTypeMap = getDefaultFileTypeMap(mimeMessage); + } + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * in multipart mode (supporting alternative texts, inline + * elements and attachments) if requested. + *

Consider using the MimeMessageHelper constructor that + * takes a multipartMode argument to choose a specific multipart + * mode other than MULTIPART_MODE_MIXED_RELATED. + *

The character encoding for the message will be taken from + * the passed-in MimeMessage object, if carried there. Else, + * JavaMail's default encoding will be used. + * + * @param mimeMessage the mime message to work on + * @param multipart whether to create a multipart message that + * supports alternative texts, inline elements and attachments + * (corresponds to MULTIPART_MODE_MIXED_RELATED) + * @throws MessagingException if multipart creation failed + * @see #MimeMessageHelper(MimeMessage, int) + * @see #getDefaultEncoding(MimeMessage) + * @see JavaMailSenderImpl#setDefaultEncoding + */ + public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart) throws MessagingException { + this(mimeMessage, multipart, null); + } + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * in multipart mode (supporting alternative texts, inline + * elements and attachments) if requested. + *

Consider using the MimeMessageHelper constructor that + * takes a multipartMode argument to choose a specific multipart + * mode other than MULTIPART_MODE_MIXED_RELATED. + * + * @param mimeMessage the mime message to work on + * @param multipart whether to create a multipart message that + * supports alternative texts, inline elements and attachments + * (corresponds to MULTIPART_MODE_MIXED_RELATED) + * @param encoding the character encoding to use for the message + * @throws MessagingException if multipart creation failed + * @see #MimeMessageHelper(MimeMessage, int, String) + */ + public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, @Nullable String encoding) + throws MessagingException { + + this(mimeMessage, (multipart ? MULTIPART_MODE_MIXED_RELATED : MULTIPART_MODE_NO), encoding); + } + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * in multipart mode (supporting alternative texts, inline + * elements and attachments) if requested. + *

The character encoding for the message will be taken from + * the passed-in MimeMessage object, if carried there. Else, + * JavaMail's default encoding will be used. + * + * @param mimeMessage the mime message to work on + * @param multipartMode which kind of multipart message to create + * (MIXED, RELATED, MIXED_RELATED, or NO) + * @throws MessagingException if multipart creation failed + * @see #MULTIPART_MODE_NO + * @see #MULTIPART_MODE_MIXED + * @see #MULTIPART_MODE_RELATED + * @see #MULTIPART_MODE_MIXED_RELATED + * @see #getDefaultEncoding(MimeMessage) + * @see JavaMailSenderImpl#setDefaultEncoding + */ + public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode) throws MessagingException { + this(mimeMessage, multipartMode, null); + } + + /** + * Create a new MimeMessageHelper for the given MimeMessage, + * in multipart mode (supporting alternative texts, inline + * elements and attachments) if requested. + * + * @param mimeMessage the mime message to work on + * @param multipartMode which kind of multipart message to create + * (MIXED, RELATED, MIXED_RELATED, or NO) + * @param encoding the character encoding to use for the message + * @throws MessagingException if multipart creation failed + * @see #MULTIPART_MODE_NO + * @see #MULTIPART_MODE_MIXED + * @see #MULTIPART_MODE_RELATED + * @see #MULTIPART_MODE_MIXED_RELATED + */ + public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode, @Nullable String encoding) + throws MessagingException { + + this.mimeMessage = mimeMessage; + createMimeMultiparts(mimeMessage, multipartMode); + this.encoding = (encoding != null ? encoding : getDefaultEncoding(mimeMessage)); + this.fileTypeMap = getDefaultFileTypeMap(mimeMessage); + } + + + /** + * Return the underlying MimeMessage object. + */ + public final MimeMessage getMimeMessage() { + return this.mimeMessage; + } + + + /** + * Determine the MimeMultipart objects to use, which will be used + * to store attachments on the one hand and text(s) and inline elements + * on the other hand. + *

Texts and inline elements can either be stored in the root element + * itself (MULTIPART_MODE_MIXED, MULTIPART_MODE_RELATED) or in a nested element + * rather than the root element directly (MULTIPART_MODE_MIXED_RELATED). + *

By default, the root MimeMultipart element will be of type "mixed" + * (MULTIPART_MODE_MIXED) or "related" (MULTIPART_MODE_RELATED). + * The main multipart element will either be added as nested element of + * type "related" (MULTIPART_MODE_MIXED_RELATED) or be identical to the root + * element itself (MULTIPART_MODE_MIXED, MULTIPART_MODE_RELATED). + * + * @param mimeMessage the MimeMessage object to add the root MimeMultipart + * object to + * @param multipartMode the multipart mode, as passed into the constructor + * (MIXED, RELATED, MIXED_RELATED, or NO) + * @throws MessagingException if multipart creation failed + * @see #setMimeMultiparts + * @see #MULTIPART_MODE_NO + * @see #MULTIPART_MODE_MIXED + * @see #MULTIPART_MODE_RELATED + * @see #MULTIPART_MODE_MIXED_RELATED + */ + protected void createMimeMultiparts(MimeMessage mimeMessage, int multipartMode) throws MessagingException { + switch (multipartMode) { + case MULTIPART_MODE_NO: + setMimeMultiparts(null, null); + break; + case MULTIPART_MODE_MIXED: + MimeMultipart mixedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_MIXED); + mimeMessage.setContent(mixedMultipart); + setMimeMultiparts(mixedMultipart, mixedMultipart); + break; + case MULTIPART_MODE_RELATED: + MimeMultipart relatedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_RELATED); + mimeMessage.setContent(relatedMultipart); + setMimeMultiparts(relatedMultipart, relatedMultipart); + break; + case MULTIPART_MODE_MIXED_RELATED: + MimeMultipart rootMixedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_MIXED); + mimeMessage.setContent(rootMixedMultipart); + MimeMultipart nestedRelatedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_RELATED); + MimeBodyPart relatedBodyPart = new MimeBodyPart(); + relatedBodyPart.setContent(nestedRelatedMultipart); + rootMixedMultipart.addBodyPart(relatedBodyPart); + setMimeMultiparts(rootMixedMultipart, nestedRelatedMultipart); + break; + default: + throw new IllegalArgumentException("Only multipart modes MIXED_RELATED, RELATED and NO supported"); + } + } + + /** + * Set the given MimeMultipart objects for use by this MimeMessageHelper. + * + * @param root the root MimeMultipart object, which attachments will be added to; + * or {@code null} to indicate no multipart at all + * @param main the main MimeMultipart object, which text(s) and inline elements + * will be added to (can be the same as the root multipart object, or an element + * nested underneath the root multipart element) + */ + protected final void setMimeMultiparts(@Nullable MimeMultipart root, @Nullable MimeMultipart main) { + this.rootMimeMultipart = root; + this.mimeMultipart = main; + } + + /** + * Return whether this helper is in multipart mode, + * i.e. whether it holds a multipart message. + * + * @see #MimeMessageHelper(MimeMessage, boolean) + */ + public final boolean isMultipart() { + return (this.rootMimeMultipart != null); + } + + /** + * Return the root MIME "multipart/mixed" object, if any. + * Can be used to manually add attachments. + *

This will be the direct content of the MimeMessage, + * in case of a multipart mail. + * + * @throws IllegalStateException if this helper is not in multipart mode + * @see #isMultipart + * @see #getMimeMessage + * @see MimeMultipart#addBodyPart + */ + public final MimeMultipart getRootMimeMultipart() throws IllegalStateException { + if (this.rootMimeMultipart == null) { + throw new IllegalStateException("Not in multipart mode - " + + "create an appropriate MimeMessageHelper via a constructor that takes a 'multipart' flag " + + "if you need to set alternative texts or add inline elements or attachments."); + } + return this.rootMimeMultipart; + } + + /** + * Return the underlying MIME "multipart/related" object, if any. + * Can be used to manually add body parts, inline elements, etc. + *

This will be nested within the root MimeMultipart, + * in case of a multipart mail. + * + * @throws IllegalStateException if this helper is not in multipart mode + * @see #isMultipart + * @see #getRootMimeMultipart + * @see MimeMultipart#addBodyPart + */ + public final MimeMultipart getMimeMultipart() throws IllegalStateException { + if (this.mimeMultipart == null) { + throw new IllegalStateException("Not in multipart mode - " + + "create an appropriate MimeMessageHelper via a constructor that takes a 'multipart' flag " + + "if you need to set alternative texts or add inline elements or attachments."); + } + return this.mimeMultipart; + } + + + /** + * Determine the default encoding for the given MimeMessage. + * + * @param mimeMessage the passed-in MimeMessage + * @return the default encoding associated with the MimeMessage, + * or {@code null} if none found + */ + @Nullable + protected String getDefaultEncoding(MimeMessage mimeMessage) { + if (mimeMessage instanceof SmartMimeMessage) { + return ((SmartMimeMessage) mimeMessage).getDefaultEncoding(); + } + return null; + } + + /** + * Return the specific character encoding used for this message, if any. + */ + @Nullable + public String getEncoding() { + return this.encoding; + } + + /** + * Determine the default Java Activation FileTypeMap for the given MimeMessage. + * + * @param mimeMessage the passed-in MimeMessage + * @return the default FileTypeMap associated with the MimeMessage, + * or a default ConfigurableMimeFileTypeMap if none found for the message + * @see ConfigurableMimeFileTypeMap + */ + protected FileTypeMap getDefaultFileTypeMap(MimeMessage mimeMessage) { + if (mimeMessage instanceof SmartMimeMessage) { + FileTypeMap fileTypeMap = ((SmartMimeMessage) mimeMessage).getDefaultFileTypeMap(); + if (fileTypeMap != null) { + return fileTypeMap; + } + } + ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap(); +// fileTypeMap.afterPropertiesSet(); + return fileTypeMap; + } + + /** + * Set the Java Activation Framework {@code FileTypeMap} to use + * for determining the content type of inline content and attachments + * that get added to the message. + *

The default is the {@code FileTypeMap} that the underlying + * MimeMessage carries, if any, or the Activation Framework's default + * {@code FileTypeMap} instance else. + * + * @see #addInline + * @see #addAttachment + * @see #getDefaultFileTypeMap(MimeMessage) + * @see JavaMailSenderImpl#setDefaultFileTypeMap + * @see FileTypeMap#getDefaultFileTypeMap + * @see ConfigurableMimeFileTypeMap + */ + public void setFileTypeMap(@Nullable FileTypeMap fileTypeMap) { + this.fileTypeMap = (fileTypeMap != null ? fileTypeMap : getDefaultFileTypeMap(getMimeMessage())); + } + + /** + * Return the {@code FileTypeMap} used by this MimeMessageHelper. + * + * @see #setFileTypeMap + */ + public FileTypeMap getFileTypeMap() { + return this.fileTypeMap; + } + + + /** + * Set whether to encode attachment filenames passed to this helper's + * {@code #addAttachment} methods. + *

The default is {@code false} for standard MIME behavior; turn this to + * {@code true} for compatibility with older email clients. On a related note, + * check out JavaMail's {@code mail.mime.encodefilename} system property. + *

NOTE: The default changed to {@code false} in 5.3, in favor of + * JavaMail's standard {@code mail.mime.encodefilename} system property. + * + * @see #addAttachment(String, DataSource) + * @see MimeBodyPart#setFileName(String) + * @since 5.2.9 + */ + public void setEncodeFilenames(boolean encodeFilenames) { + this.encodeFilenames = encodeFilenames; + } + + /** + * Return whether to encode attachment filenames passed to this helper's + * {@code #addAttachment} methods. + * + * @see #setEncodeFilenames + * @since 5.2.9 + */ + public boolean isEncodeFilenames() { + return this.encodeFilenames; + } + + /** + * Set whether to validate all addresses which get passed to this helper. + *

The default is {@code false}. + * + * @see #validateAddress + */ + public void setValidateAddresses(boolean validateAddresses) { + this.validateAddresses = validateAddresses; + } + + /** + * Return whether this helper will validate all addresses passed to it. + * + * @see #setValidateAddresses + */ + public boolean isValidateAddresses() { + return this.validateAddresses; + } + + /** + * Validate the given mail address. + * Called by all of MimeMessageHelper's address setters and adders. + *

The default implementation invokes {@link InternetAddress#validate()}, + * provided that address validation is activated for the helper instance. + * + * @param address the address to validate + * @throws AddressException if validation failed + * @see #isValidateAddresses() + * @see InternetAddress#validate() + */ + protected void validateAddress(InternetAddress address) throws AddressException { + if (isValidateAddresses()) { + address.validate(); + } + } + + /** + * Validate all given mail addresses. + *

The default implementation simply delegates to {@link #validateAddress} + * for each address. + * + * @param addresses the addresses to validate + * @throws AddressException if validation failed + * @see #validateAddress(InternetAddress) + */ + protected void validateAddresses(InternetAddress[] addresses) throws AddressException { + for (InternetAddress address : addresses) { + validateAddress(address); + } + } + + + public void setFrom(InternetAddress from) throws MessagingException { + Assert.notNull(from, "From address must not be null"); + validateAddress(from); + this.mimeMessage.setFrom(from); + } + + public void setFrom(String from) throws MessagingException { + Assert.notNull(from, "From address must not be null"); + setFrom(parseAddress(from)); + } + + public void setFrom(String from, String personal) throws MessagingException, UnsupportedEncodingException { + Assert.notNull(from, "From address must not be null"); + setFrom(getEncoding() != null ? + new InternetAddress(from, personal, getEncoding()) : new InternetAddress(from, personal)); + } + + public void setReplyTo(InternetAddress replyTo) throws MessagingException { + Assert.notNull(replyTo, "Reply-to address must not be null"); + validateAddress(replyTo); + this.mimeMessage.setReplyTo(new InternetAddress[]{replyTo}); + } + + public void setReplyTo(String replyTo) throws MessagingException { + Assert.notNull(replyTo, "Reply-to address must not be null"); + setReplyTo(parseAddress(replyTo)); + } + + public void setReplyTo(String replyTo, String personal) throws MessagingException, UnsupportedEncodingException { + Assert.notNull(replyTo, "Reply-to address must not be null"); + InternetAddress replyToAddress = (getEncoding() != null) ? + new InternetAddress(replyTo, personal, getEncoding()) : new InternetAddress(replyTo, personal); + setReplyTo(replyToAddress); + } + + + public void setTo(InternetAddress to) throws MessagingException { + Assert.notNull(to, "To address must not be null"); + validateAddress(to); + this.mimeMessage.setRecipient(Message.RecipientType.TO, to); + } + + public void setTo(InternetAddress[] to) throws MessagingException { + Assert.notNull(to, "To address array must not be null"); + validateAddresses(to); + this.mimeMessage.setRecipients(Message.RecipientType.TO, to); + } + + public void setTo(String to) throws MessagingException { + Assert.notNull(to, "To address must not be null"); + setTo(parseAddress(to)); + } + + public void setTo(String[] to) throws MessagingException { + Assert.notNull(to, "To address array must not be null"); + InternetAddress[] addresses = new InternetAddress[to.length]; + for (int i = 0; i < to.length; i++) { + addresses[i] = parseAddress(to[i]); + } + setTo(addresses); + } + + public void addTo(InternetAddress to) throws MessagingException { + Assert.notNull(to, "To address must not be null"); + validateAddress(to); + this.mimeMessage.addRecipient(Message.RecipientType.TO, to); + } + + public void addTo(String to) throws MessagingException { + Assert.notNull(to, "To address must not be null"); + addTo(parseAddress(to)); + } + + public void addTo(String to, String personal) throws MessagingException, UnsupportedEncodingException { + Assert.notNull(to, "To address must not be null"); + addTo(getEncoding() != null ? + new InternetAddress(to, personal, getEncoding()) : + new InternetAddress(to, personal)); + } + + + public void setCc(InternetAddress cc) throws MessagingException { + Assert.notNull(cc, "Cc address must not be null"); + validateAddress(cc); + this.mimeMessage.setRecipient(Message.RecipientType.CC, cc); + } + + public void setCc(InternetAddress[] cc) throws MessagingException { + Assert.notNull(cc, "Cc address array must not be null"); + validateAddresses(cc); + this.mimeMessage.setRecipients(Message.RecipientType.CC, cc); + } + + public void setCc(String cc) throws MessagingException { + Assert.notNull(cc, "Cc address must not be null"); + setCc(parseAddress(cc)); + } + + public void setCc(String[] cc) throws MessagingException { + Assert.notNull(cc, "Cc address array must not be null"); + InternetAddress[] addresses = new InternetAddress[cc.length]; + for (int i = 0; i < cc.length; i++) { + addresses[i] = parseAddress(cc[i]); + } + setCc(addresses); + } + + public void addCc(InternetAddress cc) throws MessagingException { + Assert.notNull(cc, "Cc address must not be null"); + validateAddress(cc); + this.mimeMessage.addRecipient(Message.RecipientType.CC, cc); + } + + public void addCc(String cc) throws MessagingException { + Assert.notNull(cc, "Cc address must not be null"); + addCc(parseAddress(cc)); + } + + public void addCc(String cc, String personal) throws MessagingException, UnsupportedEncodingException { + Assert.notNull(cc, "Cc address must not be null"); + addCc(getEncoding() != null ? + new InternetAddress(cc, personal, getEncoding()) : + new InternetAddress(cc, personal)); + } + + + public void setBcc(InternetAddress bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address must not be null"); + validateAddress(bcc); + this.mimeMessage.setRecipient(Message.RecipientType.BCC, bcc); + } + + public void setBcc(InternetAddress[] bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address array must not be null"); + validateAddresses(bcc); + this.mimeMessage.setRecipients(Message.RecipientType.BCC, bcc); + } + + public void setBcc(String bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address must not be null"); + setBcc(parseAddress(bcc)); + } + + public void setBcc(String[] bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address array must not be null"); + InternetAddress[] addresses = new InternetAddress[bcc.length]; + for (int i = 0; i < bcc.length; i++) { + addresses[i] = parseAddress(bcc[i]); + } + setBcc(addresses); + } + + public void addBcc(InternetAddress bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address must not be null"); + validateAddress(bcc); + this.mimeMessage.addRecipient(Message.RecipientType.BCC, bcc); + } + + public void addBcc(String bcc) throws MessagingException { + Assert.notNull(bcc, "Bcc address must not be null"); + addBcc(parseAddress(bcc)); + } + + public void addBcc(String bcc, String personal) throws MessagingException, UnsupportedEncodingException { + Assert.notNull(bcc, "Bcc address must not be null"); + addBcc(getEncoding() != null ? + new InternetAddress(bcc, personal, getEncoding()) : + new InternetAddress(bcc, personal)); + } + + private InternetAddress parseAddress(String address) throws MessagingException { + InternetAddress[] parsed = InternetAddress.parse(address); + if (parsed.length != 1) { + throw new AddressException("Illegal address", address); + } + InternetAddress raw = parsed[0]; + try { + return (getEncoding() != null ? + new InternetAddress(raw.getAddress(), raw.getPersonal(), getEncoding()) : raw); + } catch (UnsupportedEncodingException ex) { + throw new MessagingException("Failed to parse embedded personal name to correct encoding", ex); + } + } + + + /** + * Set the priority ("X-Priority" header) of the message. + * + * @param priority the priority value; + * typically between 1 (highest) and 5 (lowest) + * @throws MessagingException in case of errors + */ + public void setPriority(int priority) throws MessagingException { + this.mimeMessage.setHeader(HEADER_PRIORITY, Integer.toString(priority)); + } + + /** + * Set the sent-date of the message. + * + * @param sentDate the date to set (never {@code null}) + * @throws MessagingException in case of errors + */ + public void setSentDate(Date sentDate) throws MessagingException { + Assert.notNull(sentDate, "Sent date must not be null"); + this.mimeMessage.setSentDate(sentDate); + } + + /** + * Set the subject of the message, using the correct encoding. + * + * @param subject the subject text + * @throws MessagingException in case of errors + */ + public void setSubject(String subject) throws MessagingException { + Assert.notNull(subject, "Subject must not be null"); + if (getEncoding() != null) { + this.mimeMessage.setSubject(subject, getEncoding()); + } else { + this.mimeMessage.setSubject(subject); + } + } + + + /** + * Set the given text directly as content in non-multipart mode + * or as default body part in multipart mode. + * Always applies the default content type "text/plain". + *

NOTE: Invoke {@link #addInline} after {@code setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param text the text for the message + * @throws MessagingException in case of errors + */ + public void setText(String text) throws MessagingException { + setText(text, false); + } + + /** + * Set the given text directly as content in non-multipart mode + * or as default body part in multipart mode. + * The "html" flag determines the content type to apply. + *

NOTE: Invoke {@link #addInline} after {@code setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param text the text for the message + * @param html whether to apply content type "text/html" for an + * HTML mail, using default content type ("text/plain") else + * @throws MessagingException in case of errors + */ + public void setText(String text, boolean html) throws MessagingException { + Assert.notNull(text, "Text must not be null"); + MimePart partToUse; + if (isMultipart()) { + partToUse = getMainPart(); + } else { + partToUse = this.mimeMessage; + } + if (html) { + setHtmlTextToMimePart(partToUse, text); + } else { + setPlainTextToMimePart(partToUse, text); + } + } + + /** + * Set the given plain text and HTML text as alternatives, offering + * both options to the email client. Requires multipart mode. + *

NOTE: Invoke {@link #addInline} after {@code setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param plainText the plain text for the message + * @param htmlText the HTML text for the message + * @throws MessagingException in case of errors + */ + public void setText(String plainText, String htmlText) throws MessagingException { + Assert.notNull(plainText, "Plain text must not be null"); + Assert.notNull(htmlText, "HTML text must not be null"); + + MimeMultipart messageBody = new MimeMultipart(MULTIPART_SUBTYPE_ALTERNATIVE); + getMainPart().setContent(messageBody, CONTENT_TYPE_ALTERNATIVE); + + // Create the plain text part of the message. + MimeBodyPart plainTextPart = new MimeBodyPart(); + setPlainTextToMimePart(plainTextPart, plainText); + messageBody.addBodyPart(plainTextPart); + + // Create the HTML text part of the message. + MimeBodyPart htmlTextPart = new MimeBodyPart(); + setHtmlTextToMimePart(htmlTextPart, htmlText); + messageBody.addBodyPart(htmlTextPart); + } + + private MimeBodyPart getMainPart() throws MessagingException { + MimeMultipart mimeMultipart = getMimeMultipart(); + MimeBodyPart bodyPart = null; + for (int i = 0; i < mimeMultipart.getCount(); i++) { + BodyPart bp = mimeMultipart.getBodyPart(i); + if (bp.getFileName() == null) { + bodyPart = (MimeBodyPart) bp; + } + } + if (bodyPart == null) { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeMultipart.addBodyPart(mimeBodyPart); + bodyPart = mimeBodyPart; + } + return bodyPart; + } + + private void setPlainTextToMimePart(MimePart mimePart, String text) throws MessagingException { + if (getEncoding() != null) { + mimePart.setText(text, getEncoding()); + } else { + mimePart.setText(text); + } + } + + private void setHtmlTextToMimePart(MimePart mimePart, String text) throws MessagingException { + if (getEncoding() != null) { + mimePart.setContent(text, CONTENT_TYPE_HTML + CONTENT_TYPE_CHARSET_SUFFIX + getEncoding()); + } else { + mimePart.setContent(text, CONTENT_TYPE_HTML); + } + } + + + /** + * Add an inline element to the MimeMessage, taking the content from a + * {@code javax.activation.DataSource}. + *

Note that the InputStream returned by the DataSource implementation + * needs to be a fresh one on each call, as JavaMail will invoke + * {@code getInputStream()} multiple times. + *

NOTE: Invoke {@code addInline} after {@link #setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param contentId the content ID to use. Will end up as "Content-ID" header + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". + * Can be referenced in HTML source via src="cid:myId" expressions. + * @param dataSource the {@code javax.activation.DataSource} to take + * the content from, determining the InputStream and the content type + * @throws MessagingException in case of errors + * @see #addInline(String, File) + * @see #addInline(String, Resource) + */ + public void addInline(String contentId, DataSource dataSource) throws MessagingException { + Assert.notNull(contentId, "Content ID must not be null"); + Assert.notNull(dataSource, "DataSource must not be null"); + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeBodyPart.setDisposition(MimeBodyPart.INLINE); + mimeBodyPart.setContentID("<" + contentId + ">"); + mimeBodyPart.setDataHandler(new DataHandler(dataSource)); + getMimeMultipart().addBodyPart(mimeBodyPart); + } + + /** + * Add an inline element to the MimeMessage, taking the content from a + * {@code java.io.File}. + *

The content type will be determined by the name of the given + * content file. Do not use this for temporary files with arbitrary + * filenames (possibly ending in ".tmp" or the like)! + *

NOTE: Invoke {@code addInline} after {@link #setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param contentId the content ID to use. Will end up as "Content-ID" header + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". + * Can be referenced in HTML source via src="cid:myId" expressions. + * @param file the File resource to take the content from + * @throws MessagingException in case of errors + * @see #setText + * @see #addInline(String, Resource) + * @see #addInline(String, DataSource) + */ + public void addInline(String contentId, File file) throws MessagingException { + Assert.notNull(file, "File must not be null"); + FileDataSource dataSource = new FileDataSource(file); + dataSource.setFileTypeMap(getFileTypeMap()); + addInline(contentId, dataSource); + } + + /** + * Add an inline element to the MimeMessage, taking the content from a + * {@code org.springframework.core.io.Resource}. + *

The content type will be determined by the name of the given + * content file. Do not use this for temporary files with arbitrary + * filenames (possibly ending in ".tmp" or the like)! + *

Note that the InputStream returned by the Resource implementation + * needs to be a fresh one on each call, as JavaMail will invoke + * {@code getInputStream()} multiple times. + *

NOTE: Invoke {@code addInline} after {@link #setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param contentId the content ID to use. Will end up as "Content-ID" header + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". + * Can be referenced in HTML source via src="cid:myId" expressions. + * @param resource the resource to take the content from + * @throws MessagingException in case of errors + * @see #setText + * @see #addInline(String, File) + * @see #addInline(String, DataSource) + */ + public void addInline(String contentId, Resource resource) throws MessagingException { + Assert.notNull(resource, "Resource must not be null"); + String contentType = getFileTypeMap().getContentType(resource.getFilename()); + addInline(contentId, resource, contentType); + } + + /** + * Add an inline element to the MimeMessage, taking the content from an + * {@code org.springframework.core.InputStreamResource}, and + * specifying the content type explicitly. + *

You can determine the content type for any given filename via a Java + * Activation Framework's FileTypeMap, for example the one held by this helper. + *

Note that the InputStream returned by the InputStreamSource implementation + * needs to be a fresh one on each call, as JavaMail will invoke + * {@code getInputStream()} multiple times. + *

NOTE: Invoke {@code addInline} after {@code setText}; + * else, mail readers might not be able to resolve inline references correctly. + * + * @param contentId the content ID to use. Will end up as "Content-ID" header + * in the body part, surrounded by angle brackets: e.g. "myId" → "<myId>". + * Can be referenced in HTML source via src="cid:myId" expressions. + * @param inputStreamSource the resource to take the content from + * @param contentType the content type to use for the element + * @throws MessagingException in case of errors + * @see #setText + * @see #getFileTypeMap + * @see #addInline(String, Resource) + * @see #addInline(String, DataSource) + */ + public void addInline(String contentId, InputStreamSource inputStreamSource, String contentType) + throws MessagingException { + + Assert.notNull(inputStreamSource, "InputStreamSource must not be null"); + if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) { + throw new IllegalArgumentException( + "Passed-in Resource contains an open stream: invalid argument. " + + "JavaMail requires an InputStreamSource that creates a fresh stream for every call."); + } + DataSource dataSource = createDataSource(inputStreamSource, contentType, "inline"); + addInline(contentId, dataSource); + } + + /** + * Add an attachment to the MimeMessage, taking the content from a + * {@code javax.activation.DataSource}. + *

Note that the InputStream returned by the DataSource implementation + * needs to be a fresh one on each call, as JavaMail will invoke + * {@code getInputStream()} multiple times. + * + * @param attachmentFilename the name of the attachment as it will + * appear in the mail (the content type will be determined by this) + * @param dataSource the {@code javax.activation.DataSource} to take + * the content from, determining the InputStream and the content type + * @throws MessagingException in case of errors + * @see #addAttachment(String, InputStreamSource) + * @see #addAttachment(String, File) + */ + public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException { + Assert.notNull(attachmentFilename, "Attachment filename must not be null"); + Assert.notNull(dataSource, "DataSource must not be null"); + try { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT); + mimeBodyPart.setFileName(isEncodeFilenames() ? + MimeUtility.encodeText(attachmentFilename) : attachmentFilename); + mimeBodyPart.setDataHandler(new DataHandler(dataSource)); + getRootMimeMultipart().addBodyPart(mimeBodyPart); + } catch (UnsupportedEncodingException ex) { + throw new MessagingException("Failed to encode attachment filename", ex); + } + } + + /** + * Add an attachment to the MimeMessage, taking the content from a + * {@code java.io.File}. + *

The content type will be determined by the name of the given + * content file. Do not use this for temporary files with arbitrary + * filenames (possibly ending in ".tmp" or the like)! + * + * @param attachmentFilename the name of the attachment as it will + * appear in the mail + * @param file the File resource to take the content from + * @throws MessagingException in case of errors + * @see #addAttachment(String, InputStreamSource) + * @see #addAttachment(String, DataSource) + */ + public void addAttachment(String attachmentFilename, File file) throws MessagingException { + Assert.notNull(file, "File must not be null"); + FileDataSource dataSource = new FileDataSource(file); + dataSource.setFileTypeMap(getFileTypeMap()); + addAttachment(attachmentFilename, dataSource); + } + + /** + * Add an attachment to the MimeMessage, taking the content from an + * {@code org.springframework.core.io.InputStreamResource}. + *

The content type will be determined by the given filename for + * the attachment. Thus, any content source will be fine, including + * temporary files with arbitrary filenames. + *

Note that the InputStream returned by the InputStreamSource + * implementation needs to be a fresh one on each call, as + * JavaMail will invoke {@code getInputStream()} multiple times. + * + * @param attachmentFilename the name of the attachment as it will + * appear in the mail + * @param inputStreamSource the resource to take the content from + * (all of Spring's Resource implementations can be passed in here) + * @throws MessagingException in case of errors + * @see #addAttachment(String, File) + * @see #addAttachment(String, DataSource) + * @see Resource + */ + public void addAttachment(String attachmentFilename, InputStreamSource inputStreamSource) + throws MessagingException { + + String contentType = getFileTypeMap().getContentType(attachmentFilename); + addAttachment(attachmentFilename, inputStreamSource, contentType); + } + + /** + * Add an attachment to the MimeMessage, taking the content from an + * {@code org.springframework.core.io.InputStreamResource}. + *

Note that the InputStream returned by the InputStreamSource + * implementation needs to be a fresh one on each call, as + * JavaMail will invoke {@code getInputStream()} multiple times. + * + * @param attachmentFilename the name of the attachment as it will + * appear in the mail + * @param inputStreamSource the resource to take the content from + * (all of Spring's Resource implementations can be passed in here) + * @param contentType the content type to use for the element + * @throws MessagingException in case of errors + * @see #addAttachment(String, File) + * @see #addAttachment(String, DataSource) + * @see Resource + */ + public void addAttachment( + String attachmentFilename, InputStreamSource inputStreamSource, String contentType) + throws MessagingException { + + Assert.notNull(inputStreamSource, "InputStreamSource must not be null"); + if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) { + throw new IllegalArgumentException( + "Passed-in Resource contains an open stream: invalid argument. " + + "JavaMail requires an InputStreamSource that creates a fresh stream for every call."); + } + DataSource dataSource = createDataSource(inputStreamSource, contentType, attachmentFilename); + addAttachment(attachmentFilename, dataSource); + } + + /** + * Create an Activation Framework DataSource for the given InputStreamSource. + * + * @param inputStreamSource the InputStreamSource (typically a Spring Resource) + * @param contentType the content type + * @param name the name of the DataSource + * @return the Activation Framework DataSource + */ + protected DataSource createDataSource( + final InputStreamSource inputStreamSource, final String contentType, final String name) { + + return new DataSource() { + @Override + public InputStream getInputStream() throws IOException { + return inputStreamSource.getInputStream(); + } + + @Override + public OutputStream getOutputStream() { + throw new UnsupportedOperationException("Read-only javax.activation.DataSource"); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getName() { + return name; + } + }; + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessagePreparator.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessagePreparator.java new file mode 100644 index 0000000..3b4bc3a --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/MimeMessagePreparator.java @@ -0,0 +1,39 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import javax.mail.internet.MimeMessage; + +/** + * Callback interface for the preparation of JavaMail MIME messages. + * + *

The corresponding {@code send} methods of {@link JavaMailSender} + * will take care of the actual creation of a {@link MimeMessage} instance, + * and of proper exception conversion. + * + *

It is often convenient to use a {@link MimeMessageHelper} for populating + * the passed-in MimeMessage, in particular when working with attachments or + * special character encodings. + * See {@link MimeMessageHelper MimeMessageHelper's javadoc} for an example. + * + * @author Juergen Hoeller + * @see JavaMailSender#send(MimeMessagePreparator) + * @see JavaMailSender#send(MimeMessagePreparator[]) + * @see MimeMessageHelper + * @since 07.10.2003 + */ +@FunctionalInterface +public interface MimeMessagePreparator { + + /** + * Prepare the given new MimeMessage instance. + * + * @param mimeMessage the message to prepare + * @throws javax.mail.MessagingException passing any exceptions thrown by MimeMessage + * methods through for automatic conversion to the MailException hierarchy + * @throws java.io.IOException passing any exceptions thrown by MimeMessage methods + * through for automatic conversion to the MailException hierarchy + * @throws Exception if mail preparation failed, for example when a + * FreeMarker template cannot be rendered for the mail text + */ + void prepare(MimeMessage mimeMessage) throws Exception; + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/SmartMimeMessage.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/SmartMimeMessage.java new file mode 100644 index 0000000..c7ad534 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/SmartMimeMessage.java @@ -0,0 +1,65 @@ +package com.simaek.notify.email.embedded.mail.javamail; + +import org.springframework.lang.Nullable; + +import javax.activation.FileTypeMap; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +/** + * Special subclass of the standard JavaMail {@link MimeMessage}, carrying a + * default encoding to be used when populating the message and a default Java + * Activation {@link FileTypeMap} to be used for resolving attachment types. + * + *

Created by {@link JavaMailSenderImpl} in case of a specified default encoding + * and/or default FileTypeMap. Autodetected by {@link MimeMessageHelper}, which + * will use the carried encoding and FileTypeMap unless explicitly overridden. + * + * @author Juergen Hoeller + * @see JavaMailSenderImpl#createMimeMessage() + * @see MimeMessageHelper#getDefaultEncoding(MimeMessage) + * @see MimeMessageHelper#getDefaultFileTypeMap(MimeMessage) + * @since 1.2 + */ +class SmartMimeMessage extends MimeMessage { + + @Nullable + private final String defaultEncoding; + + @Nullable + private final FileTypeMap defaultFileTypeMap; + + + /** + * Create a new SmartMimeMessage. + * + * @param session the JavaMail Session to create the message for + * @param defaultEncoding the default encoding, or {@code null} if none + * @param defaultFileTypeMap the default FileTypeMap, or {@code null} if none + */ + public SmartMimeMessage( + Session session, @Nullable String defaultEncoding, @Nullable FileTypeMap defaultFileTypeMap) { + + super(session); + this.defaultEncoding = defaultEncoding; + this.defaultFileTypeMap = defaultFileTypeMap; + } + + + /** + * Return the default encoding of this message, or {@code null} if none. + */ + @Nullable + public final String getDefaultEncoding() { + return this.defaultEncoding; + } + + /** + * Return the default FileTypeMap of this message, or {@code null} if none. + */ + @Nullable + public final FileTypeMap getDefaultFileTypeMap() { + return this.defaultFileTypeMap; + } + +} diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/mime.types b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/mime.types new file mode 100644 index 0000000..b03625f --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/mime.types @@ -0,0 +1,326 @@ +################################################################################ +# Copyright 2002-2019 the original author or authors. +# +# Licensed 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 +# +# https://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. +################################################################################ + +################################################################################ +# +# Defaults for the Java Activation Framework (revised). +# +# Modified extensions registered in this file: +# text/plain java c c++ cpp pl cc h +# image/png png +# image/svg+xml svg +# +################################################################################ + +text/html html htm HTML HTM +text/plain txt text TXT TEXT java c c++ cpp pl cc h +image/gif gif GIF +image/ief ief +image/jpeg jpeg jpg jpe JPG +image/tiff tiff tif +image/x-xwindowdump xwd +application/postscript ai eps ps +application/rtf rtf +application/x-tex tex +application/x-texinfo texinfo texi +application/x-troff t tr roff +audio/basic au +audio/midi midi mid +audio/x-aifc aifc +audio/x-aiff aif aiff +audio/x-mpeg mpeg mpg +audio/x-wav wav +video/mpeg mpeg mpg mpe +video/quicktime qt mov +video/x-msvideo avi + +################################################################################ +# +# Additional file types adapted from +# http://sites.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html +# kindly re-licensed to Apache Software License 2.0 by Ian Graham. +# +################################################################################ + +# TEXT TYPES + +text/x-speech talk +text/css css +text/csv csv + +# IMAGE TYPES + +# X-Windows bitmap (b/w) +image/x-xbitmap xbm +# X-Windows pixelmap (8-bit color) +image/x-xpixmap xpm +# Portable Network Graphics +image/png png +# Scalable Vector Graphics +image/svg+xml svg +# Image Exchange Format (RFC 1314) +image/ief ief +# RGB +image/rgb rgb +# Group III Fax (RFC 1494) +image/g3fax g3f +# X Windowdump format +image/x-xwindowdump xwd +# Macintosh PICT format +image/x-pict pict +# PPM (UNIX PPM package) +image/x-portable-pixmap ppm +# PGM (UNIX PPM package) +image/x-portable-graymap pgm +# PBM (UNIX PPM package) +image/x-portable-bitmap pbm +# PNM (UNIX PPM package) +image/x-portable-anymap pnm +# Microsoft Windows bitmap +image/x-ms-bmp bmp +# CMU raster +image/x-cmu-raster ras +# Kodak Photo-CD +image/x-photo-cd pcd +# Computer Graphics Metafile +image/cgm cgm +# CALS Type 1 or 2 +image/x-cals mil cal +# Fractal Image Format (Iterated Systems) +image/fif fif +# QuickSilver active image (Micrografx) +image/x-mgx-dsf dsf +# CMX vector image (Corel) +image/x-cmx cmx +# Wavelet-compressed (Summus) +image/wavelet wi +# AutoCad Drawing (SoftSource) +image/vnd.dwg dwg +# AutoCad DXF file (SoftSource) +image/vnd.dxf dxf +# Simple Vector Format (SoftSource) +image/vnd.svf svf + +# AUDIO/VOICE/MUSIC RELATED TYPES + +# """basic""audio - 8-bit u-law PCM" +audio/basic au snd +# Macintosh audio format (AIpple) +audio/x-aiff aif aiff aifc +# Microsoft audio +audio/x-wav wav +# MPEG audio +audio/x-mpeg mpa abs mpega +# MPEG-2 audio +audio/x-mpeg-2 mp2a mpa2 +# compressed speech (Echo Speech Corp.) +audio/echospeech es +# Toolvox speech audio (Voxware) +audio/voxware vox +# RapidTransit compressed audio (Fast Man) +application/fastman lcc +# Realaudio (Progressive Networks) +application/x-pn-realaudio ra ram +# MIDI music data +x-music/x-midi mmid +# Koan music data (SSeyo) +application/vnd.koan skp +# Speech synthesis data (MVP Solutions) +text/x-speech talk + +# VIDEO TYPES + +# MPEG video +video/mpeg mpeg mpg mpe +# MPEG-2 video +video/mpeg-2 mpv2 mp2v +# Macintosh Quicktime +video/quicktime qt mov +# Microsoft video +video/x-msvideo avi +# SGI Movie format +video/x-sgi-movie movie +# VDOlive streaming video (VDOnet) +video/vdo vdo +# Vivo streaming video (Vivo software) +video/vnd.vivo viv + +# SPECIAL HTTP/WEB APPLICATION TYPES + +# Proxy autoconfiguration (Netscape browsers) +application/x-ns-proxy-autoconfig pac +# Netscape Cooltalk chat data (Netscape) +x-conference/x-cooltalk ice + +# TEXT-RELATED + +# PostScript +application/postscript ai eps ps +# Microsoft Rich Text Format +application/rtf rtf +# Adobe Acrobat PDF +application/pdf pdf +# Maker Interchange Format (FrameMaker) +application/vnd.mif mif +# Troff document +application/x-troff t tr roff +# Troff document with MAN macros +application/x-troff-man man +# Troff document with ME macros +application/x-troff-me me +# Troff document with MS macros +application/x-troff-ms ms +# LaTeX document +application/x-latex latex +# Tex/LateX document +application/x-tex tex +# GNU TexInfo document +application/x-texinfo texinfo texi +# TeX dvi format +application/x-dvi dvi +# MS word document +application/msword doc DOC +# Office Document Architecture +application/oda oda +# Envoy Document +application/envoy evy + +# ARCHIVE/COMPRESSED ARCHIVES + +# Gnu tar format +application/x-gtar gtar +# 4.3BSD tar format +application/x-tar tar +# POSIX tar format +application/x-ustar ustar +# Old CPIO format +application/x-bcpio bcpio +# POSIX CPIO format +application/x-cpio cpio +# UNIX sh shell archive +application/x-shar shar +# DOS/PC - Pkzipped archive +application/zip zip +# Macintosh Binhexed archive +application/mac-binhex40 hqx +# Macintosh Stuffit Archive +application/x-stuffit sit sea +# Fractal Image Format +application/fractals fif +# "Binary UUencoded" +application/octet-stream bin uu +# PC executable +application/octet-stream exe +# "WAIS ""sources""" +application/x-wais-source src wsrc +# NCSA HDF data format +application/hdf hdf + +# DOWNLOADABLE PROGRAM/SCRIPTS + +# Javascript program +text/javascript js ls mocha +# UNIX bourne shell program +application/x-sh sh +# UNIX c-shell program +application/x-csh csh +# Perl program +application/x-perl pl +# Tcl (Tool Control Language) program +application/x-tcl tcl + +# ANIMATION/MULTIMEDIA + +# FutureSplash vector animation (FutureWave) +application/futuresplash spl +# mBED multimedia data (mBED) +application/mbedlet mbd +# PowerMedia multimedia (RadMedia) +application/x-rad-powermedia rad + +# PRESENTATION + +# PowerPoint presentation (Microsoft) +application/mspowerpoint ppz +# ASAP WordPower (Software Publishing Corp.) +application/x-asap asp +# Astound Web Player multimedia data (GoldDisk) +application/astound asn + +# SPECIAL EMBEDDED OBJECT + +# OLE script e.g. Visual Basic (Ncompass) +application/x-olescript axs +# OLE Object (Microsoft/NCompass) +application/x-oleobject ods +# OpenScape OLE/OCX objects (Business@Web) +x-form/x-openscape opp +# Visual Basic objects (Amara) +application/x-webbasic wba +# Specialized data entry forms (Alpha Software) +application/x-alpha-form frm +# client-server objects (Wayfarer Communications) +x-script/x-wfxclient wfx + +# GENERAL APPLICATIONS + +# Undefined binary data (often executable progs) +application/octet-stream exe com +# Pointcast news data (Pointcast) +application/x-pcn pcn +# Excel spreadsheet (Microsoft) +application/vnd.ms-excel xls +# PowerPoint (Microsoft) +application/vnd.ms-powerpoint ppt +# Microsoft Project (Microsoft) +application/vnd.ms-project mpp +# SourceView document (Dataware Electronics) +application/vnd.svd svd +# Net Install - software install (20/20 Software) +application/x-net-install ins +# Carbon Copy - remote control/access (Microcom) +application/ccv ccv +# Spreadsheets (Visual Components) +workbook/formulaone vts + +# 2D/3D DATA/VIRTUAL REALITY TYPES + +# VRML data file +x-world/x-vrml wrl vrml +# WIRL - VRML data (VREAM) +x-world/x-vream vrw +# Play3D 3d scene data (Play3D) +application/x-p3d p3d +# Viscape Interactive 3d world data (Superscape) +x-world/x-svr svr +# WebActive 3d data (Plastic Thought) +x-world/x-wvr wvr +# QuickDraw3D scene data (Apple) +x-world/x-3dmf 3dmf + +# SCIENTIFIC/MATH/CAD TYPES + +# Mathematica notebook +application/mathematica ma +# Computational meshes for numerical simulations +x-model/x-mesh msh +# Vis5D 5-dimensional data +application/vis5d v5d +# IGES models -- CAD/CAM (CGM) data +application/iges igs +# Autocad WHIP vector drawings +drawing/x-dwf dwf + diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/package-info.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/package-info.java new file mode 100644 index 0000000..d261141 --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/javamail/package-info.java @@ -0,0 +1,11 @@ +/** + * JavaMail support for Spring's mail infrastructure. + * Provides an extended JavaMailSender interface and a MimeMessageHelper + * class for convenient population of a JavaMail MimeMessage. + */ +@NonNullApi +@NonNullFields +package com.simaek.notify.email.embedded.mail.javamail; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/package-info.java b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/package-info.java new file mode 100644 index 0000000..376aaac --- /dev/null +++ b/notify-email/src/main/java/com/simaek/notify/email/embedded/mail/package-info.java @@ -0,0 +1,10 @@ +/** + * Spring's generic mail infrastructure. + * Concrete implementations are provided in the subpackages. + */ +@NonNullApi +@NonNullFields +package com.simaek.notify.email.embedded.mail; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields;