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.UnsupportedEncodingException;
020import java.nio.charset.Charset;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Properties;
029
030import javax.mail.Authenticator;
031import javax.mail.Message;
032import javax.mail.MessagingException;
033import javax.mail.Session;
034import javax.mail.Store;
035import javax.mail.Transport;
036import javax.mail.internet.AddressException;
037import javax.mail.internet.InternetAddress;
038import javax.mail.internet.MimeMessage;
039import javax.mail.internet.MimeMultipart;
040import javax.naming.Context;
041import javax.naming.InitialContext;
042import javax.naming.NamingException;
043
044/**
045 * The base class for all email messages.  This class sets the
046 * sender's email & name, receiver's email & name, subject, and the
047 * sent date.  Subclasses are responsible for setting the message
048 * body.
049 *
050 * @since 1.0
051 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
052 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
053 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
054 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
055 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a>
056 * @author <a href="mailto:unknown">Regis Koenig</a>
057 * @author <a href="mailto:colin.chalmers@maxware.nl">Colin Chalmers</a>
058 * @author <a href="mailto:matthias@wessendorf.net">Matthias Wessendorf</a>
059 * @author <a href="mailto:corey.scott@gmail.com">Corey Scott</a>
060 * @version $Revision: 783910 $ $Date: 2009-06-11 23:13:54 +0200 (Thu, 11 Jun 2009) $
061 * @version $Id: Email.java 783910 2009-06-11 21:13:54Z sgoeschl $
062 */
063public abstract class Email
064{
065    /** Constants used by Email classes. */
066
067    /** */
068    public static final String SENDER_EMAIL = "sender.email";
069    /** */
070    public static final String SENDER_NAME = "sender.name";
071    /** */
072    public static final String RECEIVER_EMAIL = "receiver.email";
073    /** */
074    public static final String RECEIVER_NAME = "receiver.name";
075    /** */
076    public static final String EMAIL_SUBJECT = "email.subject";
077    /** */
078    public static final String EMAIL_BODY = "email.body";
079    /** */
080    public static final String CONTENT_TYPE = "content.type";
081
082    /** */
083    public static final String MAIL_HOST = "mail.smtp.host";
084    /** */
085    public static final String MAIL_PORT = "mail.smtp.port";
086    /** */
087    public static final String MAIL_SMTP_FROM = "mail.smtp.from";
088    /** */
089    public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
090    /** */
091    public static final String MAIL_SMTP_USER = "mail.smtp.user";
092    /** */
093    public static final String MAIL_SMTP_PASSWORD = "mail.smtp.password";
094    /** */
095    public static final String MAIL_TRANSPORT_PROTOCOL =
096        "mail.transport.protocol";
097    /**
098     * @since 1.1
099     */
100    public static final String MAIL_TRANSPORT_TLS = "mail.smtp.starttls.enable";
101    /** */
102    public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
103    /** */
104    public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
105    /** */
106    public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
107
108
109    /**
110     * Socket connection timeout value in milliseconds. Default is infinite timeout.
111     * @since 1.2
112     */
113    public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
114
115    /**
116     * Socket I/O timeout value in milliseconds. Default is infinite timeout.
117     * @since 1.2
118     */
119    public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
120
121
122    /** */
123    public static final String SMTP = "smtp";
124    /** */
125    public static final String TEXT_HTML = "text/html";
126    /** */
127    public static final String TEXT_PLAIN = "text/plain";
128    /** */
129    public static final String ATTACHMENTS = "attachments";
130    /** */
131    public static final String FILE_SERVER = "file.server";
132    /** */
133    public static final String MAIL_DEBUG = "mail.debug";
134
135    /** */
136    public static final String KOI8_R = "koi8-r";
137    /** */
138    public static final String ISO_8859_1 = "iso-8859-1";
139    /** */
140    public static final String US_ASCII = "us-ascii";
141
142    /** The email message to send. */
143    protected MimeMessage message;
144
145    /** The charset to use for this message */
146    protected String charset;
147
148    /** The Address of the sending party, mandatory */
149    protected InternetAddress fromAddress;
150
151    /** The Subject  */
152    protected String subject;
153
154    /** An attachment  */
155    protected MimeMultipart emailBody;
156
157    /** The content  */
158    protected Object content;
159
160    /** The content type  */
161    protected String contentType;
162
163    /** Set session debugging on or off */
164    protected boolean debug;
165
166    /** Sent date */
167    protected Date sentDate;
168
169    /**
170     * Instance of an <code>Authenticator</code> object that will be used
171     * when authentication is requested from the mail server.
172     */
173    protected Authenticator authenticator;
174
175    /**
176     * The hostname of the mail server with which to connect. If null will try
177     * to get property from system.properties. If still null, quit
178     */
179    protected String hostName;
180
181    /**
182     * The port number of the mail server to connect to.
183     * Defaults to the standard port ( 25 ).
184     */
185    protected String smtpPort = "25";
186
187    /**
188     * The port number of the SSL enabled SMTP server;
189     * defaults to the standard port, 465.
190     */
191    protected String sslSmtpPort = "465";
192
193    /** List of "to" email adresses */
194    protected List toList = new ArrayList();
195
196    /** List of "cc" email adresses */
197    protected List ccList = new ArrayList();
198
199    /** List of "bcc" email adresses */
200    protected List bccList = new ArrayList();
201
202    /** List of "replyTo" email adresses */
203    protected List replyList = new ArrayList();
204
205    /**
206     * Address to which undeliverable mail should be sent.
207     * Because this is handled by JavaMail as a String property
208     * in the mail session, this property is of type <code>String</code>
209     * rather than <code>InternetAddress</code>.
210     */
211    protected String bounceAddress;
212
213    /**
214     * Used to specify the mail headers.  Example:
215     *
216     * X-Mailer: Sendmail, X-Priority: 1( highest )
217     * or  2( high ) 3( normal ) 4( low ) and 5( lowest )
218     * Disposition-Notification-To: user@domain.net
219     */
220    protected Map headers = new HashMap();
221
222    /**
223     * Used to determine whether to use pop3 before smtp, and if so the settings.
224     */
225    protected boolean popBeforeSmtp;
226    /** the host name of the pop3 server */
227    protected String popHost;
228    /** the user name to log into the pop3 server */
229    protected String popUsername;
230    /** the password to log into the pop3 server */
231    protected String popPassword;
232
233    /** does server require TLS encryption for authentication */
234    protected boolean tls;
235    /** does the current transport use SSL encryption? */
236    protected boolean ssl;
237
238    /** socket I/O timeout value in milliseconds */
239    protected int socketTimeout;
240    /** socket connection timeout value in milliseconds */
241    protected int socketConnectionTimeout;
242
243    /** The Session to mail with */
244    private Session session;
245
246    /**
247     * Setting to true will enable the display of debug information.
248     *
249     * @param d A boolean.
250     * @since 1.0
251     */
252    public void setDebug(boolean d)
253    {
254        this.debug = d;
255    }
256
257    /**
258     * Sets the userName and password if authentication is needed.  If this
259     * method is not used, no authentication will be performed.
260     * <p>
261     * This method will create a new instance of
262     * <code>DefaultAuthenticator</code> using the supplied parameters.
263     *
264     * @param userName User name for the SMTP server
265     * @param password password for the SMTP server
266     * @see DefaultAuthenticator
267     * @see #setAuthenticator
268     * @since 1.0
269     */
270    public void setAuthentication(String userName, String password)
271    {
272        this.authenticator = new DefaultAuthenticator(userName, password);
273        this.setAuthenticator(this.authenticator);
274    }
275
276    /**
277     * Sets the <code>Authenticator</code> to be used when authentication
278     * is requested from the mail server.
279     * <p>
280     * This method should be used when your outgoing mail server requires
281     * authentication.  Your mail server must also support RFC2554.
282     *
283     * @param newAuthenticator the <code>Authenticator</code> object.
284     * @see Authenticator
285     * @since 1.0
286     */
287    public void setAuthenticator(Authenticator newAuthenticator)
288    {
289        this.authenticator = newAuthenticator;
290    }
291
292    /**
293     * Set the charset of the message.
294     *
295     * @param newCharset A String.
296     * @throws java.nio.charset.IllegalCharsetNameException if the charset name is invalid
297     * @throws java.nio.charset.UnsupportedCharsetException if no support for the named charset
298     * exists in the current JVM
299     * @since 1.0
300     */
301    public void setCharset(String newCharset)
302    {
303        Charset set = Charset.forName(newCharset);
304        this.charset = set.name();
305    }
306
307    /**
308     * Set the emailBody to a MimeMultiPart
309     *
310     * @param aMimeMultipart aMimeMultipart
311     * @since 1.0
312     */
313    public void setContent(MimeMultipart aMimeMultipart)
314    {
315        this.emailBody = aMimeMultipart;
316    }
317
318    /**
319     * Set the content & contentType
320     *
321     * @param   aObject aObject
322     * @param   aContentType aContentType
323     * @since 1.0
324     */
325    public void setContent(Object aObject, String aContentType)
326    {
327        this.content = aObject;
328        this.updateContentType(aContentType);
329    }
330
331
332    /**
333     * Update the contentType.
334     *
335     * @param   aContentType aContentType
336     * @since 1.2
337     */
338    public void updateContentType(final String aContentType)
339    {
340        if (EmailUtils.isEmpty(aContentType))
341        {
342            this.contentType = null;
343        }
344        else
345        {
346            // set the content type
347            this.contentType = aContentType;
348
349            // set the charset if the input was properly formed
350            String strMarker = "; charset=";
351            int charsetPos = aContentType.toLowerCase().indexOf(strMarker);
352
353            if (charsetPos != -1)
354            {
355                // find the next space (after the marker)
356                charsetPos += strMarker.length();
357                int intCharsetEnd =
358                    aContentType.toLowerCase().indexOf(" ", charsetPos);
359
360                if (intCharsetEnd != -1)
361                {
362                    this.charset =
363                        aContentType.substring(charsetPos, intCharsetEnd);
364                }
365                else
366                {
367                    this.charset = aContentType.substring(charsetPos);
368                }
369            }
370            else
371            {
372                // use the default charset, if one exists, for messages
373                // whose content-type is some form of text.
374                if (this.contentType.startsWith("text/") && EmailUtils.isNotEmpty(this.charset))
375                {
376                    StringBuffer contentTypeBuf = new StringBuffer(this.contentType);
377                    contentTypeBuf.append(strMarker);
378                    contentTypeBuf.append(this.charset);
379                    this.contentType = contentTypeBuf.toString();
380                }
381            }
382        }
383    }
384
385    /**
386     * Set the hostname of the outgoing mail server
387     *
388     * @param   aHostName aHostName
389     * @since 1.0
390     */
391    public void setHostName(String aHostName)
392    {
393        this.hostName = aHostName;
394    }
395
396    /**
397     * Set or disable the TLS encryption
398     *
399     * @param withTLS true if TLS needed, false otherwise
400     * @since 1.1
401     */
402    public void setTLS(boolean withTLS)
403    {
404        this.tls = withTLS;
405    }
406
407    /**
408     * Set the port number of the outgoing mail server.
409     * @param   aPortNumber aPortNumber
410     * @since 1.0
411     */
412    public void setSmtpPort(int aPortNumber)
413    {
414        if (aPortNumber < 1)
415        {
416            throw new IllegalArgumentException(
417                "Cannot connect to a port number that is less than 1 ( "
418                    + aPortNumber
419                    + " )");
420        }
421
422        this.smtpPort = Integer.toString(aPortNumber);
423    }
424
425    /**
426     * Supply a mail Session object to use. Please note that passing
427     * a username and password (in the case of mail authentication) will
428     * create a new mail session with a DefaultAuthenticator. This is a
429     * convience but might come unexpected.
430     *
431     * If mail authentication is used but NO username and password
432     * is supplied the implementation assumes that you have set a
433     * authenticator and will use the existing mail session (as expected).
434     *
435     * @param aSession mail session to be used
436     * @since 1.0
437     */
438    public void setMailSession(Session aSession)
439    {
440        EmailUtils.notNull(aSession, "no mail session supplied");
441
442        Properties sessionProperties = aSession.getProperties();
443        String auth = sessionProperties.getProperty(MAIL_SMTP_AUTH);
444
445        if ("true".equalsIgnoreCase(auth))
446        {
447            String userName = sessionProperties.getProperty(MAIL_SMTP_USER);
448            String password = sessionProperties.getProperty(MAIL_SMTP_PASSWORD);
449
450            if (EmailUtils.isNotEmpty(userName) && EmailUtils.isNotEmpty(password))
451            {
452                // only create a new mail session with an authenticator if
453                // authentication is required and no user name is given
454                this.authenticator = new DefaultAuthenticator(userName, password);
455                this.session = Session.getInstance(sessionProperties, this.authenticator);
456            }
457            else
458            {
459                // assume that the given mail session contains a working authenticator
460                this.session = aSession;
461            }
462        }
463        else
464        {
465            this.session = aSession;
466        }
467    }
468
469    /**
470     * Supply a mail Session object from a JNDI directory
471     * @param jndiName name of JNDI ressource (javax.mail.Session type), ressource
472     * if searched in java:comp/env if name dont start with "java:"
473     * @throws IllegalArgumentException JNDI name null or empty
474     * @throws NamingException ressource can be retrieved from JNDI directory
475     * @since 1.1
476     */
477    public void setMailSessionFromJNDI(String jndiName) throws NamingException
478    {
479        if (EmailUtils.isEmpty(jndiName))
480        {
481            throw new IllegalArgumentException("JNDI name missing");
482        }
483        Context ctx = null;
484        if (jndiName.startsWith("java:"))
485        {
486            ctx = new InitialContext();
487        }
488        else
489        {
490            ctx = (Context) new InitialContext().lookup("java:comp/env");
491
492        }
493        this.setMailSession((Session) ctx.lookup(jndiName));
494    }
495
496    /**
497     * Initialise a mailsession object
498     *
499     * @return A Session.
500     * @throws EmailException thrown when host name was not set.
501     * @since 1.0
502     */
503    public Session getMailSession() throws EmailException
504    {
505        if (this.session == null)
506        {
507            Properties properties = new Properties(System.getProperties());
508            properties.setProperty(MAIL_TRANSPORT_PROTOCOL, SMTP);
509
510            if (EmailUtils.isEmpty(this.hostName))
511            {
512                this.hostName = properties.getProperty(MAIL_HOST);
513            }
514
515            if (EmailUtils.isEmpty(this.hostName))
516            {
517                throw new EmailException(
518                    "Cannot find valid hostname for mail session");
519            }
520
521            properties.setProperty(MAIL_PORT, smtpPort);
522            properties.setProperty(MAIL_HOST, hostName);
523            properties.setProperty(MAIL_DEBUG, String.valueOf(this.debug));
524
525            if (this.authenticator != null)
526            {
527                properties.setProperty(MAIL_TRANSPORT_TLS, tls ? "true" : "false");
528                properties.setProperty(MAIL_SMTP_AUTH, "true");
529            }
530
531            if (this.ssl)
532            {
533                properties.setProperty(MAIL_PORT, sslSmtpPort);
534                properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, sslSmtpPort);
535                properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
536                properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false");
537            }
538
539            if (this.bounceAddress != null)
540            {
541                properties.setProperty(MAIL_SMTP_FROM, this.bounceAddress);
542            }
543
544            if (this.socketTimeout > 0)
545            {
546                properties.setProperty(MAIL_SMTP_TIMEOUT, Integer.toString(this.socketTimeout));
547            }
548
549            if (this.socketConnectionTimeout > 0)
550            {
551                properties.setProperty(MAIL_SMTP_CONNECTIONTIMEOUT, Integer.toString(this.socketConnectionTimeout));
552            }
553
554            // changed this (back) to getInstance due to security exceptions
555            // caused when testing using maven
556            this.session =
557                Session.getInstance(properties, this.authenticator);
558        }
559        return this.session;
560    }
561
562    /**
563     * Creates a InternetAddress.
564     *
565     * @param email An email address.
566     * @param name A name.
567     * @param charsetName The name of the charset to encode the name with.
568     * @return An internet address.
569     * @throws EmailException Thrown when the supplied address, name or charset were invalid.
570     */
571    private InternetAddress createInternetAddress(String email, String name, String charsetName)
572        throws EmailException
573    {
574        InternetAddress address = null;
575
576        try
577        {
578            address = new InternetAddress(email);
579
580            // check name input
581            if (EmailUtils.isEmpty(name))
582            {
583                name = email;
584            }
585
586            // check charset input.
587            if (EmailUtils.isEmpty(charsetName))
588            {
589                address.setPersonal(name);
590            }
591            else
592            {
593                // canonicalize the charset name and make sure
594                // the current platform supports it.
595                Charset set = Charset.forName(charsetName);
596                address.setPersonal(name, set.name());
597            }
598
599            // run sanity check on new InternetAddress object; if this fails
600            // it will throw AddressException.
601            address.validate();
602        }
603        catch (AddressException e)
604        {
605            throw new EmailException(e);
606        }
607        catch (UnsupportedEncodingException e)
608        {
609            throw new EmailException(e);
610        }
611        return address;
612    }
613
614
615    /**
616     * Set the FROM field of the email to use the specified address. The email
617     * address will also be used as the personal name.
618     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
619     * If it is not set, it will be encoded using
620     * the Java platform's default charset (UTF-16) if it contains
621     * non-ASCII characters; otherwise, it is used as is.
622     *
623     * @param email A String.
624     * @return An Email.
625     * @throws EmailException Indicates an invalid email address.
626     * @since 1.0
627     */
628    public Email setFrom(String email)
629        throws EmailException
630    {
631        return setFrom(email, null);
632    }
633
634    /**
635     * Set the FROM field of the email to use the specified address and the
636     * specified personal name.
637     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
638     * If it is not set, it will be encoded using
639     * the Java platform's default charset (UTF-16) if it contains
640     * non-ASCII characters; otherwise, it is used as is.
641     *
642     * @param email A String.
643     * @param name A String.
644     * @throws EmailException Indicates an invalid email address.
645     * @return An Email.
646     * @since 1.0
647     */
648    public Email setFrom(String email, String name)
649        throws EmailException
650    {
651        return setFrom(email, name, this.charset);
652    }
653
654    /**
655     * Set the FROM field of the email to use the specified address, personal
656     * name, and charset encoding for the name.
657     *
658     * @param email A String.
659     * @param name A String.
660     * @param charset The charset to encode the name with.
661     * @throws EmailException Indicates an invalid email address or charset.
662     * @return An Email.
663     * @since 1.1
664     */
665    public Email setFrom(String email, String name, String charset)
666        throws EmailException
667    {
668        this.fromAddress = createInternetAddress(email, name, charset);
669        return this;
670    }
671
672    /**
673     * Add a recipient TO to the email. The email
674     * address will also be used as the personal name.
675     * The name will be encoded by the charset of
676     * {@link #setCharset(java.lang.String) setCharset()}.
677     * If it is not set, it will be encoded using
678     * the Java platform's default charset (UTF-16) if it contains
679     * non-ASCII characters; otherwise, it is used as is.
680     *
681     * @param email A String.
682     * @throws EmailException Indicates an invalid email address.
683     * @return An Email.
684     * @since 1.0
685     */
686    public Email addTo(String email)
687        throws EmailException
688    {
689        return addTo(email, null);
690    }
691
692    /**
693     * Add a recipient TO to the email using the specified address and the
694     * specified personal name.
695     * The name will be encoded by the charset of
696     * {@link #setCharset(java.lang.String) setCharset()}.
697     * If it is not set, it will be encoded using
698     * the Java platform's default charset (UTF-16) if it contains
699     * non-ASCII characters; otherwise, it is used as is.
700     *
701     * @param email A String.
702     * @param name A String.
703     * @throws EmailException Indicates an invalid email address.
704     * @return An Email.
705     * @since 1.0
706     */
707    public Email addTo(String email, String name)
708        throws EmailException
709    {
710        return addTo(email, name, this.charset);
711    }
712
713    /**
714     * Add a recipient TO to the email using the specified address, personal
715     * name, and charset encoding for the name.
716     *
717     * @param email A String.
718     * @param name A String.
719     * @param charset The charset to encode the name with.
720     * @throws EmailException Indicates an invalid email address or charset.
721     * @return An Email.
722     * @since 1.1
723     */
724    public Email addTo(String email, String name, String charset)
725        throws EmailException
726    {
727        this.toList.add(createInternetAddress(email, name, charset));
728        return this;
729    }
730
731    /**
732     * Set a list of "TO" addresses. All elements in the specified
733     * <code>Collection</code> are expected to be of type
734     * <code>java.mail.internet.InternetAddress</code>.
735     *
736     * @param  aCollection collection of <code>InternetAddress</code> objects.
737     * @throws EmailException Indicates an invalid email address.
738     * @return An Email.
739     * @see javax.mail.internet.InternetAddress
740     * @since 1.0
741     */
742    public Email setTo(Collection aCollection) throws EmailException
743    {
744        if (aCollection == null || aCollection.isEmpty())
745        {
746            throw new EmailException("Address List provided was invalid");
747        }
748
749        this.toList = new ArrayList(aCollection);
750        return this;
751    }
752
753    /**
754     * Add a recipient CC to the email. The email
755     * address will also be used as the personal name.
756     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
757     * If it is not set, it will be encoded using
758     * the Java platform's default charset (UTF-16) if it contains
759     * non-ASCII characters; otherwise, it is used as is.
760     *
761     * @param email A String.
762     * @return An Email.
763     * @throws EmailException Indicates an invalid email address.
764     * @since 1.0
765     */
766    public Email addCc(String email)
767        throws EmailException
768    {
769        return this.addCc(email, null);
770    }
771
772    /**
773     * Add a recipient CC to the email using the specified address and the
774     * specified personal name.
775     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
776     * If it is not set, it will be encoded using
777     * the Java platform's default charset (UTF-16) if it contains
778     * non-ASCII characters; otherwise, it is used as is.
779     *
780     * @param email A String.
781     * @param name A String.
782     * @throws EmailException Indicates an invalid email address.
783     * @return An Email.
784     * @since 1.0
785     */
786    public Email addCc(String email, String name)
787        throws EmailException
788    {
789        return addCc(email, name, this.charset);
790    }
791
792    /**
793     * Add a recipient CC to the email using the specified address, personal
794     * name, and charset encoding for the name.
795     *
796     * @param email A String.
797     * @param name A String.
798     * @param charset The charset to encode the name with.
799     * @throws EmailException Indicates an invalid email address or charset.
800     * @return An Email.
801     * @since 1.1
802     */
803    public Email addCc(String email, String name, String charset)
804        throws EmailException
805    {
806        this.ccList.add(createInternetAddress(email, name, charset));
807        return this;
808    }
809
810    /**
811     * Set a list of "CC" addresses. All elements in the specified
812     * <code>Collection</code> are expected to be of type
813     * <code>java.mail.internet.InternetAddress</code>.
814     *
815     * @param aCollection collection of <code>InternetAddress</code> objects.
816     * @return An Email.
817     * @throws EmailException Indicates an invalid email address.
818     * @see javax.mail.internet.InternetAddress
819     * @since 1.0
820     */
821    public Email setCc(Collection aCollection) throws EmailException
822    {
823        if (aCollection == null || aCollection.isEmpty())
824        {
825            throw new EmailException("Address List provided was invalid");
826        }
827
828        this.ccList = new ArrayList(aCollection);
829        return this;
830    }
831
832    /**
833     * Add a blind BCC recipient to the email. The email
834     * address will also be used as the personal name.
835     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
836     * If it is not set, it will be encoded using
837     * the Java platform's default charset (UTF-16) if it contains
838     * non-ASCII characters; otherwise, it is used as is.
839     *
840     * @param email A String.
841     * @return An Email.
842     * @throws EmailException Indicates an invalid email address
843     * @since 1.0
844     */
845    public Email addBcc(String email)
846        throws EmailException
847    {
848        return this.addBcc(email, null);
849    }
850
851    /**
852     * Add a blind BCC recipient to the email using the specified address and
853     * the specified personal name.
854     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
855     * If it is not set, it will be encoded using
856     * the Java platform's default charset (UTF-16) if it contains
857     * non-ASCII characters; otherwise, it is used as is.
858     *
859     * @param email A String.
860     * @param name A String.
861     * @return An Email.
862     * @throws EmailException Indicates an invalid email address
863     * @since 1.0
864     */
865    public Email addBcc(String email, String name)
866        throws EmailException
867    {
868        return addBcc(email, name, this.charset);
869    }
870
871    /**
872     * Add a blind BCC recipient to the email using the specified address,
873     * personal name, and charset encoding for the name.
874     *
875     * @param email A String.
876     * @param name A String.
877     * @param charset The charset to encode the name with.
878     * @return An Email.
879     * @throws EmailException Indicates an invalid email address
880     * @since 1.1
881     */
882    public Email addBcc(String email, String name, String charset)
883        throws EmailException
884    {
885        this.bccList.add(createInternetAddress(email, name, charset));
886        return this;
887    }
888
889    /**
890     * Set a list of "BCC" addresses. All elements in the specified
891     * <code>Collection</code> are expected to be of type
892     * <code>java.mail.internet.InternetAddress</code>.
893     *
894     * @param   aCollection collection of <code>InternetAddress</code> objects
895     * @return  An Email.
896     * @throws EmailException Indicates an invalid email address
897     * @see javax.mail.internet.InternetAddress
898     * @since 1.0
899     */
900    public Email setBcc(Collection aCollection) throws EmailException
901    {
902        if (aCollection == null || aCollection.isEmpty())
903        {
904            throw new EmailException("Address List provided was invalid");
905        }
906
907        this.bccList = new ArrayList(aCollection);
908        return this;
909    }
910
911    /**
912     * Add a reply to address to the email. The email
913     * address will also be used as the personal name.
914     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
915     * If it is not set, it will be encoded using
916     * the Java platform's default charset (UTF-16) if it contains
917     * non-ASCII characters; otherwise, it is used as is.
918     *
919     * @param email A String.
920     * @return An Email.
921     * @throws EmailException Indicates an invalid email address
922     * @since 1.0
923     */
924    public Email addReplyTo(String email)
925        throws EmailException
926    {
927        return this.addReplyTo(email, null);
928    }
929
930    /**
931     * Add a reply to address to the email using the specified address and
932     * the specified personal name.
933     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
934     * If it is not set, it will be encoded using
935     * the Java platform's default charset (UTF-16) if it contains
936     * non-ASCII characters; otherwise, it is used as is.
937     *
938     * @param email A String.
939     * @param name A String.
940     * @return An Email.
941     * @throws EmailException Indicates an invalid email address
942     * @since 1.0
943     */
944    public Email addReplyTo(String email, String name)
945        throws EmailException
946    {
947        return addReplyTo(email, name, this.charset);
948    }
949
950    /**
951     * Add a reply to address to the email using the specified address,
952     * personal name, and charset encoding for the name.
953     *
954     * @param email A String.
955     * @param name A String.
956     * @param charset The charset to encode the name with.
957     * @return An Email.
958     * @throws EmailException Indicates an invalid email address or charset.
959     * @since 1.1
960     */
961    public Email addReplyTo(String email, String name, String charset)
962        throws EmailException
963    {
964        this.replyList.add(createInternetAddress(email, name, charset));
965        return this;
966    }
967
968    /**
969     * Set a list of reply to addresses. All elements in the specified
970     * <code>Collection</code> are expected to be of type
971     * <code>java.mail.internet.InternetAddress</code>.
972     *
973     * @param   aCollection collection of <code>InternetAddress</code> objects
974     * @return  An Email.
975     * @throws EmailException Indicates an invalid email address
976     * @see javax.mail.internet.InternetAddress
977     * @since 1.1
978     */
979    public Email setReplyTo(Collection aCollection) throws EmailException
980    {
981        if (aCollection == null || aCollection.isEmpty())
982        {
983            throw new EmailException("Address List provided was invalid");
984        }
985
986        this.replyList = new ArrayList(aCollection);
987        return this;
988    }
989
990    /**
991     * Used to specify the mail headers.  Example:
992     *
993     * X-Mailer: Sendmail, X-Priority: 1( highest )
994     * or  2( high ) 3( normal ) 4( low ) and 5( lowest )
995     * Disposition-Notification-To: user@domain.net
996     *
997     * @param map A Map.
998     * @since 1.0
999     */
1000    public void setHeaders(Map map)
1001    {
1002        Iterator iterKeyBad = map.entrySet().iterator();
1003
1004        while (iterKeyBad.hasNext())
1005        {
1006            Map.Entry entry = (Map.Entry) iterKeyBad.next();
1007            String strName = (String) entry.getKey();
1008            String strValue = (String) entry.getValue();
1009
1010            if (EmailUtils.isEmpty(strName))
1011            {
1012                throw new IllegalArgumentException("name can not be null");
1013            }
1014            if (EmailUtils.isEmpty(strValue))
1015            {
1016                throw new IllegalArgumentException("value can not be null");
1017            }
1018        }
1019
1020        // all is ok, update headers
1021        this.headers = map;
1022    }
1023
1024    /**
1025     * Adds a header ( name, value ) to the headers Map.
1026     *
1027     * @param name A String with the name.
1028     * @param value A String with the value.
1029     * @since 1.0
1030     */
1031    public void addHeader(String name, String value)
1032    {
1033        if (EmailUtils.isEmpty(name))
1034        {
1035            throw new IllegalArgumentException("name can not be null");
1036        }
1037        if (EmailUtils.isEmpty(value))
1038        {
1039            throw new IllegalArgumentException("value can not be null");
1040        }
1041
1042        this.headers.put(name, value);
1043    }
1044
1045    /**
1046     * Set the email subject.
1047     *
1048     * @param aSubject A String.
1049     * @return An Email.
1050     * @since 1.0
1051     */
1052    public Email setSubject(String aSubject)
1053    {
1054        this.subject = aSubject;
1055        return this;
1056    }
1057
1058    /**
1059     * Set the "bounce address" - the address to which undeliverable messages
1060     * will be returned.  If this value is never set, then the message will be
1061     * sent to the address specified with the System property "mail.smtp.from",
1062     * or if that value is not set, then to the "from" address.
1063     *
1064     * @param email A String.
1065     * @return An Email.
1066     * @since 1.0
1067     */
1068    public Email setBounceAddress(String email)
1069    {
1070        this.bounceAddress = email;
1071        return this;
1072    }
1073
1074
1075    /**
1076     * Define the content of the mail.  It should be overidden by the
1077     * subclasses.
1078     *
1079     * @param msg A String.
1080     * @return An Email.
1081     * @throws EmailException generic exception.
1082     * @since 1.0
1083     */
1084    public abstract Email setMsg(String msg) throws EmailException;
1085
1086    /**
1087     * Build the internal MimeMessage to be sent.
1088     *
1089     * @throws EmailException if there was an error.
1090     * @since 1.0
1091     */
1092    public void buildMimeMessage() throws EmailException
1093    {
1094        try
1095        {
1096            this.getMailSession();
1097            this.message = this.createMimeMessage(this.session);
1098
1099            if (EmailUtils.isNotEmpty(this.subject))
1100            {
1101                if (EmailUtils.isNotEmpty(this.charset))
1102                {
1103                    this.message.setSubject(this.subject, this.charset);
1104                }
1105                else
1106                {
1107                    this.message.setSubject(this.subject);
1108                }
1109            }
1110
1111            // update content type (and encoding)
1112            this.updateContentType(this.contentType);
1113
1114            if (this.content != null)
1115            {
1116                this.message.setContent(this.content, this.contentType);
1117            }
1118            else if (this.emailBody != null)
1119            {
1120                if (this.contentType == null)
1121                {
1122                    this.message.setContent(this.emailBody);
1123                }
1124                else
1125                {
1126                    this.message.setContent(this.emailBody, this.contentType);
1127                }
1128            }
1129            else
1130            {
1131                this.message.setContent("", Email.TEXT_PLAIN);
1132            }
1133
1134            if (this.fromAddress != null)
1135            {
1136                this.message.setFrom(this.fromAddress);
1137            }
1138            else
1139            {
1140                if (session.getProperty(MAIL_SMTP_FROM) == null)
1141                {
1142                    throw new EmailException("From address required");
1143                }
1144            }
1145
1146            if (this.toList.size() + this.ccList.size() + this.bccList.size() == 0)
1147            {
1148                throw new EmailException(
1149                            "At least one receiver address required");
1150            }
1151
1152            if (this.toList.size() > 0)
1153            {
1154                this.message.setRecipients(
1155                    Message.RecipientType.TO,
1156                    this.toInternetAddressArray(this.toList));
1157            }
1158
1159            if (this.ccList.size() > 0)
1160            {
1161                this.message.setRecipients(
1162                    Message.RecipientType.CC,
1163                    this.toInternetAddressArray(this.ccList));
1164            }
1165
1166            if (this.bccList.size() > 0)
1167            {
1168                this.message.setRecipients(
1169                    Message.RecipientType.BCC,
1170                    this.toInternetAddressArray(this.bccList));
1171            }
1172
1173            if (this.replyList.size() > 0)
1174            {
1175                this.message.setReplyTo(
1176                    this.toInternetAddressArray(this.replyList));
1177            }
1178
1179            if (this.headers.size() > 0)
1180            {
1181                Iterator iterHeaderKeys = this.headers.keySet().iterator();
1182                while (iterHeaderKeys.hasNext())
1183                {
1184                    String name = (String) iterHeaderKeys.next();
1185                    String value = (String) headers.get(name);
1186                    this.message.addHeader(name, value);
1187                }
1188            }
1189
1190            if (this.message.getSentDate() == null)
1191            {
1192                this.message.setSentDate(getSentDate());
1193            }
1194
1195            if (this.popBeforeSmtp)
1196            {
1197                Store store = session.getStore("pop3");
1198                store.connect(this.popHost, this.popUsername, this.popPassword);
1199            }
1200        }
1201        catch (MessagingException me)
1202        {
1203            throw new EmailException(me);
1204        }
1205    }
1206
1207    /**
1208     * Factory method to create a customized MimeMessage which can be
1209     * implemented by a derived class, e.g. to set the message id.
1210     *
1211     * @param aSession mail session to be used
1212     * @return the newly created message
1213     */
1214    protected MimeMessage createMimeMessage(Session aSession)
1215    {
1216        return new MimeMessage(aSession);
1217    }
1218
1219    /**
1220     * Sends the previously created MimeMessage to the SMTP server.
1221     *
1222     * @return the message id of the underlying MimeMessage
1223     * @throws EmailException the sending failed
1224     */
1225    public String sendMimeMessage()
1226       throws EmailException
1227    {
1228        EmailUtils.notNull(this.message, "message");
1229
1230        try
1231        {
1232            Transport.send(this.message);
1233            return this.message.getMessageID();
1234        }
1235        catch (Throwable t)
1236        {
1237            String msg = "Sending the email to the following server failed : "
1238                + this.getHostName()
1239                + ":"
1240                + this.getSmtpPort();
1241
1242            throw new EmailException(msg, t);
1243        }
1244    }
1245
1246    /**
1247     * Returns the internal MimeMessage. Please not that the
1248     * MimeMessage is build by the buildMimeMessage() method.
1249     *
1250     * @return the MimeMessage
1251     */
1252    public MimeMessage getMimeMessage()
1253    {
1254        return this.message;
1255    }
1256
1257    /**
1258     * Sends the email. Internally we build a MimeMessage
1259     * which is afterwards sent to the SMTP server.
1260     *
1261     * @return the message id of the underlying MimeMessage
1262     * @throws EmailException the sending failed
1263     */
1264    public String send() throws EmailException
1265    {
1266        this.buildMimeMessage();
1267        return this.sendMimeMessage();
1268    }
1269
1270    /**
1271     * Sets the sent date for the email.  The sent date will default to the
1272     * current date if not explictly set.
1273     *
1274     * @param date Date to use as the sent date on the email
1275     * @since 1.0
1276     */
1277    public void setSentDate(Date date)
1278    {
1279        if (date != null)
1280        {
1281            // create a seperate instance to keep findbugs happy
1282            this.sentDate = new Date(date.getTime());
1283        }
1284    }
1285
1286    /**
1287     * Gets the sent date for the email.
1288     *
1289     * @return date to be used as the sent date for the email
1290     * @since 1.0
1291     */
1292    public Date getSentDate()
1293    {
1294        if (this.sentDate == null)
1295        {
1296            return new Date();
1297        }
1298        return new Date(this.sentDate.getTime());
1299    }
1300
1301    /**
1302     * Gets the subject of the email.
1303     *
1304     * @return email subject
1305     */
1306    public String getSubject()
1307    {
1308        return this.subject;
1309    }
1310
1311    /**
1312     * Gets the sender of the email.
1313     *
1314     * @return from address
1315     */
1316    public InternetAddress getFromAddress()
1317    {
1318        return this.fromAddress;
1319    }
1320
1321    /**
1322     * Gets the host name of the SMTP server,
1323     *
1324     * @return host name
1325     */
1326    public String getHostName()
1327    {
1328        if (EmailUtils.isNotEmpty(this.hostName))
1329        {
1330            return this.hostName;
1331        }
1332        else if (this.session != null)
1333        {
1334            return this.session.getProperty(MAIL_HOST);
1335        }
1336        return null;
1337    }
1338
1339    /**
1340     * Gets the listening port of the SMTP server.
1341     *
1342     * @return smtp port
1343     */
1344    public String getSmtpPort()
1345    {
1346        if (EmailUtils.isNotEmpty(this.smtpPort))
1347        {
1348            return this.smtpPort;
1349        }
1350        else if (this.session != null)
1351        {
1352            return this.session.getProperty(MAIL_PORT);
1353        }
1354        return null;
1355    }
1356
1357    /**
1358     * Gets encryption mode for authentication
1359     *
1360     * @return true if using TLS for authentication, false otherwise
1361     * @since 1.1
1362     */
1363    public boolean isTLS()
1364    {
1365        return this.tls;
1366    }
1367
1368    /**
1369     * Utility to copy List of known InternetAddress objects into an
1370     * array.
1371     *
1372     * @param list A List.
1373     * @return An InternetAddress[].
1374     * @since 1.0
1375     */
1376    protected InternetAddress[] toInternetAddressArray(List list)
1377    {
1378        InternetAddress[] ia =
1379            (InternetAddress[]) list.toArray(new InternetAddress[list.size()]);
1380
1381        return ia;
1382    }
1383
1384    /**
1385     * Set details regarding "pop3 before smtp" authentication.
1386     *
1387     * @param newPopBeforeSmtp Wether or not to log into pop3
1388     *      server before sending mail.
1389     * @param newPopHost The pop3 host to use.
1390     * @param newPopUsername The pop3 username.
1391     * @param newPopPassword The pop3 password.
1392     * @since 1.0
1393     */
1394    public void setPopBeforeSmtp(
1395        boolean newPopBeforeSmtp,
1396        String newPopHost,
1397        String newPopUsername,
1398        String newPopPassword)
1399    {
1400        this.popBeforeSmtp = newPopBeforeSmtp;
1401        this.popHost = newPopHost;
1402        this.popUsername = newPopUsername;
1403        this.popPassword = newPopPassword;
1404    }
1405
1406    /**
1407     * Returns whether SSL encryption for the transport is currently enabled.
1408     * @return true if SSL enabled for the transport
1409     */
1410    public boolean isSSL()
1411    {
1412        return ssl;
1413    }
1414
1415    /**
1416     * Sets whether SSL encryption should be enabled for the SMTP transport.
1417     * @param ssl whether to enable the SSL transport
1418     */
1419    public void setSSL(boolean ssl)
1420    {
1421        this.ssl = ssl;
1422    }
1423
1424    /**
1425     * Returns the current SSL port used by the SMTP transport.
1426     * @return the current SSL port used by the SMTP transport
1427     */
1428    public String getSslSmtpPort()
1429    {
1430        if (EmailUtils.isNotEmpty(this.sslSmtpPort))
1431        {
1432            return this.sslSmtpPort;
1433        }
1434        else if (this.session != null)
1435        {
1436            return this.session.getProperty(MAIL_SMTP_SOCKET_FACTORY_PORT);
1437        }
1438        return null;
1439    }
1440
1441    /**
1442     * Sets the SSL port to use for the SMTP transport. Defaults to the standard
1443     * port, 465.
1444     * @param sslSmtpPort the SSL port to use for the SMTP transport
1445     */
1446    public void setSslSmtpPort(String sslSmtpPort)
1447    {
1448        this.sslSmtpPort = sslSmtpPort;
1449    }
1450
1451    /**
1452     * Get the list of "To" addresses.
1453     *
1454     * @return List addresses
1455     */
1456    public List getToAddresses()
1457    {
1458        return this.toList;
1459    }
1460
1461    /**
1462     * Get the list of "CC" addresses.
1463     *
1464     * @return List addresses
1465     */
1466    public List getCcAddresses()
1467    {
1468        return this.ccList;
1469    }
1470
1471    /**
1472     * Get the list of "Bcc" addresses.
1473     *
1474     * @return List addresses
1475     */
1476    public List getBccAddresses()
1477    {
1478        return this.bccList;
1479    }
1480
1481    /**
1482     * Get the list of "Reply-To" addresses.
1483     *
1484     * @return List addresses
1485     */
1486    public List getReplyToAddresses()
1487    {
1488        return this.replyList;
1489    }
1490
1491    /**
1492     * Get the socket connection timeout value in milliseconds.
1493     *
1494     * @return the timeout in milliseconds.
1495     * @since 1.2
1496     */
1497    public int getSocketConnectionTimeout()
1498    {
1499        return this.socketConnectionTimeout;
1500    }
1501
1502    /**
1503     * Set the socket connection timeout value in milliseconds.
1504     * Default is infinite timeout.
1505     *
1506     * @param socketConnectionTimeout the connection timeout
1507     * @since 1.2
1508     */
1509    public void setSocketConnectionTimeout(int socketConnectionTimeout)
1510    {
1511        this.socketConnectionTimeout = socketConnectionTimeout;
1512    }
1513
1514    /**
1515     * Get the socket I/O timeout value in milliseconds.
1516     *
1517     * @return the socket I/O timeout
1518     * @since 1.2
1519     */
1520    public int getSocketTimeout()
1521    {
1522        return this.socketTimeout;
1523    }
1524
1525    /**
1526     * Set the socket I/O timeout value in milliseconds.
1527     * Default is infinite timeout.
1528     *
1529     * @param socketTimeout the socket I/O timeout
1530     * @since 1.2
1531     */
1532    public void setSocketTimeout(int socketTimeout)
1533    {
1534        this.socketTimeout = socketTimeout;
1535    }
1536}