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

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

import javax.management.AttributeList;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.objectweb.carol.util.configuration.ConfigurationRepository;
import org.objectweb.carol.util.configuration.Protocol;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.jmx.J2eeObjectName;
import org.objectweb.jonas.jmx.JmxServiceImpl;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * Abstract class acting as a JMXConnector client
 * It is implemented by ServerProxy or ClusterDaemonProxy
 * @author durieuxp
 * @author S. Ali Tokmen
 */
public abstract class JMXProxy {

    /**
     * logger for traces.
     */
    protected static Logger logger = Log.getLogger("org.objectweb.jonas.domain.management");

    /**
     * Unique name of the remote object
     */
    protected String name = null;

    /**
     * Domain Name
     */
    private String domain;

    /**
     * The OBJECT_NAME of this Proxy MBean
     * Must be initialized before checking connection.
     */
    protected String objectName = null;

    /**
     * List of urls that can be used for connection
     */
    private ArrayList urls = null;

    /**
     * URL of the current connection
     */
    private String connectionUrl = null;

    /**
     * JMX connection
     */
    protected MBeanServerConnection connection = null;

    /**
     * JMX Connector
     */
    protected JMXConnector connector = null;

    /**
     * Environment used to get the connection
     */
    protected Map env = new HashMap();

    /**
     * Local JMX server.
     */
    protected JmxServiceImpl jmx = null;

    /**
     * The server's ObjectName (this corresponds to the J2EEServer MBean registered
     * in the represented server's MBean server)
     */
    protected ObjectName serverOn = null;

    /**
     * ref on the DomainMonitor
     */
    DomainMonitor dm;

    /**
     * State of the remote object (as seen from the proxy)
     */
    protected int state = INITIAL;

    /**
     * This is the initial state
     */
    public static final int INITIAL = 0;

    /**
     * This state is corresponding to the situation when the
     * JMX connection is broken - maybe temporary.
     */
    public static final int UNREACHABLE = 1;

    /**
     * Server is RUNNING and its reacheable
     */
    public static final int RUNNING = 2;

    /**
     * Server is stopped
     */
    public static final int STOPPED = 3;

    /**
     * Server which can't be reached after a several number
     * of retries
     */
    public static final int FAILED = 4;

    /**
     * Server which started without the discovery and can't be reached after
     * a several number of retries
     */
    public static final int UNKNOWN = 5;

    /**
     * Server is starting (temporary state)
     */
    public static final int STARTING = 6;

    /**
     * Server is stopping (temporary state)
     */
    public static final int STOPPING = 7;

    /**
     * Constructor: saves information and connects if urls is not null or
     * the proxy is created for the local server.
     * @param dm reference to the domain monitor
     * @param name the proxy name
     * @param urls the urls that can be used to establish connection
     */
    public JMXProxy(DomainMonitor dm, String name, Collection urls) {
        this.dm = dm;
        this.name = name;
        logger.log(BasicLevel.DEBUG, name);

        // Useful for registering mbeans
        jmx = (JmxServiceImpl) ServiceManager.getInstance().getJmxService();
        domain = JonasObjectName.getDomain();
        serverOn = J2eeObjectName.J2EEServer(domain, name);
        logger.log(BasicLevel.DEBUG, serverOn);

        // Connection may be done later or now
        if (urls != null || jmx.getJonasServerName().equals(getName())) {
            if (connect(urls)) {
                // If connection was established, set state to "RUNNING".
                state = RUNNING;
            }
        }
    }
    /**
    * Return the int giving the server state
    * @return the state value as an int
    */
    public int getServerState() {
        return state;
    }

    /**
     * MBean method returning the state as a String
     * Return the String form of the server state
     * @return the state value
     */
    public String getState() {
        switch (state) {
            case INITIAL:
                return "INIT";
            case UNREACHABLE:
                return "UNREACHABLE";
            case RUNNING:
                return "RUNNING";
            case STOPPED:
                return "STOPPED";
            case FAILED:
                return "FAILED";
            case STARTING:
                return "STARTING";
            case STOPPING:
                return "STOPPING";
            default:
                return "UNKNOWN";
        }
    }

    /**
     * Disconnect the proxy if connected.
     */
    public void disconnect() {
        connection = null;
        try {
            connector.close();
        } catch (Exception e) {
        }
    }

    /**
     * Try to connect this Proxy to its Server
     * @param urls the urls that can be used to establish connection
     * @return True if connected
     */
    public boolean connect(Collection urls) {
        logger.log(BasicLevel.DEBUG, name);

        if (jmx.getJonasServerName().equals(getName())) {
            try {
                disconnect();
                connection = jmx.getJmxServer();
                return true;
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "JMXProxy.connect (to local) error: " + e);
                return false;
            }
        }

        this.urls = new ArrayList(urls);

        // For now, just use the first url
        // Later: try all urls until connection is OK.
        String urlstr = (String) urls.iterator().next();

        JMXServiceURL url = null;

        // Create the JMXConnector client
        try {
            url = new JMXServiceURL(urlstr);
        } catch (MalformedURLException e) {
            logger.log(BasicLevel.ERROR, "Malformed URL:" + urlstr);
            return false;
        }
        if (url.getProtocol().equals("iiop")) {
            try {
                env.put("java.naming.corba.orb", new InitialContext().lookup("java:comp/ORB"));
            } catch (NamingException e) {
                logger.log(BasicLevel.ERROR, "Cannot get InitialContext: " + e);
            }
        }

        String initCtxClass = null;
        String providerUrl = null;
        try {
            String urlPath = url.getURLPath();
            String protocol = JmxServiceImpl.getProtocolFromJmxConnectorUrl(urlPath);
            Protocol p = ConfigurationRepository.getProtocol(protocol);
            initCtxClass = p.getInitialContextFactoryClassName();
            providerUrl = JmxServiceImpl.getProviderUrlFromJmxConnectorUrl(urlPath);
        } catch (Exception e) {
            logger.log(BasicLevel.WARN, "Unable to get the InitialContext from the protocol '"
                    + url.getProtocol() + "'");
        }
        if (initCtxClass != null) {
            env.put(Context.INITIAL_CONTEXT_FACTORY, initCtxClass);
            env.put(Context.PROVIDER_URL, providerUrl);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Setting the InitialContext to " + initCtxClass);
            }
        }

        // First close old connections
        disconnect();

        // Get the Connection to the JMXConnector
        try {
            // Check if a password has been defined for that host
            AuthenticationInformation authInfo = dm.getAuthenticationInformation(this.name);
            if (authInfo != null) {
                String[] creds = {authInfo.getUsername(), authInfo.decodePassword()};
                env.put(JMXConnector.CREDENTIALS, creds);
            }

            connector = JMXConnectorFactory.connect(url, env);
            connection = connector.getMBeanServerConnection();
        } catch (IOException e) {
            logger.log(BasicLevel.ERROR, "JMXConnectorFactory error: " + e);
        }

        if (connection == null) {
            connectionUrl = null;
            return false;
        } else {
            if (checkConnection(connection)) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "connected to server " + name + " using URL: " + urlstr);
                }
                connectionUrl = urlstr;
                return true;
            } else {
                connectionUrl = null;
                return false;
            }
        }
    }
    /**
     * Used by the connect method to check that the newlly created connection works
     * @param connection connection to check
     * @return true if access to the MBean server does not generate an exception,
     * false otherwise
     */
    private boolean checkConnection(MBeanServerConnection connection) {
        try {
            int nb = connection.getMBeanCount().intValue();
            return true;
        } catch (IOException e) {
            logger.log(BasicLevel.DEBUG, name + " : IOException");
        }
        return false;
    }
    /**
     * Check the established connection to the remote object, or try to establish
     * a connection if the connection object is null.
     * @return true if connection is ok.
     */
    protected boolean checkConnection() {
        if (connection == null) {
            logger.log(BasicLevel.DEBUG, name + " : null connection");
            return connect(this.urls);
        }
        try {
            int nb = connection.getMBeanCount().intValue();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, name + ": mbeans nb =" + nb);
            }
            return true;
        } catch (IOException e) {
            logger.log(BasicLevel.DEBUG, name + " : IOException");
        }
        return false;
    }

    /**
     * @return The name of the remote object
     */
    public String getName() {
        return name;
    }

    /**
     * Return this MBean's name
     * @return The name of the MBean (see OBJECT_NAME in the JSR77)
     */
    public String getObjectName() {
        return objectName;
    }

    /**
     * Set its OBJECT_NAME and register the MBean
     * @param on OBJECT_NAME
     */
    public void setObjectName(String on) {
        objectName = on;
        jmx.registerMBean(this, on);
    }
    /**
     * @return the connection to the remote MBean server
     */
    public MBeanServerConnection getConnection() {
        return connection;
    }

    // ---------------------------------------------------------------------------
    // Methods to access the remote mbean server
    // All this replace the corresponding ManagementReprImpl methods, i.e. :
    //     ManagementReprLoader.loadServerReprForCluster(name, connection);
    //     return JonasManagementRepr.isRegistered(on, name);
    // See jonas.jmx.ManagementReprImpl
    // ---------------------------------------------------------------------------

    /**
     * Check if an ObjectName is registered on the remote server
     * @param on the ObjectName to be checked
     * @return true if registered
     */
    public boolean isRegistered(ObjectName on) {
        if (!checkConnection()) {
            logger.log(BasicLevel.ERROR, "Not yet connected!");
            return false;
        }
        try {
            return connection.isRegistered(on);
        } catch (IOException e) {
            logger.log(BasicLevel.ERROR, "IO ERROR", e);
            return false;
        }
    }

    /**
     * Get an MBean Attribute on the remote server
     * @param on the MBean's ObjectName
     * @param attribute the attribute name
     * @return the attribute value
     */
    public Object getAttribute(ObjectName on, String attribute) {
        if (!checkConnection()) {
            logger.log(BasicLevel.ERROR, "Not yet connected!");
            return null;
        }
        try {
            return connection.getAttribute(on, attribute);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get attribute", e);
            return null;
        }
    }

    /**
     * Get a group of MBean Attributes on the remote server
     * @param on the MBean's ObjectName
     * @param attributes the attributes names
     * @return a list of Attribute objects containing for each attribute
     * its name and value
     */
    public AttributeList getAttributes(ObjectName on, String[] attributes) {
        if (!checkConnection()) {
            logger.log(BasicLevel.ERROR, "Not yet connected!");
            return null;
        }
        try {
            return connection.getAttributes(on, attributes);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get attribute", e);
            return null;
        }
    }

    /**
     * @param on the MBean's ObjectName
     * @return A set containing the ObjectNames for the MBeans selected.
     */
    public Set queryNames(ObjectName on) {
        if (!checkConnection()) {
            logger.log(BasicLevel.ERROR, "Not yet connected!");
            return null;
        }
        try {
            return connection.queryNames(on, null);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get attribute", e);
            return null;
        }
    }

    /**
     * @return the current domain name
     */
    public String getDomain() {
        return domain;
    }

    /**
     * @return the URL of the current connection
     */
    public String getConnectionUrl() {
        return connectionUrl;
    }
    /**
     * @return the list of urls that can be used for connection
     */
    public ArrayList getUrls() {
        return urls;
    }
    /**
     * @param urls the list of urls that can be used for connection
     */
    /*
    public void setUrls(ArrayList urls) {
        this.urls = urls;
    }*/
}
