/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2009 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.cmi.internal;

import java.io.File;
import java.net.URL;
import java.util.List;

import javax.ejb.EJBObject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceRegistration;
import org.ow2.carol.util.configuration.ConfigurationRepository;
import org.ow2.cmi.component.event.EventComponent;
import org.ow2.cmi.controller.common.ClusterViewManager;
import org.ow2.cmi.controller.factory.ClusterViewManagerFactory;
import org.ow2.cmi.controller.server.AbsServerClusterViewManager;
import org.ow2.cmi.controller.server.IServerConfig;
import org.ow2.cmi.controller.server.ServerClusterViewManager;
import org.ow2.cmi.osgi.ILoadBalancingService;
import org.ow2.jonas.cmi.CmiService;
import org.ow2.jonas.cmi.internal.event.BeanEventListener;
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.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.service.ServiceException;
import org.ow2.util.component.api.Component;
import org.ow2.util.event.api.IEventListener;
import org.ow2.util.event.api.IEventService;

/**
 * CMI Service interface. It provides a way to use CMI with JOnAS.
 * @author Loris Bouzonnet
 */
public class CmiServiceImpl extends AbsServiceImpl implements CmiService {

    /**
     * Logger for traces.
     */
    private static Logger cmilogger = Log.getLogger("org.ow2.jonas.cmi");

    /**
     * The JMX service.
     */
    private JmxService jmxService = null;

    /**
     * OSGi bundle context.
     */
    private final BundleContext context;

    /**
     * The manager of cluster view.
     */
    private static ClusterViewManager clusterViewManager;

    /**
     * Registration of the cluster view manager.
     */
    private ServiceRegistration clusterViewManagerRegistration;

    /**
     * The EventService.
     */
    private IEventService eventService = null;

    /**
     * Manager for load balancing policy/strategy.
     */
    private CmiServicePolicyStrategyManager policyStrategyManager = null;

    /**
     * Listener for Load Balancing services.
     */
    private LoadBalancingListener listener;

    /**
     * bean event listener.
     */
    IEventListener beanEventListener = null;

    /**
     * Constructor providing the bundle context.
     * @param context the bundle context
     */
    public CmiServiceImpl(final BundleContext context) {
        this.context = context;
    }

    /**
     * Start the service.
     * @throws ServiceException when cannot configure or start the CMI instance.
     */
    @Override
    protected void doStart() throws ServiceException {

        File deployDirectory = new File(JProp.getJonasBase(), CmiServiceProperty.DEFAULT_DEPLOY_DIRECTORY.getPropertyName());
        final URL cmiConfUrl;
        try {
            File cmiConf = new File(JProp.getConfDir(), CmiServiceHelper.CMI_CONFIGURATION_FILE_NAME);
            if (cmiConf.exists()) {
                cmiConfUrl = cmiConf.toURI().toURL();
            } else {
                cmilogger.log(BasicLevel.ERROR, "Missing file '" + CmiServiceHelper.CMI_CONFIGURATION_FILE_NAME
                        + "' in JONAS_BASE : " + JProp.getConfDir());
                throw new ServiceException("Missing file '" + CmiServiceHelper.CMI_CONFIGURATION_FILE_NAME
                        + "' in JONAS_BASE : " + JProp.getConfDir());

            }

        } catch (Exception e) {
            cmilogger.log(BasicLevel.ERROR, "Cannot configure Carol to use CMI", e);
            throw new ServiceException("Cannot configure Carol to use CMI", e);
        }

        final ClusterViewManagerFactory clusterViewManagerFactory = ClusterViewManagerFactory.getFactory();

        // Start the CMI server in an execution block
        IExecution<Boolean> startExec = new IExecution<Boolean>() {
            public Boolean execute() throws Exception {
                ConfigurationRepository.getServerConfiguration().enableCMI(cmiConfUrl);
                clusterViewManager = clusterViewManagerFactory.create();
                if (eventService != null) {
                    List<Component> components = clusterViewManagerFactory.getConfig().getComponents().getComponents();
                    if (components != null) {
                        for (Component eventComponent : components) {
                            if (EventComponent.class.isAssignableFrom(eventComponent.getClass())) {
                                ((EventComponent) eventComponent).setEventService(eventService);
                            }
                        }
                    }
                    if (clusterViewManager instanceof ServerClusterViewManager) {
                        beanEventListener = new BeanEventListener((ServerClusterViewManager)clusterViewManager);
                        eventService.registerListener(beanEventListener, (
                                (BeanEventListener) beanEventListener).getEventProviderFilter());
                    }
                }
                return clusterViewManager.start();
            }
        };
        // Execute
        ExecutionResult<Boolean> startExecResult = RunnableHelper.execute(getClass().getClassLoader(), startExec);

        if (startExecResult.hasException()) {
            // Disable binding into the cluster
            try {
                ConfigurationRepository.getServerConfiguration().disableCMI();
            } catch (Exception e) {
                cmilogger.log(BasicLevel.WARN, "Cannot disable cmi in Carol", e);
            }
            cmilogger.log(BasicLevel.ERROR, "Cannot start the server-side manager", startExecResult.getException());
            throw new ServiceException("Cannot start the CMI server", startExecResult.getException());
        }
        // Set the deploy directory of.
        if (deployDirectory.exists()) {
            cmilogger.log(BasicLevel.DEBUG, "Deploy directory of CMI service is: " + deployDirectory.getAbsolutePath());
            try {
                IServerConfig srvConfig = (IServerConfig) clusterViewManager.getConfig();
                srvConfig.setDeployDirectory(deployDirectory.getAbsolutePath());
            }catch (ClassCastException e) {
                cmilogger.log(BasicLevel.DEBUG, "Set deploy directory only if CMI is server");
            }catch (Exception e1) {
                cmilogger.log(BasicLevel.DEBUG, "Cannot set deploy directory", e1);
            }

        } else {
            cmilogger.log(BasicLevel.DEBUG, "No deploy directory for CMI service");
        }

        // FIXME Sometimes cause a deadlock with Felix and iPOJO: should be done
        // by the CMI bundle
        // Register the cluster view manager as a service
        clusterViewManagerRegistration = context.registerService(ClusterViewManager.class.getName(), clusterViewManager, null);

        if (clusterViewManager instanceof ServerClusterViewManager) {
            policyStrategyManager = new CmiServicePolicyStrategyManager((ServerClusterViewManager) clusterViewManager);
            /**
             * The LoadBalancingListener will delegate processing of
             * policy/strategy services to policyStrategyManager
             */
            listener = new LoadBalancingListener(policyStrategyManager);

            /**
             * Add listener for load balancing service
             */
            cmilogger.log(BasicLevel.DEBUG, "Adding listener for load balancing service ");
            try {
                context.addServiceListener(listener, "(objectClass=" + ILoadBalancingService.class.getName() + ")");
            } catch (InvalidSyntaxException e) {
                // TODO Auto-generated catch block
                cmilogger.log(BasicLevel.WARN, "Unable to add service listener for " + ILoadBalancingService.class.getName()
                        + " because: " + e);
            }
        }

    }

    /**
     * Stop the service.
     * @throws ServiceException when the CMI server side manager cannot be
     *         stopped.
     */
    @Override
    protected void doStop() throws ServiceException {

        if (listener != null) {
            context.removeServiceListener(listener);
        }

        // Unregister the cluster view manager of the OSGi bundle context
        if (clusterViewManagerRegistration != null) {
            clusterViewManagerRegistration.unregister();
        }

        // Disable binding into the cluster
        try {
            ConfigurationRepository.getServerConfiguration().disableCMI();
        } catch (Exception e) {
            cmilogger.log(BasicLevel.WARN, "Cannot disable cmi in Carol", e);
        }

        // Stop it
        if (clusterViewManager != null) {
            try {
                ((AbsServerClusterViewManager) clusterViewManager).stop();
            } catch (Exception e) {
                cmilogger.log(BasicLevel.ERROR, "Cannot stop the server-side manager", e);
                throw new ServiceException("Cannot stop the server-side manager", e);
            }
        }
    }

    /**
     * @return the jmxService
     */
    protected JmxService getJmxService() {
        return jmxService;
    }

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

    /**
     * @param eventService the EventService to set
     */
    public void setEventService(final IEventService eventService) {
        this.eventService = eventService;
    }

    /**
     * Add a bean to the cluster.
     * @param jndiName name of the bean
     * @param clusterConfigMapping The cluster configuration
     * @param homeClass class of the home interface
     * @param remoteClass class of the remote interface
     * @param classLoader the classloader used by the container of this bean
     * @param stateful true if the bean has a state
     * @param clusterReplicated true if the bean is replicated (ha service is
     *        required)
     * @param env a given environment.
     * @throws Exception if the provided policy of load-balancing is not valid
     */
    public void addClusteredObject(final String jndiName, final Object clusterConfig, final Class<?> homeClass,
            final Class<? extends EJBObject> remoteClass, final ClassLoader classLoader, final boolean stateful,
            final boolean clusterReplicated) throws Exception {
        cmilogger.log(BasicLevel.DEBUG, "Adding clustered object named: " + jndiName);
        ((BeanEventListener) beanEventListener).addClusteredObject(jndiName, clusterConfig, homeClass, remoteClass,
                classLoader, stateful, clusterReplicated);

    }

    /**
     * Removes the given clustered object.
     * @param jndiName Name of the object to remove
     * @throws Exception any
     */
    public void removeClusteredObject(final String jndiName) throws Exception {
        cmilogger.log(BasicLevel.DEBUG, "Removing clustered object named :" + jndiName);
        ((BeanEventListener) beanEventListener).removeClusteredObject(jndiName);
    }

    /**
     * @return the beanEventListener
     */
    public IEventListener getBeanEventListener() {
        return beanEventListener;
    }

    /**
     * @param beanEventListener the beanEventListener to set
     */
    public void setBeanEventListener(final IEventListener beanEventListener) {
        this.beanEventListener = beanEventListener;
    }

    /**
     * Gets the clusterview manager
     * @return
     */
    public synchronized static ClusterViewManager getClusterViewManager() {
        return clusterViewManager;
    }
}
