001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.mail.javamail;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.ArrayList;
022: import java.util.Date;
023: import java.util.LinkedHashMap;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Properties;
027:
028: import javax.activation.FileTypeMap;
029: import javax.mail.AuthenticationFailedException;
030: import javax.mail.MessagingException;
031: import javax.mail.NoSuchProviderException;
032: import javax.mail.Session;
033: import javax.mail.Transport;
034: import javax.mail.internet.MimeMessage;
035:
036: import org.springframework.mail.MailAuthenticationException;
037: import org.springframework.mail.MailException;
038: import org.springframework.mail.MailParseException;
039: import org.springframework.mail.MailPreparationException;
040: import org.springframework.mail.MailSendException;
041: import org.springframework.mail.SimpleMailMessage;
042: import org.springframework.util.Assert;
043:
044: /**
045: * Production implementation of the {@link JavaMailSender} interface,
046: * supporting both JavaMail {@link MimeMessage MimeMessages} and Spring
047: * {@link SimpleMailMessage SimpleMailMessages}. Can also be used as a
048: * plain {@link org.springframework.mail.MailSender} implementation.
049: *
050: * <p>Allows for defining all settings locally as bean properties.
051: * Alternatively, a pre-configured JavaMail {@link javax.mail.Session} can be
052: * specified, possibly pulled from an application server's JNDI environment.
053: *
054: * <p>Non-default properties in this object will always override the settings
055: * in the JavaMail <code>Session</code>. Note that if overriding all values locally,
056: * there is no added value in setting a pre-configured <code>Session</code>.
057: *
058: * @author Dmitriy Kopylenko
059: * @author Juergen Hoeller
060: * @since 10.09.2003
061: * @see javax.mail.internet.MimeMessage
062: * @see javax.mail.Session
063: * @see #setSession
064: * @see #setJavaMailProperties
065: * @see #setHost
066: * @see #setPort
067: * @see #setUsername
068: * @see #setPassword
069: */
070: public class JavaMailSenderImpl implements JavaMailSender {
071:
072: /** The default protocol: 'smtp' */
073: public static final String DEFAULT_PROTOCOL = "smtp";
074:
075: /** The default port: -1 */
076: public static final int DEFAULT_PORT = -1;
077:
078: private static final String HEADER_MESSAGE_ID = "Message-ID";
079:
080: private Properties javaMailProperties = new Properties();
081:
082: private Session session;
083:
084: private String protocol = DEFAULT_PROTOCOL;
085:
086: private String host;
087:
088: private int port = DEFAULT_PORT;
089:
090: private String username;
091:
092: private String password;
093:
094: private String defaultEncoding;
095:
096: private FileTypeMap defaultFileTypeMap;
097:
098: /**
099: * Create a new instance of the <code>JavaMailSenderImpl</code> class.
100: * <p>Initializes the {@link #setDefaultFileTypeMap "defaultFileTypeMap"}
101: * property with a default {@link ConfigurableMimeFileTypeMap}.
102: */
103: public JavaMailSenderImpl() {
104: ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap();
105: fileTypeMap.afterPropertiesSet();
106: this .defaultFileTypeMap = fileTypeMap;
107: }
108:
109: /**
110: * Set JavaMail properties for the <code>Session</code>.
111: * <p>A new <code>Session</code> will be created with those properties.
112: * Use either this method or {@link #setSession}, but not both.
113: * <p>Non-default properties in this instance will override given
114: * JavaMail properties.
115: */
116: public void setJavaMailProperties(Properties javaMailProperties) {
117: this .javaMailProperties = javaMailProperties;
118: synchronized (this ) {
119: this .session = null;
120: }
121: }
122:
123: /**
124: * Allow Map access to the JavaMail properties of this sender,
125: * with the option to add or override specific entries.
126: * <p>Useful for specifying entries directly, for example via
127: * "javaMailProperties[mail.smtp.auth]".
128: */
129: public Properties getJavaMailProperties() {
130: return this .javaMailProperties;
131: }
132:
133: /**
134: * Set the JavaMail <code>Session</code>, possibly pulled from JNDI.
135: * <p>Default is a new <code>Session</code> without defaults, that is
136: * completely configured via this instance's properties.
137: * <p>If using a pre-configured <code>Session</code>, non-default properties
138: * in this instance will override the settings in the <code>Session</code>.
139: * @see #setJavaMailProperties
140: */
141: public synchronized void setSession(Session session) {
142: Assert.notNull(session, "Session must not be null");
143: this .session = session;
144: }
145:
146: /**
147: * Return the JavaMail <code>Session</code>,
148: * lazily initializing it if hasn't been specified explicitly.
149: */
150: public synchronized Session getSession() {
151: if (this .session == null) {
152: this .session = Session.getInstance(this .javaMailProperties);
153: }
154: return this .session;
155: }
156:
157: /**
158: * Set the mail protocol. Default is "smtp".
159: */
160: public void setProtocol(String protocol) {
161: this .protocol = protocol;
162: }
163:
164: /**
165: * Return the mail protocol.
166: */
167: public String getProtocol() {
168: return this .protocol;
169: }
170:
171: /**
172: * Set the mail server host, typically an SMTP host.
173: * <p>Default is the default host of the underlying JavaMail Session.
174: */
175: public void setHost(String host) {
176: this .host = host;
177: }
178:
179: /**
180: * Return the mail server host.
181: */
182: public String getHost() {
183: return this .host;
184: }
185:
186: /**
187: * Set the mail server port.
188: * <p>Default is {@link #DEFAULT_PORT}, letting JavaMail use the default
189: * SMTP port (25).
190: */
191: public void setPort(int port) {
192: this .port = port;
193: }
194:
195: /**
196: * Return the mail server port.
197: */
198: public int getPort() {
199: return this .port;
200: }
201:
202: /**
203: * Set the username for the account at the mail host, if any.
204: * <p>Note that the underlying JavaMail <code>Session</code> has to be
205: * configured with the property <code>"mail.smtp.auth"</code> set to
206: * <code>true</code>, else the specified username will not be sent to the
207: * mail server by the JavaMail runtime. If you are not explicitly passing
208: * in a <code>Session</code> to use, simply specify this setting via
209: * {@link #setJavaMailProperties}.
210: * @see #setSession
211: * @see #setPassword
212: */
213: public void setUsername(String username) {
214: this .username = username;
215: }
216:
217: /**
218: * Return the username for the account at the mail host.
219: */
220: public String getUsername() {
221: return this .username;
222: }
223:
224: /**
225: * Set the password for the account at the mail host, if any.
226: * <p>Note that the underlying JavaMail <code>Session</code> has to be
227: * configured with the property <code>"mail.smtp.auth"</code> set to
228: * <code>true</code>, else the specified password will not be sent to the
229: * mail server by the JavaMail runtime. If you are not explicitly passing
230: * in a <code>Session</code> to use, simply specify this setting via
231: * {@link #setJavaMailProperties}.
232: * @see #setSession
233: * @see #setUsername
234: */
235: public void setPassword(String password) {
236: this .password = password;
237: }
238:
239: /**
240: * Return the password for the account at the mail host.
241: */
242: public String getPassword() {
243: return this .password;
244: }
245:
246: /**
247: * Set the default encoding to use for {@link MimeMessage MimeMessages}
248: * created by this instance.
249: * <p>Such an encoding will be auto-detected by {@link MimeMessageHelper}.
250: */
251: public void setDefaultEncoding(String defaultEncoding) {
252: this .defaultEncoding = defaultEncoding;
253: }
254:
255: /**
256: * Return the default encoding for {@link MimeMessage MimeMessages},
257: * or <code>null</code> if none.
258: */
259: public String getDefaultEncoding() {
260: return this .defaultEncoding;
261: }
262:
263: /**
264: * Set the default Java Activation {@link FileTypeMap} to use for
265: * {@link MimeMessage MimeMessages} created by this instance.
266: * <p>A <code>FileTypeMap</code> specified here will be autodetected by
267: * {@link MimeMessageHelper}, avoiding the need to specify the
268: * <code>FileTypeMap</code> for each <code>MimeMessageHelper</code> instance.
269: * <p>For example, you can specify a custom instance of Spring's
270: * {@link ConfigurableMimeFileTypeMap} here. If not explicitly specified,
271: * a default <code>ConfigurableMimeFileTypeMap</code> will be used, containing
272: * an extended set of MIME type mappings (as defined by the
273: * <code>mime.types</code> file contained in the Spring jar).
274: * @see MimeMessageHelper#setFileTypeMap
275: */
276: public void setDefaultFileTypeMap(FileTypeMap defaultFileTypeMap) {
277: this .defaultFileTypeMap = defaultFileTypeMap;
278: }
279:
280: /**
281: * Return the default Java Activation {@link FileTypeMap} for
282: * {@link MimeMessage MimeMessages}, or <code>null</code> if none.
283: */
284: public FileTypeMap getDefaultFileTypeMap() {
285: return this .defaultFileTypeMap;
286: }
287:
288: //---------------------------------------------------------------------
289: // Implementation of MailSender
290: //---------------------------------------------------------------------
291:
292: public void send(SimpleMailMessage simpleMessage)
293: throws MailException {
294: send(new SimpleMailMessage[] { simpleMessage });
295: }
296:
297: public void send(SimpleMailMessage[] simpleMessages)
298: throws MailException {
299: List mimeMessages = new ArrayList(simpleMessages.length);
300: for (int i = 0; i < simpleMessages.length; i++) {
301: SimpleMailMessage simpleMessage = simpleMessages[i];
302: MimeMailMessage message = new MimeMailMessage(
303: createMimeMessage());
304: simpleMessage.copyTo(message);
305: mimeMessages.add(message.getMimeMessage());
306: }
307: doSend((MimeMessage[]) mimeMessages
308: .toArray(new MimeMessage[mimeMessages.size()]),
309: simpleMessages);
310: }
311:
312: //---------------------------------------------------------------------
313: // Implementation of JavaMailSender
314: //---------------------------------------------------------------------
315:
316: /**
317: * This implementation creates a SmartMimeMessage, holding the specified
318: * default encoding and default FileTypeMap. This special defaults-carrying
319: * message will be autodetected by {@link MimeMessageHelper}, which will use
320: * the carried encoding and FileTypeMap unless explicitly overridden.
321: * @see #setDefaultEncoding
322: * @see #setDefaultFileTypeMap
323: */
324: public MimeMessage createMimeMessage() {
325: return new SmartMimeMessage(getSession(), getDefaultEncoding(),
326: getDefaultFileTypeMap());
327: }
328:
329: public MimeMessage createMimeMessage(InputStream contentStream)
330: throws MailException {
331: try {
332: return new MimeMessage(getSession(), contentStream);
333: } catch (MessagingException ex) {
334: throw new MailParseException(
335: "Could not parse raw MIME content", ex);
336: }
337: }
338:
339: public void send(MimeMessage mimeMessage) throws MailException {
340: send(new MimeMessage[] { mimeMessage });
341: }
342:
343: public void send(MimeMessage[] mimeMessages) throws MailException {
344: doSend(mimeMessages, null);
345: }
346:
347: public void send(MimeMessagePreparator mimeMessagePreparator)
348: throws MailException {
349: send(new MimeMessagePreparator[] { mimeMessagePreparator });
350: }
351:
352: public void send(MimeMessagePreparator[] mimeMessagePreparators)
353: throws MailException {
354: try {
355: List mimeMessages = new ArrayList(
356: mimeMessagePreparators.length);
357: for (int i = 0; i < mimeMessagePreparators.length; i++) {
358: MimeMessage mimeMessage = createMimeMessage();
359: mimeMessagePreparators[i].prepare(mimeMessage);
360: mimeMessages.add(mimeMessage);
361: }
362: send((MimeMessage[]) mimeMessages
363: .toArray(new MimeMessage[mimeMessages.size()]));
364: } catch (MailException ex) {
365: throw ex;
366: } catch (MessagingException ex) {
367: throw new MailParseException(ex);
368: } catch (IOException ex) {
369: throw new MailPreparationException(ex);
370: } catch (Exception ex) {
371: throw new MailPreparationException(ex);
372: }
373: }
374:
375: /**
376: * Actually send the given array of MimeMessages via JavaMail.
377: * @param mimeMessages MimeMessage objects to send
378: * @param originalMessages corresponding original message objects
379: * that the MimeMessages have been created from (with same array
380: * length and indices as the "mimeMessages" array), if any
381: * @throws org.springframework.mail.MailAuthenticationException
382: * in case of authentication failure
383: * @throws org.springframework.mail.MailSendException
384: * in case of failure when sending a message
385: */
386: protected void doSend(MimeMessage[] mimeMessages,
387: Object[] originalMessages) throws MailException {
388: Map failedMessages = new LinkedHashMap();
389: try {
390: Transport transport = getTransport(getSession());
391: transport.connect(getHost(), getPort(), getUsername(),
392: getPassword());
393: try {
394: for (int i = 0; i < mimeMessages.length; i++) {
395: MimeMessage mimeMessage = mimeMessages[i];
396: try {
397: if (mimeMessage.getSentDate() == null) {
398: mimeMessage.setSentDate(new Date());
399: }
400: String messageId = mimeMessage.getMessageID();
401: mimeMessage.saveChanges();
402: if (messageId != null) {
403: // Preserve explicitly specified message id...
404: mimeMessage.setHeader(HEADER_MESSAGE_ID,
405: messageId);
406: }
407: transport.sendMessage(mimeMessage, mimeMessage
408: .getAllRecipients());
409: } catch (MessagingException ex) {
410: Object original = (originalMessages != null ? originalMessages[i]
411: : mimeMessage);
412: failedMessages.put(original, ex);
413: }
414: }
415: } finally {
416: transport.close();
417: }
418: } catch (AuthenticationFailedException ex) {
419: throw new MailAuthenticationException(ex);
420: } catch (MessagingException ex) {
421: throw new MailSendException(
422: "Mail server connection failed", ex);
423: }
424: if (!failedMessages.isEmpty()) {
425: throw new MailSendException(failedMessages);
426: }
427: }
428:
429: /**
430: * Obtain a Transport object from the given JavaMail Session,
431: * using the configured protocol.
432: * <p>Can be overridden in subclasses, e.g. to return a mock Transport object.
433: * @see javax.mail.Session#getTransport(String)
434: * @see #getProtocol()
435: */
436: protected Transport getTransport(Session session)
437: throws NoSuchProviderException {
438: return session.getTransport(getProtocol());
439: }
440:
441: }
|