/*
 * 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
 *
 *      http://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 org.springframework.mail.javamail;

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.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 org.springframework.lang.Nullable;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailParseException;
import org.springframework.mail.MailPreparationException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.util.Assert;

Production implementation of the JavaMailSender interface, supporting both JavaMail MimeMessages and Spring SimpleMailMessages. Can also be used as a plain MailSender implementation.

Allows for defining all settings locally as bean properties. Alternatively, a pre-configured JavaMail 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 Session. Note that if overriding all values locally, there is no added value in setting a pre-configured Session.

Author:Dmitriy Kopylenko, Juergen Hoeller
See Also:
Since:10.09.2003
/** * 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. * * <p>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. * * <p>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'.
/** The default protocol: 'smtp'. */
public static final String DEFAULT_PROTOCOL = "smtp";
The default port: -1.
/** 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 JavaMailSenderImpl class.

Initializes the "defaultFileTypeMap" property with a default ConfigurableMimeFileTypeMap.

/** * Create a new instance of the {@code JavaMailSenderImpl} class. * <p>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 Session.

A new Session will be created with those properties. Use either this method or setSession, but not both.

Non-default properties in this instance will override given JavaMail properties.

/** * Set JavaMail properties for the {@code Session}. * <p>A new {@code Session} will be created with those properties. * Use either this method or {@link #setSession}, but not both. * <p>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 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 "javaMailProperties[mail.smtp.auth]".

/** * Allow Map access to the JavaMail properties of this sender, * with the option to add or override specific entries. * <p>Useful for specifying entries directly, for example via * "javaMailProperties[mail.smtp.auth]". */
public Properties getJavaMailProperties() { return this.javaMailProperties; }
Set the JavaMail Session, possibly pulled from JNDI.

Default is a new Session without defaults, that is completely configured via this instance's properties.

If using a pre-configured Session, non-default properties in this instance will override the settings in the Session.

See Also:
/** * Set the JavaMail {@code Session}, possibly pulled from JNDI. * <p>Default is a new {@code Session} without defaults, that is * completely configured via this instance's properties. * <p>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 Session, lazily initializing it if hasn't been specified explicitly.
/** * Return the JavaMail {@code Session}, * lazily initializing it if 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".
/** * Set the mail protocol. Default is "smtp". */
public void setProtocol(@Nullable String protocol) { this.protocol = protocol; }
Return the mail 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.

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

Default is DEFAULT_PORT, letting JavaMail use the default SMTP port (25).

/** * Set the mail server port. * <p>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.
/** * 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 Session has to be configured with the property "mail.smtp.auth" set to 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 Session to use, simply specify this setting via setJavaMailProperties.

See Also:
/** * Set the username for the account at the mail host, if any. * <p>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.
/** * 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 Session has to be configured with the property "mail.smtp.auth" set to 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 Session to use, simply specify this setting via setJavaMailProperties.

See Also:
/** * Set the password for the account at the mail host, if any. * <p>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.
/** * Return the password for the account at the mail host. */
@Nullable public String getPassword() { return this.password; }
Set the default encoding to use for MimeMessages created by this instance.

Such an encoding will be auto-detected by MimeMessageHelper.

/** * Set the default encoding to use for {@link MimeMessage MimeMessages} * created by this instance. * <p>Such an encoding will be auto-detected by {@link MimeMessageHelper}. */
public void setDefaultEncoding(@Nullable String defaultEncoding) { this.defaultEncoding = defaultEncoding; }
Return the default encoding for MimeMessages, or null if none.
/** * 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 FileTypeMap to use for MimeMessages created by this instance.

A FileTypeMap specified here will be autodetected by MimeMessageHelper, avoiding the need to specify the FileTypeMap for each MimeMessageHelper instance.

For example, you can specify a custom instance of Spring's ConfigurableMimeFileTypeMap here. If not explicitly specified, a default ConfigurableMimeFileTypeMap will be used, containing an extended set of MIME type mappings (as defined by the mime.types file contained in the Spring jar).

See Also:
/** * Set the default Java Activation {@link FileTypeMap} to use for * {@link MimeMessage MimeMessages} created by this instance. * <p>A {@code FileTypeMap} specified here will be autodetected by * {@link MimeMessageHelper}, avoiding the need to specify the * {@code FileTypeMap} for each {@code MimeMessageHelper} instance. * <p>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 FileTypeMap for MimeMessages, or null if none.
/** * 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<MimeMessage> 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 MimeMessageHelper, which will use the carried encoding and FileTypeMap unless explicitly overridden.
See Also:
/** * 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<MimeMessage> 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 MessagingException if the connection attempt failed.
/** * 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.
Params:
  • mimeMessages – the MimeMessage objects to send
  • 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:
/** * 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 org.springframework.mail.MailAuthenticationException * in case of authentication failure * @throws org.springframework.mail.MailSendException * in case of failure when sending a message */
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException { Map<Object, Exception> 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.
Throws:
See Also:
Returns:the connected Transport object
Since:4.1.2
/** * 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 * @since 4.1.2 * @see #getTransport * @see #getHost() * @see #getPort() * @see #getUsername() * @see #getPassword() */
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 Also:
/** * Obtain a Transport object from the given JavaMail Session, * using the configured protocol. * <p>Can be overridden in subclasses, e.g. to return a mock Transport object. * @see javax.mail.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); } }