/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 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$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.jmx.internal;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;

import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.jmx.Interceptor;
import org.ow2.jonas.jmx.JManagementIdentifier;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.jmx.internal.interceptor.InvocationHandlerImpl;
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.log.LogFactory;

/**
 * JMX Service implementation. This singleton class must exist in each jonas
 * server that is to be administered via JMX. Its main role is to initialize the
 * service (the singleton object).
 * @author Guillaume Riviere
 * @author Michel Bruno
 * @author Adriana Danes
 * @author Mohammed Boukada (Proxy MBeanServer)
 */
public abstract class BaseJMXService extends AbsServiceImpl implements JmxService {

    /**
     * Trace logger.
     */
    private static Logger logger = Log.getLogger(Log.JONAS_JMX_PREFIX);

    /**
     * Logger OW2.
     */
    private static org.ow2.util.log.Log log = LogFactory.getLog(BaseJMXService.class);


    /**
     * The Identifier in charge of creating the right ObjectName for a given
     * instance.
     */
    private Map<Class<?>, JManagementIdentifier> identifiers = null;

    /**
     * @return Logger logger object
     */
    protected static Logger getLogger() {
        return logger;
    }

    /**
     * JMX server (MBean server) instance.
     */
    private MBeanServer jmxServer = null;

    /**
     * Jakarta commons modeler registry.
     */
    private Registry modelerRegistry = null;

    /**
     * default MBeanServer ID.
     */
    private String mbeanServerID = null;

    /**
     * List of registered MBeans object names.
     */
    private List<ObjectName> registeredMBeansObjectNames = null;

    /**
     * List of registered ModelMBeans object names.
     */
    private List<ObjectName> registeredModelMBeansObjectNames = null;

    /**
     * Handle invocation to MBeanServer
     */
    private InvocationHandlerImpl invocationHandler = null;

    /**
     * Static description of all the packages that contain mbeans-descriptors.xml.
     */
    private static final String[] PACKAGE_DESCRIPTORS = {
        "org.ow2.jonas.web.tomcat6",
        "org.ow2.jonas.server"};

    /**
     * Set the mbean server id.
     * @param serverId mbean server ID
     */
    public void setMBeanServerID(final String serverId) {
        this.mbeanServerID = serverId;
    }

    public void setMBeanServer(final MBeanServer mbs) {
        jmxServer = mbs;
        MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
        if (Proxy.isProxyClass(mbeanServer.getClass())) {
            invocationHandler = (InvocationHandlerImpl) Proxy.getInvocationHandler(mbeanServer);
        }
    }

    /**
     * Start the Service Initialization of the service is already done.
     * @throws ServiceException the service could not be started
     */
    @Override
    public void doStart() throws ServiceException {
        //super.initLogger(Log.getLogger(Log.JONAS_MANAGEMENT_PREFIX));
        identifiers = new Hashtable<Class<?>, JManagementIdentifier>();

        // Store registered MBeans
        registeredMBeansObjectNames = new ArrayList<ObjectName>();
        registeredModelMBeansObjectNames = new ArrayList<ObjectName>();

        if (jmxServer == null) {
            // Backward compatibility
            List mbeanServers = MBeanServerFactory.findMBeanServer(mbeanServerID);
            if (mbeanServers.size() == 0) {
                throw new ServiceException("No MBean server found for id '" + mbeanServerID + "'.");
            }
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Use first MBean server ID found");
            }
            jmxServer = (MBeanServer) mbeanServers.get(0);

            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "JMX Service initialized");
            }
        }

        modelerRegistry = getModelRegistry();
    }

    /**
     * Stop this service.
     * @throws ServiceException the service could not be stopped
     */
    @Override
    public void doStop() throws ServiceException {
        if (jmxServer != null) {
            // Unregister all non already unregistered MBeans and ModelMBeans
            for (int i = registeredMBeansObjectNames.size() - 1; i >= 0; i--) {
                unregisterMBean(registeredMBeansObjectNames.get(i));
            }
            for (int i = registeredModelMBeansObjectNames.size() - 1; i >= 0; i--) {
                unregisterMBean(registeredModelMBeansObjectNames.get(i));
            }
        }
    }

    /**
     * @return The local reference of the MBean server
     */
    public MBeanServer getJmxServer() {
        return this.jmxServer;
    }

    /**
     * @return The Connection interface to the MBean server
     */
    public MBeanServerConnection getJmxServerConnection() {
        return this.jmxServer;
    }

    /**
     * Register an MBean in the JOnAS MBeanServer.
     * @param mbean MBean object to register
     * @param objectName the String representation of the MBean's ObjectName
     */
    public void registerMBean(final Object mbean, final String objectName) {
        try {
            ObjectName on = ObjectName.getInstance(objectName);
            registerMBean(mbean, on);
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.ERROR, objectName + ":" + e);
        }
    }

    /**
     * Register an MBean in the JOnAS MBeanServer.
     * @param mbean MBean object to register
     * @param objectName the MBean's ObjectName
     */
    public void registerMBean(final Object mbean, final ObjectName objectName) {
        if (jmxServer == null) {
            logger.log(BasicLevel.ERROR, "JMX Server not yet initialized");
            return;
        }
        try {
            jmxServer.registerMBean(mbean, objectName);
            registeredMBeansObjectNames.add(objectName);
        } catch (InstanceAlreadyExistsException e) {
            logger.log(BasicLevel.WARN, objectName + ":" + e);
        } catch (MBeanRegistrationException e) {
            logger.log(BasicLevel.ERROR, objectName + ":" + e);
        } catch (NotCompliantMBeanException e) {
            logger.log(BasicLevel.ERROR, objectName + ":" + e);
        }
    }

    /**
     * Unregister an MBean from the JOnAS MBeanServer.
     * @param objectName the MBean's ObjectName
     */
    public void unregisterMBean(final ObjectName objectName) {
        if (jmxServer == null) {
            logger.log(BasicLevel.ERROR, "JMX Server not yet initialized");
            return;
        }
        try {
            jmxServer.unregisterMBean(objectName);
            registeredMBeansObjectNames.remove(objectName);
        } catch (InstanceNotFoundException e) {
            logger.log(BasicLevel.ERROR, objectName + ":" + e);
        } catch (MBeanRegistrationException e) {
            logger.log(BasicLevel.ERROR, objectName + ":" + e);
        }
    }

    public ManagedBean findManagedBean(final String name) {
        return modelerRegistry.findManagedBean(name);
    }

    /**
     * Register a Model MBean in the JOnAS MBeanServer.
     * @param mbean MBean object to register
     * @param objectName the Mbean ObjectName
     * @throws Exception throwed when registering a modeler MBean
     */
    public ModelMBean registerModelMBean(final Object mbean, final ObjectName objectName, final ManagedBean managedBean) throws Exception {
        if (modelerRegistry == null) {
            logger.log(BasicLevel.ERROR, "Commons Modeler MBeans Registry not yet created");
            return null;
        }
        // Wrap call to modelerRegistry.registerComponent() into an IExecution module
        // in order to use the mbean's class loader
        ClassLoader cl = mbean.getClass().getClassLoader();
        IExecution<ModelMBean> exec = new IExecution<ModelMBean>() {

            public ModelMBean execute() throws Exception {
                ModelMBean modelMBean = managedBean.createMBean(mbean);
                jmxServer.registerMBean(modelMBean, objectName);
                registeredModelMBeansObjectNames.add(objectName);
                return modelMBean;
            }

        };
        ExecutionResult<ModelMBean> res = RunnableHelper.execute(cl, exec);
        if (res.hasException()) {
            throw res.getException();
        }
        return res.getResult();
    }
    /**
     * Register a Model MBean in the JOnAS MBeanServer.
     * @param mbean MBean object to register
     * @param objectName the Mbean ObjectName
     * @throws Exception throwed when registering a modeler MBean
     */
    public void registerModelMBean(final Object mbean, final ObjectName objectName) throws Exception {
        if (modelerRegistry == null) {
            logger.log(BasicLevel.ERROR, "Commons Modeler MBeans Registry not yet created");
            return;
        }

        // Wrap call to modelerRegistry.registerComponent() into an IExecution module
        // in order to use the mbean's class loader
        ClassLoader cl = mbean.getClass().getClassLoader();
        IExecution<Void> exec = new IExecution<Void>() {

            public Void execute() throws Exception {
                modelerRegistry.registerComponent(mbean, objectName, null);
                registeredModelMBeansObjectNames.add(objectName);
                return null;
            }

        };
        ExecutionResult<Void> res = RunnableHelper.execute(cl, exec);
        if (res.hasException()) {
            throw res.getException();
        }
    }

    /**
     * Register a Model MBean in the JOnAS MBeanServer.
     * @param mbean MBean object to register
     * @param objectName the Mbean stringified ObjectName
     * @throws Exception throwed when registering a modeler MBean
     */
    public void registerModelMBean(final Object mbean, final String objectName) throws Exception {
        registerModelMBean(mbean, ObjectName.getInstance(objectName));
    }

    /**
     * Unegister a Model MBean from the JOnAS MBeanServer.
     * @param objectName the Mbean ObjectName
     */
    public void unregisterModelMBean(final ObjectName objectName) {
        if (modelerRegistry == null) {
            logger.log(BasicLevel.ERROR, "Modeler MBeans Registry not yet created");
            return;
        }
        modelerRegistry.unregisterComponent(objectName);
        registeredModelMBeansObjectNames.remove(objectName);
    }

    /**
     * Create Registry for model MBeans and load known descriptors.
     * @return the created commens modeler registry
     */
    private Registry getModelRegistry() {
        // Load registry
        Registry registry = Registry.getRegistry(null, null);
        ClassLoader cl = BaseJMXService.class.getClassLoader();

        // Load descriptors
        for (int i = 0; i < PACKAGE_DESCRIPTORS.length; i++) {
            registry.loadDescriptors(PACKAGE_DESCRIPTORS[i], cl);
        }

        // Log loaded descriptors
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            String s;
            String[] as = registry.findManagedBeans();
            logger.log(BasicLevel.DEBUG, ">>> List of all MBeans descriptors");
            for (int i = 0; i < as.length; i++) {
                s = ">>> " + i + ") " + as[i];
                logger.log(BasicLevel.DEBUG, s);
            }
            logger.log(BasicLevel.DEBUG, "<<< List of all MBeans descriptors");
        }
        return registry;
    }

    /**
     * Load additional mbean descriptors.
     * @param packageName name of the package containing the descriptors file
     * @param cl class loader containing the resource
     */
    public void loadDescriptors(final String packageName, ClassLoader cl) {
        if (cl == null) {
            cl = BaseJMXService.class.getClassLoader();
        }
        modelerRegistry.loadDescriptors(packageName, cl);
    }

    /**
     * Register the instance as a ModelMBean using the delegate.
     * @param <T> instance Type
     * @param instance Object instance to be managed
     * @return the MBean's OBJECT_NAME
     * @throws Exception if registration fails.
     */
    public <T> String registerMBean(final T instance) throws Exception {
        String on = getObjectName(instance);
        registerModelMBean(instance, on);
        return on;
    }

    /**
     * Unregister the given Object.
     * @param <T> instance Type
     * @param instance Instance to be deregistered.
     * @throws Exception if unregistration fails.
     */
    public <T> void unregisterMBean(final T instance) throws Exception {
        String on = getObjectName(instance);
        unregisterModelMBean(new ObjectName(on));
    }

    /**
     * @param <T> instance Type
     * @param instance Object instance to be managed
     * @return Returns the instance ObjectName.
     * @throws ServiceException if registration fails.
     */
    public <T> String getObjectName(final T instance) throws ServiceException {

        JManagementIdentifier<T> identifier = getIdentifier(instance);

        // gather the ObjectName
        if (identifier != null) {
            // override domain name value
            identifier.setDomain(getDomainName());
            identifier.setServerName(getJonasServerName());

            // Build ObjectName
            StringBuilder sb = new StringBuilder();
            sb.append(identifier.getDomain());
            sb.append(":");
            sb.append(identifier.getTypeProperty());
            String additionnal = identifier.getAdditionnalProperties(instance);
            if (additionnal != null && (!"".equals(additionnal))) {
                sb.append(",");
                sb.append(additionnal);
            }
            sb.append(",");
            sb.append("name=");
            sb.append(identifier.getNamePropertyValue(instance));
            return sb.toString();
        }

        return null;

    }

    /**
     * @param <T> instance type
     * @param instance instance to be managed.
     * @return Returns an {@link JManagementIdentifier} for the given Resource type.
     * @throws ServiceException if the Identifier cannot be returned.
     */
    @SuppressWarnings("unchecked")
    private <T> JManagementIdentifier<T> getIdentifier(final T instance) throws ServiceException {

        // try to use the cached identifier
        Class clz = instance.getClass();
        if (identifiers.containsKey(clz)) {
            return identifiers.get(clz);
        }

        // the identifier was not cached
        String mbeanClassname = instance.getClass().getName();
        String mbeanPackage = mbeanClassname.substring(0, mbeanClassname
                .lastIndexOf(".") + 1);

        // looking for a class named :
        // <mbean-package>.mbean.<mbean-model-name>Identifier
        // ex : org.ow2.jonas.ear.internal.mbean.EARModuleIdentifier
        String identifierClassname = mbeanPackage + "mbean." + clz.getSimpleName() + "Identifier";

        // Instantiate it by using classloader of the given instance
        try {
            Class<? extends JManagementIdentifier> cls = instance.getClass().getClassLoader().loadClass(
                    identifierClassname).asSubclass(JManagementIdentifier.class);
            JManagementIdentifier<T> id = cls.newInstance();

            // cache the identifier
            identifiers.put(clz, id);

            return id;
        } catch (ClassNotFoundException e) {
            throw new ServiceException("Identifier Class not found", e);
        } catch (InstantiationException e) {
            throw new ServiceException("Identifier Class not instantiated", e);
        } catch (IllegalAccessException e) {
            throw new ServiceException("Identifier Class not instantiated", e);
        }
    }

    /**
     * Add an interceptor to invocationHandler
     * @param interceptor
     */
    public void addInterceptor (Interceptor interceptor) {
        if (invocationHandler == null) {
            logger.log(BasicLevel.WARN, "'"+ interceptor+"' interceptor cannot be added: not a MBeanServer proxy.");
        } else {
            this.invocationHandler.addInterceptor(interceptor);
            logger.log(BasicLevel.INFO, "'"+ interceptor+"' interceptor was added to MBeanServer proxy.");
        }
    }

    /**
     * Remove an interceptor from invocation handler
     * @param interceptor
     */
    public void removeInterceptor (Interceptor interceptor) {
        if (invocationHandler != null) {
            this.invocationHandler.removeInterceptor(interceptor);
            logger.log(BasicLevel.INFO, "'"+ interceptor+"' interceptor was removed from MBeanServer proxy.");
        }
    }
}
