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 */
048public class MultiPartEmail extends Email
049{
050    /** Body portion of the email. */
051    private MimeMultipart container;
052
053    /** The message container. */
054    private BodyPart primaryBodyPart;
055
056    /** The MIME subtype. */
057    private String subType;
058
059    /** Indicates if the message has been initialized. */
060    private boolean initialized;
061
062    /** Indicates if attachments have been added to the message. */
063    private boolean boolHasAttachments;
064
065    /**
066     * Set the MIME subtype of the email.
067     *
068     * @param aSubType MIME subtype of the email
069     * @since 1.0
070     */
071    public void setSubType(final String aSubType)
072    {
073        this.subType = aSubType;
074    }
075
076    /**
077     * Get the MIME subtype of the email.
078     *
079     * @return MIME subtype of the email
080     * @since 1.0
081     */
082    public String getSubType()
083    {
084        return subType;
085    }
086
087    /**
088     * Add a new part to the email.
089     *
090     * @param partContent The content.
091     * @param partContentType The content type.
092     * @return An Email.
093     * @throws EmailException see javax.mail.internet.MimeBodyPart
094     *  for definitions
095     * @since 1.0
096     */
097    public Email addPart(final String partContent, final String partContentType)
098        throws EmailException
099    {
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}