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