1 /*
2  * Copyright 2013-2019 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.cloud.aws.mail.simplemail;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.lang.reflect.Constructor;
23 import java.nio.ByteBuffer;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Properties;
27
28 import javax.activation.FileTypeMap;
29 import javax.mail.MessagingException;
30 import javax.mail.Session;
31 import javax.mail.internet.MimeMessage;
32
33 import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
34 import com.amazonaws.services.simpleemail.model.RawMessage;
35 import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;
36 import com.amazonaws.services.simpleemail.model.SendRawEmailResult;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import org.springframework.beans.BeanUtils;
41 import org.springframework.mail.MailException;
42 import org.springframework.mail.MailParseException;
43 import org.springframework.mail.MailPreparationException;
44 import org.springframework.mail.MailSendException;
45 import org.springframework.mail.javamail.ConfigurableMimeFileTypeMap;
46 import org.springframework.mail.javamail.JavaMailSender;
47 import org.springframework.mail.javamail.MimeMessageHelper;
48 import org.springframework.mail.javamail.MimeMessagePreparator;
49 import org.springframework.util.Assert;
50 import org.springframework.util.ClassUtils;
51
52 /**
53  * {@link JavaMailSender} implementation that allows to send {@link MimeMessage} using the
54  * Simple E-Mail Service. In contrast to {@link SimpleEmailServiceMailSender} this class
55  * also allows the use of attachment and other mime parts inside mail messages.
56  *
57  * @author Agim Emruli
58  * @since 1.0
59  */

60 public class SimpleEmailServiceJavaMailSender extends SimpleEmailServiceMailSender
61         implements JavaMailSender {
62
63     private static final Logger LOGGER = LoggerFactory
64             .getLogger(SimpleEmailServiceMailSender.class);
65
66     private static final String SMART_MIME_MESSAGE_CLASS_NAME = "org.springframework.mail.javamail.SmartMimeMessage";
67
68     private Properties javaMailProperties = new Properties();
69
70     private volatile Session session;
71
72     private String defaultEncoding;
73
74     private FileTypeMap defaultFileTypeMap;
75
76     public SimpleEmailServiceJavaMailSender(
77             AmazonSimpleEmailService amazonSimpleEmailService) {
78         super(amazonSimpleEmailService);
79     }
80
81     /**
82      * Allow Map access to the JavaMail properties of this sender, with the option to add
83      * or override specific entries.
84      * <p>
85      * Useful for specifying entries directly, for example via
86      * "javaMailProperties[mail.from]".
87      * @return java mail properties
88      */

89     protected Properties getJavaMailProperties() {
90         return this.javaMailProperties;
91     }
92
93     /**
94      * Set JavaMail properties for the {@code Session}.
95      * <p>
96      * A new {@code Session} will be created with those properties.
97      * <p>
98      * Non-default properties in this instance will override given JavaMail properties.
99      * @param javaMailProperties java mail props
100      */

101     public void setJavaMailProperties(Properties javaMailProperties) {
102         this.javaMailProperties = javaMailProperties;
103         this.session = null;
104     }
105
106     /**
107      * Return the JavaMail {@code Session}, lazily initializing it if hasn't been
108      * specified explicitly.
109      * @return cached session or a new one from java mail properties
110      */

111     protected Session getSession() {
112         if (this.session == null) {
113             this.session = Session.getInstance(getJavaMailProperties());
114         }
115         return this.session;
116     }
117
118     /**
119      * Set the JavaMail {@code Session}, possibly pulled from JNDI.
120      * <p>
121      * Default is a new {@code Session} without defaults, that is completely configured
122      * via this instance's properties.
123      * <p>
124      * If using a pre-configured {@code Session}, non-default properties in this instance
125      * will override the settings in the {@code Session}.
126      * @param session JavaMail session
127      * @see #setJavaMailProperties
128      */

129     public void setSession(Session session) {
130         Assert.notNull(session, "Session must not be null");
131         this.session = session;
132     }
133
134     /**
135      * Set the default encoding to use for {@link MimeMessage MimeMessages} created by
136      * this instance.
137      * <p>
138      * Such an encoding will be auto-detected by {@link MimeMessageHelper}.
139      * @param defaultEncoding default encoding for mime messages
140      */

141     public void setDefaultEncoding(String defaultEncoding) {
142         this.defaultEncoding = defaultEncoding;
143     }
144
145     /**
146      * Set the default Java Activation {@link FileTypeMap} to use for {@link MimeMessage
147      * MimeMessages} created by this instance.
148      * <p>
149      * A {@code FileTypeMap} specified here will be autodetected by
150      * {@link MimeMessageHelper}, avoiding the need to specify the {@code FileTypeMap} for
151      * each {@code MimeMessageHelper} instance.
152      * <p>
153      * For example, you can specify a custom instance of Spring's
154      * {@link ConfigurableMimeFileTypeMap} here. If not explicitly specified, a default
155      * {@code ConfigurableMimeFileTypeMap} will be used, containing an extended set of
156      * MIME type mappings (as defined by the {@code mime.types} file contained in the
157      * Spring jar).
158      * @param defaultFileTypeMap Java Activation file type map
159      * @see MimeMessageHelper#setFileTypeMap
160      */

161     public void setDefaultFileTypeMap(FileTypeMap defaultFileTypeMap) {
162         this.defaultFileTypeMap = defaultFileTypeMap;
163     }
164
165     @Override
166     public MimeMessage createMimeMessage() {
167
168         // We have to use reflection as SmartMimeMessage is not package-private
169         if (ClassUtils.isPresent(SMART_MIME_MESSAGE_CLASS_NAME,
170                 ClassUtils.getDefaultClassLoader())) {
171             Class<?> smartMimeMessage = ClassUtils.resolveClassName(
172                     SMART_MIME_MESSAGE_CLASS_NAME, ClassUtils.getDefaultClassLoader());
173             Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(
174                     smartMimeMessage, Session.class, String.class, FileTypeMap.class);
175             if (constructor != null) {
176                 Object mimeMessage = BeanUtils.instantiateClass(constructor, getSession(),
177                         this.defaultEncoding, this.defaultFileTypeMap);
178                 return (MimeMessage) mimeMessage;
179             }
180         }
181
182         return new MimeMessage(getSession());
183     }
184
185     @Override
186     public MimeMessage createMimeMessage(InputStream contentStream) throws MailException {
187         try {
188             return new MimeMessage(getSession(), contentStream);
189         }
190         catch (MessagingException e) {
191             throw new MailParseException("Could not parse raw MIME content", e);
192         }
193     }
194
195     @Override
196     public void send(MimeMessage mimeMessage) throws MailException {
197         this.send(new MimeMessage[] { mimeMessage });
198     }
199
200     @SuppressWarnings("OverloadedVarargsMethod")
201     @Override
202     public void send(MimeMessage... mimeMessages) throws MailException {
203         Map<Object, Exception> failedMessages = new HashMap<>();
204
205         for (MimeMessage mimeMessage : mimeMessages) {
206             try {
207                 RawMessage rm = createRawMessage(mimeMessage);
208                 SendRawEmailResult sendRawEmailResult = getEmailService()
209                         .sendRawEmail(new SendRawEmailRequest(rm));
210                 if (LOGGER.isDebugEnabled()) {
211                     LOGGER.debug("Message with id: {} successfully send",
212                             sendRawEmailResult.getMessageId());
213                 }
214                 mimeMessage.setHeader("Message-ID", sendRawEmailResult.getMessageId());
215             }
216             catch (Exception e) {
217                 // Ignore Exception because we are collecting and throwing all if any
218                 // noinspection ThrowableResultOfMethodCallIgnored
219                 failedMessages.put(mimeMessage, e);
220             }
221         }
222
223         if (!failedMessages.isEmpty()) {
224             throw new MailSendException(failedMessages);
225         }
226     }
227
228     @Override
229     public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException {
230         send(new MimeMessagePreparator[] { mimeMessagePreparator });
231     }
232
233     @SuppressWarnings("OverloadedVarargsMethod")
234     @Override
235     public void send(MimeMessagePreparator... mimeMessagePreparators)
236             throws MailException {
237         MimeMessage mimeMessage = createMimeMessage();
238         for (MimeMessagePreparator mimeMessagePreparator : mimeMessagePreparators) {
239             try {
240                 mimeMessagePreparator.prepare(mimeMessage);
241             }
242             catch (Exception e) {
243                 throw new MailPreparationException(e);
244             }
245         }
246         send(mimeMessage);
247     }
248
249     private RawMessage createRawMessage(MimeMessage mimeMessage) {
250         ByteArrayOutputStream out;
251         try {
252             out = new ByteArrayOutputStream();
253             mimeMessage.writeTo(out);
254         }
255         catch (IOException e) {
256             throw new MailPreparationException(e);
257         }
258         catch (MessagingException e) {
259             throw new MailParseException(e);
260         }
261         return new RawMessage(ByteBuffer.wrap(out.toByteArray()));
262     }
263
264 }
265