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     */
017    package org.apache.commons.mail;
018    
019    import java.io.UnsupportedEncodingException;
020    import java.nio.charset.Charset;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Date;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Properties;
029    
030    import javax.mail.Authenticator;
031    import javax.mail.Message;
032    import javax.mail.MessagingException;
033    import javax.mail.Session;
034    import javax.mail.Store;
035    import javax.mail.Transport;
036    import javax.mail.internet.AddressException;
037    import javax.mail.internet.InternetAddress;
038    import javax.mail.internet.MimeMessage;
039    import javax.mail.internet.MimeMultipart;
040    import javax.naming.Context;
041    import javax.naming.InitialContext;
042    import 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     */
063    public 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    }