/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2009-2012 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2012 The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * OpenNMS(R) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenNMS(R).  If not, see:
 *      http://www.gnu.org/licenses/
 *
 * For more information contact:
 *     OpenNMS(R) Licensing <license@opennms.org>
 *     http://www.opennms.org/
 *     http://www.opennms.com/
 *******************************************************************************/

package org.opennms.javamail;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Part;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.event.TransportEvent;
import javax.mail.event.TransportListener;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.opennms.core.utils.PropertiesUtils;
import org.opennms.core.utils.ThreadCategory;
import org.springframework.util.StringUtils;

/**
 * Sends an email message using the Java Mail API
 *
 * @author <A HREF="mailto:david@opennms.org">David Hustace </A>
 * @version $Id: $
 */
public class JavaMailer {
    private static final String DEFAULT_FROM_ADDRESS = "root@[127.0.0.1]";
//  private static final String DEFAULT_TO_ADDRESS = "root@[127.0.0.1]";
    private static final String DEFAULT_MAIL_HOST = "127.0.0.1";
    private static final boolean DEFAULT_AUTHENTICATE = false;
    private static final String DEFAULT_AUTHENTICATE_USER = "opennms";
    private static final String DEFAULT_AUTHENTICATE_PASSWORD = "opennms";
    private static final String DEFAULT_MAILER = "smtpsend";
    private static final String DEFAULT_TRANSPORT = "smtp";
    private static final boolean DEFAULT_MAILER_DEBUG = false;
    private static final boolean DEFAULT_USE_JMTA = true;
    private static final String DEFAULT_CONTENT_TYPE = "text/plain";
    private static final String DEFAULT_CHARSET = "us-ascii";
    private static final String DEFAULT_ENCODING = "Q"; // I think this means quoted-printable encoding, see bug 2825
    private static final boolean DEFAULT_STARTTLS_ENABLE = false;
    private static final boolean DEFAULT_QUIT_WAIT = true;
    private static final int DEFAULT_SMTP_PORT = 25;
    private static final boolean DEFAULT_SMTP_SSL_ENABLE = false;

    private Session m_session = null;

    /*
     * properties from configuration
     */
    private Properties m_mailProps;
    
    /*
     * fields from properties used for deterministic behavior of the mailer
     */
    private boolean m_debug;
    private String m_mailHost;
    private boolean m_useJMTA;
    private String m_mailer;
    private String m_transport;
    private String m_from;
    private boolean m_authenticate;
    private String m_user;
    private String m_password;
    private String m_contentType;
    private String m_charSet;
    private String m_encoding;
    private boolean m_startTlsEnabled;
    private boolean m_quitWait;
    private int m_smtpPort;
    private boolean m_smtpSsl;

    /*
     * Basic messaging fields
     */
    private String m_to;
    private String m_subject;
    private String m_messageText;
    private String m_fileName;
    private InputStream m_inputStream;
    private String m_inputStreamName;
    private String m_inputStreamContentType;
    
    private Map<String,String> m_extraHeaders = new HashMap<String,String>();

    
    /**
     * <p>Constructor for JavaMailer.</p>
     *
     * @param javamailProps a {@link java.util.Properties} object.
     * @throws org.opennms.javamail.JavaMailerException if any.
     */
    public JavaMailer(Properties javamailProps) throws JavaMailerException {
        
        try {
            configureProperties(javamailProps);
        } catch (IOException e) {
            throw new JavaMailerException("Failed to construct mailer", e);
        }
        
        //Now set the properties into the session
        m_session = Session.getInstance(getMailProps(), createAuthenticator());
    }

    /**
     * Default constructor.  Default properties from javamailer-properties are set into session.  To change these
     * properties, retrieve the current properties from the session and override as needed.
     *
     * @throws IOException if any.
     * @throws org.opennms.javamail.JavaMailerException if any.
     */
    public JavaMailer() throws JavaMailerException {
        this(new Properties());
    }

    /**
     * This method uses a properties file reader to pull in opennms styled javamail properties and sets
     * the actual javamail properties.  This is here to preserve the backwards compatibility but configuration
     * will probably change soon.
     * 
     * @throws IOException
     */
    
    private void configureProperties(Properties javamailProps) throws IOException {
        
        //this loads the opennms defined properties
        m_mailProps = JavaMailerConfig.getProperties();
        
        //this sets any javamail defined properties sent in to the constructor
        m_mailProps.putAll(javamailProps);
        
        /*
         * fields from properties used for deterministic behavior of the mailer
         */
        m_debug = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.debug", DEFAULT_MAILER_DEBUG);
        m_mailHost = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.mailHost", DEFAULT_MAIL_HOST);
        m_useJMTA = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.useJMTA", DEFAULT_USE_JMTA);
        m_mailer = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.mailer", DEFAULT_MAILER);
        m_transport = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.transport", DEFAULT_TRANSPORT);
        m_from = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.fromAddress", DEFAULT_FROM_ADDRESS);
        m_authenticate = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.authenticate", DEFAULT_AUTHENTICATE);
        m_user = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.authenticateUser", DEFAULT_AUTHENTICATE_USER);
        m_password = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.authenticatePassword", DEFAULT_AUTHENTICATE_PASSWORD);
        m_contentType = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.messageContentType", DEFAULT_CONTENT_TYPE);
        m_charSet = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.charset", DEFAULT_CHARSET);
        m_encoding = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.encoding", DEFAULT_ENCODING);
        m_startTlsEnabled = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.starttls.enable", DEFAULT_STARTTLS_ENABLE);
        m_quitWait = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.quitwait", DEFAULT_QUIT_WAIT);
        m_smtpPort = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.smtpport", DEFAULT_SMTP_PORT);
        m_smtpSsl = PropertiesUtils.getProperty(m_mailProps, "org.opennms.core.utils.smtpssl.enable", DEFAULT_SMTP_SSL_ENABLE);

        //Set the actual JavaMailProperties... any that are defined in the file will not be overridden
        //Eventually, all configuration will be defined in properties and this strange parsing will not happen
        //TODO: fix this craziness!
        
        if (!m_mailProps.containsKey("mail.smtp.auth")) {
            m_mailProps.setProperty("mail.smtp.auth", String.valueOf(isAuthenticate()));
        }
        if (!m_mailProps.containsKey("mail.smtp.starttls.enable")) {
            m_mailProps.setProperty("mail.smtp.starttls.enable", String.valueOf(isStartTlsEnabled()));
        }
        if (!m_mailProps.containsKey("mail.smtp.quitwait")) {
            m_mailProps.setProperty("mail.smtp.quitwait", String.valueOf(isQuitWait()));
        }
        if (!m_mailProps.containsKey("mail.smtp.port")) {
            m_mailProps.setProperty("mail.smtp.port", String.valueOf(getSmtpPort()));
        }
        if (isSmtpSsl()) {
            if (!m_mailProps.containsKey("mail.smtps.auth")) {
                m_mailProps.setProperty("mail.smtps.auth", String.valueOf(isAuthenticate()));
            }
            if (!m_mailProps.containsKey("mail.smtps.socketFactory.class")) {
                m_mailProps.setProperty("mail.smtps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            }
            if (!m_mailProps.containsKey("mail.smtps.socketFactory.port")) {
                m_mailProps.setProperty("mail.smtps.socketFactory.port", String.valueOf(getSmtpPort()));
            }
//            if (!getMailProps().containsKey("mail.smtp.socketFactory.fallback")) {
//                getMailProps().setProperty("mail.smtp.socketFactory.fallback", "false");
//            }
        }
        
        if (!m_mailProps.containsKey("mail.smtp.quitwait")) {
            m_mailProps.setProperty("mail.smtp.quitwait", "true");
        }
        //getMailProps().setProperty("mail.store.protocol", "pop3");
        
    }

    /**
     * Sends a message based on properties set on this bean.
     *
     * @throws org.opennms.javamail.JavaMailerException if any.
     */
    public void mailSend() throws JavaMailerException {
        log().debug(createSendLogMsg());        
        sendMessage(buildMessage());
    }

    /**
     * Helper method to create an Authenticator based on Password Authentication
     *
     * @return a {@link javax.mail.Authenticator} object.
     */
    public Authenticator createAuthenticator() {
        Authenticator auth;
        if (isAuthenticate()) {
            auth = new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(getUser(), getPassword());
                }
            };
        } else {
            auth = null;
        }
        return auth;
    }

    /**
     * Build a complete message ready for sending.
     *
     * @return completed message, ready to be passed to Transport.sendMessage
     * @throws org.opennms.javamail.JavaMailerException if any of the underlying operations fail
     */
    public Message buildMessage() throws JavaMailerException {
        try {
            checkEnvelopeAndContents();
            MimeMessage message = initializeMessage();

            // The next line has been commented, because it prevents the usage of internationalized characters and makes the email unreadable.
            // String encodedText = MimeUtility.encodeText(getMessageText(), m_charSet, m_encoding);
            String encodedText = getMessageText();
            if ((getFileName() == null) && (getInputStream() == null))  {
                message.setContent(encodedText, m_contentType+"; charset="+m_charSet);
            } else if (getFileName() == null) {
                BodyPart streamBodyPart = new MimeBodyPart();
                streamBodyPart.setDataHandler(new DataHandler(new InputStreamDataSource(m_inputStreamName, m_inputStreamContentType, m_inputStream)));
                streamBodyPart.setFileName(m_inputStreamName);
                streamBodyPart.setHeader("Content-Transfer-Encoding", "base64");  
                streamBodyPart.setDisposition(Part.ATTACHMENT); 
                MimeMultipart mp = new MimeMultipart();
                mp.addBodyPart(streamBodyPart);
                message.setContent(mp);
            } else {
                BodyPart bp = new MimeBodyPart();
                bp.setContent(encodedText, m_contentType+"; charset="+m_charSet);
                MimeMultipart mp = new MimeMultipart();
                mp.addBodyPart(bp);
                mp.addBodyPart(createFileAttachment(new File(getFileName())));
                message.setContent(mp);
            }

            message.setHeader("X-Mailer", getMailer());
            message.setSentDate(new Date());

            message.saveChanges();

            return message;
        } catch (AddressException e) {
            log().error("Java Mailer Addressing exception: ", e);
            throw new JavaMailerException("Java Mailer Addressing exception: ", e);
        } catch (MessagingException e) {
            log().error("Java Mailer messaging exception: ", e);
            throw new JavaMailerException("Java Mailer messaging exception: ", e);
//        } catch (UnsupportedEncodingException e) {
//            log().error("Java Mailer messaging exception: ", e);
//            throw new JavaMailerException("Java Mailer encoding exception: ", e);
        }
    }

    /**
     * Helper method to that creates a MIME message.
     * @param session
     * @return
     * @throws MessagingException
     * @throws AddressException
     */
    private MimeMessage initializeMessage() throws MessagingException, AddressException {
        MimeMessage message;
        message = new MimeMessage(getSession());
        message.setFrom(new InternetAddress(getFrom()));
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(getTo(), false));
        message.setSubject(getSubject(), m_charSet);
        for (final String key : getExtraHeaders().keySet()) {
        	message.setHeader(key, m_extraHeaders.get(key));
        }
        return message;
    }

    /**
     * @return
     */
    private String createSendLogMsg() {
        StringBuffer sb = new StringBuffer();
        sb.append("\n\tTo: ");
        sb.append(getTo());
        sb.append("\n\tFrom: ");
        sb.append(getFrom());
        sb.append("\n\tSubject is: ");
        sb.append(getSubject());
        sb.append("\n\tFile: ");
        sb.append(getFileName()!=null ? getFileName() : "no file attached");
        sb.append("\n\n");
        sb.append(getMessageText());
        sb.append("\n");
        return sb.toString();
    }

    /**
     * Create a file attachment as a MimeBodyPart, checking to see if the file
     * exists before we create the attachment.
     *
     * @param file file to attach
     * @return attachment body part
     * @throws javax.mail.MessagingException if we can't set the data handler or
     *      the file name on the MimeBodyPart
     * @throws org.opennms.javamail.JavaMailerException if the file does not exist or is not
     *      readable
     */
    public MimeBodyPart createFileAttachment(final File file) throws MessagingException, JavaMailerException {
        if (!file.exists()) {
            log().error("File attachment '" + file.getAbsolutePath() + "' does not exist.");
            throw new JavaMailerException("File attachment '" + file.getAbsolutePath() + "' does not exist.");
        }
        if (!file.canRead()) {
            log().error("File attachment '" + file.getAbsolutePath() + "' is not readable.");
            throw new JavaMailerException("File attachment '" + file.getAbsolutePath() + "' is not readable.");
        }

        MimeBodyPart bodyPart = new MimeBodyPart();
        FileDataSource fds = new FileDataSource(file);
        bodyPart.setDataHandler(new DataHandler(fds));
        bodyPart.setFileName(fds.getName());
        return bodyPart;
    }

    /**
     * Check that required envelope and message contents properties have been
     * set. 
     * 
     * @throws JavaMailerException if any of the required properties have not
     *      been set 
     */
    private void checkEnvelopeAndContents() throws JavaMailerException {
        if (getFrom() == null) {
            throw new JavaMailerException("Cannot have a null from address.");
        }
        if ("".equals(getFrom())) {
            throw new JavaMailerException("Cannot have an empty from address.");
        }
        if (getTo() == null) {
            throw new JavaMailerException("Cannot have a null to address.");
        }
        if ("".equals(getTo())) {
            throw new JavaMailerException("Cannot have an empty to address.");
        }
        if (getSubject() == null) {
            throw new JavaMailerException("Cannot have a null subject.");
        }
        if ("".equals(getSubject())) {
            throw new JavaMailerException("Cannot have an empty subject.");
        }
        if (getMessageText() == null) {
            throw new JavaMailerException("Cannot have a null messageText.");
        }
        if ("".equals(getMessageText())) {
            throw new JavaMailerException("Cannot have an empty messageText.");
        }
    }

    /**
     * Send message.
     *
     * @param message a {@link javax.mail.Message} object.
     * @throws org.opennms.javamail.JavaMailerException if any.
     */
    public void sendMessage(Message message) throws JavaMailerException {
        Transport t = null;
        try {
            t = getSession().getTransport(getTransport());
            log().debug("for transport name '" + getTransport() + "' got: " + t.getClass().getName() + "@" + Integer.toHexString(t.hashCode()));

            LoggingTransportListener listener = new LoggingTransportListener(log());
            t.addTransportListener(listener);

            if (t.getURLName().getProtocol().equals("mta")) {
                // JMTA throws an AuthenticationFailedException if we call connect()
                log().debug("transport is 'mta', not trying to connect()");
            } else if (isAuthenticate()) {
                log().debug("authenticating to " + getMailHost());
                t.connect(getMailHost(), getSmtpPort(), getUser(), getPassword());
            } else {
                log().debug("not authenticating to " + getMailHost());
                t.connect(getMailHost(), getSmtpPort(), null, null);
            }

            t.sendMessage(message, message.getAllRecipients());
            listener.assertAllMessagesDelivered();
        } catch (NoSuchProviderException e) {
            log().error("Couldn't get a transport: " + e, e);
            throw new JavaMailerException("Couldn't get a transport: " + e, e);
        } catch (MessagingException e) {
            log().error("Java Mailer messaging exception: " + e, e);
            throw new JavaMailerException("Java Mailer messaging exception: " + e, e);
        } finally {
            try {
                if (t != null && t.isConnected()) {
                    t.close();
                }
            } catch (MessagingException e) {
                throw new JavaMailerException("Java Mailer messaging exception on transport close: " + e, e);
            }
        }
    }
    
    private class InputStreamDataSource implements DataSource {  
        
        private String name;  
        private String contentType;  
        private ByteArrayOutputStream baos;  
          
        InputStreamDataSource(String name, String contentType, InputStream inputStream) throws JavaMailerException {  
            this.name = name;  
            this.contentType = contentType;
            
            log().debug("setting contentType " + this.contentType);
              
            baos = new ByteArrayOutputStream();  
              
            int read;  
            byte[] buff = new byte[256];  
            try {
                while((read = inputStream.read(buff)) != -1) {  
                    baos.write(buff, 0, read);  
                }
            } catch (IOException e) {
                log().error("Could not read attachment from input stream: " + e, e);
                throw new JavaMailerException("Could not read attachment from input stream: " + e, e);
            }  
        }  
          
        public String getContentType() {
            log().debug("getContentType: " + contentType);
            return contentType;  
        }  
   
        public InputStream getInputStream() throws IOException {  
            return new ByteArrayInputStream(baos.toByteArray());  
        }  
   
        public String getName() {  
            return name;  
        }  
   
        public OutputStream getOutputStream() throws IOException {  
            throw new IOException("Cannot write to this read-only resource");  
        }  
    } 

    /**
     * <p>getPassword</p>
     *
     * @return Returns the password.
     */
    public String getPassword() {
        return m_password;
    }

    /**
     * <p>setPassword</p>
     *
     * @param password
     *            The password to set.
     */
    public void setPassword(String password) {
        m_password = password;
    }

    /**
     * <p>getUser</p>
     *
     * @return Returns the user.
     */
    public String getUser() {
        return m_user;
    }

    /**
     * <p>setUser</p>
     *
     * @param user
     *            The user to set.
     */
    public void setUser(String user) {
        m_user = user;
    }

    /**
     * <p>isUseJMTA</p>
     *
     * @return Returns the _useMailHost.
     */
    public boolean isUseJMTA() {
        return m_useJMTA;
    }

    /**
     * <p>setUseJMTA</p>
     *
     * @param useMTA a boolean.
     */
    public void setUseJMTA(boolean useMTA) {
        m_useJMTA = useMTA;
    }

    /**
     * <p>getFrom</p>
     *
     * @return Returns the from address.
     */
    public String getFrom() {
        return m_from;
    }

    /**
     * <p>setFrom</p>
     *
     * @param from
     *            The from address to set.
     */
    public void setFrom(String from) {
        m_from = from;
    }

    /**
     * <p>isAuthenticate</p>
     *
     * @return Returns the authenticate boolean.
     */
    public boolean isAuthenticate() {
        return m_authenticate;
    }

    /**
     * <p>setAuthenticate</p>
     *
     * @param authenticate
     *            The authenticate boolean to set.
     */
    public void setAuthenticate(boolean authenticate) {
        m_authenticate = authenticate;
    }

    /**
     * <p>getFileName</p>
     *
     * @return Returns the file name attachment.
     */
    public String getFileName() {
        return m_fileName;
    }

    /**
     * <p>setFileName</p>
     *
     * @param fileName a {@link java.lang.String} object.
     */
    public void setFileName(String fileName) {
        m_fileName = fileName;
    }
    
    /**
     * <p>getInputStream</p>
     *
     * @return Returns the input stream attachment.
     */
    public InputStream getInputStream() {
        return m_inputStream;
    }

    /**
     * <p>setInputStream</p>
     *
     * @param inputStream
     *            Sets the input stream to be attached to the message.
     */
    public void setInputStream(InputStream inputStream) {
        m_inputStream = inputStream;
    }
    
    /**
     * <p>getInputStreamName</p>
     *
     * @return Returns the name to use for stream attachments..
     */
    public String getInputStreamName() {
        return m_inputStreamName;
    }

    /**
     * <p>setInputStreamName</p>
     *
     * @param inputStreamName
     *            Sets the name to use for stream attachments.
     */
    public void setInputStreamName(String inputStreamName) {
        m_inputStreamName = inputStreamName;
    }
    
    /**
     * <p>getInputStreamContentType</p>
     *
     * @return Returns the name to use for stream attachments..
     */
    public String getInputStreamContentType() {
        return m_inputStreamContentType;
    }

    /**
     * <p>setInputStreamContentType</p>
     *
     * @param inputStreamContentType a {@link java.lang.String} object.
     */
    public void setInputStreamContentType(String inputStreamContentType) {
        m_inputStreamContentType = inputStreamContentType;
    }

    /**
     * <p>getMailHost</p>
     *
     * @return Returns the mail host.
     */
    public String getMailHost() {
        return m_mailHost;
    }

    /**
     * <p>setMailHost</p>
     *
     * @param mail_host
     *            Sets the mail host.
     */
    public void setMailHost(String mail_host) {
        m_mailHost = mail_host;
    }

    /**
     * <p>getMailer</p>
     *
     * @return Returns the mailer.
     */
    public String getMailer() {
        return m_mailer;
    }

    /**
     * <p>setMailer</p>
     *
     * @param mailer
     *            Sets the mailer.
     */
    public void setMailer(String mailer) {
        m_mailer = mailer;
    }

    /**
     * <p>getMessageText</p>
     *
     * @return Returns the message text.
     */
    public String getMessageText() {
        return m_messageText;
    }

    /**
     * <p>setMessageText</p>
     *
     * @param messageText
     *            Sets the message text.
     */
    public void setMessageText(String messageText) {
        m_messageText = messageText;
    }

    /**
     * <p>getSubject</p>
     *
     * @return Returns the message Subject.
     */
    public String getSubject() {
        return m_subject;
    }

    /**
     * <p>setSubject</p>
     *
     * @param subject
     *            Sets the message Subject.
     */
    public void setSubject(String subject) {
        m_subject = subject;
    }

    /**
     * <p>getTo</p>
     *
     * @return Returns the To address.
     */
    public String getTo() {
        return m_to;
    }

    /**
     * <p>setTo</p>
     *
     * @param to
     *            Sets the To address.
     */
    public void setTo(String to) {
        m_to = to;
    }

    /**
     * <p>getTransport</p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getTransport() {
        if (isUseJMTA()) {
            return "mta";
        } else {
            return m_transport;
        }
    }

    /**
     * <p>setTransport</p>
     *
     * @param transport a {@link java.lang.String} object.
     */
    public void setTransport(String transport) {
        m_transport = transport;
    }

    /**
     * <p>isDebug</p>
     *
     * @return a boolean.
     */
    public boolean isDebug() {
        return m_debug;
    }

    /**
     * <p>setDebug</p>
     *
     * @param debug a boolean.
     */
    public void setDebug(boolean debug) {
        m_debug = debug;
        if (isDebug()) {
            m_session.setDebugOut(new PrintStream(new LoggingByteArrayOutputStream(log()), true));
        }
        m_session.setDebug(isDebug());
    }

    /**
     * @return log4j Category
     */
    private ThreadCategory log() {
        return ThreadCategory.getInstance(getClass());
    }

    public static class LoggingByteArrayOutputStream extends ByteArrayOutputStream {
        private ThreadCategory m_category;

        public LoggingByteArrayOutputStream(ThreadCategory threadCategory) {
            m_category = threadCategory;
        }

        @Override
        public void flush() throws IOException {
            super.flush();

            String buffer = toString().replaceAll("\n", "");
            if (buffer.length() > 0) {
                m_category.debug(buffer);   
            }

            reset();
        }
    }

    public static class LoggingTransportListener implements TransportListener {
        private ThreadCategory m_category;
        private List<Address> m_invalidAddresses = new ArrayList<Address>();
        private List<Address> m_validSentAddresses = new ArrayList<Address>();
        private List<Address> m_validUnsentAddresses = new ArrayList<Address>();

        public LoggingTransportListener(ThreadCategory threadCategory) {
            m_category = threadCategory;
        }

        public void messageDelivered(TransportEvent event) {
            logEvent("message delivered", event);
        }

        public void messageNotDelivered(TransportEvent event) {
            logEvent("message not delivered", event);
        }

        public void messagePartiallyDelivered(TransportEvent event) {
            logEvent("message partially delivered", event);
        }

        private void logEvent(String message, TransportEvent event) {
            if (event.getInvalidAddresses() != null && event.getInvalidAddresses().length > 0) {
                m_invalidAddresses.addAll(Arrays.asList(event.getInvalidAddresses()));
                m_category.error(message + ": invalid addresses: " + StringUtils.arrayToDelimitedString(event.getInvalidAddresses(), ", "));
            }
            if (event.getValidSentAddresses() != null && event.getValidSentAddresses().length > 0) {
                m_validSentAddresses.addAll(Arrays.asList(event.getValidSentAddresses()));
                m_category.debug(message + ": valid sent addresses: " + StringUtils.arrayToDelimitedString(event.getValidSentAddresses(), ", "));
            }
            if (event.getValidUnsentAddresses() != null && event.getValidUnsentAddresses().length > 0) {
                m_validUnsentAddresses.addAll(Arrays.asList(event.getValidUnsentAddresses()));
                m_category.error(message + ": valid unsent addresses: " + StringUtils.arrayToDelimitedString(event.getValidUnsentAddresses(), ", "));
            }
        }

        public boolean hasAnythingBeenReceived() {
            return m_invalidAddresses.size() != 0 || m_validSentAddresses.size() != 0 || m_validUnsentAddresses.size() != 0;
        }

        /**
         * We sleep up to ten times for 10ms, checking to see if anything has
         * been received because the notifications are done by a separate
         * thread.  We also wait another 50ms after we see the first
         * notification come in, just to see if anything else trickles in.
         * This isn't perfect, but it's somewhat of a shot in the dark to
         * hope that we catch most things, to try to catch as many errors
         * as possible so we can fairly reliably report if anything had
         * problems. 
         * 
         * @throws JavaMailerException
         */
        public void assertAllMessagesDelivered() throws JavaMailerException {
            for (int i = 0; i < 10; i++) {
                if (hasAnythingBeenReceived()) {
                    break;
                }

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // Do nothing
                }
            }

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // Do nothing
            }

            if (m_invalidAddresses.size() == 0 && m_validUnsentAddresses.size() == 0) {
                // Nothing failed, so just return
                return;
            }

            throw new JavaMailerException("Not all messages delivered:\n"
                    + "\t" + m_validSentAddresses.size() + " messages were sent to valid addresses: " + StringUtils.collectionToDelimitedString(m_validSentAddresses, ", ") + "\n"
                    + "\t" + m_validUnsentAddresses.size() + " messages were not sent to valid addresses: " + StringUtils.collectionToDelimitedString(m_validUnsentAddresses, ", ") + "\n"
                    + "\t" + m_invalidAddresses.size() + " messages had invalid addresses: " + StringUtils.collectionToDelimitedString(m_invalidAddresses, ", "));
        }

    }

    /**
     * <p>getSession</p>
     *
     * @return the session
     */
    public Session getSession() {
        return m_session;
    }

    /**
     * <p>setSession</p>
     *
     * @param session the session to set
     */
    public void setSession(Session session) {
        m_session = session;
    }

    /**
     * <p>getContentType</p>
     *
     * @return the contentType
     */
    public String getContentType() {
        return m_contentType;
    }

    /**
     * <p>setContentType</p>
     *
     * @param contentType the contentType to set
     */
    public void setContentType(String contentType) {
        m_contentType = contentType;
    }

    /**
     * <p>getCharSet</p>
     *
     * @return the charSet
     */
    public String getCharSet() {
        return m_charSet;
    }

    /**
     * <p>setCharSet</p>
     *
     * @param charSet the charSet to set
     */
    public void setCharSet(String charSet) {
        m_charSet = charSet;
    }

    /**
     * <p>getEncoding</p>
     *
     * @return the encoding
     */
    public String getEncoding() {
        return m_encoding;
    }

    /**
     * <p>setEncoding</p>
     *
     * @param encoding the encoding to set
     */
    public void setEncoding(String encoding) {
        m_encoding = encoding;
    }

    /**
     * <p>isStartTlsEnabled</p>
     *
     * @return the startTlsEnabled
     */
    public boolean isStartTlsEnabled() {
        return m_startTlsEnabled;
    }

    /**
     * <p>setStartTlsEnabled</p>
     *
     * @param startTlsEnabled the startTlsEnabled to set
     */
    public void setStartTlsEnabled(boolean startTlsEnabled) {
        m_startTlsEnabled = startTlsEnabled;
    }

    /**
     * <p>isQuitWait</p>
     *
     * @return the quitWait
     */
    public boolean isQuitWait() {
        return m_quitWait;
    }

    /**
     * <p>setQuitWait</p>
     *
     * @param quitWait the quitWait to set
     */
    public void setQuitWait(boolean quitWait) {
        m_quitWait = quitWait;
    }

    /**
     * <p>getSmtpPort</p>
     *
     * @return the smtpPort
     */
    public int getSmtpPort() {
        return m_smtpPort;
    }

    /**
     * <p>setSmtpPort</p>
     *
     * @param smtpPort the smtpPort to set
     */
    public void setSmtpPort(int smtpPort) {
        m_smtpPort = smtpPort;
    }

    /**
     * <p>isSmtpSsl</p>
     *
     * @return the smtpSsl
     */
    public boolean isSmtpSsl() {
        return m_smtpSsl;
    }

    /**
     * <p>setSmtpSsl</p>
     *
     * @param smtpSsl the smtpSsl to set
     */
    public void setSmtpSsl(boolean smtpSsl) {
        m_smtpSsl = smtpSsl;
    }

    /**
     * This returns the properties configured in the javamail-configuration.properties file.
     *
     * @return a {@link java.util.Properties} object.
     */
    public Properties getMailProps() {
        return m_mailProps;
    }

    public Map<String,String> getExtraHeaders() {
    	return m_extraHeaders;
    }
    
    public void setExtraHeaders(final Map<String,String> headers) {
    	m_extraHeaders = headers;
    }
    
    public void addExtraHeader(final String key, final String value) {
    	m_extraHeaders.put(key, value);
    }
}
