View Javadoc
1   /*
2    * Copyright 2002-2013 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    *      http://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.mail.javamail;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.util.Date;
25  import javax.activation.DataHandler;
26  import javax.activation.DataSource;
27  import javax.activation.FileDataSource;
28  import javax.activation.FileTypeMap;
29  import javax.mail.BodyPart;
30  import javax.mail.Message;
31  import javax.mail.MessagingException;
32  import javax.mail.internet.AddressException;
33  import javax.mail.internet.InternetAddress;
34  import javax.mail.internet.MimeBodyPart;
35  import javax.mail.internet.MimeMessage;
36  import javax.mail.internet.MimeMultipart;
37  import javax.mail.internet.MimePart;
38  import javax.mail.internet.MimeUtility;
39  
40  import org.springframework.core.io.InputStreamSource;
41  import org.springframework.core.io.Resource;
42  import org.springframework.util.Assert;
43  
44  /**
45   * Helper class for populating a {@link javax.mail.internet.MimeMessage}.
46   *
47   * <p>Mirrors the simple setters of {@link org.springframework.mail.SimpleMailMessage},
48   * directly applying the values to the underlying MimeMessage. Allows for defining
49   * a character encoding for the entire message, automatically applied by all methods
50   * of this helper class.
51   *
52   * <p>Offers support for HTML text content, inline elements such as images, and typical
53   * mail attachments. Also supports personal names that accompany mail addresses. Note that
54   * advanced settings can still be applied directly to the underlying MimeMessage object!
55   *
56   * <p>Typically used in {@link MimeMessagePreparator} implementations or
57   * {@link JavaMailSender} client code: simply instantiating it as a MimeMessage wrapper,
58   * invoking setters on the wrapper, using the underlying MimeMessage for mail sending.
59   * Also used internally by {@link JavaMailSenderImpl}.
60   *
61   * <p>Sample code for an HTML mail with an inline image and a PDF attachment:
62   *
63   * <pre class="code">
64   * mailSender.send(new MimeMessagePreparator() {
65   *   public void prepare(MimeMessage mimeMessage) throws MessagingException {
66   *     MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
67   *     message.setFrom("me@mail.com");
68   *     message.setTo("you@mail.com");
69   *     message.setSubject("my subject");
70   *     message.setText("my text &lt;img src='cid:myLogo'&gt;", true);
71   *     message.addInline("myLogo", new ClassPathResource("img/mylogo.gif"));
72   *     message.addAttachment("myDocument.pdf", new ClassPathResource("doc/myDocument.pdf"));
73   *   }
74   * });</pre>
75   *
76   * Consider using {@link MimeMailMessage} (which implements the common
77   * {@link org.springframework.mail.MailMessage} interface, just like
78   * {@link org.springframework.mail.SimpleMailMessage}) on top of this helper,
79   * in order to let message population code interact with a simple message
80   * or a MIME message through a common interface.
81   *
82   * <p><b>Warning regarding multipart mails:</b> Simple MIME messages that
83   * just contain HTML text but no inline elements or attachments will work on
84   * more or less any email client that is capable of HTML rendering. However,
85   * inline elements and attachments are still a major compatibility issue
86   * between email clients: It's virtually impossible to get inline elements
87   * and attachments working across Microsoft Outlook, Lotus Notes and Mac Mail.
88   * Consider choosing a specific multipart mode for your needs: The javadoc
89   * on the MULTIPART_MODE constants contains more detailed information.
90   *
91   * @author Juergen Hoeller
92   * @since 19.01.2004
93   * @see #setText(String, boolean)
94   * @see #setText(String, String)
95   * @see #addInline(String, org.springframework.core.io.Resource)
96   * @see #addAttachment(String, org.springframework.core.io.InputStreamSource)
97   * @see #MULTIPART_MODE_MIXED_RELATED
98   * @see #MULTIPART_MODE_RELATED
99   * @see #getMimeMessage()
100  * @see JavaMailSender
101  */
102 public class MimeMessageHelper {
103 
104 	/**
105 	 * Constant indicating a non-multipart message.
106 	 */
107 	public static final int MULTIPART_MODE_NO = 0;
108 
109 	/**
110 	 * Constant indicating a multipart message with a single root multipart
111 	 * element of type "mixed". Texts, inline elements and attachements
112 	 * will all get added to that root element.
113 	 * <p>This was Spring 1.0's default behavior. It is known to work properly
114 	 * on Outlook. However, other mail clients tend to misinterpret inline
115 	 * elements as attachments and/or show attachments inline as well.
116 	 */
117 	public static final int MULTIPART_MODE_MIXED = 1;
118 
119 	/**
120 	 * Constant indicating a multipart message with a single root multipart
121 	 * element of type "related". Texts, inline elements and attachements
122 	 * will all get added to that root element.
123 	 * <p>This was the default behavior from Spring 1.1 up to 1.2 final.
124 	 * This is the "Microsoft multipart mode", as natively sent by Outlook.
125 	 * It is known to work properly on Outlook, Outlook Express, Yahoo Mail, and
126 	 * to a large degree also on Mac Mail (with an additional attachment listed
127 	 * for an inline element, despite the inline element also shown inline).
128 	 * Does not work properly on Lotus Notes (attachments won't be shown there).
129 	 */
130 	public static final int MULTIPART_MODE_RELATED = 2;
131 
132 	/**
133 	 * Constant indicating a multipart message with a root multipart element
134 	 * "mixed" plus a nested multipart element of type "related". Texts and
135 	 * inline elements will get added to the nested "related" element,
136 	 * while attachments will get added to the "mixed" root element.
137 	 * <p>This is the default since Spring 1.2.1. This is arguably the most correct
138 	 * MIME structure, according to the MIME spec: It is known to work properly
139 	 * on Outlook, Outlook Express, Yahoo Mail, and Lotus Notes. Does not work
140 	 * properly on Mac Mail. If you target Mac Mail or experience issues with
141 	 * specific mails on Outlook, consider using MULTIPART_MODE_RELATED instead.
142 	 */
143 	public static final int MULTIPART_MODE_MIXED_RELATED = 3;
144 
145 
146 	private static final String MULTIPART_SUBTYPE_MIXED = "mixed";
147 
148 	private static final String MULTIPART_SUBTYPE_RELATED = "related";
149 
150 	private static final String MULTIPART_SUBTYPE_ALTERNATIVE = "alternative";
151 
152 	private static final String CONTENT_TYPE_ALTERNATIVE = "text/alternative";
153 
154 	private static final String CONTENT_TYPE_HTML = "text/html";
155 
156 	private static final String CONTENT_TYPE_CHARSET_SUFFIX = ";charset=";
157 
158 	private static final String HEADER_PRIORITY = "X-Priority";
159 
160 	private static final String HEADER_CONTENT_ID = "Content-ID";
161 
162 
163 	private final MimeMessage mimeMessage;
164 
165 	private MimeMultipart rootMimeMultipart;
166 
167 	private MimeMultipart mimeMultipart;
168 
169 	private final String encoding;
170 
171 	private FileTypeMap fileTypeMap;
172 
173 	private boolean validateAddresses = false;
174 
175 
176 	/**
177 	 * Create a new MimeMessageHelper for the given MimeMessage,
178 	 * assuming a simple text message (no multipart content,
179 	 * i.e. no alternative texts and no inline elements or attachments).
180 	 * <p>The character encoding for the message will be taken from
181 	 * the passed-in MimeMessage object, if carried there. Else,
182 	 * JavaMail's default encoding will be used.
183 	 * @param mimeMessage MimeMessage to work on
184 	 * @see #MimeMessageHelper(javax.mail.internet.MimeMessage, boolean)
185 	 * @see #getDefaultEncoding(javax.mail.internet.MimeMessage)
186 	 * @see JavaMailSenderImpl#setDefaultEncoding
187 	 */
188 	public MimeMessageHelper(MimeMessage mimeMessage) {
189 		this(mimeMessage, null);
190 	}
191 
192 	/**
193 	 * Create a new MimeMessageHelper for the given MimeMessage,
194 	 * assuming a simple text message (no multipart content,
195 	 * i.e. no alternative texts and no inline elements or attachments).
196 	 * @param mimeMessage MimeMessage to work on
197 	 * @param encoding the character encoding to use for the message
198 	 * @see #MimeMessageHelper(javax.mail.internet.MimeMessage, boolean)
199 	 */
200 	public MimeMessageHelper(MimeMessage mimeMessage, String encoding) {
201 		this.mimeMessage = mimeMessage;
202 		this.encoding = (encoding != null ? encoding : getDefaultEncoding(mimeMessage));
203 		this.fileTypeMap = getDefaultFileTypeMap(mimeMessage);
204 	}
205 
206 	/**
207 	 * Create a new MimeMessageHelper for the given MimeMessage,
208 	 * in multipart mode (supporting alternative texts, inline
209 	 * elements and attachments) if requested.
210 	 * <p>Consider using the MimeMessageHelper constructor that
211 	 * takes a multipartMode argument to choose a specific multipart
212 	 * mode other than MULTIPART_MODE_MIXED_RELATED.
213 	 * <p>The character encoding for the message will be taken from
214 	 * the passed-in MimeMessage object, if carried there. Else,
215 	 * JavaMail's default encoding will be used.
216 	 * @param mimeMessage MimeMessage to work on
217 	 * @param multipart whether to create a multipart message that
218 	 * supports alternative texts, inline elements and attachments
219 	 * (corresponds to MULTIPART_MODE_MIXED_RELATED)
220 	 * @throws MessagingException if multipart creation failed
221 	 * @see #MimeMessageHelper(javax.mail.internet.MimeMessage, int)
222 	 * @see #getDefaultEncoding(javax.mail.internet.MimeMessage)
223 	 * @see JavaMailSenderImpl#setDefaultEncoding
224 	 */
225 	public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart) throws MessagingException {
226 		this(mimeMessage, multipart, null);
227 	}
228 
229 	/**
230 	 * Create a new MimeMessageHelper for the given MimeMessage,
231 	 * in multipart mode (supporting alternative texts, inline
232 	 * elements and attachments) if requested.
233 	 * <p>Consider using the MimeMessageHelper constructor that
234 	 * takes a multipartMode argument to choose a specific multipart
235 	 * mode other than MULTIPART_MODE_MIXED_RELATED.
236 	 * @param mimeMessage MimeMessage to work on
237 	 * @param multipart whether to create a multipart message that
238 	 * supports alternative texts, inline elements and attachments
239 	 * (corresponds to MULTIPART_MODE_MIXED_RELATED)
240 	 * @param encoding the character encoding to use for the message
241 	 * @throws MessagingException if multipart creation failed
242 	 * @see #MimeMessageHelper(javax.mail.internet.MimeMessage, int, String)
243 	 */
244 	public MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, String encoding)
245 			throws MessagingException {
246 
247 		this(mimeMessage, (multipart ? MULTIPART_MODE_MIXED_RELATED : MULTIPART_MODE_NO), encoding);
248 	}
249 
250 	/**
251 	 * Create a new MimeMessageHelper for the given MimeMessage,
252 	 * in multipart mode (supporting alternative texts, inline
253 	 * elements and attachments) if requested.
254 	 * <p>The character encoding for the message will be taken from
255 	 * the passed-in MimeMessage object, if carried there. Else,
256 	 * JavaMail's default encoding will be used.
257 	 * @param mimeMessage MimeMessage to work on
258 	 * @param multipartMode which kind of multipart message to create
259 	 * (MIXED, RELATED, MIXED_RELATED, or NO)
260 	 * @throws MessagingException if multipart creation failed
261 	 * @see #MULTIPART_MODE_NO
262 	 * @see #MULTIPART_MODE_MIXED
263 	 * @see #MULTIPART_MODE_RELATED
264 	 * @see #MULTIPART_MODE_MIXED_RELATED
265 	 * @see #getDefaultEncoding(javax.mail.internet.MimeMessage)
266 	 * @see JavaMailSenderImpl#setDefaultEncoding
267 	 */
268 	public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode) throws MessagingException {
269 		this(mimeMessage, multipartMode, null);
270 	}
271 
272 	/**
273 	 * Create a new MimeMessageHelper for the given MimeMessage,
274 	 * in multipart mode (supporting alternative texts, inline
275 	 * elements and attachments) if requested.
276 	 * @param mimeMessage MimeMessage to work on
277 	 * @param multipartMode which kind of multipart message to create
278 	 * (MIXED, RELATED, MIXED_RELATED, or NO)
279 	 * @param encoding the character encoding to use for the message
280 	 * @throws MessagingException if multipart creation failed
281 	 * @see #MULTIPART_MODE_NO
282 	 * @see #MULTIPART_MODE_MIXED
283 	 * @see #MULTIPART_MODE_RELATED
284 	 * @see #MULTIPART_MODE_MIXED_RELATED
285 	 */
286 	public MimeMessageHelper(MimeMessage mimeMessage, int multipartMode, String encoding)
287 			throws MessagingException {
288 
289 		this.mimeMessage = mimeMessage;
290 		createMimeMultiparts(mimeMessage, multipartMode);
291 		this.encoding = (encoding != null ? encoding : getDefaultEncoding(mimeMessage));
292 		this.fileTypeMap = getDefaultFileTypeMap(mimeMessage);
293 	}
294 
295 
296 	/**
297 	 * Return the underlying MimeMessage object.
298 	 */
299 	public final MimeMessage getMimeMessage() {
300 		return this.mimeMessage;
301 	}
302 
303 
304 	/**
305 	 * Determine the MimeMultipart objects to use, which will be used
306 	 * to store attachments on the one hand and text(s) and inline elements
307 	 * on the other hand.
308 	 * <p>Texts and inline elements can either be stored in the root element
309 	 * itself (MULTIPART_MODE_MIXED, MULTIPART_MODE_RELATED) or in a nested element
310 	 * rather than the root element directly (MULTIPART_MODE_MIXED_RELATED).
311 	 * <p>By default, the root MimeMultipart element will be of type "mixed"
312 	 * (MULTIPART_MODE_MIXED) or "related" (MULTIPART_MODE_RELATED).
313 	 * The main multipart element will either be added as nested element of
314 	 * type "related" (MULTIPART_MODE_MIXED_RELATED) or be identical to the root
315 	 * element itself (MULTIPART_MODE_MIXED, MULTIPART_MODE_RELATED).
316 	 * @param mimeMessage the MimeMessage object to add the root MimeMultipart
317 	 * object to
318 	 * @param multipartMode the multipart mode, as passed into the constructor
319 	 * (MIXED, RELATED, MIXED_RELATED, or NO)
320 	 * @throws MessagingException if multipart creation failed
321 	 * @see #setMimeMultiparts
322 	 * @see #MULTIPART_MODE_NO
323 	 * @see #MULTIPART_MODE_MIXED
324 	 * @see #MULTIPART_MODE_RELATED
325 	 * @see #MULTIPART_MODE_MIXED_RELATED
326 	 */
327 	protected void createMimeMultiparts(MimeMessage mimeMessage, int multipartMode) throws MessagingException {
328 		switch (multipartMode) {
329 			case MULTIPART_MODE_NO:
330 				setMimeMultiparts(null, null);
331 				break;
332 			case MULTIPART_MODE_MIXED:
333 				MimeMultipart mixedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_MIXED);
334 				mimeMessage.setContent(mixedMultipart);
335 				setMimeMultiparts(mixedMultipart, mixedMultipart);
336 				break;
337 			case MULTIPART_MODE_RELATED:
338 				MimeMultipart relatedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_RELATED);
339 				mimeMessage.setContent(relatedMultipart);
340 				setMimeMultiparts(relatedMultipart, relatedMultipart);
341 				break;
342 			case MULTIPART_MODE_MIXED_RELATED:
343 				MimeMultipart rootMixedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_MIXED);
344 				mimeMessage.setContent(rootMixedMultipart);
345 				MimeMultipart nestedRelatedMultipart = new MimeMultipart(MULTIPART_SUBTYPE_RELATED);
346 				MimeBodyPart relatedBodyPart = new MimeBodyPart();
347 				relatedBodyPart.setContent(nestedRelatedMultipart);
348 				rootMixedMultipart.addBodyPart(relatedBodyPart);
349 				setMimeMultiparts(rootMixedMultipart, nestedRelatedMultipart);
350 				break;
351 			default:
352 				throw new IllegalArgumentException("Only multipart modes MIXED_RELATED, RELATED and NO supported");
353 		}
354 	}
355 
356 	/**
357 	 * Set the given MimeMultipart objects for use by this MimeMessageHelper.
358 	 * @param root the root MimeMultipart object, which attachments will be added to;
359 	 * or {@code null} to indicate no multipart at all
360 	 * @param main the main MimeMultipart object, which text(s) and inline elements
361 	 * will be added to (can be the same as the root multipart object, or an element
362 	 * nested underneath the root multipart element)
363 	 */
364 	protected final void setMimeMultiparts(MimeMultipart root, MimeMultipart main) {
365 		this.rootMimeMultipart = root;
366 		this.mimeMultipart = main;
367 	}
368 
369 	/**
370 	 * Return whether this helper is in multipart mode,
371 	 * i.e. whether it holds a multipart message.
372 	 * @see #MimeMessageHelper(MimeMessage, boolean)
373 	 */
374 	public final boolean isMultipart() {
375 		return (this.rootMimeMultipart != null);
376 	}
377 
378 	/**
379 	 * Throw an IllegalStateException if this helper is not in multipart mode.
380 	 */
381 	private void checkMultipart() throws IllegalStateException {
382 		if (!isMultipart()) {
383 			throw new IllegalStateException("Not in multipart mode - " +
384 				"create an appropriate MimeMessageHelper via a constructor that takes a 'multipart' flag " +
385 				"if you need to set alternative texts or add inline elements or attachments.");
386 		}
387 	}
388 
389 	/**
390 	 * Return the root MIME "multipart/mixed" object, if any.
391 	 * Can be used to manually add attachments.
392 	 * <p>This will be the direct content of the MimeMessage,
393 	 * in case of a multipart mail.
394 	 * @throws IllegalStateException if this helper is not in multipart mode
395 	 * @see #isMultipart
396 	 * @see #getMimeMessage
397 	 * @see javax.mail.internet.MimeMultipart#addBodyPart
398 	 */
399 	public final MimeMultipart getRootMimeMultipart() throws IllegalStateException {
400 		checkMultipart();
401 		return this.rootMimeMultipart;
402 	}
403 
404 	/**
405 	 * Return the underlying MIME "multipart/related" object, if any.
406 	 * Can be used to manually add body parts, inline elements, etc.
407 	 * <p>This will be nested within the root MimeMultipart,
408 	 * in case of a multipart mail.
409 	 * @throws IllegalStateException if this helper is not in multipart mode
410 	 * @see #isMultipart
411 	 * @see #getRootMimeMultipart
412 	 * @see javax.mail.internet.MimeMultipart#addBodyPart
413 	 */
414 	public final MimeMultipart getMimeMultipart() throws IllegalStateException {
415 		checkMultipart();
416 		return this.mimeMultipart;
417 	}
418 
419 
420 	/**
421 	 * Determine the default encoding for the given MimeMessage.
422 	 * @param mimeMessage the passed-in MimeMessage
423 	 * @return the default encoding associated with the MimeMessage,
424 	 * or {@code null} if none found
425 	 */
426 	protected String getDefaultEncoding(MimeMessage mimeMessage) {
427 		if (mimeMessage instanceof SmartMimeMessage) {
428 			return ((SmartMimeMessage) mimeMessage).getDefaultEncoding();
429 		}
430 		return null;
431 	}
432 
433 	/**
434 	 * Return the specific character encoding used for this message, if any.
435 	 */
436 	public String getEncoding() {
437 		return this.encoding;
438 	}
439 
440 	/**
441 	 * Determine the default Java Activation FileTypeMap for the given MimeMessage.
442 	 * @param mimeMessage the passed-in MimeMessage
443 	 * @return the default FileTypeMap associated with the MimeMessage,
444 	 * or a default ConfigurableMimeFileTypeMap if none found for the message
445 	 * @see ConfigurableMimeFileTypeMap
446 	 */
447 	protected FileTypeMap getDefaultFileTypeMap(MimeMessage mimeMessage) {
448 		if (mimeMessage instanceof SmartMimeMessage) {
449 			FileTypeMap fileTypeMap = ((SmartMimeMessage) mimeMessage).getDefaultFileTypeMap();
450 			if (fileTypeMap != null) {
451 				return fileTypeMap;
452 			}
453 		}
454 		ConfigurableMimeFileTypeMap fileTypeMap = new ConfigurableMimeFileTypeMap();
455 		fileTypeMap.afterPropertiesSet();
456 		return fileTypeMap;
457 	}
458 
459 	/**
460 	 * Set the Java Activation Framework {@code FileTypeMap} to use
461 	 * for determining the content type of inline content and attachments
462 	 * that get added to the message.
463 	 * <p>Default is the {@code FileTypeMap} that the underlying
464 	 * MimeMessage carries, if any, or the Activation Framework's default
465 	 * {@code FileTypeMap} instance else.
466 	 * @see #addInline
467 	 * @see #addAttachment
468 	 * @see #getDefaultFileTypeMap(javax.mail.internet.MimeMessage)
469 	 * @see JavaMailSenderImpl#setDefaultFileTypeMap
470 	 * @see javax.activation.FileTypeMap#getDefaultFileTypeMap
471 	 * @see ConfigurableMimeFileTypeMap
472 	 */
473 	public void setFileTypeMap(FileTypeMap fileTypeMap) {
474 		this.fileTypeMap = (fileTypeMap != null ? fileTypeMap : getDefaultFileTypeMap(getMimeMessage()));
475 	}
476 
477 	/**
478 	 * Return the {@code FileTypeMap} used by this MimeMessageHelper.
479 	 */
480 	public FileTypeMap getFileTypeMap() {
481 		return this.fileTypeMap;
482 	}
483 
484 
485 	/**
486 	 * Set whether to validate all addresses which get passed to this helper.
487 	 * Default is "false".
488 	 * <p>Note that this is by default just available for JavaMail >= 1.3.
489 	 * You can override the default {@code validateAddress method} for
490 	 * validation on older JavaMail versions (or for custom validation).
491 	 * @see #validateAddress
492 	 */
493 	public void setValidateAddresses(boolean validateAddresses) {
494 		this.validateAddresses = validateAddresses;
495 	}
496 
497 	/**
498 	 * Return whether this helper will validate all addresses passed to it.
499 	 */
500 	public boolean isValidateAddresses() {
501 		return this.validateAddresses;
502 	}
503 
504 	/**
505 	 * Validate the given mail address.
506 	 * Called by all of MimeMessageHelper's address setters and adders.
507 	 * <p>Default implementation invokes {@code InternetAddress.validate()},
508 	 * provided that address validation is activated for the helper instance.
509 	 * <p>Note that this method will just work on JavaMail >= 1.3. You can override
510 	 * it for validation on older JavaMail versions or for custom validation.
511 	 * @param address the address to validate
512 	 * @throws AddressException if validation failed
513 	 * @see #isValidateAddresses()
514 	 * @see javax.mail.internet.InternetAddress#validate()
515 	 */
516 	protected void validateAddress(InternetAddress address) throws AddressException {
517 		if (isValidateAddresses()) {
518 			address.validate();
519 		}
520 	}
521 
522 	/**
523 	 * Validate all given mail addresses.
524 	 * Default implementation simply delegates to validateAddress for each address.
525 	 * @param addresses the addresses to validate
526 	 * @throws AddressException if validation failed
527 	 * @see #validateAddress(InternetAddress)
528 	 */
529 	protected void validateAddresses(InternetAddress[] addresses) throws AddressException {
530 		for (InternetAddress address : addresses) {
531 			validateAddress(address);
532 		}
533 	}
534 
535 
536 	public void setFrom(InternetAddress from) throws MessagingException {
537 		Assert.notNull(from, "From address must not be null");
538 		validateAddress(from);
539 		this.mimeMessage.setFrom(from);
540 	}
541 
542 	public void setFrom(String from) throws MessagingException {
543 		Assert.notNull(from, "From address must not be null");
544 		setFrom(parseAddress(from));
545 	}
546 
547 	public void setFrom(String from, String personal) throws MessagingException, UnsupportedEncodingException {
548 		Assert.notNull(from, "From address must not be null");
549 		setFrom(getEncoding() != null ?
550 			new InternetAddress(from, personal, getEncoding()) : new InternetAddress(from, personal));
551 	}
552 
553 	public void setReplyTo(InternetAddress replyTo) throws MessagingException {
554 		Assert.notNull(replyTo, "Reply-to address must not be null");
555 		validateAddress(replyTo);
556 		this.mimeMessage.setReplyTo(new InternetAddress[] {replyTo});
557 	}
558 
559 	public void setReplyTo(String replyTo) throws MessagingException {
560 		Assert.notNull(replyTo, "Reply-to address must not be null");
561 		setReplyTo(parseAddress(replyTo));
562 	}
563 
564 	public void setReplyTo(String replyTo, String personal) throws MessagingException, UnsupportedEncodingException {
565 		Assert.notNull(replyTo, "Reply-to address must not be null");
566 		InternetAddress replyToAddress = (getEncoding() != null) ?
567 				new InternetAddress(replyTo, personal, getEncoding()) : new InternetAddress(replyTo, personal);
568 		setReplyTo(replyToAddress);
569 	}
570 
571 
572 	public void setTo(InternetAddress to) throws MessagingException {
573 		Assert.notNull(to, "To address must not be null");
574 		validateAddress(to);
575 		this.mimeMessage.setRecipient(Message.RecipientType.TO, to);
576 	}
577 
578 	public void setTo(InternetAddress[] to) throws MessagingException {
579 		Assert.notNull(to, "To address array must not be null");
580 		validateAddresses(to);
581 		this.mimeMessage.setRecipients(Message.RecipientType.TO, to);
582 	}
583 
584 	public void setTo(String to) throws MessagingException {
585 		Assert.notNull(to, "To address must not be null");
586 		setTo(parseAddress(to));
587 	}
588 
589 	public void setTo(String[] to) throws MessagingException {
590 		Assert.notNull(to, "To address array must not be null");
591 		InternetAddress[] addresses = new InternetAddress[to.length];
592 		for (int i = 0; i < to.length; i++) {
593 			addresses[i] = parseAddress(to[i]);
594 		}
595 		setTo(addresses);
596 	}
597 
598 	public void addTo(InternetAddress to) throws MessagingException {
599 		Assert.notNull(to, "To address must not be null");
600 		validateAddress(to);
601 		this.mimeMessage.addRecipient(Message.RecipientType.TO, to);
602 	}
603 
604 	public void addTo(String to) throws MessagingException {
605 		Assert.notNull(to, "To address must not be null");
606 		addTo(parseAddress(to));
607 	}
608 
609 	public void addTo(String to, String personal) throws MessagingException, UnsupportedEncodingException {
610 		Assert.notNull(to, "To address must not be null");
611 		addTo(getEncoding() != null ?
612 			new InternetAddress(to, personal, getEncoding()) :
613 			new InternetAddress(to, personal));
614 	}
615 
616 
617 	public void setCc(InternetAddress cc) throws MessagingException {
618 		Assert.notNull(cc, "Cc address must not be null");
619 		validateAddress(cc);
620 		this.mimeMessage.setRecipient(Message.RecipientType.CC, cc);
621 	}
622 
623 	public void setCc(InternetAddress[] cc) throws MessagingException {
624 		Assert.notNull(cc, "Cc address array must not be null");
625 		validateAddresses(cc);
626 		this.mimeMessage.setRecipients(Message.RecipientType.CC, cc);
627 	}
628 
629 	public void setCc(String cc) throws MessagingException {
630 		Assert.notNull(cc, "Cc address must not be null");
631 		setCc(parseAddress(cc));
632 	}
633 
634 	public void setCc(String[] cc) throws MessagingException {
635 		Assert.notNull(cc, "Cc address array must not be null");
636 		InternetAddress[] addresses = new InternetAddress[cc.length];
637 		for (int i = 0; i < cc.length; i++) {
638 			addresses[i] = parseAddress(cc[i]);
639 		}
640 		setCc(addresses);
641 	}
642 
643 	public void addCc(InternetAddress cc) throws MessagingException {
644 		Assert.notNull(cc, "Cc address must not be null");
645 		validateAddress(cc);
646 		this.mimeMessage.addRecipient(Message.RecipientType.CC, cc);
647 	}
648 
649 	public void addCc(String cc) throws MessagingException {
650 		Assert.notNull(cc, "Cc address must not be null");
651 		addCc(parseAddress(cc));
652 	}
653 
654 	public void addCc(String cc, String personal) throws MessagingException, UnsupportedEncodingException {
655 		Assert.notNull(cc, "Cc address must not be null");
656 		addCc(getEncoding() != null ?
657 			new InternetAddress(cc, personal, getEncoding()) :
658 			new InternetAddress(cc, personal));
659 	}
660 
661 
662 	public void setBcc(InternetAddress bcc) throws MessagingException {
663 		Assert.notNull(bcc, "Bcc address must not be null");
664 		validateAddress(bcc);
665 		this.mimeMessage.setRecipient(Message.RecipientType.BCC, bcc);
666 	}
667 
668 	public void setBcc(InternetAddress[] bcc) throws MessagingException {
669 		Assert.notNull(bcc, "Bcc address array must not be null");
670 		validateAddresses(bcc);
671 		this.mimeMessage.setRecipients(Message.RecipientType.BCC, bcc);
672 	}
673 
674 	public void setBcc(String bcc) throws MessagingException {
675 		Assert.notNull(bcc, "Bcc address must not be null");
676 		setBcc(parseAddress(bcc));
677 	}
678 
679 	public void setBcc(String[] bcc) throws MessagingException {
680 		Assert.notNull(bcc, "Bcc address array must not be null");
681 		InternetAddress[] addresses = new InternetAddress[bcc.length];
682 		for (int i = 0; i < bcc.length; i++) {
683 			addresses[i] = parseAddress(bcc[i]);
684 		}
685 		setBcc(addresses);
686 	}
687 
688 	public void addBcc(InternetAddress bcc) throws MessagingException {
689 		Assert.notNull(bcc, "Bcc address must not be null");
690 		validateAddress(bcc);
691 		this.mimeMessage.addRecipient(Message.RecipientType.BCC, bcc);
692 	}
693 
694 	public void addBcc(String bcc) throws MessagingException {
695 		Assert.notNull(bcc, "Bcc address must not be null");
696 		addBcc(parseAddress(bcc));
697 	}
698 
699 	public void addBcc(String bcc, String personal) throws MessagingException, UnsupportedEncodingException {
700 		Assert.notNull(bcc, "Bcc address must not be null");
701 		addBcc(getEncoding() != null ?
702 			new InternetAddress(bcc, personal, getEncoding()) :
703 			new InternetAddress(bcc, personal));
704 	}
705 
706 	private InternetAddress parseAddress(String address) throws MessagingException {
707 		InternetAddress[] parsed = InternetAddress.parse(address);
708 		if (parsed.length != 1) {
709 			throw new AddressException("Illegal address", address);
710 		}
711 		InternetAddress raw = parsed[0];
712 		try {
713 			return (getEncoding() != null ?
714 					new InternetAddress(raw.getAddress(), raw.getPersonal(), getEncoding()) : raw);
715 		}
716 		catch (UnsupportedEncodingException ex) {
717 			throw new MessagingException("Failed to parse embedded personal name to correct encoding", ex);
718 		}
719 	}
720 
721 
722 	/**
723 	 * Set the priority ("X-Priority" header) of the message.
724 	 * @param priority the priority value;
725 	 * typically between 1 (highest) and 5 (lowest)
726 	 * @throws MessagingException in case of errors
727 	 */
728 	public void setPriority(int priority) throws MessagingException {
729 		this.mimeMessage.setHeader(HEADER_PRIORITY, Integer.toString(priority));
730 	}
731 
732 	/**
733 	 * Set the sent-date of the message.
734 	 * @param sentDate the date to set (never {@code null})
735 	 * @throws MessagingException in case of errors
736 	 */
737 	public void setSentDate(Date sentDate) throws MessagingException {
738 		Assert.notNull(sentDate, "Sent date must not be null");
739 		this.mimeMessage.setSentDate(sentDate);
740 	}
741 
742 	/**
743 	 * Set the subject of the message, using the correct encoding.
744 	 * @param subject the subject text
745 	 * @throws MessagingException in case of errors
746 	 */
747 	public void setSubject(String subject) throws MessagingException {
748 		Assert.notNull(subject, "Subject must not be null");
749 		if (getEncoding() != null) {
750 			this.mimeMessage.setSubject(subject, getEncoding());
751 		}
752 		else {
753 			this.mimeMessage.setSubject(subject);
754 		}
755 	}
756 
757 
758 	/**
759 	 * Set the given text directly as content in non-multipart mode
760 	 * or as default body part in multipart mode.
761 	 * Always applies the default content type "text/plain".
762 	 * <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
763 	 * else, mail readers might not be able to resolve inline references correctly.
764 	 * @param text the text for the message
765 	 * @throws MessagingException in case of errors
766 	 */
767 	public void setText(String text) throws MessagingException {
768 		setText(text, false);
769 	}
770 
771 	/**
772 	 * Set the given text directly as content in non-multipart mode
773 	 * or as default body part in multipart mode.
774 	 * The "html" flag determines the content type to apply.
775 	 * <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
776 	 * else, mail readers might not be able to resolve inline references correctly.
777 	 * @param text the text for the message
778 	 * @param html whether to apply content type "text/html" for an
779 	 * HTML mail, using default content type ("text/plain") else
780 	 * @throws MessagingException in case of errors
781 	 */
782 	public void setText(String text, boolean html) throws MessagingException {
783 		Assert.notNull(text, "Text must not be null");
784 		MimePart partToUse;
785 		if (isMultipart()) {
786 			partToUse = getMainPart();
787 		}
788 		else {
789 			partToUse = this.mimeMessage;
790 		}
791 		if (html) {
792 			setHtmlTextToMimePart(partToUse, text);
793 		}
794 		else {
795 			setPlainTextToMimePart(partToUse, text);
796 		}
797 	}
798 
799 	/**
800 	 * Set the given plain text and HTML text as alternatives, offering
801 	 * both options to the email client. Requires multipart mode.
802 	 * <p><b>NOTE:</b> Invoke {@link #addInline} <i>after</i> {@code setText};
803 	 * else, mail readers might not be able to resolve inline references correctly.
804 	 * @param plainText the plain text for the message
805 	 * @param htmlText the HTML text for the message
806 	 * @throws MessagingException in case of errors
807 	 */
808 	public void setText(String plainText, String htmlText) throws MessagingException {
809 		Assert.notNull(plainText, "Plain text must not be null");
810 		Assert.notNull(htmlText, "HTML text must not be null");
811 
812 		MimeMultipart messageBody = new MimeMultipart(MULTIPART_SUBTYPE_ALTERNATIVE);
813 		getMainPart().setContent(messageBody, CONTENT_TYPE_ALTERNATIVE);
814 
815 		// Create the plain text part of the message.
816 		MimeBodyPart plainTextPart = new MimeBodyPart();
817 		setPlainTextToMimePart(plainTextPart, plainText);
818 		messageBody.addBodyPart(plainTextPart);
819 
820 		// Create the HTML text part of the message.
821 		MimeBodyPart htmlTextPart = new MimeBodyPart();
822 		setHtmlTextToMimePart(htmlTextPart, htmlText);
823 		messageBody.addBodyPart(htmlTextPart);
824 	}
825 
826 	private MimeBodyPart getMainPart() throws MessagingException {
827 		MimeMultipart mimeMultipart = getMimeMultipart();
828 		MimeBodyPart bodyPart = null;
829 		for (int i = 0; i < mimeMultipart.getCount(); i++) {
830 			BodyPart bp = mimeMultipart.getBodyPart(i);
831 			if (bp.getFileName() == null) {
832 				bodyPart = (MimeBodyPart) bp;
833 			}
834 		}
835 		if (bodyPart == null) {
836 			MimeBodyPart mimeBodyPart = new MimeBodyPart();
837 			mimeMultipart.addBodyPart(mimeBodyPart);
838 			bodyPart = mimeBodyPart;
839 		}
840 		return bodyPart;
841 	}
842 
843 	private void setPlainTextToMimePart(MimePart mimePart, String text) throws MessagingException {
844 		if (getEncoding() != null) {
845 			mimePart.setText(text, getEncoding());
846 		}
847 		else {
848 			mimePart.setText(text);
849 		}
850 	}
851 
852 	private void setHtmlTextToMimePart(MimePart mimePart, String text) throws MessagingException {
853 		if (getEncoding() != null) {
854 			mimePart.setContent(text, CONTENT_TYPE_HTML + CONTENT_TYPE_CHARSET_SUFFIX + getEncoding());
855 		}
856 		else {
857 			mimePart.setContent(text, CONTENT_TYPE_HTML);
858 		}
859 	}
860 
861 
862 	/**
863 	 * Add an inline element to the MimeMessage, taking the content from a
864 	 * {@code javax.activation.DataSource}.
865 	 * <p>Note that the InputStream returned by the DataSource implementation
866 	 * needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
867 	 * {@code getInputStream()} multiple times.
868 	 * <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
869 	 * else, mail readers might not be able to resolve inline references correctly.
870 	 * @param contentId the content ID to use. Will end up as "Content-ID" header
871 	 * in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
872 	 * Can be referenced in HTML source via src="cid:myId" expressions.
873 	 * @param dataSource the {@code javax.activation.DataSource} to take
874 	 * the content from, determining the InputStream and the content type
875 	 * @throws MessagingException in case of errors
876 	 * @see #addInline(String, java.io.File)
877 	 * @see #addInline(String, org.springframework.core.io.Resource)
878 	 */
879 	public void addInline(String contentId, DataSource dataSource) throws MessagingException {
880 		Assert.notNull(contentId, "Content ID must not be null");
881 		Assert.notNull(dataSource, "DataSource must not be null");
882 		MimeBodyPart mimeBodyPart = new MimeBodyPart();
883 		mimeBodyPart.setDisposition(MimeBodyPart.INLINE);
884 		// We're using setHeader here to remain compatible with JavaMail 1.2,
885 		// rather than JavaMail 1.3's setContentID.
886 		mimeBodyPart.setHeader(HEADER_CONTENT_ID, "<" + contentId + ">");
887 		mimeBodyPart.setDataHandler(new DataHandler(dataSource));
888 		getMimeMultipart().addBodyPart(mimeBodyPart);
889 	}
890 
891 	/**
892 	 * Add an inline element to the MimeMessage, taking the content from a
893 	 * {@code java.io.File}.
894 	 * <p>The content type will be determined by the name of the given
895 	 * content file. Do not use this for temporary files with arbitrary
896 	 * filenames (possibly ending in ".tmp" or the like)!
897 	 * <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
898 	 * else, mail readers might not be able to resolve inline references correctly.
899 	 * @param contentId the content ID to use. Will end up as "Content-ID" header
900 	 * in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
901 	 * Can be referenced in HTML source via src="cid:myId" expressions.
902 	 * @param file the File resource to take the content from
903 	 * @throws MessagingException in case of errors
904 	 * @see #setText
905 	 * @see #addInline(String, org.springframework.core.io.Resource)
906 	 * @see #addInline(String, javax.activation.DataSource)
907 	 */
908 	public void addInline(String contentId, File file) throws MessagingException {
909 		Assert.notNull(file, "File must not be null");
910 		FileDataSource dataSource = new FileDataSource(file);
911 		dataSource.setFileTypeMap(getFileTypeMap());
912 		addInline(contentId, dataSource);
913 	}
914 
915 	/**
916 	 * Add an inline element to the MimeMessage, taking the content from a
917 	 * {@code org.springframework.core.io.Resource}.
918 	 * <p>The content type will be determined by the name of the given
919 	 * content file. Do not use this for temporary files with arbitrary
920 	 * filenames (possibly ending in ".tmp" or the like)!
921 	 * <p>Note that the InputStream returned by the Resource implementation
922 	 * needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
923 	 * {@code getInputStream()} multiple times.
924 	 * <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@link #setText};
925 	 * else, mail readers might not be able to resolve inline references correctly.
926 	 * @param contentId the content ID to use. Will end up as "Content-ID" header
927 	 * in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
928 	 * Can be referenced in HTML source via src="cid:myId" expressions.
929 	 * @param resource the resource to take the content from
930 	 * @throws MessagingException in case of errors
931 	 * @see #setText
932 	 * @see #addInline(String, java.io.File)
933 	 * @see #addInline(String, javax.activation.DataSource)
934 	 */
935 	public void addInline(String contentId, Resource resource) throws MessagingException {
936 		Assert.notNull(resource, "Resource must not be null");
937 		String contentType = getFileTypeMap().getContentType(resource.getFilename());
938 		addInline(contentId, resource, contentType);
939 	}
940 
941 	/**
942 	 * Add an inline element to the MimeMessage, taking the content from an
943 	 * {@code org.springframework.core.InputStreamResource}, and
944 	 * specifying the content type explicitly.
945 	 * <p>You can determine the content type for any given filename via a Java
946 	 * Activation Framework's FileTypeMap, for example the one held by this helper.
947 	 * <p>Note that the InputStream returned by the InputStreamSource implementation
948 	 * needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
949 	 * {@code getInputStream()} multiple times.
950 	 * <p><b>NOTE:</b> Invoke {@code addInline} <i>after</i> {@code setText};
951 	 * else, mail readers might not be able to resolve inline references correctly.
952 	 * @param contentId the content ID to use. Will end up as "Content-ID" header
953 	 * in the body part, surrounded by angle brackets: e.g. "myId" -> "&lt;myId&gt;".
954 	 * Can be referenced in HTML source via src="cid:myId" expressions.
955 	 * @param inputStreamSource the resource to take the content from
956 	 * @param contentType the content type to use for the element
957 	 * @throws MessagingException in case of errors
958 	 * @see #setText
959 	 * @see #getFileTypeMap
960 	 * @see #addInline(String, org.springframework.core.io.Resource)
961 	 * @see #addInline(String, javax.activation.DataSource)
962 	 */
963 	public void addInline(String contentId, InputStreamSource inputStreamSource, String contentType)
964 			throws MessagingException {
965 
966 		Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
967 		if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) {
968 			throw new IllegalArgumentException(
969 					"Passed-in Resource contains an open stream: invalid argument. " +
970 					"JavaMail requires an InputStreamSource that creates a fresh stream for every call.");
971 		}
972 		DataSource dataSource = createDataSource(inputStreamSource, contentType, "inline");
973 		addInline(contentId, dataSource);
974 	}
975 
976 	/**
977 	 * Add an attachment to the MimeMessage, taking the content from a
978 	 * {@code javax.activation.DataSource}.
979 	 * <p>Note that the InputStream returned by the DataSource implementation
980 	 * needs to be a <i>fresh one on each call</i>, as JavaMail will invoke
981 	 * {@code getInputStream()} multiple times.
982 	 * @param attachmentFilename the name of the attachment as it will
983 	 * appear in the mail (the content type will be determined by this)
984 	 * @param dataSource the {@code javax.activation.DataSource} to take
985 	 * the content from, determining the InputStream and the content type
986 	 * @throws MessagingException in case of errors
987 	 * @see #addAttachment(String, org.springframework.core.io.InputStreamSource)
988 	 * @see #addAttachment(String, java.io.File)
989 	 */
990 	public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException {
991 		Assert.notNull(attachmentFilename, "Attachment filename must not be null");
992 		Assert.notNull(dataSource, "DataSource must not be null");
993 		try {
994 			MimeBodyPart mimeBodyPart = new MimeBodyPart();
995 			mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT);
996 			mimeBodyPart.setFileName(MimeUtility.encodeText(attachmentFilename));
997 			mimeBodyPart.setDataHandler(new DataHandler(dataSource));
998 			getRootMimeMultipart().addBodyPart(mimeBodyPart);
999 		}
1000 		catch (UnsupportedEncodingException ex) {
1001 			throw new MessagingException("Failed to encode attachment filename", ex);
1002 		}
1003 	}
1004 
1005 	/**
1006 	 * Add an attachment to the MimeMessage, taking the content from a
1007 	 * {@code java.io.File}.
1008 	 * <p>The content type will be determined by the name of the given
1009 	 * content file. Do not use this for temporary files with arbitrary
1010 	 * filenames (possibly ending in ".tmp" or the like)!
1011 	 * @param attachmentFilename the name of the attachment as it will
1012 	 * appear in the mail
1013 	 * @param file the File resource to take the content from
1014 	 * @throws MessagingException in case of errors
1015 	 * @see #addAttachment(String, org.springframework.core.io.InputStreamSource)
1016 	 * @see #addAttachment(String, javax.activation.DataSource)
1017 	 */
1018 	public void addAttachment(String attachmentFilename, File file) throws MessagingException {
1019 		Assert.notNull(file, "File must not be null");
1020 		FileDataSource dataSource = new FileDataSource(file);
1021 		dataSource.setFileTypeMap(getFileTypeMap());
1022 		addAttachment(attachmentFilename, dataSource);
1023 	}
1024 
1025 	/**
1026 	 * Add an attachment to the MimeMessage, taking the content from an
1027 	 * {@code org.springframework.core.io.InputStreamResource}.
1028 	 * <p>The content type will be determined by the given filename for
1029 	 * the attachment. Thus, any content source will be fine, including
1030 	 * temporary files with arbitrary filenames.
1031 	 * <p>Note that the InputStream returned by the InputStreamSource
1032 	 * implementation needs to be a <i>fresh one on each call</i>, as
1033 	 * JavaMail will invoke {@code getInputStream()} multiple times.
1034 	 * @param attachmentFilename the name of the attachment as it will
1035 	 * appear in the mail
1036 	 * @param inputStreamSource the resource to take the content from
1037 	 * (all of Spring's Resource implementations can be passed in here)
1038 	 * @throws MessagingException in case of errors
1039 	 * @see #addAttachment(String, java.io.File)
1040 	 * @see #addAttachment(String, javax.activation.DataSource)
1041 	 * @see org.springframework.core.io.Resource
1042 	 */
1043 	public void addAttachment(String attachmentFilename, InputStreamSource inputStreamSource)
1044 			throws MessagingException {
1045 
1046 		String contentType = getFileTypeMap().getContentType(attachmentFilename);
1047 		addAttachment(attachmentFilename, inputStreamSource, contentType);
1048 	}
1049 
1050 	/**
1051 	 * Add an attachment to the MimeMessage, taking the content from an
1052 	 * {@code org.springframework.core.io.InputStreamResource}.
1053 	 * <p>Note that the InputStream returned by the InputStreamSource
1054 	 * implementation needs to be a <i>fresh one on each call</i>, as
1055 	 * JavaMail will invoke {@code getInputStream()} multiple times.
1056 	 * @param attachmentFilename the name of the attachment as it will
1057 	 * appear in the mail
1058 	 * @param inputStreamSource the resource to take the content from
1059 	 * (all of Spring's Resource implementations can be passed in here)
1060 	 * @param contentType the content type to use for the element
1061 	 * @throws MessagingException in case of errors
1062 	 * @see #addAttachment(String, java.io.File)
1063 	 * @see #addAttachment(String, javax.activation.DataSource)
1064 	 * @see org.springframework.core.io.Resource
1065 	 */
1066 	public void addAttachment(
1067 			String attachmentFilename, InputStreamSource inputStreamSource, String contentType)
1068 			throws MessagingException {
1069 
1070 		Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
1071 		if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) {
1072 			throw new IllegalArgumentException(
1073 					"Passed-in Resource contains an open stream: invalid argument. " +
1074 					"JavaMail requires an InputStreamSource that creates a fresh stream for every call.");
1075 		}
1076 		DataSource dataSource = createDataSource(inputStreamSource, contentType, attachmentFilename);
1077 		addAttachment(attachmentFilename, dataSource);
1078 	}
1079 
1080 	/**
1081 	 * Create an Activation Framework DataSource for the given InputStreamSource.
1082 	 * @param inputStreamSource the InputStreamSource (typically a Spring Resource)
1083 	 * @param contentType the content type
1084 	 * @param name the name of the DataSource
1085 	 * @return the Activation Framework DataSource
1086 	 */
1087 	protected DataSource createDataSource(
1088 		final InputStreamSource inputStreamSource, final String contentType, final String name) {
1089 
1090 		return new DataSource() {
1091 			@Override
1092 			public InputStream getInputStream() throws IOException {
1093 				return inputStreamSource.getInputStream();
1094 			}
1095 			@Override
1096 			public OutputStream getOutputStream() {
1097 				throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
1098 			}
1099 			@Override
1100 			public String getContentType() {
1101 				return contentType;
1102 			}
1103 			@Override
1104 			public String getName() {
1105 				return name;
1106 			}
1107 		};
1108 	}
1109 
1110 }