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