/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2006 Bull S.A.S.
 * Contact: jonas@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
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.cluster.daemon.mbean;

import org.ow2.jonas.cluster.daemon.ClusterDaemon;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
 * This helper class allow to start a JMX remote connector allowing to connect remote applications.
 * This could be for example a JSR88 provider.
 * @author Florent Benoit
 * @author S. Ali Tokmen
 */
public final class JMXRemoteHelper {

    /**
     * JMX connector (server side).
     */
    private static JMXConnectorServer jmxConnectorServer = null;

    /**
     * Default URL.
     */
    private static final String DEFAULT_URL = "service:jmx:rmi:///jndi/rmi://localhost:1099/jonasConnector";

    /**
     * ObjectName for the connector.
     */
    private static final String DEFAULT_NAME_CONNECTOR = "connectors:name=JMXRemoteConnector";

    /**
     * MBean server connection
     */
    private static MBeanServerConnection mbscnx = null;


    /**
     * Utility class, no public constructor.
     */
    private JMXRemoteHelper() {

    }


    /**
     * Build a new JMX Remote connector
     * @param url JMX remote url.
     * @param env Environment to use as base when creating the connector.
     *            Can be null.
     * @throws JMXRemoteException if jmx connector can't be built.
     */
    private static void init(String url, Map env) throws JMXRemoteException {

        // Create connector
        Map environment = new HashMap();
        JMXServiceURL jmxServiceURL = null;
        try {
            jmxServiceURL = new JMXServiceURL(url);
        } catch (MalformedURLException e) {
            throw new JMXRemoteException("Cannot create jmxservice url with url '" + url + "'.", e);
        }
        environment.put("jmx.remote.jndi.rebind", "true");
        if (env != null) {
           environment.putAll(env);
        }
        try {
            jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, environment, null);
        } catch (IOException e) {
            throw new JMXRemoteException("Cannot create new JMX Connector", e);
        }

    }

    /**
     * Connect to a JMX Remote connector by calling
     * {@link JMXRemoteHelper#connect(String, String, String)}(url, null, null).
     * @param url JMX remote url.
     * @return MBeanServerConnection MBean server connection
     * @throws JMXRemoteException if jmx connector can't be connected.
     */
    public static MBeanServerConnection connect(String url) throws JMXRemoteException {
        return connect(url, null, null);
    }

    /**
     * Connect to a JMX Remote connector. If either username or password
     * (or both) is null, no credentials are provided for connection.
     * @param url JMX remote url.
     * @param username User name to use, can be null.
     * @param password Password to use, can be null.
     * @return MBeanServerConnection MBean server connection
     * @throws JMXRemoteException if jmx connector can't be connected.
     */
    public static MBeanServerConnection connect(String url, String username, String password) throws JMXRemoteException {
        if (mbscnx != null) {
            return mbscnx;
        }

        JMXServiceURL jmxServiceURL = null;
        try {
            jmxServiceURL = new JMXServiceURL(url);
        } catch (MalformedURLException e) {
            throw new JMXRemoteException("Cannot create jmxservice url with url '" + url + "'.", e);
        }
        try {
            Map env = null;
            if(username != null && password != null) {
                env = new HashMap(1);
                String[] creds = {username, password};
                env.put(JMXConnector.CREDENTIALS, creds);
            }
            JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, env);
            mbscnx = jmxConnector.getMBeanServerConnection();
        } catch (IOException e) {
            throw new JMXRemoteException("Cannot connect to JMX remote with '" + url + "'.", e);
        }
        return mbscnx;
    }

    /**
     * Calls {@link JMXRemoteHelper#startConnector(String, String, Map)}(url, connectorName, null).
     * @param url JMX remote url
     * @param connectorName connector name
     * @throws JMXRemoteException if the connector can't be started.
     */
    public static synchronized void startConnector(String url, String connectorName) throws JMXRemoteException {
        startConnector(url, connectorName, null);
    }

    /**
     * Start a JMX connector (used to do remote administration).
     * @param url JMX remote url
     * @param connectorName connector name
     * @param env Environment to use as base when creating the connector,
     *            used to provide security. Can be null.
     * @throws JMXRemoteException if the connector can't be started.
     * @return The created JMX connector server.
     */
    public static synchronized JMXConnectorServer startConnector(String url, String connectorName, Map<String, String> env) throws JMXRemoteException {
        // Create connector if null
        if (jmxConnectorServer == null) {
            if (url == null) {
                init(DEFAULT_URL, env);
            } else {
                init(url, env);
            }
        }

        // Create MBean for this connector
        ObjectName connectorServerName = null;
        String objName = null;
        try {
            if (connectorName == null) {
                objName = DEFAULT_NAME_CONNECTOR;
            } else {
                objName = connectorName;
            }
            connectorServerName = new ObjectName(objName);
        } catch (MalformedObjectNameException e) {
            throw new JMXRemoteException("Cannot create ObjectName with name '" + objName + "'", e);
        } catch (NullPointerException e) {
            throw new JMXRemoteException("Cannot create ObjectName with name '" + objName + "'", e);
        }

        // register it
        try {
            MBeanServerHelper.getMBeanServer().registerMBean(jmxConnectorServer, connectorServerName);
        } catch (InstanceAlreadyExistsException e) {
            throw new JMXRemoteException("Cannot register Mbean with the name '" + connectorServerName + "'", e);
        } catch (MBeanRegistrationException e) {
            throw new JMXRemoteException("Cannot register Mbean with the name '" + connectorServerName + "'", e);
        } catch (NotCompliantMBeanException e) {
            throw new JMXRemoteException("Cannot register Mbean with the name '" + connectorServerName + "'", e);
        }

        // Start connector
        ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(ClusterDaemon.class.getClassLoader());
            jmxConnectorServer.start();
        } catch (IOException e) {
            throw new JMXRemoteException("Cannot start the jmx connector", e);
        } finally {
            Thread.currentThread().setContextClassLoader(threadClassLoader);
        }
        return jmxConnectorServer;
    }



    /**
     * Get ObjectInstance
     * @param cnx MBean server connection
     * @param pattern MBean name
     * @return ObjectInstance
     */
    public static ObjectInstance getInstance(MBeanServerConnection cnx, String pattern) {

        Set oiset = null ;
        ObjectInstance oi = null;
        try {
            ObjectName on = new ObjectName(pattern);
            oiset = cnx.queryMBeans(on, null);

        } catch (Exception ex){
                ex.printStackTrace();
        }
        if (! oiset.isEmpty()) oi = (ObjectInstance) oiset.iterator().next();

        return oi;
    }
}
