View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.mail;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.UnsupportedEncodingException;
23  import java.net.URL;
24  
25  import javax.activation.DataHandler;
26  import javax.activation.DataSource;
27  import javax.activation.FileDataSource;
28  import javax.activation.URLDataSource;
29  import javax.mail.BodyPart;
30  import javax.mail.MessagingException;
31  import javax.mail.internet.MimeBodyPart;
32  import javax.mail.internet.MimeMultipart;
33  import javax.mail.internet.MimePart;
34  import javax.mail.internet.MimeUtility;
35  
36  /**
37   * A multipart email.
38   *
39   * <p>This class is used to send multi-part internet email like
40   * messages with attachments.
41   *
42   * <p>To create a multi-part email, call the default constructor and
43   * then you can call setMsg() to set the message and call the
44   * different attach() methods.
45   *
46   * @since 1.0
47   */
48  public class MultiPartEmail extends Email
49  {
50      /** Body portion of the email. */
51      private MimeMultipart container;
52  
53      /** The message container. */
54      private BodyPart primaryBodyPart;
55  
56      /** The MIME subtype. */
57      private String subType;
58  
59      /** Indicates if the message has been initialized. */
60      private boolean initialized;
61  
62      /** Indicates if attachments have been added to the message. */
63      private boolean boolHasAttachments;
64  
65      /**
66       * Set the MIME subtype of the email.
67       *
68       * @param aSubType MIME subtype of the email
69       * @since 1.0
70       */
71      public void setSubType(final String aSubType)
72      {
73          this.subType = aSubType;
74      }
75  
76      /**
77       * Get the MIME subtype of the email.
78       *
79       * @return MIME subtype of the email
80       * @since 1.0
81       */
82      public String getSubType()
83      {
84          return subType;
85      }
86  
87      /**
88       * Add a new part to the email.
89       *
90       * @param partContent The content.
91       * @param partContentType The content type.
92       * @return An Email.
93       * @throws EmailException see javax.mail.internet.MimeBodyPart
94       *  for definitions
95       * @since 1.0
96       */
97      public Email addPart(final String partContent, final String partContentType)
98          throws EmailException
99      {
100             final BodyPart bodyPart = createBodyPart();
101         try
102         {
103             bodyPart.setContent(partContent, partContentType);
104             getContainer().addBodyPart(bodyPart);
105         }
106         catch (final MessagingException me)
107         {
108             throw new EmailException(me);
109         }
110 
111         return this;
112     }
113 
114     /**
115      * Add a new part to the email.
116      *
117      * @param multipart The MimeMultipart.
118      * @return An Email.
119      * @throws EmailException see javax.mail.internet.MimeBodyPart
120      *  for definitions
121      *  @since 1.0
122      */
123     public Email addPart(final MimeMultipart multipart) throws EmailException
124     {
125         try
126         {
127             return addPart(multipart, getContainer().getCount());
128         }
129         catch (final MessagingException me)
130         {
131             throw new EmailException(me);
132         }
133     }
134 
135     /**
136      * Add a new part to the email.
137      *
138      * @param multipart The part to add.
139      * @param index The index to add at.
140      * @return The email.
141      * @throws EmailException An error occurred while adding the part.
142      * @since 1.0
143      */
144     public Email addPart(final MimeMultipart multipart, final int index) throws EmailException
145     {
146         final BodyPart bodyPart = createBodyPart();
147         try
148         {
149             bodyPart.setContent(multipart);
150             getContainer().addBodyPart(bodyPart, index);
151         }
152         catch (final MessagingException me)
153         {
154             throw new EmailException(me);
155         }
156 
157         return this;
158     }
159 
160     /**
161      * Initialize the multipart email.
162      * @since 1.0
163      */
164     protected void init()
165     {
166         if (initialized)
167         {
168             throw new IllegalStateException("Already initialized");
169         }
170 
171         container = createMimeMultipart();
172         super.setContent(container);
173 
174         initialized = true;
175     }
176 
177     /**
178      * Set the message of the email.
179      *
180      * @param msg A String.
181      * @return An Email.
182      * @throws EmailException see javax.mail.internet.MimeBodyPart
183      *  for definitions
184      * @since 1.0
185      */
186     @Override
187     public Email setMsg(final String msg) throws EmailException
188     {
189         // throw exception on null message
190         if (EmailUtils.isEmpty(msg))
191         {
192             throw new EmailException("Invalid message supplied");
193         }
194         try
195         {
196             final BodyPart primary = getPrimaryBodyPart();
197 
198             if (primary instanceof MimePart && EmailUtils.isNotEmpty(charset))
199             {
200                 ((MimePart) primary).setText(msg, charset);
201             }
202             else
203             {
204                 primary.setText(msg);
205             }
206         }
207         catch (final MessagingException me)
208         {
209             throw new EmailException(me);
210         }
211         return this;
212     }
213 
214     /**
215      * Does the work of actually building the MimeMessage. Please note that
216      * a user rarely calls this method directly and only if he/she is
217      * interested in the sending the underlying MimeMessage without
218      * commons-email.
219      *
220      * @throws EmailException if there was an error.
221      * @since 1.0
222      */
223     @Override
224     public void buildMimeMessage() throws EmailException
225     {
226         try
227         {
228             if (primaryBodyPart != null)
229             {
230                 // before a multipart message can be sent, we must make sure that
231                 // the content for the main body part was actually set.  If not,
232                 // an IOException will be thrown during super.send().
233 
234                 final BodyPart body = this.getPrimaryBodyPart();
235                 try
236                 {
237                     body.getContent();
238                 }
239                 catch (final IOException e) // NOPMD
240                 {
241                     // do nothing here.
242                     // content will be set to an empty string as a result.
243                     // (Should this really be rethrown as an email exception?)
244                     // throw new EmailException(e);
245                 }
246             }
247 
248             if (subType != null)
249             {
250                 getContainer().setSubType(subType);
251             }
252 
253             super.buildMimeMessage();
254         }
255         catch (final MessagingException me)
256         {
257             throw new EmailException(me);
258         }
259     }
260 
261     /**
262      * Attach a file.
263      *
264      * @param file A file attachment
265      * @return A MultiPartEmail.
266      * @throws EmailException see javax.mail.internet.MimeBodyPart
267      *  for definitions
268      * @since 1.3
269      */
270     public MultiPartEmail attach(final File file)
271         throws EmailException
272     {
273         final String fileName = file.getAbsolutePath();
274 
275         try
276         {
277             if (!file.exists())
278             {
279                 throw new IOException("\"" + fileName + "\" does not exist");
280             }
281 
282             final FileDataSource fds = new FileDataSource(file);
283 
284             return attach(fds, file.getName(), null, EmailAttachment.ATTACHMENT);
285         }
286         catch (final IOException e)
287         {
288             throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
289         }
290     }
291 
292     /**
293      * Attach an EmailAttachment.
294      *
295      * @param attachment An EmailAttachment.
296      * @return A MultiPartEmail.
297      * @throws EmailException see javax.mail.internet.MimeBodyPart
298      *  for definitions
299      * @since 1.0
300      */
301     public MultiPartEmail attach(final EmailAttachment attachment)
302         throws EmailException
303     {
304         MultiPartEmail result = null;
305 
306         if (attachment == null)
307         {
308             throw new EmailException("Invalid attachment supplied");
309         }
310 
311         final URL url = attachment.getURL();
312 
313         if (url == null)
314         {
315             String fileName = null;
316             try
317             {
318                 fileName = attachment.getPath();
319                 final File file = new File(fileName);
320                 if (!file.exists())
321                 {
322                     throw new IOException("\"" + fileName + "\" does not exist");
323                 }
324                 result =
325                     attach(
326                         new FileDataSource(file),
327                         attachment.getName(),
328                         attachment.getDescription(),
329                         attachment.getDisposition());
330             }
331             catch (final IOException e)
332             {
333                 throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
334             }
335         }
336         else
337         {
338             result =
339                 attach(
340                     url,
341                     attachment.getName(),
342                     attachment.getDescription(),
343                     attachment.getDisposition());
344         }
345 
346         return result;
347     }
348 
349     /**
350      * Attach a file located by its URL.  The disposition of the file
351      * is set to mixed.
352      *
353      * @param url The URL of the file (may be any valid URL).
354      * @param name The name field for the attachment.
355      * @param description A description for the attachment.
356      * @return A MultiPartEmail.
357      * @throws EmailException see javax.mail.internet.MimeBodyPart
358      *  for definitions
359      * @since 1.0
360      */
361     public MultiPartEmail attach(final URL url, final String name, final String description)
362         throws EmailException
363     {
364         return attach(url, name, description, EmailAttachment.ATTACHMENT);
365     }
366 
367     /**
368      * Attach a file located by its URL.
369      *
370      * @param url The URL of the file (may be any valid URL).
371      * @param name The name field for the attachment.
372      * @param description A description for the attachment.
373      * @param disposition Either mixed or inline.
374      * @return A MultiPartEmail.
375      * @throws EmailException see javax.mail.internet.MimeBodyPart
376      *  for definitions
377      * @since 1.0
378      */
379     public MultiPartEmail attach(
380         final URL url,
381         final String name,
382         final String description,
383         final String disposition)
384         throws EmailException
385     {
386         // verify that the URL is valid
387        try
388        {
389            final InputStream is = url.openStream();
390            is.close();
391        }
392        catch (final IOException e)
393        {
394            throw new EmailException("Invalid URL set:" + url, e);
395        }
396 
397        return attach(new URLDataSource(url), name, description, disposition);
398     }
399 
400     /**
401      * Attach a file specified as a DataSource interface.
402      *
403      * @param ds A DataSource interface for the file.
404      * @param name The name field for the attachment.
405      * @param description A description for the attachment.
406      * @return A MultiPartEmail.
407      * @throws EmailException see javax.mail.internet.MimeBodyPart
408      *  for definitions
409      * @since 1.0
410      */
411     public MultiPartEmail attach(
412         final DataSource ds,
413         final String name,
414         final String description)
415         throws EmailException
416     {
417         // verify that the DataSource is valid
418         try
419         {
420             final InputStream is = ds != null ? ds.getInputStream() : null;
421             if (is != null)
422             {
423                 // close the input stream to prevent file locking on windows
424                 is.close();
425             }
426 
427             if (is == null)
428             {
429                 throw new EmailException("Invalid Datasource");
430             }
431         }
432         catch (final IOException e)
433         {
434             throw new EmailException("Invalid Datasource", e);
435         }
436 
437         return attach(ds, name, description, EmailAttachment.ATTACHMENT);
438     }
439 
440     /**
441      * Attach a file specified as a DataSource interface.
442      *
443      * @param ds A DataSource interface for the file.
444      * @param name The name field for the attachment.
445      * @param description A description for the attachment.
446      * @param disposition Either mixed or inline.
447      * @return A MultiPartEmail.
448      * @throws EmailException see javax.mail.internet.MimeBodyPart
449      *  for definitions
450      * @since 1.0
451      */
452     public MultiPartEmail attach(
453         final DataSource ds,
454         String name,
455         final String description,
456         final String disposition)
457         throws EmailException
458     {
459         if (EmailUtils.isEmpty(name))
460         {
461             name = ds.getName();
462         }
463         final BodyPart bodyPart = createBodyPart();
464         try
465         {
466             bodyPart.setDisposition(disposition);
467             bodyPart.setFileName(MimeUtility.encodeText(name));
468             bodyPart.setDescription(description);
469             bodyPart.setDataHandler(new DataHandler(ds));
470 
471             getContainer().addBodyPart(bodyPart);
472         }
473         catch (final UnsupportedEncodingException uee)
474         {
475             // in case the filename could not be encoded
476             throw new EmailException(uee);
477         }
478         catch (final MessagingException me)
479         {
480             throw new EmailException(me);
481         }
482         setBoolHasAttachments(true);
483 
484         return this;
485     }
486 
487     /**
488      * Gets first body part of the message.
489      *
490      * @return The primary body part.
491      * @throws MessagingException An error occurred while getting the primary body part.
492      * @since 1.0
493      */
494     protected BodyPart getPrimaryBodyPart() throws MessagingException
495     {
496         if (!initialized)
497         {
498             init();
499         }
500 
501         // Add the first body part to the message.  The fist body part must be
502         if (this.primaryBodyPart == null)
503         {
504             primaryBodyPart = createBodyPart();
505             getContainer().addBodyPart(primaryBodyPart, 0);
506         }
507 
508         return primaryBodyPart;
509     }
510 
511     /**
512      * Gets the message container.
513      *
514      * @return The message container.
515      * @since 1.0
516      */
517     protected MimeMultipart getContainer()
518     {
519         if (!initialized)
520         {
521             init();
522         }
523         return container;
524     }
525 
526     /**
527      * Creates a body part object.
528      * Can be overridden if you don't want to create a BodyPart.
529      *
530      * @return the created body part
531      */
532     protected BodyPart createBodyPart()
533     {
534         return new MimeBodyPart();
535     }
536 
537     /**
538      * Creates a mime multipart object.
539      *
540      * @return the created mime part
541      */
542     protected MimeMultipart createMimeMultipart()
543     {
544         return new MimeMultipart();
545     }
546 
547     /**
548      * Checks whether there are attachments.
549      *
550      * @return true if there are attachments
551      * @since 1.0
552      */
553     public boolean isBoolHasAttachments()
554     {
555         return boolHasAttachments;
556     }
557 
558     /**
559      * Sets whether there are attachments.
560      *
561      * @param b  the attachments flag
562      * @since 1.0
563      */
564     public void setBoolHasAttachments(final boolean b)
565     {
566         boolHasAttachments = b;
567     }
568 
569     /**
570      * Checks if this object is initialized.
571      *
572      * @return true if initialized
573      */
574     protected boolean isInitialized()
575     {
576         return initialized;
577     }
578 
579     /**
580      * Sets the initialized status of this object.
581      *
582      * @param b  the initialized status flag
583      */
584     protected void setInitialized(final boolean b)
585     {
586         initialized = b;
587     }
588 
589 }