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