/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-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 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
 *
 * --------------------------------------------------------------------------
 * $Id:RegistryServiceImpl.java 10372 2007-05-14 13:58:42Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.registry.carol;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.carol.jndi.ns.NameServiceManager;
import org.ow2.carol.jndi.spi.VirtualJNDIContextFactory;
import org.ow2.carol.jndi.spi.VirtualJNDILookup;
import org.ow2.carol.util.configuration.CarolDefaultValues;
import org.ow2.carol.util.configuration.ConfigurationException;
import org.ow2.carol.util.configuration.ConfigurationRepository;
import org.ow2.carol.util.configuration.ProtocolConfiguration;
import org.ow2.jonas.configuration.DeploymentPlanDeployer;
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.loader.OSGiClassLoader;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.naming.JComponentContextFactory;
import org.ow2.jonas.naming.JComponentContextFactoryDelegate;
import org.ow2.jonas.registry.RegistryService;
import org.ow2.jonas.registry.carol.delegate.ORBCCFDelegate;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;

/**
 * Implementation of the {@link RegistryService} interface.
 * @author Helene Joanin Contributor(s): Julien Lehembre (Libelis)
 */
public class CarolRegistryService extends AbsServiceImpl implements RegistryService {

    /**
     * Management loggers.
     */
    private static Logger logger = Log.getLogger(Log.JONAS_REGISTRY_PREFIX);

    /**
     * Automatic mode.
     */
    public static final String DEFAULT_MODE = "automatic";

    /**
     * Collocated mode.
     */
    public static final String COLLOCATED = "collocated";

    /**
     * Remote mode.
     */
    public static final String REMOTE = "remote";

    /**
     * Should the local registry be started ?
     */
    private boolean bStartRegistry = true;

    /**
     * Should this service ignore registry startup errors ?
     */
    private boolean bIgnoreError = true;

    /**
     * The ORB Delegate.
     */
    private JComponentContextFactoryDelegate delegate = null;

    /**
     * {@link ComponentContextFactory} factory.
     */
    private JComponentContextFactory factory = null;

    /**
     * Carol Configuration file.
     */
    private URL configurationURL;

    /**
     * {@link MBeanServer}.
     */
    private MBeanServer mbeanServer = null;

    /**
     * {@link DeploymentPlanDeployer} service reference.
     */
    private DeploymentPlanDeployer deploymentPlanDeployer = null;

    /**
     * Initial Context.
     */
    private InitialContext ictx = null;

    /**
     * Is IIOP protocol enabled?
     */
    private boolean iiopEnabled = false;

    /**
     * Property for the security propagation.
     */
    private static final String SECURITY_PROPAGATION = "jonas.security.propagation";

    /**
     * Property for the Csiv2 propagation.
     */
    private static final String CSIV2_PROPAGATION = "jonas.csiv2.propagation";

    /**
     * Property for the transaction propagation.
     */
    private static final String TRANSACTION_PROPAGATION = "jonas.transaction.propagation";

    /**
     * @param mode Registry mode (default, collocated or remote).
     */
    public void setMode(final String mode) {
        if (DEFAULT_MODE.equalsIgnoreCase(mode)) {
            setStartRegistry(true);
            setIgnoreError(true);
        } else if (COLLOCATED.equalsIgnoreCase(mode)) {
            setStartRegistry(true);
            setIgnoreError(false);
        } else if (REMOTE.equalsIgnoreCase(mode)) {
            setStartRegistry(false);
            setIgnoreError(false);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void doStart() throws ServiceException {
        logger.log(BasicLevel.DEBUG, "Starting Carol Registry Service");


        String oldValue = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);

        // Configure Carol in an execution block
        IExecution<Void> exec = new IExecution<Void>() {
            public Void execute() throws Exception {
                // Set carol mode in server mode (will use fixed port if set)
                System.setProperty(CarolDefaultValues.SERVER_MODE, "true");
                // Configure Carol
                try {
                    ConfigurationRepository.init(configurationURL, getDomainName(), getJonasServerName(), getMBeanServerId());
                } catch (ConfigurationException e) {
                    throw new ServiceException("Cannot init Carol", e);
                }
                // Deploy the IRMI bundle if needed
                if (getProtocolConfiguration("irmi") != null) {
                    deploymentPlanDeployer.deploy("irmi");
                }
                // Force static initialization of PRODelegate
                try {
                    // we do not care the result of this action (exception or not)
                    // we only need the delegation reference in PortableRemoteObject to be initialized
                    PortableRemoteObject.narrow(null, null);
                } catch (Exception e) {
                    // exception is not a problem
                    logger.log(BasicLevel.DEBUG, "PortableRemoteObject initialized");
                }
                return null;
            }
        };

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

        // Throw an ServiceException if needed
        if (result.hasException()) {
            throw new ServiceException(result.getException().getMessage(), result.getException());
        }

        // Set the previous value due to start JNDI flag of carol
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, oldValue);

        try {
            initInterceptors();
        } catch (ConfigurationException e) {
            throw new ServiceException("Cannot init Carol interceptors", e);
        }

        // Start Carol in an execution block
        exec = new IExecution<Void>() {
            public Void execute() throws Exception {
                if (bStartRegistry) {
                    NameServiceManager nameServiceManager = NameServiceManager.getNameServiceManager();
                    try {
                        if (bIgnoreError) {
                            // start the carol name service manager
                            nameServiceManager.startNonStartedNS();
                        } else {
                            nameServiceManager.startNS();
                        }
                    } catch (Exception e) {
                        throw new ServiceException("Cannot start the registry", e);
                    }
                }
                return null;
            }
        };

        // Execute
        result = RunnableHelper.execute(new OSGiClassLoader(), exec);

        // Throw an ServiceException if needed
        if (result.hasException()) {
            throw new ServiceException(result.getException().getMessage(), result.getException());
        }

        if (iiopEnabled) {
            // Add delegate
            try {
                // Should be done by the container
                setDelegate(new ORBCCFDelegate());
                factory.addDelegate(delegate);
            } catch (NamingException e) {
                throw new ServiceException("Cannot add the delegate", e);
            }
        }

        // Use VirtualJNDIContextFactory as the InitialContext factory
        // Must be done AFTER carol init because carol sets this property internally.
        String initialContextFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
        System.setProperty(VirtualJNDIContextFactory.PARENT_INITIAL_CONTEXT_FACTORY, initialContextFactory);
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, VirtualJNDIContextFactory.class.getName());

        logger.log(BasicLevel.INFO, "Carol Registry Service started");
    }

    /**
     * Initialize interceptors.
     * Deploy some IIOP bundles if needed.
     * @throws ConfigurationException if interceptors cannot be inserted into Carol.
     */
    private void initInterceptors() throws ConfigurationException {
        boolean security = Boolean.parseBoolean(getServerProperties().getValue(SECURITY_PROPAGATION));
        boolean transaction = Boolean.parseBoolean(getServerProperties().getValue(TRANSACTION_PROPAGATION));
        boolean csiv2 = Boolean.parseBoolean(getServerProperties().getValue(CSIV2_PROPAGATION));

        iiopEnabled = ConfigurationRepository.getConfiguration("iiop") != null;

        boolean deploySecurityInterceptors = false;

        if (security) {
            if (iiopEnabled) {
                deploySecurityInterceptors = true;
                // iiop interceptors flag
                ConfigurationRepository.addInterceptors("iiop", "org.ow2.jonas.security.interceptors.iiop.SecurityInitializer");
            }
        }

        if (iiopEnabled && csiv2) {
            deploySecurityInterceptors = true;
            ConfigurationRepository.addInterceptors("iiop", "org.ow2.jonas.security.iiop.Csiv2Initializer");
        }

        // Deploy before the IIOP transaction interceptors
        if (deploySecurityInterceptors) {
            try {
                deploymentPlanDeployer.deploy("sec-interceptors-iiop");
            } catch (DeployerException e) {
                throw new ConfigurationException(e);
            }
        }

        if (transaction) {
            // iiop interceptors flag
            if (iiopEnabled) {
                // Deploy before the IIOP security interceptors
                try {
                    deploymentPlanDeployer.deploy("trans-interceptors-iiop");
                } catch (DeployerException e) {
                    throw new ConfigurationException(e);
                }
                ConfigurationRepository.addInterceptors("iiop", "org.ow2.jonas.tm.jotm.ots.OTSORBInitializer");
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void doStop() throws ServiceException {
        logger.log(BasicLevel.DEBUG, "Stopping Carol Registry Service");

        try {
            // Stop the carol name service manager
            NameServiceManager.getNameServiceManager().stopNS();
        } catch (Exception e) {
            throw new ServiceException("Problem when stopping registry service", e);
        }

        logger.log(BasicLevel.INFO, "Carol Registry Service stopped");
    }

    /**
     * {@inheritDoc}
     */
    public List<String> getActiveProtocolNames() {
        List<String> protocols = new ArrayList<String>();

        ProtocolConfiguration[] configs = ConfigurationRepository.getConfigurations();
        for (int i = 0; i < configs.length; i++) {
            protocols.add(configs[i].getName());
        }
        return protocols;
    }

    /**
     * {@inheritDoc}
     */
    public String getDefaultProtocolName() {
        return ConfigurationRepository.getDefaultConfiguration().getName();
    }

    /**
     * {@inheritDoc}
     */
    public int getExportedObjectPort(final String protocolName) {
        ProtocolConfiguration config = getProtocolConfiguration(protocolName);
        String key = CarolDefaultValues.CAROL_PREFIX + "." + protocolName + ".server.port";
        String port = config.getProperties().getProperty(key, "0");
        return Integer.valueOf(port);
    }

    /**
     * {@inheritDoc}
     */
    public String getInitialContextFactoryName(final String protocolName) {
        ProtocolConfiguration config = getProtocolConfiguration(protocolName);
        return config.getProtocol().getInitialContextFactoryClassName();
    }

    /**
     * {@inheritDoc}
     */
    public URI getProviderURL(final String protocolName) {
        ProtocolConfiguration config = getProtocolConfiguration(protocolName);
        return URI.create(config.getProviderURL());
    }

    /**
     * {@inheritDoc}
     */
    public void setDefaultProtocol(final String protocolName) {
        ProtocolConfiguration config = getProtocolConfiguration(protocolName);
        ConfigurationRepository.setCurrentConfiguration(config);
    }

    /**
     * @param protocolName protocol name.
     * @return Returns the {@link ProtocolConfiguration} with the given name.
     */
    private ProtocolConfiguration getProtocolConfiguration(final String protocolName) {
        return ConfigurationRepository.getConfiguration(protocolName);
    }

    /**
     * Bind the {@link JComponentContextFactory} to use.
     * @param factory selected factory
     */
    public void setComponentContextFactory(final JComponentContextFactory factory) {
        this.factory = factory;
    }

    /**
     * Bind the {@link JComponentContextFactoryDelegate} to use.
     * @param delegate selected delegate
     */
    // TODO Remove it in full OSGi mode
    public void setDelegate(final JComponentContextFactoryDelegate delegate) {
        this.delegate = delegate;
    }

    /**
     * @param startRegistry the bStartRegistry to set
     */
    public void setStartRegistry(final boolean startRegistry) {
        bStartRegistry = startRegistry;
    }

    /**
     * @param ignoreError the bIgnoreError to set
     */
    public void setIgnoreError(final boolean ignoreError) {
        bIgnoreError = ignoreError;
    }

    /**
     * @param configurationURL the configurationURL to set
     */
    public void setConfigurationURL(final URL configurationURL) {
        this.configurationURL = configurationURL;
    }

    /**
     * @param configurationURL the configurationURL to set
     * @throws MalformedURLException throws if the URL is not valid
     */
    public void setConfiguration(final String configurationURL) throws MalformedURLException {
        setConfigurationURL(new URL(configurationURL));
    }

    /**
     * Get the initialContext used in this jonas server.
     * @return InitialContext the initial context.
     * @throws NamingException
     */
    public InitialContext getRegistryContext() {
        if (ictx == null) {
            // Get InitialContext with the correct class loader
            // We need to access the carol classes here.
            IExecution<InitialContext> ie = new IExecution<InitialContext>() {
                public InitialContext execute() throws Exception {
                    return new InitialContext();
                }
            };
            ExecutionResult<InitialContext> result = null;
            result = RunnableHelper.execute(getClass().getClassLoader(), ie);
            if (result.hasException()) {
                logger.log(BasicLevel.ERROR, result.getException());
            }
            ictx = result.getResult();
        }
        return ictx;
    }

    /**
     * Returns the MBean server agent identity.
     * @return The agent identity.
     * @throws ServiceException If the MBeanServerId cannot be retrieved
     */
    private String getMBeanServerId() throws ServiceException {
        try {
            ObjectName on = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");
            return (String) mbeanServer.getAttribute(on, "MBeanServerId");
        } catch (Exception e) {
            throw new ServiceException("MBeanServerId cannot be retrieved", e);
        }
    }

    /**
     * @param mbeanServer the mbeanServer to set
     */
    public void setMbeanServer(final MBeanServer mbeanServer) {
        this.mbeanServer = mbeanServer;
    }

    /**
     * @param deploymentPlanDeployer the deploymentPlanDeployer to set
     */
    public void setDeploymentPlanDeployer(final DeploymentPlanDeployer deploymentPlanDeployer) {
        this.deploymentPlanDeployer = deploymentPlanDeployer;
    }

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

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

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