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

package org.ow2.jonas.lib.management.domain.proxy;

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

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.management.domain.AuthenticationInformation;
import org.ow2.jonas.lib.management.domain.DomainMonitor;
import org.ow2.jonas.lib.management.javaee.J2EEServerState;
import org.ow2.jonas.lib.util.Log;

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

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

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

    /**
     * Domain name.
     */
    private String domain;

    /**
     * The OBJECT_NAME of the associated MBean.
     * Must be initialized before checking connection.
     */
    private String objectName = null;

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

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

    /**
     * JMX connection.
     */
    private MBeanServerConnection connection = null;

    /**
     * JMX Connector.
     */
    private JMXConnector connector = null;

    /**
     * Reference to the JMX service used to register MBean.
     */
    private JmxService jmx = null;

    /**
     * Reference to the DomainMonitor.
     */
    private DomainMonitor dm = null;

    /**
     * The proxy current state.
     */
    private J2EEServerState state = J2EEServerState.INITIAL;

    /**
     * Constructor.
     * @param name the proxy name
     * @param urls the urls that can be used to establish connection
     * @param dm reference to the domain monitor
     */
    public JMXProxy(final DomainMonitor dm, final String name, final Collection urls) {
        this.dm = dm;
        this.name = name;
        logger.log(BasicLevel.DEBUG, name);

        // Useful for registering mbeans
        jmx = dm.getJmxService();
        domain = dm.getDomainName();
        // Connection may be done later or now
        if (urls != null) {
            if (connect(urls)) {
                // If connection was established, set state to "RUNNING".
                state = J2EEServerState.RUNNING;
            }
        }
    }

    /**
     * MBean method returning the state.
     * @return the state value
     */
    public String getState() {
        return state.toString();
    }

    /**
     * Only used by ServerProxy.
     * @return state
     */
    public J2EEServerState getJ2EEServerState() {
        return state;
    }

    /**
     * Change state.
     * @param state new state
     */
    protected void setState(final J2EEServerState state) {
        this.state = state;
    }

    /**
     * Disconnect the proxy.
     */
    public void disconnect() {
        if (connector != null) {
            try {
                connector.close();
            } catch (IOException e) {
                logger.log(BasicLevel.DEBUG, "Unable to close connection for " + name +
                        " on domain " + domain + " because" + e);
            }
        }
        connection = null;
    }

    /**
     * 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(final Collection urls) {
        logger.log(BasicLevel.DEBUG, name);

        // Get JMXURLs from the given String collection of urls.
        this.urls = new ArrayList(urls);
        // For now, just use the first url.
        // Later: try all urls until connection is OK.
        JMXServiceURL url = null;
        String urlstr = (String) urls.iterator().next();
        try {
            url = new JMXServiceURL(urlstr);
        } catch (MalformedURLException e) {
            logger.log(BasicLevel.ERROR, "Malformed URL:" + urlstr);
            return false;
        }
        connectionUrl = urlstr;
        try {
            // Get authentication information, if any, from the domain manager (dm)
            String username = null;
            String password = null;
            AuthenticationInformation authInfo = dm.getAuthenticationInformation(this.name);
            if (authInfo != null) {
                username = authInfo.getUsername();
                password = authInfo.decodePassword();
            }

            // Create connector and establish connection
            connector = ConnectorUtils.getConnector(url, username, password, logger);

            if (connector != null) {
                Subject subject = null;
                connection = connector.getMBeanServerConnection(subject);
                if (testConnection(connection)) {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "connected to server " + name + " using URL: " + urlstr);
                    }
                    return true;
                }
            }
        } catch (Exception e) {
            // The user should be notified.
            logger.log(BasicLevel.INFO, "Could not establish connection for " + urlstr + " named: " + name + " on domain : " + domain + ".");
            logger.log(BasicLevel.DEBUG, "Could not establish connection for " + urlstr + " named: " + name + " on domain : " + domain + "." + e);
        }
        return false;
    }

    /**
     * Used by the connect method to check that the newly 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 testConnection(final MBeanServerConnection connection) {
        try {
            connection.getMBeanCount().intValue();
            return true;
        } catch (IOException e) {
            logger.log(BasicLevel.DEBUG, name + " : IOException");
        }
        return false;
    }

    /**
     * Check the established connection to the remote server, or try to establish
     * a connection if the connection object is null.
     * @return true if connection is ok. false otherwise.
     */
    protected boolean checkConnection() {
        if (connection == null) {
            logger.log(BasicLevel.DEBUG, name + " : null connection, try to connect");
            // Don't need to test connection as the connect() method does this
            return connect(this.urls);
        }
        return testConnection(connection);
    }

    /**
     * @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(final String on) {
        objectName = on;
        jmx.registerMBean(this, on);
    }
    /**
     * @return the connection to the remote MBean server
     */
    public MBeanServerConnection getConnection() {
        return connection;
    }

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

    /**
     * @return the URL of the current connection
     */
    public String getConnectionUrl() {
        return connectionUrl;
    }
    /**
     * @return the urls that can be used for connection
     */
    public ArrayList getUrls() {
        return urls;
    }

    /**
     * Set connection to the managed server's MBeanServer. Currently this method is called
     * only in the case of a master server which creates its own ServerProxy.
     * @param connection connection to the managed MBeanServer.
     */
    public void setConnection(final MBeanServerConnection connection) {
        this.connection = connection;
        state = J2EEServerState.RUNNING;
    }

    /**
     *
     * @return reference of DomainMonitor
     */
    public DomainMonitor getDm() {
        return dm;
    }
    // ---------------------------------------------------------------------------
    // Methods to access the remote mbean server
    // ---------------------------------------------------------------------------

    /**
     * Check if an ObjectName is registered on the remote server.
     * @param on the ObjectName to be checked
     * @return true if registered
     */
    public boolean isRegistered(final 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(final ObjectName on, final 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;
        }
    }

    /**
     * Set an MBean Attribute on the remote server.
     * @param on the MBean's ObjectName
     * @param attribute the attribute name
     */
    public void setAttribute(final ObjectName on, final String attribute, final Object value) {
        if (!checkConnection()) {
            logger.log(BasicLevel.ERROR, "Not yet connected!");
        }
        try {
            Attribute oAttribute = new Attribute(attribute, value);
            connection.setAttribute(on, oAttribute);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot set attribute", e);
        }
    }

    /**
     * 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(final ObjectName on, final 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(final 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;
        }
    }

}
