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