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 = EmailUtils.replaceEndOfLineCharactersWithSpaces(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        if (email != null && !email.isEmpty())
1071        {
1072            try
1073            {
1074                 this.bounceAddress = createInternetAddress(email, null, this.charset).getAddress();
1075            }
1076            catch (final EmailException e)
1077            {
1078                // Can't throw 'EmailException' to keep backward-compatibility                                                      
1079                throw new IllegalArgumentException("Failed to set the bounce address : " + email, e);
1080            }
1081        }
1082        else
1083            {
1084                this.bounceAddress = email;
1085            }
1086
1087        return this;
1088    }
1089
1090
1091    /**
1092     * Define the content of the mail.  It should be overidden by the
1093     * subclasses.
1094     *
1095     * @param msg A String.
1096     * @return An Email.
1097     * @throws EmailException generic exception.
1098     * @since 1.0
1099     */
1100    public abstract Email setMsg(String msg) throws EmailException;
1101
1102    /**
1103     * Build the internal MimeMessage to be sent.
1104     *
1105     * @throws EmailException if there was an error.
1106     * @since 1.0
1107     */
1108    public void buildMimeMessage() throws EmailException
1109    {
1110        try
1111        {
1112            this.getMailSession();
1113            this.message = this.createMimeMessage(this.session);
1114
1115            if (EmailUtils.isNotEmpty(this.subject))
1116            {
1117                if (EmailUtils.isNotEmpty(this.charset))
1118                {
1119                    this.message.setSubject(this.subject, this.charset);
1120                }
1121                else
1122                {
1123                    this.message.setSubject(this.subject);
1124                }
1125            }
1126
1127            // update content type (and encoding)
1128            this.updateContentType(this.contentType);
1129
1130            if (this.content != null)
1131            {
1132                this.message.setContent(this.content, this.contentType);
1133            }
1134            else if (this.emailBody != null)
1135            {
1136                if (this.contentType == null)
1137                {
1138                    this.message.setContent(this.emailBody);
1139                }
1140                else
1141                {
1142                    this.message.setContent(this.emailBody, this.contentType);
1143                }
1144            }
1145            else
1146            {
1147                this.message.setContent("", Email.TEXT_PLAIN);
1148            }
1149
1150            if (this.fromAddress != null)
1151            {
1152                this.message.setFrom(this.fromAddress);
1153            }
1154            else
1155            {
1156                if (session.getProperty(MAIL_SMTP_FROM) == null)
1157                {
1158                    throw new EmailException("From address required");
1159                }
1160            }
1161
1162            if (this.toList.size() + this.ccList.size() + this.bccList.size() == 0)
1163            {
1164                throw new EmailException(
1165                            "At least one receiver address required");
1166            }
1167
1168            if (this.toList.size() > 0)
1169            {
1170                this.message.setRecipients(
1171                    Message.RecipientType.TO,
1172                    this.toInternetAddressArray(this.toList));
1173            }
1174
1175            if (this.ccList.size() > 0)
1176            {
1177                this.message.setRecipients(
1178                    Message.RecipientType.CC,
1179                    this.toInternetAddressArray(this.ccList));
1180            }
1181
1182            if (this.bccList.size() > 0)
1183            {
1184                this.message.setRecipients(
1185                    Message.RecipientType.BCC,
1186                    this.toInternetAddressArray(this.bccList));
1187            }
1188
1189            if (this.replyList.size() > 0)
1190            {
1191                this.message.setReplyTo(
1192                    this.toInternetAddressArray(this.replyList));
1193            }
1194
1195            if (this.headers.size() > 0)
1196            {
1197                Iterator iterHeaderKeys = this.headers.keySet().iterator();
1198                while (iterHeaderKeys.hasNext())
1199                {
1200                    String name = (String) iterHeaderKeys.next();
1201                    String value = (String) headers.get(name);
1202                    this.message.addHeader(name, value);
1203                }
1204            }
1205
1206            if (this.message.getSentDate() == null)
1207            {
1208                this.message.setSentDate(getSentDate());
1209            }
1210
1211            if (this.popBeforeSmtp)
1212            {
1213                Store store = session.getStore("pop3");
1214                store.connect(this.popHost, this.popUsername, this.popPassword);
1215            }
1216        }
1217        catch (MessagingException me)
1218        {
1219            throw new EmailException(me);
1220        }
1221    }
1222
1223    /**
1224     * Factory method to create a customized MimeMessage which can be
1225     * implemented by a derived class, e.g. to set the message id.
1226     *
1227     * @param aSession mail session to be used
1228     * @return the newly created message
1229     */
1230    protected MimeMessage createMimeMessage(Session aSession)
1231    {
1232        return new MimeMessage(aSession);
1233    }
1234
1235    /**
1236     * Sends the previously created MimeMessage to the SMTP server.
1237     *
1238     * @return the message id of the underlying MimeMessage
1239     * @throws EmailException the sending failed
1240     */
1241    public String sendMimeMessage()
1242       throws EmailException
1243    {
1244        EmailUtils.notNull(this.message, "message");
1245
1246        try
1247        {
1248            Transport.send(this.message);
1249            return this.message.getMessageID();
1250        }
1251        catch (Throwable t)
1252        {
1253            String msg = "Sending the email to the following server failed : "
1254                + this.getHostName()
1255                + ":"
1256                + this.getSmtpPort();
1257
1258            throw new EmailException(msg, t);
1259        }
1260    }
1261
1262    /**
1263     * Returns the internal MimeMessage. Please not that the
1264     * MimeMessage is build by the buildMimeMessage() method.
1265     *
1266     * @return the MimeMessage
1267     */
1268    public MimeMessage getMimeMessage()
1269    {
1270        return this.message;
1271    }
1272
1273    /**
1274     * Sends the email. Internally we build a MimeMessage
1275     * which is afterwards sent to the SMTP server.
1276     *
1277     * @return the message id of the underlying MimeMessage
1278     * @throws EmailException the sending failed
1279     */
1280    public String send() throws EmailException
1281    {
1282        this.buildMimeMessage();
1283        return this.sendMimeMessage();
1284    }
1285
1286    /**
1287     * Sets the sent date for the email.  The sent date will default to the
1288     * current date if not explictly set.
1289     *
1290     * @param date Date to use as the sent date on the email
1291     * @since 1.0
1292     */
1293    public void setSentDate(Date date)
1294    {
1295        if (date != null)
1296        {
1297            // create a seperate instance to keep findbugs happy
1298            this.sentDate = new Date(date.getTime());
1299        }
1300    }
1301
1302    /**
1303     * Gets the sent date for the email.
1304     *
1305     * @return date to be used as the sent date for the email
1306     * @since 1.0
1307     */
1308    public Date getSentDate()
1309    {
1310        if (this.sentDate == null)
1311        {
1312            return new Date();
1313        }
1314        return new Date(this.sentDate.getTime());
1315    }
1316
1317    /**
1318     * Gets the subject of the email.
1319     *
1320     * @return email subject
1321     */
1322    public String getSubject()
1323    {
1324        return this.subject;
1325    }
1326
1327    /**
1328     * Gets the sender of the email.
1329     *
1330     * @return from address
1331     */
1332    public InternetAddress getFromAddress()
1333    {
1334        return this.fromAddress;
1335    }
1336
1337    /**
1338     * Gets the host name of the SMTP server,
1339     *
1340     * @return host name
1341     */
1342    public String getHostName()
1343    {
1344        if (EmailUtils.isNotEmpty(this.hostName))
1345        {
1346            return this.hostName;
1347        }
1348        else if (this.session != null)
1349        {
1350            return this.session.getProperty(MAIL_HOST);
1351        }
1352        return null;
1353    }
1354
1355    /**
1356     * Gets the listening port of the SMTP server.
1357     *
1358     * @return smtp port
1359     */
1360    public String getSmtpPort()
1361    {
1362        if (EmailUtils.isNotEmpty(this.smtpPort))
1363        {
1364            return this.smtpPort;
1365        }
1366        else if (this.session != null)
1367        {
1368            return this.session.getProperty(MAIL_PORT);
1369        }
1370        return null;
1371    }
1372
1373    /**
1374     * Gets encryption mode for authentication
1375     *
1376     * @return true if using TLS for authentication, false otherwise
1377     * @since 1.1
1378     */
1379    public boolean isTLS()
1380    {
1381        return this.tls;
1382    }
1383
1384    /**
1385     * Utility to copy List of known InternetAddress objects into an
1386     * array.
1387     *
1388     * @param list A List.
1389     * @return An InternetAddress[].
1390     * @since 1.0
1391     */
1392    protected InternetAddress[] toInternetAddressArray(List list)
1393    {
1394        InternetAddress[] ia =
1395            (InternetAddress[]) list.toArray(new InternetAddress[list.size()]);
1396
1397        return ia;
1398    }
1399
1400    /**
1401     * Set details regarding "pop3 before smtp" authentication.
1402     *
1403     * @param newPopBeforeSmtp Wether or not to log into pop3
1404     *      server before sending mail.
1405     * @param newPopHost The pop3 host to use.
1406     * @param newPopUsername The pop3 username.
1407     * @param newPopPassword The pop3 password.
1408     * @since 1.0
1409     */
1410    public void setPopBeforeSmtp(
1411        boolean newPopBeforeSmtp,
1412        String newPopHost,
1413        String newPopUsername,
1414        String newPopPassword)
1415    {
1416        this.popBeforeSmtp = newPopBeforeSmtp;
1417        this.popHost = newPopHost;
1418        this.popUsername = newPopUsername;
1419        this.popPassword = newPopPassword;
1420    }
1421
1422    /**
1423     * Returns whether SSL encryption for the transport is currently enabled.
1424     * @return true if SSL enabled for the transport
1425     */
1426    public boolean isSSL()
1427    {
1428        return ssl;
1429    }
1430
1431    /**
1432     * Sets whether SSL encryption should be enabled for the SMTP transport.
1433     * @param ssl whether to enable the SSL transport
1434     */
1435    public void setSSL(boolean ssl)
1436    {
1437        this.ssl = ssl;
1438    }
1439
1440    /**
1441     * Returns the current SSL port used by the SMTP transport.
1442     * @return the current SSL port used by the SMTP transport
1443     */
1444    public String getSslSmtpPort()
1445    {
1446        if (EmailUtils.isNotEmpty(this.sslSmtpPort))
1447        {
1448            return this.sslSmtpPort;
1449        }
1450        else if (this.session != null)
1451        {
1452            return this.session.getProperty(MAIL_SMTP_SOCKET_FACTORY_PORT);
1453        }
1454        return null;
1455    }
1456
1457    /**
1458     * Sets the SSL port to use for the SMTP transport. Defaults to the standard
1459     * port, 465.
1460     * @param sslSmtpPort the SSL port to use for the SMTP transport
1461     */
1462    public void setSslSmtpPort(String sslSmtpPort)
1463    {
1464        this.sslSmtpPort = sslSmtpPort;
1465    }
1466
1467    /**
1468     * Get the list of "To" addresses.
1469     *
1470     * @return List addresses
1471     */
1472    public List getToAddresses()
1473    {
1474        return this.toList;
1475    }
1476
1477    /**
1478     * Get the list of "CC" addresses.
1479     *
1480     * @return List addresses
1481     */
1482    public List getCcAddresses()
1483    {
1484        return this.ccList;
1485    }
1486
1487    /**
1488     * Get the list of "Bcc" addresses.
1489     *
1490     * @return List addresses
1491     */
1492    public List getBccAddresses()
1493    {
1494        return this.bccList;
1495    }
1496
1497    /**
1498     * Get the list of "Reply-To" addresses.
1499     *
1500     * @return List addresses
1501     */
1502    public List getReplyToAddresses()
1503    {
1504        return this.replyList;
1505    }
1506
1507    /**
1508     * Get the socket connection timeout value in milliseconds.
1509     *
1510     * @return the timeout in milliseconds.
1511     * @since 1.2
1512     */
1513    public int getSocketConnectionTimeout()
1514    {
1515        return this.socketConnectionTimeout;
1516    }
1517
1518    /**
1519     * Set the socket connection timeout value in milliseconds.
1520     * Default is infinite timeout.
1521     *
1522     * @param socketConnectionTimeout the connection timeout
1523     * @since 1.2
1524     */
1525    public void setSocketConnectionTimeout(int socketConnectionTimeout)
1526    {
1527        this.socketConnectionTimeout = socketConnectionTimeout;
1528    }
1529
1530    /**
1531     * Get the socket I/O timeout value in milliseconds.
1532     *
1533     * @return the socket I/O timeout
1534     * @since 1.2
1535     */
1536    public int getSocketTimeout()
1537    {
1538        return this.socketTimeout;
1539    }
1540
1541    /**
1542     * Set the socket I/O timeout value in milliseconds.
1543     * Default is infinite timeout.
1544     *
1545     * @param socketTimeout the socket I/O timeout
1546     * @since 1.2
1547     */
1548    public void setSocketTimeout(int socketTimeout)
1549    {
1550        this.socketTimeout = socketTimeout;
1551    }
1552}