/*
* Copyright 2013-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
*
* 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.cloud.aws.mail.simplemail;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.model.RawMessage;
import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;
import com.amazonaws.services.simpleemail.model.SendRawEmailResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.mail.MailException;
import org.springframework.mail.MailParseException;
import org.springframework.mail.MailPreparationException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.ConfigurableMimeFileTypeMap;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import javax.activation.FileTypeMap;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* {@link JavaMailSender} implementation that allows to send {@link MimeMessage} using the Simple E-Mail Service. In
* contrast to {@link SimpleEmailServiceMailSender} this class also allows the use of attachment and other mime parts
* inside mail messages.
*
* @author Agim Emruli
* @since 1.0
*/
public class SimpleEmailServiceJavaMailSender extends SimpleEmailServiceMailSender implements JavaMailSender {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleEmailServiceMailSender.class);
private static final String SMART_MIME_MESSAGE_CLASS_NAME = "org.springframework.mail.javamail.SmartMimeMessage";
private Properties javaMailProperties = new Properties();
private volatile Session session;
private String defaultEncoding;
private FileTypeMap defaultFileTypeMap;
public SimpleEmailServiceJavaMailSender(AmazonSimpleEmailService amazonSimpleEmailService) {
super(amazonSimpleEmailService);
}
/**
* Set JavaMail properties for the {@code Session}.
* <p>A new {@code Session} will be created with those properties.
* <p>Non-default properties in this instance will override given
* JavaMail properties.
*/
public void setJavaMailProperties(Properties javaMailProperties) {
this.javaMailProperties = javaMailProperties;
this.session = null;
}
/**
* 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.from]".
*/
protected Properties getJavaMailProperties() {
return this.javaMailProperties;
}
/**
* 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 void setSession(Session session) {
Assert.notNull(session, "Session must not be null");
this.session = session;
}
/**
* Return the JavaMail {@code Session},
* lazily initializing it if hasn't been specified explicitly.
*/
protected Session getSession() {
if (this.session == null) {
this.session = Session.getInstance(getJavaMailProperties());
}
return this.session;
}
/**
* 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(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* 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(FileTypeMap defaultFileTypeMap) {
this.defaultFileTypeMap = defaultFileTypeMap;
}
@Override
public MimeMessage createMimeMessage() {
// We have to use reflection as SmartMimeMessage is not package-private
if (ClassUtils.isPresent(SMART_MIME_MESSAGE_CLASS_NAME, ClassUtils.getDefaultClassLoader())) {
Class<?> smartMimeMessage = ClassUtils.resolveClassName(SMART_MIME_MESSAGE_CLASS_NAME, ClassUtils.getDefaultClassLoader());
Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(smartMimeMessage, Session.class, String.class, FileTypeMap.class);
if (constructor != null) {
Object mimeMessage = BeanUtils.instantiateClass(constructor, getSession(), this.defaultEncoding, this.defaultFileTypeMap);
return (MimeMessage) mimeMessage;
}
}
return new MimeMessage(getSession());
}
@Override
public MimeMessage createMimeMessage(InputStream contentStream) throws MailException {
try {
return new MimeMessage(getSession(), contentStream);
} catch (MessagingException e) {
throw new MailParseException("Could not parse raw MIME content", e);
}
}
@Override
public void send(MimeMessage mimeMessage) throws MailException {
this.send(new MimeMessage[]{mimeMessage});
}
@SuppressWarnings("OverloadedVarargsMethod")
@Override
public void send(MimeMessage... mimeMessages) throws MailException {
Map<Object, Exception> failedMessages = new HashMap<>();
for (MimeMessage mimeMessage : mimeMessages) {
try {
RawMessage rm = createRawMessage(mimeMessage);
SendRawEmailResult sendRawEmailResult = getEmailService().sendRawEmail(new SendRawEmailRequest(rm));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Message with id: {} successfully send", sendRawEmailResult.getMessageId());
}
} catch (Exception e) {
//Ignore Exception because we are collecting and throwing all if any
//noinspection ThrowableResultOfMethodCallIgnored
failedMessages.put(mimeMessage, e);
}
}
if (!failedMessages.isEmpty()) {
throw new MailSendException(failedMessages);
}
}
@Override
public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException {
send(new MimeMessagePreparator[]{mimeMessagePreparator});
}
@SuppressWarnings("OverloadedVarargsMethod")
@Override
public void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException {
MimeMessage mimeMessage = createMimeMessage();
for (MimeMessagePreparator mimeMessagePreparator : mimeMessagePreparators) {
try {
mimeMessagePreparator.prepare(mimeMessage);
} catch (Exception e) {
throw new MailPreparationException(e);
}
}
send(mimeMessage);
}
private RawMessage createRawMessage(MimeMessage mimeMessage) {
ByteArrayOutputStream out;
try {
out = new ByteArrayOutputStream();
mimeMessage.writeTo(out);
} catch (IOException e) {
throw new MailPreparationException(e);
} catch (MessagingException e) {
throw new MailParseException(e);
}
return new RawMessage(ByteBuffer.wrap(out.toByteArray()));
}
}