com.wl4g.infra.common.notification.email.internal.JavaMailSenderImpl Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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.wl4g.infra.common.notification.email.internal;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.activation.FileTypeMap;
import javax.annotation.Nullable;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
import com.wl4g.infra.common.lang.Assert2;
import com.wl4g.infra.common.notification.email.internal.MimeMessageHelper.SmartMimeMessage;
/**
* 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 org.springframework.mail.MailSender} implementation.
*
*
* Allows for defining all settings locally as bean properties. Alternatively, a
* pre-configured JavaMail {@link javax.mail.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
* @since 10.09.2003
* @see javax.mail.internet.MimeMessage
* @see javax.mail.Session
* @see #setSession
* @see #setJavaMailProperties
* @see #setHost
* @see #setPort
* @see #setUsername
* @see #setPassword
*/
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.init();
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) {
Assert2.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 com.wl4g.infra.common.notification.email.internal.springframework.mail.MailAuthenticationException
* in case of authentication failure
* @throws com.wl4g.infra.common.notification.email.internal.springframework.mail.MailSendException
* in case of failure when sending a message
*/
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
Map