MultiPartEmail.java

  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.javax;

  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.UnsupportedEncodingException;
  22. import java.net.URL;
  23. import java.nio.file.Files;
  24. import java.nio.file.OpenOption;
  25. import java.nio.file.Path;
  26. import java.util.Objects;

  27. import javax.activation.DataHandler;
  28. import javax.activation.DataSource;
  29. import javax.activation.FileDataSource;
  30. import javax.activation.FileTypeMap;
  31. import javax.activation.URLDataSource;
  32. import javax.mail.BodyPart;
  33. import javax.mail.MessagingException;
  34. import javax.mail.internet.MimeBodyPart;
  35. import javax.mail.internet.MimeMultipart;
  36. import javax.mail.internet.MimePart;
  37. import javax.mail.internet.MimeUtility;

  38. import org.apache.commons.mail2.core.EmailException;
  39. import org.apache.commons.mail2.core.EmailUtils;
  40. import org.apache.commons.mail2.javax.activation.PathDataSource;

  41. /**
  42.  * A multipart email.
  43.  * <p>
  44.  * This class is used to send multi-part internet email like messages with attachments.
  45.  * </p>
  46.  * <p>
  47.  * 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.
  48.  * </p>
  49.  *
  50.  * @since 1.0
  51.  */
  52. public class MultiPartEmail extends Email {

  53.     /** Body portion of the email. */
  54.     private MimeMultipart container;

  55.     /** The message container. */
  56.     private BodyPart primaryBodyPart;

  57.     /** The MIME subtype. */
  58.     private String subType;

  59.     /** Indicates if the message has been initialized. */
  60.     private boolean initialized;

  61.     /** Indicates if attachments have been added to the message. */
  62.     private boolean hasAttachments;

  63.     /**
  64.      * Constructs a new instance.
  65.      */
  66.     public MultiPartEmail() {
  67.         // empty
  68.     }

  69.     /**
  70.      * Adds a new part to the email.
  71.      *
  72.      * @param multipart The MimeMultipart.
  73.      * @return An Email.
  74.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  75.      * @since 1.0
  76.      */
  77.     public Email addPart(final MimeMultipart multipart) throws EmailException {
  78.         try {
  79.             return addPart(multipart, getContainer().getCount());
  80.         } catch (final MessagingException e) {
  81.             throw new EmailException(e);
  82.         }
  83.     }

  84.     /**
  85.      * Adds a new part to the email.
  86.      *
  87.      * @param multipart The part to add.
  88.      * @param index     The index to add at.
  89.      * @return The email.
  90.      * @throws EmailException An error occurred while adding the part.
  91.      * @since 1.0
  92.      */
  93.     public Email addPart(final MimeMultipart multipart, final int index) throws EmailException {
  94.         final BodyPart bodyPart = createBodyPart();
  95.         try {
  96.             bodyPart.setContent(multipart);
  97.             getContainer().addBodyPart(bodyPart, index);
  98.         } catch (final MessagingException e) {
  99.             throw new EmailException(e);
  100.         }

  101.         return this;
  102.     }

  103.     /**
  104.      * Adds a new part to the email.
  105.      *
  106.      * @param partContent     The content.
  107.      * @param partContentType The content type.
  108.      * @return An Email.
  109.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  110.      * @since 1.0
  111.      */
  112.     public Email addPart(final String partContent, final String partContentType) throws EmailException {
  113.         final BodyPart bodyPart = createBodyPart();
  114.         try {
  115.             bodyPart.setContent(partContent, partContentType);
  116.             getContainer().addBodyPart(bodyPart);
  117.         } catch (final MessagingException e) {
  118.             throw new EmailException(e);
  119.         }

  120.         return this;
  121.     }

  122.     /**
  123.      * Attaches a file specified as a DataSource interface.
  124.      *
  125.      * @param dataSource  A DataSource interface for the file.
  126.      * @param name        The name field for the attachment.
  127.      * @param description A description for the attachment.
  128.      * @return A MultiPartEmail.
  129.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  130.      * @since 1.0
  131.      */
  132.     public MultiPartEmail attach(final DataSource dataSource, final String name, final String description) throws EmailException {
  133.         EmailException.checkNonNull(dataSource, () -> "Invalid Datasource.");
  134.         // verify that the DataSource is valid
  135.         try (InputStream inputStream = dataSource.getInputStream()) {
  136.             EmailException.checkNonNull(inputStream, () -> "Invalid Datasource.");
  137.         } catch (final IOException e) {
  138.             throw new EmailException("Invalid Datasource.", e);
  139.         }
  140.         return attach(dataSource, name, description, EmailAttachment.ATTACHMENT);
  141.     }

  142.     /**
  143.      * Attaches a file specified as a DataSource interface.
  144.      *
  145.      * @param dataSource  A DataSource interface for the file.
  146.      * @param name        The name field for the attachment.
  147.      * @param description A description for the attachment.
  148.      * @param disposition Either mixed or inline.
  149.      * @return A MultiPartEmail.
  150.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  151.      * @since 1.0
  152.      */
  153.     public MultiPartEmail attach(final DataSource dataSource, String name, final String description, final String disposition) throws EmailException {
  154.         if (EmailUtils.isEmpty(name)) {
  155.             name = dataSource.getName();
  156.         }
  157.         try {
  158.             final BodyPart bodyPart = createBodyPart();
  159.             bodyPart.setDisposition(disposition);
  160.             bodyPart.setFileName(MimeUtility.encodeText(name));
  161.             bodyPart.setDescription(description);
  162.             bodyPart.setDataHandler(new DataHandler(dataSource));
  163.             getContainer().addBodyPart(bodyPart);
  164.         } catch (final UnsupportedEncodingException | MessagingException e) {
  165.             // in case the file name could not be encoded
  166.             throw new EmailException(e);
  167.         }
  168.         setBoolHasAttachments(true);
  169.         return this;
  170.     }

  171.     /**
  172.      * Attaches an EmailAttachment.
  173.      *
  174.      * @param attachment An EmailAttachment.
  175.      * @return A MultiPartEmail.
  176.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  177.      * @since 1.0
  178.      */
  179.     public MultiPartEmail attach(final EmailAttachment attachment) throws EmailException {
  180.         EmailException.checkNonNull(attachment, () -> "Invalid attachment.");
  181.         MultiPartEmail result = null;
  182.         final URL url = attachment.getURL();
  183.         if (url == null) {
  184.             String fileName = null;
  185.             try {
  186.                 fileName = attachment.getPath();
  187.                 final File file = new File(fileName);
  188.                 if (!file.exists()) {
  189.                     throw new IOException("\"" + fileName + "\" does not exist");
  190.                 }
  191.                 result = attach(new FileDataSource(file), attachment.getName(), attachment.getDescription(), attachment.getDisposition());
  192.             } catch (final IOException e) {
  193.                 throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
  194.             }
  195.         } else {
  196.             result = attach(url, attachment.getName(), attachment.getDescription(), attachment.getDisposition());
  197.         }
  198.         return result;
  199.     }

  200.     /**
  201.      * Attaches a file.
  202.      *
  203.      * @param file A file attachment
  204.      * @return A MultiPartEmail.
  205.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  206.      * @since 1.3
  207.      */
  208.     public MultiPartEmail attach(final File file) throws EmailException {
  209.         final String fileName = file.getAbsolutePath();
  210.         try {
  211.             if (!file.exists()) {
  212.                 throw new IOException("\"" + fileName + "\" does not exist");
  213.             }
  214.             return attach(new FileDataSource(file), file.getName(), null, EmailAttachment.ATTACHMENT);
  215.         } catch (final IOException e) {
  216.             throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
  217.         }
  218.     }

  219.     /**
  220.      * Attaches a path.
  221.      *
  222.      * @param file    A file attachment.
  223.      * @param options options for opening file streams.
  224.      * @return A MultiPartEmail.
  225.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  226.      * @since 1.6.0
  227.      */
  228.     public MultiPartEmail attach(final Path file, final OpenOption... options) throws EmailException {
  229.         final Path fileName = file.toAbsolutePath();
  230.         try {
  231.             if (!Files.exists(file)) {
  232.                 throw new IOException("\"" + fileName + "\" does not exist");
  233.             }
  234.             return attach(new PathDataSource(file, FileTypeMap.getDefaultFileTypeMap(), options), Objects.toString(file.getFileName(), null), null,
  235.                     EmailAttachment.ATTACHMENT);
  236.         } catch (final IOException e) {
  237.             throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
  238.         }
  239.     }

  240.     /**
  241.      * Attaches a file located by its URL. The disposition of the file is set to mixed.
  242.      *
  243.      * @param url         The URL of the file (may be any valid URL).
  244.      * @param name        The name field for the attachment.
  245.      * @param description A description for the attachment.
  246.      * @return A MultiPartEmail.
  247.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  248.      * @since 1.0
  249.      */
  250.     public MultiPartEmail attach(final URL url, final String name, final String description) throws EmailException {
  251.         return attach(url, name, description, EmailAttachment.ATTACHMENT);
  252.     }

  253.     /**
  254.      * Attaches a file located by its URL.
  255.      *
  256.      * @param url         The URL of the file (may be any valid URL).
  257.      * @param name        The name field for the attachment.
  258.      * @param description A description for the attachment.
  259.      * @param disposition Either mixed or inline.
  260.      * @return A MultiPartEmail.
  261.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  262.      * @since 1.0
  263.      */
  264.     public MultiPartEmail attach(final URL url, final String name, final String description, final String disposition) throws EmailException {
  265.         // verify that the URL is valid
  266.         try {
  267.             url.openStream().close();
  268.         } catch (final IOException e) {
  269.             throw new EmailException("Invalid URL set:" + url, e);
  270.         }
  271.         return attach(new URLDataSource(url), name, description, disposition);
  272.     }

  273.     /**
  274.      * 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
  275.      * MimeMessage without commons-email.
  276.      *
  277.      * @throws EmailException if there was an error.
  278.      * @since 1.0
  279.      */
  280.     @Override
  281.     public void buildMimeMessage() throws EmailException {
  282.         try {
  283.             if (primaryBodyPart != null) {
  284.                 // before a multipart message can be sent, we must make sure that
  285.                 // the content for the main body part was actually set. If not,
  286.                 // an IOException will be thrown during super.send().

  287.                 final BodyPart body = getPrimaryBodyPart();
  288.                 try {
  289.                     body.getContent();
  290.                 } catch (final IOException e) { // NOPMD
  291.                     // do nothing here.
  292.                     // content will be set to an empty string as a result.
  293.                     // (Should this really be rethrown as an email exception?)
  294.                     // throw new EmailException(e);
  295.                 }
  296.             }

  297.             if (subType != null) {
  298.                 getContainer().setSubType(subType);
  299.             }

  300.             super.buildMimeMessage();
  301.         } catch (final MessagingException e) {
  302.             throw new EmailException(e);
  303.         }
  304.     }

  305.     /**
  306.      * Creates a body part object. Can be overridden if you don't want to create a BodyPart.
  307.      *
  308.      * @return the created body part
  309.      */
  310.     protected BodyPart createBodyPart() {
  311.         return new MimeBodyPart();
  312.     }

  313.     /**
  314.      * Creates a mime multipart object.
  315.      *
  316.      * @return the created mime part
  317.      */
  318.     protected MimeMultipart createMimeMultipart() {
  319.         return new MimeMultipart();
  320.     }

  321.     /**
  322.      * Gets the message container.
  323.      *
  324.      * @return The message container.
  325.      * @since 1.0
  326.      */
  327.     protected MimeMultipart getContainer() {
  328.         if (!initialized) {
  329.             init();
  330.         }
  331.         return container;
  332.     }

  333.     /**
  334.      * Gets first body part of the message.
  335.      *
  336.      * @return The primary body part.
  337.      * @throws MessagingException An error occurred while getting the primary body part.
  338.      * @since 1.0
  339.      */
  340.     protected BodyPart getPrimaryBodyPart() throws MessagingException {
  341.         if (!initialized) {
  342.             init();
  343.         }
  344.         // Add the first body part to the message. The fist body part must be
  345.         if (primaryBodyPart == null) {
  346.             primaryBodyPart = createBodyPart();
  347.             getContainer().addBodyPart(primaryBodyPart, 0);
  348.         }
  349.         return primaryBodyPart;
  350.     }

  351.     /**
  352.      * Gets the MIME subtype of the email.
  353.      *
  354.      * @return MIME subtype of the email
  355.      * @since 1.0
  356.      */
  357.     public String getSubType() {
  358.         return subType;
  359.     }

  360.     /**
  361.      * Initialize the multipart email.
  362.      *
  363.      * @since 1.0
  364.      */
  365.     protected void init() {
  366.         if (initialized) {
  367.             throw new IllegalStateException("Already initialized");
  368.         }
  369.         container = createMimeMultipart();
  370.         super.setContent(container);
  371.         initialized = true;
  372.     }

  373.     /**
  374.      * Tests whether there are attachments.
  375.      *
  376.      * @return true if there are attachments
  377.      * @since 1.0
  378.      */
  379.     public boolean isBoolHasAttachments() {
  380.         return hasAttachments;
  381.     }

  382.     /**
  383.      * Tests if this object is initialized.
  384.      *
  385.      * @return true if initialized
  386.      */
  387.     protected boolean isInitialized() {
  388.         return initialized;
  389.     }

  390.     /**
  391.      * Sets whether there are attachments.
  392.      *
  393.      * @param hasAttachments the attachments flag
  394.      * @since 1.0
  395.      */
  396.     public void setBoolHasAttachments(final boolean hasAttachments) {
  397.         this.hasAttachments = hasAttachments;
  398.     }

  399.     /**
  400.      * Sets the initialized status of this object.
  401.      *
  402.      * @param initialized the initialized status flag
  403.      */
  404.     protected void setInitialized(final boolean initialized) {
  405.         this.initialized = initialized;
  406.     }

  407.     /**
  408.      * Sets the message of the email.
  409.      *
  410.      * @param msg A String.
  411.      * @return An Email.
  412.      * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
  413.      * @since 1.0
  414.      */
  415.     @Override
  416.     public Email setMsg(final String msg) throws EmailException {
  417.         EmailException.checkNonEmpty(msg, () -> "Invalid message.");
  418.         try {
  419.             final BodyPart primary = getPrimaryBodyPart();
  420.             if (primary instanceof MimePart && EmailUtils.isNotEmpty(getCharsetName())) {
  421.                 ((MimePart) primary).setText(msg, getCharsetName());
  422.             } else {
  423.                 primary.setText(msg);
  424.             }
  425.         } catch (final MessagingException e) {
  426.             throw new EmailException(e);
  427.         }
  428.         return this;
  429.     }

  430.     /**
  431.      * Sets the MIME subtype of the email.
  432.      *
  433.      * @param subType MIME subtype of the email
  434.      * @since 1.0
  435.      */
  436.     public void setSubType(final String subType) {
  437.         this.subType = subType;
  438.     }

  439. }