/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Florent BENOIT & Ludovic BERT
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.mail;


import java.io.FileNotFoundException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;
import org.objectweb.jonas.common.JModule;
import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.common.PropDump;
import org.objectweb.jonas.jmx.J2eeObjectName;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.mail.factory.JavaMail;
import org.objectweb.jonas.mail.factory.JavaMailMimePartDS;
import org.objectweb.jonas.mail.factory.JavaMailMimePartDSResource;
import org.objectweb.jonas.mail.factory.JavaMailSession;
import org.objectweb.jonas.mail.factory.JavaMailSessionResource;
import org.objectweb.jonas.management.JonasMBeanTools;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
/**
 * This class provides an implementation of the javaMail service.
 * @author Florent Benoit
 * @author Ludovic Bert
 * Contributor(s):
 * Adriana Danes :
 * - Make possible to change configuration of a JOnAS mail factory object.
 * - Make possible to change the JNDI name of a JOnAS mail factory object.
 */
public class MailServiceImpl extends AbsServiceImpl
    implements MailService, MailServiceImplMBean {

    /**
     * The list of the registered mail session factories.
     */
    private Vector mailSessionList = null;

    /**
     * The list of the registered mime part data source factories.
     */
    private Vector mailMimePartDSList = null;

    /**
     * Reference to a MBean server.
     */
    private MBeanServer mbeanServer = null;

    /**
     * Initial Context for Naming
     */
    private Context ictx = null;

    // the name of the JOnAS server that is administerd by the MBeanServer
    // the current management domain's name
    protected String serverName = null;
    protected String domainName = null;

    /**
     * Logger for this service.
     */
    private static Logger logger = null;

    /**
     * List of the factories names to load when starting the MailService
     */
    private Vector factoryNames = new Vector();

    /**
     *  List of the javax.mail.Session factories currently loaded
     */
    private Hashtable jMailSessionFactories = new Hashtable();

    /**
     *  List of the javax.mail.internet.MimePartDataSource factories currently loaded
     */
    private Hashtable jMailMimePartDSFactories = new Hashtable();

    /**
     *  List of the binded factories (jndi name -> factory name)
     *  This is needed by EJB components in order to find out the factory name
     *  based upon their jndi name
     */
    private Hashtable bindedFactories = new Hashtable();

    /**
     * Session Factory
     */
    private static final int JAVAX_MAIL_SESSION_FACTORY = 1;

    /**
     * MimepartDatasource Factory
     */
    private static final int JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE = 2;

    /**
     * JOnAS-specific properties (name)
     */
    public static final String PROPERTY_NAME = "mail.factory.name";

    /**
     * JOnAS-specific properties (type)
     */
    public static final String PROPERTY_TYPE = "mail.factory.type";

    public static final String SESSION_PROPERTY_TYPE = "javax.mail.Session";
    public static final String MIMEPART_PROPERTY_TYPE = "javax.mail.internet.MimePartDataSource";

    /**
     * Mail service configuration parameters (factories)
     */
    public static final String FACTORIES = "jonas.service.mail.factories";

    /**
     * Mail service configuration parameters (class)
     */
    public static final String CLASS = "jonas.service.mail.class";

    /**
     * Init the Mail service.
     * @param ctx the configuration of the Ear service.
     * @throws ServiceException if the initialization failed.
     */
    protected void doInit(Context ctx) throws ServiceException {

        // get logger for this service
        logger = Log.getLogger(Log.JONAS_MAIL_PREFIX);
        // the logger for reconfig management
        super.initLogger(Log.getLogger(Log.JONAS_MANAGEMENT_PREFIX));

        //Get the inital Context
        try {
            ictx = new InitialContext();
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot create initial context during the mail service initializing");
            throw new ServiceException("Cannot create initial context during the mail service initializing", e);
        }

        // Get the current server and domain names
        try {
            serverName = (String) ctx.lookup(JProp.JONAS_NAME);
            domainName = (String) ctx.lookup(JProp.DOMAIN_NAME);
        } catch (NamingException ne) {
            logger.log(BasicLevel.DEBUG, "Cannot initialize the Mail service " + ne);
            throw new ServiceException("Cannot initialize the Mail service", ne);
        }

        // Get the JMX Server via JMX Service
        try {
            mbeanServer =
                ((JmxService) ServiceManager.getInstance().getJmxService()).getJmxServer();
        } catch (Exception e) {
            // the JMX service may not be started
            mbeanServer = null;
        }

        // Get the list of the factory names
        String factories = null;
        try {
            factories = (String) ctx.lookup(FACTORIES);
        } catch (NamingException e) {
            ; // No problem if there is no value for 'factories'
        }
        if (factories != null) {
            StringTokenizer st = new StringTokenizer(factories, ",");
            while (st.hasMoreTokens()) {
                factoryNames.add(st.nextToken().trim());
            }
        }
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Mail service initialized");
        }

    }

    /**
     * Start the Mail Service.
     * @throws ServiceException if the initialization failed.
     */
    protected void doStart() throws ServiceException {

        // creates each factory
        String factoryName = null;
        for (int i = 0; i < factoryNames.size(); i++) {
            factoryName = (String) factoryNames.elementAt(i);
            try {
                JProp prop = JProp.getInstance(factoryName);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Creating mail factory " + factoryName);
                }
                createMailFactory(factoryName, prop.getConfigFileEnv());
            } catch (Exception e) {
                if (logger.isLoggable(BasicLevel.ERROR)) {
                    logger.log(BasicLevel.ERROR, "JOnAS: Cannot create mail factory " + factoryName + " : " + e);
                    logger.log(BasicLevel.ERROR, "Please check the " + factoryName + ".properties file");
                }
            }
        }

        try {
            // Register MailService MBean : MailServiceImplMBean
            if (mbeanServer != null) {
                mbeanServer.registerMBean(this, JonasObjectName.mailService());
            }
        } catch (JMException e) {
            throw new ServiceException("Cannot start the MailService: " , e);
        }

    }

    /**
     * Stop the Mail service.
     * @throws ServiceException if the stop failed.
     */
    protected void doStop() throws ServiceException {

        try {
            unbindMailFactories();
        } catch (MailServiceException e) {
            logger.log(BasicLevel.ERROR, "Cannot unbind mail factories " + e);
            throw new ServiceException("Cannot unbind mail factories ", e);
        }

        if (mbeanServer != null) {
            try {
                mbeanServer.unregisterMBean(JonasObjectName.mailService());
                // unregister all mail factories
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Cannot stop the Mail Service (JMX): " + e.getMessage());
                throw new ServiceException("Cannot stop the mail service (JMX).", e);
            }
        }
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "mail service stopped");
        }
    }

    /**
     * This method is used when a Mail Factory configuration is modified via jonasAdmin.
     * In this case, the updated JavaMail object (JavaMailSession or JavaMailMimePartDS object)
     * must be rebound in JNDI
     * @param factory the factory
     * @throws MailServiceException if the recreation of
     * the factory failed.
     */
    public void recreateJavaMailFactory(JavaMail factory) throws MailServiceException {
        String jndiName = factory.getName();
        // Rebind the factory object in the naming context
        try {
            ictx.rebind(jndiName, factory);
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot bind mail factory '" + jndiName + "'  :" + e.getMessage());
            throw new MailServiceException("Cannot bind mail factory " + jndiName + ".", e);
        }
    }

    /**
     * This method is used when a particular Mail Factory configuration operation is done via jonasAdmin :
     * when the JNDI name of this resource is modified.
     * In this case, the initial JavaMail object (JavaMailSession or JavaMailMimePartDS object) must be unbound
     * and the updated JavaMail object must be reloaded.
     * Also, the Mail Service private data structures must be updated.
     * @param oldName old name of the factory
     * @param factory the new factory
     * @throws MailServiceException if the rename of the
     * the factory failed.
     */
    public void renameJavaMailFactory(String oldName, JavaMail factory) throws MailServiceException {

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "In renameMailFactory, old name = " + oldName);
        }
        try {
            ictx.unbind(oldName);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, oldName + " unbound");
            }
        } catch (Exception e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Warning: cannot unbind mail factory object named " + oldName);
            }
        }
        String jndiName = factory.getName();
        // Rebind the factory object in the naming context
        try {
            ictx.rebind(jndiName, factory);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "factory rebound under the name " + jndiName);
            }
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot bind mail factory '" + jndiName + "'  :" + e.getMessage());
            throw new MailServiceException("Cannot bind mail factory " + jndiName + ".", e);
        }
        bindedFactories.put(jndiName, factory.getFactoryName());
        bindedFactories.remove(oldName);
    }

    /**
     * Create a mail factory with the specified properties and register it
     * into the registry.
     * @param factoryName name of the factory to create
     * @param props the properties used to configure the mail factory.
     * @throws MailServiceException if the creation or the registration of
     * the factory failed.
     */
    public void createMailFactory(String factoryName, Properties props)
        throws MailServiceException {

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            PropDump.print("These are the properties from which the MailService picks to construct Mail Factories", props, logger, BasicLevel.DEBUG);
        }

        Object factory = null;

        //Factory type/jndi name must be non null
        String factoryType = props.getProperty(PROPERTY_TYPE);
        String jndiName = props.getProperty(PROPERTY_NAME);

        if (jndiName == null) {
            logger.log(BasicLevel.ERROR, "The property 'mail.factory.name' is a required property.");
            throw new MailServiceException("The property 'mail.factory.name' is a required property for this factory.");
        }

        if (factoryType == null) {
            logger.log(BasicLevel.ERROR, "The property 'mail.factory.type' is a required property.");
            throw new MailServiceException("The property 'mail.factory.type' is a required property for this factory.");
        }

        // Verify that jndi name not already used
        if (bindedFactories.containsKey(jndiName)) {
            logger.log(BasicLevel.ERROR, "There is already a factory bound with the name " + jndiName);
            throw new MailServiceException("There is already a factory bound with the name '" + jndiName +  "', please correct the provided configuration properties");
        }

        //Define the type of our factory
        int typeOfFactory;
        if (factoryType.equalsIgnoreCase("javax.mail.Session")) {
            typeOfFactory = JAVAX_MAIL_SESSION_FACTORY;
        } else if (factoryType.equalsIgnoreCase("javax.mail.internet.MimePartDataSource")) {
            typeOfFactory = JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE;
        } else {
            typeOfFactory = 0;
        }

        // Create the factory object and register it in the internal data structure
        switch (typeOfFactory) {

        case JAVAX_MAIL_SESSION_FACTORY :
            JavaMailSession sessionFactory = new JavaMailSession(factoryName, jndiName, props);
            jMailSessionFactories.put(factoryName, sessionFactory);
            factory = sessionFactory;
            break;

        case JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE :
            JavaMailMimePartDS mimeFactory = new JavaMailMimePartDS(factoryName, jndiName, props);
            jMailMimePartDSFactories.put(factoryName, mimeFactory);
            factory = mimeFactory;
            break;

        default :
            throw new MailServiceException("Can not create a factory of the type '" + factoryType + "'. This type is incorrect.");
        }

        // Bind the factory object in the naming context
        try {
            ictx.rebind(jndiName, factory);
            bindedFactories.put(jndiName, factoryName);
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot bind mail factory '" + jndiName + "'  :" + e.getMessage());
            throw new MailServiceException("Cannot bind mail factory " + jndiName + ".", e);
        }

        logger.log(BasicLevel.INFO, "Mapping Mail Factory " + factoryType + " on " + jndiName);

        // Register the factory object as an MBean with the jmx server
        // Get the MBean register (and load mbeans-descriptors)
        Registry oRegistry = JonasMBeanTools.getRegistry();

        // Register the factory object as an MBean with the jmx server
        try {
            if (mbeanServer != null) {
                ObjectName on = null;
                switch (typeOfFactory) {
                case JAVAX_MAIL_SESSION_FACTORY :
                    // J2EEManagement

                    on = J2eeObjectName.JavaMailResource(domainName, factoryName, serverName, SESSION_PROPERTY_TYPE);
                    JavaMailSessionResource javaMailSessionResource = new JavaMailSessionResource(on.toString(), false, false, false, (JavaMailSession) factory);
                    // JSR77
                    ManagedBean oManaged = oRegistry.findManagedBean("JavaMailSessionResource");
                    ModelMBean oMBean = oManaged.createMBean(javaMailSessionResource);
                    mbeanServer.registerMBean(oMBean, on);
                    //mbeanServer.registerMBean(s_mbean, on);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Register session mail factory with name " + factoryName);
                    }
                    break;
                case JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE :
                    // J2EEManagement
                    on = J2eeObjectName.JavaMailResource(domainName, factoryName, serverName, MIMEPART_PROPERTY_TYPE);
                    JavaMailMimePartDSResource javaMailMimePartDSResource = new JavaMailMimePartDSResource(on.toString(), false, false, false, (JavaMailMimePartDS) factory);
                    // JSR77
                    oManaged = oRegistry.findManagedBean("JavaMailMimePartDSResource");
                    oMBean = oManaged.createMBean(javaMailMimePartDSResource);
                    mbeanServer.registerMBean(oMBean, on);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Register mime mail factory with name " + factoryName);
                    }
                }
            }
        } catch (JMException me) {
            throw new MailServiceException("Cannot register mail factory '" + factoryName + "' in JMX server", me);
        } catch (Exception e) {
            logger.log(BasicLevel.WARN, "Could not register JavaMailResource MBean");
        }


    }

    /**
     * Create a mail factory with the specified properties and register it
     * into the registry.
     * @param name the mail factory name
     * @param props the properties used to configure the mail factory.
     * @param loadFromFile true if the mail factory is loaded from a .properties file
     * @throws MailServiceException if the creation or the registration of
     * the factory failed.
     * @throws MailServiceException
     */
    public void createMailFactoryMBean(String name, Properties props, Boolean loadFromFile)
        throws MailServiceException {

        boolean fromFile = loadFromFile.booleanValue();
        if (!fromFile) {
            try {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Call getInstance on JProp in order to create the properties file");
                }
                JProp.getInstance(name, props);
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Cannot create mail factory " + name + " as cannot create properties file : " + e.toString());
                throw new ServiceException("MailService: Cannot create mail factory " + name + ",\n" + e.toString());
            }
        }
        //call the internal proc
        try {
            createMailFactory(name, props);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot create mail factory: " + name);
            throw new ServiceException("MailService: Cannot create mail factory: " + name + ",\n" + e.toString());
        }
    }

    /**
     * Unregister all the binding factories on the server and in JMX Server.
     * @throws MailServiceException if the unregistration of the factories
     * failed.
     */
    public void unbindMailFactories() throws MailServiceException {
        //Unbind all factories
        for (Enumeration e = jMailSessionFactories.keys(); e.hasMoreElements();) {
            unbindMailFactoryMBean((String) e.nextElement());
        }
        for (Enumeration e = jMailMimePartDSFactories.keys(); e.hasMoreElements();) {
            unbindMailFactoryMBean((String) e.nextElement());
        }
    }

    /**
     * Unregister the factory with the given name.
     * @param factoryName the name of the factory to unbind.
     * @throws MailServiceException if the unregistration of the factory
     * failed.
     */
    public void unbindMailFactoryMBean(String factoryName)
        throws MailServiceException {

        // determine the type of the factory and the jndi name
        String name = null;
        int typeOfFactory;
        JavaMailSession jmailSession = (JavaMailSession) jMailSessionFactories.get(factoryName);
        JavaMailMimePartDS jMailMimePartDS = (JavaMailMimePartDS) jMailMimePartDSFactories.get(factoryName);
        if (jmailSession != null) {
            name = jmailSession.getName();
            typeOfFactory = JAVAX_MAIL_SESSION_FACTORY;
        } else if (jMailMimePartDS != null) {
            name = jMailMimePartDS.getName();
            typeOfFactory = JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE;
        } else {
            throw new MailServiceException("Can not unload the mail factory '" + factoryName + "' (this is not a known factory name");
        }

        // unbind the factory
        try {
            ictx.unbind(name);
            bindedFactories.remove(name);
        } catch (NamingException e) {
            throw new MailServiceException("Can not unbind the factory '" + name + "'.", e);
        }

        // remove JProp instance corresponding to this datasource in order to
        // allow re-create in case its re-loaded
        JProp.removeInstance(name);

        // De-register the factory object from the jmx server and remove the factory from the internal data structure
        try {
            if (mbeanServer != null) {
                ObjectName on = null;
                switch (typeOfFactory) {
                case JAVAX_MAIL_SESSION_FACTORY :
                    // J2EEManagement
                    on = J2eeObjectName.JavaMailResource(domainName, factoryName, serverName, SESSION_PROPERTY_TYPE);
                    mbeanServer.unregisterMBean(on);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Unregister session mail factory with name " + factoryName);
                    }
                    jMailSessionFactories.remove(factoryName);
                    break;
                case JAVAX_MAIL_INTERNET_MIMEPARTDATASOURCE :
                    // J2EEManagement
                    on = J2eeObjectName.JavaMailResource(domainName, factoryName, serverName, MIMEPART_PROPERTY_TYPE);
                    mbeanServer.unregisterMBean(on);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Unregister mime mail factory with name " + factoryName);
                    }
                    jMailMimePartDSFactories.remove(factoryName);
                }
            }
        } catch (JMException me) {
            throw new MailServiceException("Cannot unregister mail factory '" + factoryName + "' from the JMX server", me);
        }


    }

    /**
     * Gets the factory name given the jndi name. Null is returned if the given name is not binded.
     * @param jndiName the jndi name
     * @return the factory name given the jndi name. Null is returned if the given name is not binded.
     */
    public String getFactoryName(String jndiName) {
        return (String) bindedFactories.get(jndiName);
    }

    /**
     * Gets the total number of mail factories available in JOnAS
     * @return Integer Total number of mail factories available in JOnAS
     */
    public Integer getCurrentNumberOfMailFactories() {
        return new Integer(jMailSessionFactories.size() + jMailMimePartDSFactories.size());
    }

    /**
     * Gets the number of Session mail factories available in JOnAS
     * @return Integer Number of Session mail factories available in JOnAS
     */
    public Integer getCurrentNumberOfSessionMailFactories() {
        return new Integer(jMailSessionFactories.size());
    }

    /**
     * Gets the integer Number of internet
     * @return Integer Number of internet.MimePartDataSource mail factories available in JOnAS
     */
    public Integer getCurrentNumberOfMimeMailFactories() {
        return new Integer(jMailMimePartDSFactories.size());
    }

    /**
     * Gets the mail factory configuration properties from a local file
     * @param configFile configuration to use
     * @return mail factory configuration properties from a local file
     * @throws Exception if it fails
     */
    public Properties getMailFactoryPropertiesFile(String configFile) throws Exception {
        try {
            return JProp.getInstance(configFile).getConfigFileEnv();
        } catch (Exception e) {
            if (e instanceof FileNotFoundException) {
                logger.log(BasicLevel.ERROR, "Please check if " + configFile + ".properties is available in JONAS_ROOT/config, HOME, or .");
            } else {
                logger.log(BasicLevel.ERROR, "Error occured when reading file " + configFile);
            }
            throw e;
        }
    }

    /**
     * MBean method:
     * @return the list of properties files describing mail factories found in JONAS_BASE/conf
     */
    public List getMailFactoryPropertiesFiles() throws Exception {
        return JModule.getMailFactoryPropsInDir();
    }
    /**
     * MBean method:
     * @return the list of properties files describing mail factories found in JONAS_BASE/conf
     */
    public List getMimePartMailFactoryPropertiesFiles() throws Exception {
        return JModule.getMailFactoryPropsInDir(MIMEPART_PROPERTY_TYPE);
    }
    /**
     * MBean method:
     * @return the list of properties files describing mail factories found in JONAS_BASE/conf
     */
    public List getSessionMailFactoryPropertiesFiles() throws Exception {
        return JModule.getMailFactoryPropsInDir(SESSION_PROPERTY_TYPE);
    }
}
