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