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.mail2.jakarta;
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  import java.nio.file.Files;
25  import java.nio.file.OpenOption;
26  import java.nio.file.Path;
27  import java.util.Objects;
28  
29  import org.apache.commons.mail2.core.EmailException;
30  import org.apache.commons.mail2.core.EmailUtils;
31  import org.apache.commons.mail2.jakarta.activation.PathDataSource;
32  
33  import jakarta.activation.DataHandler;
34  import jakarta.activation.DataSource;
35  import jakarta.activation.FileDataSource;
36  import jakarta.activation.FileTypeMap;
37  import jakarta.activation.URLDataSource;
38  import jakarta.mail.BodyPart;
39  import jakarta.mail.MessagingException;
40  import jakarta.mail.internet.MimeBodyPart;
41  import jakarta.mail.internet.MimeMultipart;
42  import jakarta.mail.internet.MimePart;
43  import jakarta.mail.internet.MimeUtility;
44  
45  /**
46   * A multipart email.
47   * <p>
48   * This class is used to send multi-part internet email like messages with attachments.
49   * </p>
50   * <p>
51   * To create a multi-part email, call the default constructor and then you can call setMsg() to set the message and call the different attach() methods.
52   * </p>
53   *
54   * @since 1.0
55   */
56  public class MultiPartEmail extends Email {
57  
58      /** Body portion of the email. */
59      private MimeMultipart container;
60  
61      /** The message container. */
62      private BodyPart primaryBodyPart;
63  
64      /** The MIME subtype. */
65      private String subType;
66  
67      /** Indicates if the message has been initialized. */
68      private boolean initialized;
69  
70      /** Indicates if attachments have been added to the message. */
71      private boolean hasAttachments;
72  
73      /**
74       * Constructs a new instance.
75       */
76      public MultiPartEmail() {
77          // empty
78      }
79  
80      /**
81       * Adds a new part to the email.
82       *
83       * @param multipart The MimeMultipart.
84       * @return An Email.
85       * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
86       * @since 1.0
87       */
88      public Email addPart(final MimeMultipart multipart) throws EmailException {
89          try {
90              return addPart(multipart, getContainer().getCount());
91          } catch (final MessagingException e) {
92              throw new EmailException(e);
93          }
94      }
95  
96      /**
97       * Adds a new part to the email.
98       *
99       * @param multipart The part to add.
100      * @param index     The index to add at.
101      * @return The email.
102      * @throws EmailException An error occurred while adding the part.
103      * @since 1.0
104      */
105     public Email addPart(final MimeMultipart multipart, final int index) throws EmailException {
106         final BodyPart bodyPart = createBodyPart();
107         try {
108             bodyPart.setContent(multipart);
109             getContainer().addBodyPart(bodyPart, index);
110         } catch (final MessagingException e) {
111             throw new EmailException(e);
112         }
113 
114         return this;
115     }
116 
117     /**
118      * Adds a new part to the email.
119      *
120      * @param partContent     The content.
121      * @param partContentType The content type.
122      * @return An Email.
123      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
124      * @since 1.0
125      */
126     public Email addPart(final String partContent, final String partContentType) throws EmailException {
127         final BodyPart bodyPart = createBodyPart();
128         try {
129             bodyPart.setContent(partContent, partContentType);
130             getContainer().addBodyPart(bodyPart);
131         } catch (final MessagingException e) {
132             throw new EmailException(e);
133         }
134 
135         return this;
136     }
137 
138     /**
139      * Attaches a file specified as a DataSource interface.
140      *
141      * @param dataSource  A DataSource interface for the file.
142      * @param name        The name field for the attachment.
143      * @param description A description for the attachment.
144      * @return A MultiPartEmail.
145      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
146      * @since 1.0
147      */
148     public MultiPartEmail attach(final DataSource dataSource, final String name, final String description) throws EmailException {
149         EmailException.checkNonNull(dataSource, () -> "Invalid Datasource.");
150         // verify that the DataSource is valid
151         try (InputStream inputStream = dataSource.getInputStream()) {
152             EmailException.checkNonNull(inputStream, () -> "Invalid Datasource.");
153         } catch (final IOException e) {
154             throw new EmailException("Invalid Datasource.", e);
155         }
156         return attach(dataSource, name, description, EmailAttachment.ATTACHMENT);
157     }
158 
159     /**
160      * Attaches a file specified as a DataSource interface.
161      *
162      * @param dataSource  A DataSource interface for the file.
163      * @param name        The name field for the attachment.
164      * @param description A description for the attachment.
165      * @param disposition Either mixed or inline.
166      * @return A MultiPartEmail.
167      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
168      * @since 1.0
169      */
170     public MultiPartEmail attach(final DataSource dataSource, String name, final String description, final String disposition) throws EmailException {
171         if (EmailUtils.isEmpty(name)) {
172             name = dataSource.getName();
173         }
174         try {
175             final BodyPart bodyPart = createBodyPart();
176             bodyPart.setDisposition(disposition);
177             bodyPart.setFileName(MimeUtility.encodeText(name));
178             bodyPart.setDescription(description);
179             bodyPart.setDataHandler(new DataHandler(dataSource));
180             getContainer().addBodyPart(bodyPart);
181         } catch (final UnsupportedEncodingException | MessagingException e) {
182             // in case the file name could not be encoded
183             throw new EmailException(e);
184         }
185         setBoolHasAttachments(true);
186         return this;
187     }
188 
189     /**
190      * Attaches an EmailAttachment.
191      *
192      * @param attachment An EmailAttachment.
193      * @return A MultiPartEmail.
194      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
195      * @since 1.0
196      */
197     public MultiPartEmail attach(final EmailAttachment attachment) throws EmailException {
198         EmailException.checkNonNull(attachment, () -> "Invalid attachment.");
199         MultiPartEmail result = null;
200         final URL url = attachment.getURL();
201         if (url == null) {
202             String fileName = null;
203             try {
204                 fileName = attachment.getPath();
205                 final File file = new File(fileName);
206                 if (!file.exists()) {
207                     throw new IOException("\"" + fileName + "\" does not exist");
208                 }
209                 result = attach(new FileDataSource(file), attachment.getName(), attachment.getDescription(), attachment.getDisposition());
210             } catch (final IOException e) {
211                 throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
212             }
213         } else {
214             result = attach(url, attachment.getName(), attachment.getDescription(), attachment.getDisposition());
215         }
216         return result;
217     }
218 
219     /**
220      * Attaches a file.
221      *
222      * @param file A file attachment
223      * @return A MultiPartEmail.
224      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
225      * @since 1.3
226      */
227     public MultiPartEmail attach(final File file) throws EmailException {
228         final String fileName = file.getAbsolutePath();
229         try {
230             if (!file.exists()) {
231                 throw new IOException("\"" + fileName + "\" does not exist");
232             }
233             return attach(new FileDataSource(file), file.getName(), null, EmailAttachment.ATTACHMENT);
234         } catch (final IOException e) {
235             throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
236         }
237     }
238 
239     /**
240      * Attaches a path.
241      *
242      * @param file    A file attachment.
243      * @param options options for opening file streams.
244      * @return A MultiPartEmail.
245      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
246      * @since 1.6.0
247      */
248     public MultiPartEmail attach(final Path file, final OpenOption... options) throws EmailException {
249         final Path fileName = file.toAbsolutePath();
250         try {
251             if (!Files.exists(file)) {
252                 throw new IOException("\"" + fileName + "\" does not exist");
253             }
254             return attach(new PathDataSource(file, FileTypeMap.getDefaultFileTypeMap(), options), Objects.toString(file.getFileName(), null), null,
255                     EmailAttachment.ATTACHMENT);
256         } catch (final IOException e) {
257             throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
258         }
259     }
260 
261     /**
262      * Attaches a file located by its URL. The disposition of the file is set to mixed.
263      *
264      * @param url         The URL of the file (may be any valid URL).
265      * @param name        The name field for the attachment.
266      * @param description A description for the attachment.
267      * @return A MultiPartEmail.
268      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
269      * @since 1.0
270      */
271     public MultiPartEmail attach(final URL url, final String name, final String description) throws EmailException {
272         return attach(url, name, description, EmailAttachment.ATTACHMENT);
273     }
274 
275     /**
276      * Attaches a file located by its URL.
277      *
278      * @param url         The URL of the file (may be any valid URL).
279      * @param name        The name field for the attachment.
280      * @param description A description for the attachment.
281      * @param disposition Either mixed or inline.
282      * @return A MultiPartEmail.
283      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
284      * @since 1.0
285      */
286     public MultiPartEmail attach(final URL url, final String name, final String description, final String disposition) throws EmailException {
287         // verify that the URL is valid
288         try {
289             url.openStream().close();
290         } catch (final IOException e) {
291             throw new EmailException("Invalid URL set:" + url, e);
292         }
293         return attach(new URLDataSource(url), name, description, disposition);
294     }
295 
296     /**
297      * Builds the MimeMessage. Please note that a user rarely calls this method directly and only if he/she is interested in the sending the underlying
298      * MimeMessage without commons-email.
299      *
300      * @throws EmailException if there was an error.
301      * @since 1.0
302      */
303     @Override
304     public void buildMimeMessage() throws EmailException {
305         try {
306             if (primaryBodyPart != null) {
307                 // before a multipart message can be sent, we must make sure that
308                 // the content for the main body part was actually set. If not,
309                 // an IOException will be thrown during super.send().
310 
311                 final BodyPart body = getPrimaryBodyPart();
312                 try {
313                     body.getContent();
314                 } catch (final IOException e) { // NOPMD
315                     // do nothing here.
316                     // content will be set to an empty string as a result.
317                     // (Should this really be rethrown as an email exception?)
318                     // throw new EmailException(e);
319                 }
320             }
321 
322             if (subType != null) {
323                 getContainer().setSubType(subType);
324             }
325 
326             super.buildMimeMessage();
327         } catch (final MessagingException e) {
328             throw new EmailException(e);
329         }
330     }
331 
332     /**
333      * Creates a body part object. Can be overridden if you don't want to create a BodyPart.
334      *
335      * @return the created body part
336      */
337     protected BodyPart createBodyPart() {
338         return new MimeBodyPart();
339     }
340 
341     /**
342      * Creates a mime multipart object.
343      *
344      * @return the created mime part
345      */
346     protected MimeMultipart createMimeMultipart() {
347         return new MimeMultipart();
348     }
349 
350     /**
351      * Gets the message container.
352      *
353      * @return The message container.
354      * @since 1.0
355      */
356     protected MimeMultipart getContainer() {
357         if (!initialized) {
358             init();
359         }
360         return container;
361     }
362 
363     /**
364      * Gets first body part of the message.
365      *
366      * @return The primary body part.
367      * @throws MessagingException An error occurred while getting the primary body part.
368      * @since 1.0
369      */
370     protected BodyPart getPrimaryBodyPart() throws MessagingException {
371         if (!initialized) {
372             init();
373         }
374         // Add the first body part to the message. The fist body part must be
375         if (primaryBodyPart == null) {
376             primaryBodyPart = createBodyPart();
377             getContainer().addBodyPart(primaryBodyPart, 0);
378         }
379         return primaryBodyPart;
380     }
381 
382     /**
383      * Gets the MIME subtype of the email.
384      *
385      * @return MIME subtype of the email
386      * @since 1.0
387      */
388     public String getSubType() {
389         return subType;
390     }
391 
392     /**
393      * Initialize the multipart email.
394      *
395      * @since 1.0
396      */
397     protected void init() {
398         if (initialized) {
399             throw new IllegalStateException("Already initialized");
400         }
401         container = createMimeMultipart();
402         super.setContent(container);
403         initialized = true;
404     }
405 
406     /**
407      * Tests whether there are attachments.
408      *
409      * @return true if there are attachments
410      * @since 1.0
411      */
412     public boolean isBoolHasAttachments() {
413         return hasAttachments;
414     }
415 
416     /**
417      * Tests if this object is initialized.
418      *
419      * @return true if initialized
420      */
421     protected boolean isInitialized() {
422         return initialized;
423     }
424 
425     /**
426      * Sets whether there are attachments.
427      *
428      * @param hasAttachments the attachments flag
429      * @since 1.0
430      */
431     public void setBoolHasAttachments(final boolean hasAttachments) {
432         this.hasAttachments = hasAttachments;
433     }
434 
435     /**
436      * Sets the initialized status of this object.
437      *
438      * @param initialized the initialized status flag
439      */
440     protected void setInitialized(final boolean initialized) {
441         this.initialized = initialized;
442     }
443 
444     /**
445      * Sets the message of the email.
446      *
447      * @param msg A String.
448      * @return An Email.
449      * @throws EmailException see jakarta.mail.internet.MimeBodyPart for definitions
450      * @since 1.0
451      */
452     @Override
453     public Email setMsg(final String msg) throws EmailException {
454         EmailException.checkNonEmpty(msg, () -> "Invalid message.");
455         try {
456             final BodyPart primary = getPrimaryBodyPart();
457             if (primary instanceof MimePart && EmailUtils.isNotEmpty(getCharsetName())) {
458                 ((MimePart) primary).setText(msg, getCharsetName());
459             } else {
460                 primary.setText(msg);
461             }
462         } catch (final MessagingException e) {
463             throw new EmailException(e);
464         }
465         return this;
466     }
467 
468     /**
469      * Sets the MIME subtype of the email.
470      *
471      * @param subType MIME subtype of the email
472      * @since 1.0
473      */
474     public void setSubType(final String subType) {
475         this.subType = subType;
476     }
477 
478 }