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;
024
025import javax.activation.DataHandler;
026import javax.activation.DataSource;
027import javax.activation.FileDataSource;
028import javax.activation.URLDataSource;
029import javax.mail.BodyPart;
030import javax.mail.MessagingException;
031import javax.mail.internet.MimeBodyPart;
032import javax.mail.internet.MimeMultipart;
033import javax.mail.internet.MimePart;
034import javax.mail.internet.MimeUtility;
035
036/**
037 * A multipart email.
038 *
039 * <p>This class is used to send multi-part internet email like
040 * messages with attachments.
041 *
042 * <p>To create a multi-part email, call the default constructor and
043 * then you can call setMsg() to set the message and call the
044 * different attach() methods.
045 *
046 * @since 1.0
047 * @version $Id: MultiPartEmail.java 1606710 2014-06-30 12:29:13Z ggregory $
048 */
049public class MultiPartEmail extends Email
050{
051    /** Body portion of the email. */
052    private MimeMultipart container;
053
054    /** The message container. */
055    private BodyPart primaryBodyPart;
056
057    /** The MIME subtype. */
058    private String subType;
059
060    /** Indicates if the message has been initialized. */
061    private boolean initialized;
062
063    /** Indicates if attachments have been added to the message. */
064    private boolean boolHasAttachments;
065
066    /**
067     * Set the MIME subtype of the email.
068     *
069     * @param aSubType MIME subtype of the email
070     * @since 1.0
071     */
072    public void setSubType(final String aSubType)
073    {
074        this.subType = aSubType;
075    }
076
077    /**
078     * Get the MIME subtype of the email.
079     *
080     * @return MIME subtype of the email
081     * @since 1.0
082     */
083    public String getSubType()
084    {
085        return subType;
086    }
087
088    /**
089     * Add a new part to the email.
090     *
091     * @param partContent The content.
092     * @param partContentType The content type.
093     * @return An Email.
094     * @throws EmailException see javax.mail.internet.MimeBodyPart
095     *  for definitions
096     * @since 1.0
097     */
098    public Email addPart(final String partContent, final String partContentType)
099        throws EmailException
100    {
101            final BodyPart bodyPart = createBodyPart();
102        try
103        {
104            bodyPart.setContent(partContent, partContentType);
105            getContainer().addBodyPart(bodyPart);
106        }
107        catch (final MessagingException me)
108        {
109            throw new EmailException(me);
110        }
111
112        return this;
113    }
114
115    /**
116     * Add a new part to the email.
117     *
118     * @param multipart The MimeMultipart.
119     * @return An Email.
120     * @throws EmailException see javax.mail.internet.MimeBodyPart
121     *  for definitions
122     *  @since 1.0
123     */
124    public Email addPart(final MimeMultipart multipart) throws EmailException
125    {
126        try
127        {
128            return addPart(multipart, getContainer().getCount());
129        }
130        catch (final MessagingException me)
131        {
132            throw new EmailException(me);
133        }
134    }
135
136    /**
137     * Add a new part to the email.
138     *
139     * @param multipart The part to add.
140     * @param index The index to add at.
141     * @return The email.
142     * @throws EmailException An error occurred while adding the part.
143     * @since 1.0
144     */
145    public Email addPart(final MimeMultipart multipart, final int index) throws EmailException
146    {
147            final BodyPart bodyPart = createBodyPart();
148        try
149        {
150            bodyPart.setContent(multipart);
151            getContainer().addBodyPart(bodyPart, index);
152        }
153        catch (final MessagingException me)
154        {
155            throw new EmailException(me);
156        }
157
158        return this;
159    }
160
161    /**
162     * Initialize the multipart email.
163     * @since 1.0
164     */
165    protected void init()
166    {
167        if (initialized)
168        {
169            throw new IllegalStateException("Already initialized");
170        }
171
172        container = createMimeMultipart();
173        super.setContent(container);
174
175        initialized = true;
176    }
177
178    /**
179     * Set the message of the email.
180     *
181     * @param msg A String.
182     * @return An Email.
183     * @throws EmailException see javax.mail.internet.MimeBodyPart
184     *  for definitions
185     * @since 1.0
186     */
187    @Override
188    public Email setMsg(final String msg) throws EmailException
189    {
190        // throw exception on null message
191        if (EmailUtils.isEmpty(msg))
192        {
193            throw new EmailException("Invalid message supplied");
194        }
195        try
196        {
197            final BodyPart primary = getPrimaryBodyPart();
198
199            if (primary instanceof MimePart && EmailUtils.isNotEmpty(charset))
200            {
201                ((MimePart) primary).setText(msg, charset);
202            }
203            else
204            {
205                primary.setText(msg);
206            }
207        }
208        catch (final MessagingException me)
209        {
210            throw new EmailException(me);
211        }
212        return this;
213    }
214
215    /**
216     * Does the work of actually building the MimeMessage. Please note that
217     * a user rarely calls this method directly and only if he/she is
218     * interested in the sending the underlying MimeMessage without
219     * commons-email.
220     *
221     * @exception EmailException if there was an error.
222     * @since 1.0
223     */
224    @Override
225    public void buildMimeMessage() throws EmailException
226    {
227        try
228        {
229            if (primaryBodyPart != null)
230            {
231                // before a multipart message can be sent, we must make sure that
232                // the content for the main body part was actually set.  If not,
233                // an IOException will be thrown during super.send().
234
235                final BodyPart body = this.getPrimaryBodyPart();
236                try
237                {
238                    body.getContent();
239                }
240                catch (final IOException e) // NOPMD
241                {
242                    // do nothing here.
243                    // content will be set to an empty string as a result.
244                    // (Should this really be rethrown as an email exception?)
245                    // throw new EmailException(e);
246                }
247            }
248
249            if (subType != null)
250            {
251                getContainer().setSubType(subType);
252            }
253
254            super.buildMimeMessage();
255        }
256        catch (final MessagingException me)
257        {
258            throw new EmailException(me);
259        }
260    }
261
262    /**
263     * Attach a file.
264     *
265     * @param file A file attachment
266     * @return A MultiPartEmail.
267     * @throws EmailException see javax.mail.internet.MimeBodyPart
268     *  for definitions
269     * @since 1.3
270     */
271    public MultiPartEmail attach(final File file)
272        throws EmailException
273    {
274        final String fileName = file.getAbsolutePath();
275
276        try
277        {
278            if (!file.exists())
279            {
280                throw new IOException("\"" + fileName + "\" does not exist");
281            }
282
283            final FileDataSource fds = new FileDataSource(file);
284
285            return attach(fds, file.getName(), null, EmailAttachment.ATTACHMENT);
286        }
287        catch (final IOException e)
288        {
289            throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
290        }
291    }
292
293    /**
294     * Attach an EmailAttachment.
295     *
296     * @param attachment An EmailAttachment.
297     * @return A MultiPartEmail.
298     * @throws EmailException see javax.mail.internet.MimeBodyPart
299     *  for definitions
300     * @since 1.0
301     */
302    public MultiPartEmail attach(final EmailAttachment attachment)
303        throws EmailException
304    {
305        MultiPartEmail result = null;
306
307        if (attachment == null)
308        {
309            throw new EmailException("Invalid attachment supplied");
310        }
311
312        final URL url = attachment.getURL();
313
314        if (url == null)
315        {
316            String fileName = null;
317            try
318            {
319                fileName = attachment.getPath();
320                final File file = new File(fileName);
321                if (!file.exists())
322                {
323                    throw new IOException("\"" + fileName + "\" does not exist");
324                }
325                result =
326                    attach(
327                        new FileDataSource(file),
328                        attachment.getName(),
329                        attachment.getDescription(),
330                        attachment.getDisposition());
331            }
332            catch (final IOException e)
333            {
334                throw new EmailException("Cannot attach file \"" + fileName + "\"", e);
335            }
336        }
337        else
338        {
339            result =
340                attach(
341                    url,
342                    attachment.getName(),
343                    attachment.getDescription(),
344                    attachment.getDisposition());
345        }
346
347        return result;
348    }
349
350    /**
351     * Attach a file located by its URL.  The disposition of the file
352     * is set to mixed.
353     *
354     * @param url The URL of the file (may be any valid URL).
355     * @param name The name field for the attachment.
356     * @param description A description for the attachment.
357     * @return A MultiPartEmail.
358     * @throws EmailException see javax.mail.internet.MimeBodyPart
359     *  for definitions
360     * @since 1.0
361     */
362    public MultiPartEmail attach(final URL url, final String name, final String description)
363        throws EmailException
364    {
365        return attach(url, name, description, EmailAttachment.ATTACHMENT);
366    }
367
368    /**
369     * Attach a file located by its URL.
370     *
371     * @param url The URL of the file (may be any valid URL).
372     * @param name The name field for the attachment.
373     * @param description A description for the attachment.
374     * @param disposition Either mixed or inline.
375     * @return A MultiPartEmail.
376     * @throws EmailException see javax.mail.internet.MimeBodyPart
377     *  for definitions
378     * @since 1.0
379     */
380    public MultiPartEmail attach(
381        final URL url,
382        final String name,
383        final String description,
384        final String disposition)
385        throws EmailException
386    {
387        // verify that the URL is valid
388       try
389       {
390           final InputStream is = url.openStream();
391           is.close();
392       }
393       catch (final IOException e)
394       {
395           throw new EmailException("Invalid URL set:" + url, e);
396       }
397
398       return attach(new URLDataSource(url), name, description, disposition);
399    }
400
401    /**
402     * Attach a file specified as a DataSource interface.
403     *
404     * @param ds A DataSource interface for the file.
405     * @param name The name field for the attachment.
406     * @param description A description for the attachment.
407     * @return A MultiPartEmail.
408     * @throws EmailException see javax.mail.internet.MimeBodyPart
409     *  for definitions
410     * @since 1.0
411     */
412    public MultiPartEmail attach(
413        final DataSource ds,
414        final String name,
415        final String description)
416        throws EmailException
417    {
418        // verify that the DataSource is valid
419        try
420        {
421            final InputStream is = ds != null ? ds.getInputStream() : null;
422            if (is != null)
423            {
424                // close the input stream to prevent file locking on windows
425                is.close();
426            }
427
428            if (is == null)
429            {
430                throw new EmailException("Invalid Datasource");
431            }
432        }
433        catch (final IOException e)
434        {
435            throw new EmailException("Invalid Datasource", e);
436        }
437
438        return attach(ds, name, description, EmailAttachment.ATTACHMENT);
439    }
440
441    /**
442     * Attach a file specified as a DataSource interface.
443     *
444     * @param ds A DataSource interface for the file.
445     * @param name The name field for the attachment.
446     * @param description A description for the attachment.
447     * @param disposition Either mixed or inline.
448     * @return A MultiPartEmail.
449     * @throws EmailException see javax.mail.internet.MimeBodyPart
450     *  for definitions
451     * @since 1.0
452     */
453    public MultiPartEmail attach(
454        final DataSource ds,
455        String name,
456        final String description,
457        final String disposition)
458        throws EmailException
459    {
460        if (EmailUtils.isEmpty(name))
461        {
462            name = ds.getName();
463        }
464        final BodyPart bodyPart = createBodyPart();
465        try
466        {
467            bodyPart.setDisposition(disposition);
468            bodyPart.setFileName(MimeUtility.encodeText(name));
469            bodyPart.setDescription(description);
470            bodyPart.setDataHandler(new DataHandler(ds));
471
472            getContainer().addBodyPart(bodyPart);
473        }
474        catch (final UnsupportedEncodingException uee)
475        {
476            // in case the filename could not be encoded
477            throw new EmailException(uee);
478        }
479        catch (final MessagingException me)
480        {
481            throw new EmailException(me);
482        }
483        setBoolHasAttachments(true);
484
485        return this;
486    }
487
488    /**
489     * Gets first body part of the message.
490     *
491     * @return The primary body part.
492     * @throws MessagingException An error occurred while getting the primary body part.
493     * @since 1.0
494     */
495    protected BodyPart getPrimaryBodyPart() throws MessagingException
496    {
497        if (!initialized)
498        {
499            init();
500        }
501
502        // Add the first body part to the message.  The fist body part must be
503        if (this.primaryBodyPart == null)
504        {
505            primaryBodyPart = createBodyPart();
506            getContainer().addBodyPart(primaryBodyPart, 0);
507        }
508
509        return primaryBodyPart;
510    }
511
512    /**
513     * Gets the message container.
514     *
515     * @return The message container.
516     * @since 1.0
517     */
518    protected MimeMultipart getContainer()
519    {
520        if (!initialized)
521        {
522            init();
523        }
524        return container;
525    }
526
527    /**
528     * Creates a body part object.
529     * Can be overridden if you don't want to create a BodyPart.
530     *
531     * @return the created body part
532     */
533    protected BodyPart createBodyPart()
534    {
535        return new MimeBodyPart();
536    }
537
538    /**
539     * Creates a mime multipart object.
540     *
541     * @return the created mime part
542     */
543    protected MimeMultipart createMimeMultipart()
544    {
545        return new MimeMultipart();
546    }
547
548    /**
549     * Checks whether there are attachments.
550     *
551     * @return true if there are attachments
552     * @since 1.0
553     */
554    public boolean isBoolHasAttachments()
555    {
556        return boolHasAttachments;
557    }
558
559    /**
560     * Sets whether there are attachments.
561     *
562     * @param b  the attachments flag
563     * @since 1.0
564     */
565    public void setBoolHasAttachments(final boolean b)
566    {
567        boolHasAttachments = b;
568    }
569
570    /**
571     * Checks if this object is initialized.
572     *
573     * @return true if initialized
574     */
575    protected boolean isInitialized()
576    {
577        return initialized;
578    }
579
580    /**
581     * Sets the initialized status of this object.
582     *
583     * @param b  the initialized status flag
584     */
585    protected void setInitialized(final boolean b)
586    {
587        initialized = b;
588    }
589
590}