    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.mail;
    018
    019import java.io.File;
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.UnsupportedEncodingException;
    023import java.net.URL;
    024import java.nio.file.Files;
    025import java.nio.file.OpenOption;
    026import java.nio.file.Path;
    027import java.util.Objects;
    028
    029import javax.activation.DataHandler;
    030import javax.activation.DataSource;
    031import javax.activation.FileDataSource;
    032import javax.activation.FileTypeMap;
    033import javax.activation.URLDataSource;
    034import javax.mail.BodyPart;
    035import javax.mail.MessagingException;
    036import javax.mail.internet.MimeBodyPart;
    037import javax.mail.internet.MimeMultipart;
    038import javax.mail.internet.MimePart;
    039import javax.mail.internet.MimeUtility;
    040
    041import org.apache.commons.mail.activation.PathDataSource;
    042
    043/**
    044 * A multipart email.
    045 * <p>
    046 * This class is used to send multi-part internet email like messages with attachments.
    047 * </p>
    048 * <p>
    049 * 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.
    050 * </p>
    051 *
    052 * @since 1.0
    053 */
    054public class MultiPartEmail extends Email {
    055
    056    /** Body portion of the email. */
    057    private MimeMultipart container;
    058
    059    /** The message container. */
    060    private BodyPart primaryBodyPart;
    061
    062    /** The MIME subtype. */
    063    private String subType;
    064
    065    /** Indicates if the message has been initialized. */
    066    private boolean initialized;
    067
    068    /** Indicates if attachments have been added to the message. */
    069    private boolean hasAttachments;
    070
    071    /**
    072     * Constructs a new instance.
    073     */
    074    public MultiPartEmail() {
    075        // empty
    076    }
    077
    078    /**
    079     * Adds a new part to the email.
    080     *
    081     * @param multipart The MimeMultipart.
    082     * @return An Email.
    083     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    084     * @since 1.0
    085     */
    086    public Email addPart(final MimeMultipart multipart) throws EmailException {
    087        try {
    088            return addPart(multipart, getContainer().getCount());
    089        } catch (final MessagingException e) {
    090            throw new EmailException(e);
    091        }
    092    }
    093
    094    /**
    095     * Adds a new part to the email.
    096     *
    097     * @param multipart The part to add.
    098     * @param index     The index to add at.
    099     * @return The email.
    100     * @throws EmailException An error occurred while adding the part.
    101     * @since 1.0
    102     */
    103    public Email addPart(final MimeMultipart multipart, final int index) throws EmailException {
    104        final BodyPart bodyPart = createBodyPart();
    105        try {
    106            bodyPart.setContent(multipart);
    107            getContainer().addBodyPart(bodyPart, index);
    108        } catch (final MessagingException e) {
    109            throw new EmailException(e);
    110        }
    111
    112        return this;
    113    }
    114
    115    /**
    116     * Adds a new part to the email.
    117     *
    118     * @param partContent     The content.
    119     * @param partContentType The content type.
    120     * @return An Email.
    121     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    122     * @since 1.0
    123     */
    124    public Email addPart(final String partContent, final String partContentType) throws EmailException {
    125        final BodyPart bodyPart = createBodyPart();
    126        try {
    127            bodyPart.setContent(partContent, partContentType);
    128            getContainer().addBodyPart(bodyPart);
    129        } catch (final MessagingException e) {
    130            throw new EmailException(e);
    131        }
    132
    133        return this;
    134    }
    135
    136    /**
    137     * Attaches a file specified as a DataSource interface.
    138     *
    139     * @param dataSource  A DataSource interface for the file.
    140     * @param name        The name field for the attachment.
    141     * @param description A description for the attachment.
    142     * @return A MultiPartEmail.
    143     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    144     * @since 1.0
    145     */
    146    public MultiPartEmail attach(final DataSource dataSource, final String name, final String description) throws EmailException {
    147        EmailException.checkNonNull(dataSource, () -> "Invalid Datasource.");
    148        // verify that the DataSource is valid
    149        try (InputStream inputStream = dataSource.getInputStream()) {
    150            EmailException.checkNonNull(inputStream, () -> "Invalid Datasource.");
    151        } catch (final IOException e) {
    152            throw new EmailException("Invalid Datasource.", e);
    153        }
    154        return attach(dataSource, name, description, EmailAttachment.ATTACHMENT);
    155    }
    156
    157    /**
    158     * Attaches a file specified as a DataSource interface.
    159     *
    160     * @param dataSource  A DataSource interface for the file.
    161     * @param name        The name field for the attachment.
    162     * @param description A description for the attachment.
    163     * @param disposition Either mixed or inline.
    164     * @return A MultiPartEmail.
    165     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    166     * @since 1.0
    167     */
    168    public MultiPartEmail attach(final DataSource dataSource, String name, final String description, final String disposition) throws EmailException {
    169        if (EmailUtils.isEmpty(name)) {
    170            name = dataSource.getName();
    171        }
    172        try {
    173            final BodyPart bodyPart = createBodyPart();
    174            bodyPart.setDisposition(disposition);
    175            bodyPart.setFileName(MimeUtility.encodeText(name));
    176            bodyPart.setDescription(description);
    177            bodyPart.setDataHandler(new DataHandler(dataSource));
    178            getContainer().addBodyPart(bodyPart);
    179        } catch (final UnsupportedEncodingException | MessagingException e) {
    180            // in case the file name could not be encoded
    181            throw new EmailException(e);
    182        }
    183        setBoolHasAttachments(true);
    184        return this;
    185    }
    186
    187    /**
    188     * Attaches an EmailAttachment.
    189     *
    190     * @param attachment An EmailAttachment.
    191     * @return A MultiPartEmail.
    192     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    193     * @since 1.0
    194     */
    195    public MultiPartEmail attach(final EmailAttachment attachment) throws EmailException {
    196        EmailException.checkNonNull(attachment, () -> "Invalid attachment.");
    197        MultiPartEmail result = null;
    198        final URL url = attachment.getURL();
    199        if (url == null) {
    200            String fileName = null;
    201            try {
    202                fileName = attachment.getPath();
    203                final File file = new File(fileName);
    204                if (!file.exists()) {
    205                    throw new IOException("\"" + fileName + "\" does not exist");
    206                }
    207                result = attach(new FileDataSource(file), attachment.getName(), attachment.getDescription(), attachment.getDisposition());
    208            } catch (final IOException e) {
    209                throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
    210            }
    211        } else {
    212            result = attach(url, attachment.getName(), attachment.getDescription(), attachment.getDisposition());
    213        }
    214        return result;
    215    }
    216
    217    /**
    218     * Attaches a file.
    219     *
    220     * @param file A file attachment
    221     * @return A MultiPartEmail.
    222     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    223     * @since 1.3
    224     */
    225    public MultiPartEmail attach(final File file) throws EmailException {
    226        final String fileName = file.getAbsolutePath();
    227        try {
    228            if (!file.exists()) {
    229                throw new IOException("\"" + fileName + "\" does not exist");
    230            }
    231            return attach(new FileDataSource(file), file.getName(), null, EmailAttachment.ATTACHMENT);
    232        } catch (final IOException e) {
    233            throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
    234        }
    235    }
    236
    237    /**
    238     * Attaches a path.
    239     *
    240     * @param file    A file attachment.
    241     * @param options options for opening file streams.
    242     * @return A MultiPartEmail.
    243     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    244     * @since 1.6.0
    245     */
    246    public MultiPartEmail attach(final Path file, final OpenOption... options) throws EmailException {
    247        final Path fileName = file.toAbsolutePath();
    248        try {
    249            if (!Files.exists(file)) {
    250                throw new IOException("\"" + fileName + "\" does not exist");
    251            }
    252            return attach(new PathDataSource(file, FileTypeMap.getDefaultFileTypeMap(), options), Objects.toString(file.getFileName(), null), null,
    253                    EmailAttachment.ATTACHMENT);
    254        } catch (final IOException e) {
    255            throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
    256        }
    257    }
    258
    259    /**
    260     * Attaches a file located by its URL. The disposition of the file is set to mixed.
    261     *
    262     * @param url         The URL of the file (may be any valid URL).
    263     * @param name        The name field for the attachment.
    264     * @param description A description for the attachment.
    265     * @return A MultiPartEmail.
    266     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    267     * @since 1.0
    268     */
    269    public MultiPartEmail attach(final URL url, final String name, final String description) throws EmailException {
    270        return attach(url, name, description, EmailAttachment.ATTACHMENT);
    271    }
    272
    273    /**
    274     * Attaches a file located by its URL.
    275     *
    276     * @param url         The URL of the file (may be any valid URL).
    277     * @param name        The name field for the attachment.
    278     * @param description A description for the attachment.
    279     * @param disposition Either mixed or inline.
    280     * @return A MultiPartEmail.
    281     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    282     * @since 1.0
    283     */
    284    public MultiPartEmail attach(final URL url, final String name, final String description, final String disposition) throws EmailException {
    285        // verify that the URL is valid
    286        try {
    287            url.openStream().close();
    288        } catch (final IOException e) {
    289            throw new EmailException("Invalid URL set:" + url, e);
    290        }
    291        return attach(new URLDataSource(url), name, description, disposition);
    292    }
    293
    294    /**
    295     * 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
    296     * MimeMessage without commons-email.
    297     *
    298     * @throws EmailException if there was an error.
    299     * @since 1.0
    300     */
    301    @Override
    302    public void buildMimeMessage() throws EmailException {
    303        try {
    304            if (primaryBodyPart != null) {
    305                // before a multipart message can be sent, we must make sure that
    306                // the content for the main body part was actually set. If not,
    307                // an IOException will be thrown during super.send().
    308
    309                final BodyPart body = getPrimaryBodyPart();
    310                try {
    311                    body.getContent();
    312                } catch (final IOException e) // NOPMD
    313                {
    314                    // do nothing here.
    315                    // content will be set to an empty string as a result.
    316                    // (Should this really be rethrown as an email exception?)
    317                    // throw new EmailException(e);
    318                }
    319            }
    320
    321            if (subType != null) {
    322                getContainer().setSubType(subType);
    323            }
    324
    325            super.buildMimeMessage();
    326        } catch (final MessagingException e) {
    327            throw new EmailException(e);
    328        }
    329    }
    330
    331    /**
    332     * Creates a body part object. Can be overridden if you don't want to create a BodyPart.
    333     *
    334     * @return the created body part
    335     */
    336    protected BodyPart createBodyPart() {
    337        return new MimeBodyPart();
    338    }
    339
    340    /**
    341     * Creates a mime multipart object.
    342     *
    343     * @return the created mime part
    344     */
    345    protected MimeMultipart createMimeMultipart() {
    346        return new MimeMultipart();
    347    }
    348
    349    /**
    350     * Gets the message container.
    351     *
    352     * @return The message container.
    353     * @since 1.0
    354     */
    355    protected MimeMultipart getContainer() {
    356        if (!initialized) {
    357            init();
    358        }
    359        return container;
    360    }
    361
    362    /**
    363     * Gets first body part of the message.
    364     *
    365     * @return The primary body part.
    366     * @throws MessagingException An error occurred while getting the primary body part.
    367     * @since 1.0
    368     */
    369    protected BodyPart getPrimaryBodyPart() throws MessagingException {
    370        if (!initialized) {
    371            init();
    372        }
    373        // Add the first body part to the message. The fist body part must be
    374        if (primaryBodyPart == null) {
    375            primaryBodyPart = createBodyPart();
    376            getContainer().addBodyPart(primaryBodyPart, 0);
    377        }
    378        return primaryBodyPart;
    379    }
    380
    381    /**
    382     * Gets the MIME subtype of the email.
    383     *
    384     * @return MIME subtype of the email
    385     * @since 1.0
    386     */
    387    public String getSubType() {
    388        return subType;
    389    }
    390
    391    /**
    392     * Initialize the multipart email.
    393     *
    394     * @since 1.0
    395     */
    396    protected void init() {
    397        if (initialized) {
    398            throw new IllegalStateException("Already initialized");
    399        }
    400        container = createMimeMultipart();
    401        super.setContent(container);
    402        initialized = true;
    403    }
    404
    405    /**
    406     * Tests whether there are attachments.
    407     *
    408     * @return true if there are attachments
    409     * @since 1.0
    410     */
    411    public boolean isBoolHasAttachments() {
    412        return hasAttachments;
    413    }
    414
    415    /**
    416     * Tests if this object is initialized.
    417     *
    418     * @return true if initialized
    419     */
    420    protected boolean isInitialized() {
    421        return initialized;
    422    }
    423
    424    /**
    425     * Sets whether there are attachments.
    426     *
    427     * @param hasAttachments the attachments flag
    428     * @since 1.0
    429     */
    430    public void setBoolHasAttachments(final boolean hasAttachments) {
    431        this.hasAttachments = hasAttachments;
    432    }
    433
    434    /**
    435     * Sets the initialized status of this object.
    436     *
    437     * @param initialized the initialized status flag
    438     */
    439    protected void setInitialized(final boolean initialized) {
    440        this.initialized = initialized;
    441    }
    442
    443    /**
    444     * Sets the message of the email.
    445     *
    446     * @param msg A String.
    447     * @return An Email.
    448     * @throws EmailException see javax.mail.internet.MimeBodyPart for definitions
    449     * @since 1.0
    450     */
    451    @Override
    452    public Email setMsg(final String msg) throws EmailException {
    453        EmailException.checkNonEmpty(msg, () -> "Invalid message.");
    454        try {
    455            final BodyPart primary = getPrimaryBodyPart();
    456            if (primary instanceof MimePart && EmailUtils.isNotEmpty(getCharsetName())) {
    457                ((MimePart) primary).setText(msg, getCharsetName());
    458            } else {
    459                primary.setText(msg);
    460            }
    461        } catch (final MessagingException e) {
    462            throw new EmailException(e);
    463        }
    464        return this;
    465    }
    466
    467    /**
    468     * Sets the MIME subtype of the email.
    469     *
    470     * @param subType MIME subtype of the email
    471     * @since 1.0
    472     */
    473    public void setSubType(final String subType) {
    474        this.subType = subType;
    475    }
    476
    477}