/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 Bull S.A.S.
 * Contact: jonas-team@ow2.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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ejb.easybeans;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Map;

import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.ow2.easybeans.api.EZBContainer;
import org.ow2.easybeans.api.EZBServer;
import org.ow2.easybeans.container.mdb.helper.MDBResourceAdapterHelper;
import org.ow2.easybeans.jmx.MBeansHelper;
import org.ow2.easybeans.naming.interceptors.ENCManager;
import org.ow2.easybeans.osgi.archive.BundleArchiveFactory;
import org.ow2.easybeans.osgi.extension.EasyBeansOSGiExtension;
import org.ow2.easybeans.security.propagation.context.SecurityCurrent;
import org.ow2.easybeans.server.Embedded;
import org.ow2.easybeans.server.EmbeddedConfigurator;
import org.ow2.easybeans.server.EmbeddedException;
import org.ow2.jonas.cmi.CmiService;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.jmbeans.JNotification;
import org.ow2.jonas.lib.jmbeans.ServiceManagerUpdater;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.naming.JNamingManager;
import org.ow2.jonas.resource.ResourceService;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.util.archive.impl.ArchiveManager;
import org.ow2.util.ee.deploy.api.deployer.IDeployerManager;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.url.URLUtils;

/**
 * Implementation of the service that runs the EasyBeans EJB3 container.
 * @author Florent Benoit
 *         Contributors:
 *              S. Ali Tokmen (versioning)
 */
public class EasyBeansService extends AbsServiceImpl implements IEasyBeansService, NotificationListener {

    /**
     * Name of the configuration file of EasyBeans for JOnAS.
     */
    public static final String EASYBEANS_CONFIG_FILE = "easybeans-jonas.xml";

    /**
     * Name of the configuration file of EasyBeans for JOnAS with a support of clustering.
     */
    public static final String EASYBEANS_CLUSTER_CONFIG_FILE = "easybeans-cluster-jonas.xml";

    /**
     * The name of the JONAS_BASE directory.
     */
    protected static final String JONAS_BASE = JProp.getJonasBase();

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(EasyBeansService.class);

    /**
     * URL of the configuration file.
     */
    private URL xmlConfigurationURL = null;

    /**
     * URL of the extra configuration file for clustering.
     */
    private URL cmiXMLConfigurationURL = null;

    /**
     * Embedded instance.
     */
    private Embedded embedded = null;

    /**
     * Reference to the deployer.
     */
    private EasyBeansDeployer easyBeansDeployer = null;

    /**
     * Reference to the JMX service.
     */
    private JmxService jmxService = null;

    /**
     * Reference to the MBeans library.
     * This one register J2EEServer.
     * It's only here because we need to be started <b>after</b>
     * the {@link ServiceManagerUpdater}.
     */
    @SuppressWarnings("unused")
    private ServiceManagerUpdater serviceManagerUpdater = null;

    /**
     * Reference to the resource service (used by MDB only).
     * This is then an optional service.
     */
    private ResourceService resourceService = null;

    /**
     * DeployerManager service.
     */
    private IDeployerManager deployerManager;

    /**
     * Reference to the CMI service.
     */
    private CmiService cmiService = null;

    /**
     * OSGi Bundle context.
     */
    private BundleContext bundleContext = null;

    /**
     * Reference to the Naming Manager of JOnAS.
     */
    private static JNamingManager namingManager = null;

    /**
     * OSGi Service Registration.
     */
    private ServiceRegistration embeddedServiceRegistration = null;

    /**
     * OSGi bundle factory.
     */
    private BundleArchiveFactory bundleArchiveFactory = null;

    /**
     * Default empty constructor.
     */
    public EasyBeansService() {

    }

    /**
     * Constructor in OSGi mode. It provides the bundle context.
     * @param bundleContext the given bundle context.
     */
    public EasyBeansService(final BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }

    /**
     * Abstract start-up method to be implemented by sub-classes.
     * @throws ServiceException service start-up failed
     */
    @Override
    protected void doStart() throws ServiceException {

        // EasyBeans should use the JOnAS domain name and server name
        MBeansHelper.setDomainName(jmxService.getDomainName());
        MBeansHelper.setServerName(jmxService.getJonasServerName());

        File configurationFile = new File(JONAS_BASE + File.separator + "conf" + File.separator + EASYBEANS_CONFIG_FILE);
        if (!configurationFile.exists()) {
            throw new ServiceException("The configuration file '" + configurationFile + "' was not found in the classloader");
        }

        // Get URL
        xmlConfigurationURL = URLUtils.fileToURL(configurationFile);

        // Clustering requires an extra configuration
        if(cmiService != null && cmiService.isStarted()) {
            // Get URL
            cmiXMLConfigurationURL =
                bundleContext.getBundle().getResource(EASYBEANS_CLUSTER_CONFIG_FILE);
            if(cmiXMLConfigurationURL == null) {
                throw new ServiceException("The configuration file '" + EASYBEANS_CLUSTER_CONFIG_FILE
                        + "' was not found in the classloader");
            }
        }

        // Create Finder for Resource Adapter
        JOnASResourceAdapterFinder resourceAdapterFinder = new JOnASResourceAdapterFinder();
        resourceAdapterFinder.setEasyBeansService(this);

        try {
            // Set value
            MDBResourceAdapterHelper.setResourceAdapterFinder(resourceAdapterFinder);

            // Init EasyBeans ENC Manager
            // TODO : Get the InterceptorClass if it has already be set
            ENCManager.setInterceptorClass(JOnASENCInterceptor.class);
        } catch (IllegalStateException e) {
            // Do nothing
            logger.debug("Interceptor class may have already be initialized before", e);
        }

        // Sets the JOnAS 5 Security wrapper.
        try {
            SecurityCurrent.setSecurityCurrent(new JOnASSecurityCurrent());
        } catch (IllegalStateException e) {
            logger.debug("SecurityCurrent may have already be initialized before", e);
        }

        // Configure some dialects.
        configureExtraDialects();

        // Start the EasyBeans server in an execution block
        IExecution<Void> startExec = new IExecution<Void>() {
            public Void execute() throws EmbeddedException {
                embedded = EmbeddedConfigurator.create(xmlConfigurationURL);

                if(cmiXMLConfigurationURL != null) {
                    // Add the cmi component to the given embedded instance
                    EmbeddedConfigurator.init(embedded, cmiXMLConfigurationURL);
                }
                embedded.start();
                return null;
            }
        };

        // Execute
        ExecutionResult<Void> startExecResult = RunnableHelper.execute(getClass().getClassLoader(), startExec);

        // Throw an ServiceException if needed
        if (startExecResult.hasException()) {
            logger.error("Cannot start the EasyBeans server", startExecResult.getException());
            throw new ServiceException("Cannot start the EasyBeans Server", startExecResult.getException());
        }

        /**
         * Enable OSGi extension of EasyBeans if there is a bundlecontext
         */
        if (bundleContext != null) {
            IExecution<Void> addOSGiExtension = new IExecution<Void>() {
                public Void execute() throws EmbeddedException {
                    // Add extension factory
                    EasyBeansOSGiExtension extension = new EasyBeansOSGiExtension();
                    extension.setBundleContext(bundleContext);
                    embedded.getServerConfig().addExtensionFactory(extension);

                    ArchiveManager am = ArchiveManager.getInstance();
                    bundleArchiveFactory = new BundleArchiveFactory();
                    am.addFactory(bundleArchiveFactory);
                    return null;
                }
            };

            // Execute
            ExecutionResult<Void> addOSGiExtensionResult = RunnableHelper.execute(getClass().getClassLoader(), addOSGiExtension);
            // Throw an ServiceException if needed
            if (addOSGiExtensionResult.hasException()) {
                logger.error("Cannot start the EasyBeans server", startExecResult.getException());
                throw new ServiceException("Cannot start the EasyBeans Server", startExecResult.getException());
            }
        }

        // Check the server state
        try {
            ObjectName j2eeServerName = J2eeObjectName.J2EEServer(getDomainName(),
                    getJonasServerName());

            // Register the OSGi service if the server is RUNNING
            String serverState = (String) jmxService.getJmxServer().getAttribute(j2eeServerName, "state");
            if (serverState.equals("RUNNING")) {
                registerEmbeddedService();
            } else {
                // Add notification listener
                // Wait for the server becomes RUNNING
                jmxService.getJmxServer().addNotificationListener(j2eeServerName, this, null, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Create deployer
        easyBeansDeployer = new EasyBeansDeployer();
        easyBeansDeployer.setEmbedded(embedded);
        easyBeansDeployer.setServerProperties(getServerProperties());

        // Register the deployer
        deployerManager.register(easyBeansDeployer);

    }

    /**
     * Abstract method for service stopping to be implemented by sub-classes.
     * @throws ServiceException service stopping failed
     */
    @Override
    protected void doStop() throws ServiceException {

        if (deployerManager != null) {
            // Unregister the deployer
            deployerManager.unregister(easyBeansDeployer);
        }

        // Remove OSGi extension
        if (bundleContext != null) {
            IExecution<Void> removeOSGiExtension = new IExecution<Void>() {
                public Void execute() throws EmbeddedException {
                    if (bundleArchiveFactory != null) {
                        ArchiveManager am = ArchiveManager.getInstance();
                        am.removeFactory(bundleArchiveFactory);
                        bundleArchiveFactory = null;
                    }

                    // Unregister service
                    if (embeddedServiceRegistration != null) {
                        embeddedServiceRegistration.unregister();
                        embeddedServiceRegistration = null;
                    }
                    return null;
                }
            };

            // Execute
            ExecutionResult<Void> removeOSGiExtensionResult = RunnableHelper.execute(getClass().getClassLoader(),
                    removeOSGiExtension);
            // Throw an ServiceException if needed
            if (removeOSGiExtensionResult.hasException()) {
                throw new ServiceException("Cannot stop EasyBeans", removeOSGiExtensionResult.getException());
            }

        }

        // Stop the EasyBeans server
        if (embedded != null) {
            try {
             // Stop the EasyBeans server in an execution block
                IExecution<Void> stopExec = new IExecution<Void>() {
                    public Void execute() throws EmbeddedException {
                        embedded.stop();
                        return null;
                    }
                };

                // Execute
                ExecutionResult<Void> stopExecResult = RunnableHelper.execute(getClass().getClassLoader(), stopExec);

                // Throw an ServiceException if needed
                if (stopExecResult.hasException()) {
                    throw new EmbeddedException(stopExecResult.getException());
                }


            } catch (EmbeddedException e) {
                throw new ServiceException("Cannot stop the EasyBeans component", e);
            }
        }
    }


    /**
     * Configure some dialects that may be used with some databases used by JOnAS.
     */
    @SuppressWarnings("unchecked")
    protected void configureExtraDialects() {
        // Add Hibernate dialect
        try {
            Class<?> hibernateDialectClass = Embedded.class.getClassLoader().loadClass(
                    "org.hibernate.dialect.DialectFactory");
            Field mapper = hibernateDialectClass.getDeclaredField("MAPPERS");
            mapper.setAccessible(true);
            Map<String, Object> map = null;
            map = (Map<String, Object>) mapper.get(null);
            Class<?> versionInsensitiveMapperClass = Embedded.class.getClassLoader().loadClass(
                    "org.hibernate.dialect.DialectFactory$VersionInsensitiveMapper");
            Constructor<?> c = versionInsensitiveMapperClass.getConstructor(String.class);
            Object dialect = c.newInstance("org.hibernate.dialect.Oracle9Dialect");
            map.put("Oracle9i Enterprise Edition", dialect);
            mapper.setAccessible(false);
        } catch (Exception e) {
            logger.debug("Cannot configure some dialects used by Hibernate", e);
        }
    }


    /**
     * {@inheritDoc}
     */
    public EZBServer getEasyBeansServer() {
        return embedded;
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param deployerManager the deployerManagerService to set
     */
    public void setDeployerManager(final IDeployerManager deployerManager) {
        this.deployerManager = deployerManager;
    }

    /**
     * Returns the NamingManger.
     * @return the NamingManger
     */
    public static JNamingManager getNamingManager() {
        return namingManager;
    }

    /**
     * Sets the NamingManger.
     * @param naming the NamingManger to set
     */
    public void setNamingManager(final JNamingManager naming) {
        namingManager = naming;
    }

    /**
     * @param resourceService the resourceService to set
     */
    public void setResourceService(final ResourceService resourceService) {
        this.resourceService = resourceService;
    }

    /**
     * @return the resourceService
     */
    public ResourceService getResourceService() {
        return resourceService;
    }

    /**
     * Set a reference to the CMI service.
     * @param cmiService a reference to the CMI service
     */
    public void setCmiService(final CmiService cmiService) {
        this.cmiService = cmiService;
    }

    /**
     * Return a reference to the CMI service.
     * @return a reference to the CMI service
     */
    public CmiService getCmiService() {
        return cmiService;
    }


    /**
     * Adds the given container.
     * @param ejbContainer the EJB3 bundle container to add
     */
    public void addContainer(final EZBContainer ejbContainer) {
        embedded.addContainer(ejbContainer);
    }

    /**
     * Remove the given container.
     * @param ejbContainer the given container
     */
    public void removeContainer(final EZBContainer ejbContainer) {
        embedded.removeContainer(ejbContainer);
    }

    /**
     * {@inheritDoc}
     */
    public void handleNotification(final Notification notification, final Object handback) {
        // Detect notification when the server is RUNNING
        if (embeddedServiceRegistration == null && notification.getType().equals(JNotification.runningType)) {
            // Register the OSGi service
            registerEmbeddedService();
        }
    }

    /**
     * Register Embedded OSGi service.
     */
    private void registerEmbeddedService() {
        embeddedServiceRegistration = bundleContext.registerService(Embedded.class.getName(), embedded, null);
    }

    /**
     * @param versioningService  The versioning service to set.
     */
    public void setVersioningService(final VersioningService versioningService) {
        easyBeansDeployer.setVersioningService(versioningService);
    }

    /**
     * Sets the versioning service to null.
     */
    public void unsetVersioningService() {
        easyBeansDeployer.unsetVersioningService();
    }

    /**
     * @return The versioning service.
     */
    public VersioningService getVersioningService() {
        return easyBeansDeployer.getVersioningService();
    }

    /**
     * @param serviceManagerUpdater the serviceManagerUpdater to set
     */
    public void setServiceManagerUpdater(final ServiceManagerUpdater serviceManagerUpdater) {
        this.serviceManagerUpdater = serviceManagerUpdater;
    }
}
