/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * 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.ee.mejb;

import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.ejb.CreateException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.j2ee.ListenerRegistration;
import javax.management.remote.JMXServiceURL;

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.DomainMonitor;
import org.ow2.jonas.lib.util.Log;

/**
 * This is the Management EJB implementation for JOnAS.
 * A MEJB instance is created and deployed at JOnAS start time.
 * It is registered in the ejb/mgmt naming subcontext.
 *
 * The current implementation allows access to managed resources registered in
 * the current (local) MBean server via the standard management methods defined in the
 * javax.management.j2ee.Management interface.
 *
 * It also allows access to managed resources registered in remote MBean servers
 * which belong to the current management domain, via management methods exposed as a
 * WebService endpoint (defined in the ManagementEndpoint interface).
 *
 * @author Adriana Danes
 * @author Vivek Lakshmanan
 * @author Matt Wringe
 */
public class ManagementBean implements SessionBean, ManagementEndpoint {

    /**
     * Logger
     */
    private static Logger logger = Log.getLogger("org.ow2.jonas.ee.mejb");
    /**
     * reference to the context object
     */
    private SessionContext sessionContext = null;
    /**
     * The current JOnAS server's jmx service.
     * The jmx service is used to get connections to remote MBean servers
     */
    private JmxService jmxService = null;
    /**
     * Connection to the current JOnAS server's MBean server
     */
    private MBeanServerConnection jmxServerConnection = null;
    /**
     * The current server name
     */
    private String serverName = null;
    /**
     * The current domain name
     */
    private String domainName = null;
    /**
     * The remote NotificationListener proxy name
     */
    private String proxyName = "MEJB_listener";

    /**
     * ejbCreate method
     *
     * Get the MBeanServer reference to allow local access
     */
    public void ejbCreate() throws CreateException {
        try {
            // TODO : find a 'very better' way to retrieve the MBeanServer used by JOnAS
            // Look if there is already a MBeanServer for JOnAS.
            List<?> mbeanServers = MBeanServerFactory.findMBeanServer(null);
            if (mbeanServers.size() > 0) {
                jmxServerConnection = (MBeanServerConnection) mbeanServers.get(0);
            }
        } catch (Exception e) {
            throw new CreateException("Could not create Management bean: " + e.getMessage());
        }
    }

    /*====================== javax.ejb.SessionBean implementation =================*/

    public void ejbActivate() {
        // Nothing to do when the MEJB is activated
    }
    public void ejbPassivate() {
        // Nothing to do when the MEJB is passivated
    }
    public void ejbRemove() {
        // Nothing to do when the MEJB is removed
    }
    /**
     * Sets the associated session context.
     * @param sessionContext - A SessionContext interface for the instance.
     */
    public void setSessionContext(final SessionContext sessionContext) {
        this.sessionContext = sessionContext;
    }

    /*========================= Management interface implementation ============================*/
    /*=============== The management methods are invoked on the local MBeanServer ========*/


    public Object getAttribute(final ObjectName name, final String attribute) throws
    MBeanException,
    AttributeNotFoundException,
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        try {
            return jmxServerConnection.getAttribute(name, attribute);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("Object getAttribute(ObjectName, String) failed", ioe);
        }
    }

    public AttributeList getAttributes(final ObjectName name, final String[] attributes) throws
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        try {
            return jmxServerConnection.getAttributes(name, attributes);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("AttributeList getAttributes(ObjectName, String[]) failed", ioe);
        }
    }

    public String getDefaultDomain() throws RemoteException {

        try {
            return jmxServerConnection.getDefaultDomain();
        } catch (java.io.IOException ioe) {
            throw new RemoteException("String getDefaultDomain() failed", ioe);
        }
    }

    public Integer getMBeanCount() throws RemoteException {

        try {
            return jmxServerConnection.getMBeanCount();
        } catch (java.io.IOException ioe) {
            throw new RemoteException("Integer getMBeanCount() failed", ioe);
        }
    }

    public MBeanInfo getMBeanInfo(final ObjectName name) throws
    IntrospectionException,
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        try {
            return jmxServerConnection.getMBeanInfo(name);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("MBeanInfo getMBeanInfo(ObjectName) failed", ioe);
        }
    }

    public Object invoke(final ObjectName name, final String operationName, final Object[] params, final String[] signature) throws
    MBeanException,
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        try {
            return jmxServerConnection.invoke(name, operationName, params, signature);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("Object invoke(ObjectName, String, Object[], String[]) failed", ioe);
        }
    }

    public boolean isRegistered(final ObjectName name) throws RemoteException {

        try {
            return jmxServerConnection.isRegistered(name);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("boolean isRegistered(ObjectName) failed", ioe);
        }
    }

    public Set queryNames(final ObjectName name, final QueryExp query) throws RemoteException {
        try {
            return jmxServerConnection.queryNames(name, query);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("Set queryNames(ObjectName, QueryExp)  failed", ioe);
        }
    }

    public void setAttribute(final ObjectName name, final Attribute attribute) throws
    MBeanException,
    AttributeNotFoundException,
    InstanceNotFoundException,
    InvalidAttributeValueException,
    ReflectionException,
    RemoteException {

        try {
            jmxServerConnection.setAttribute(name, attribute);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("void setAttribute(ObjectName, Attribute) failed", ioe);
        }
    }

    public AttributeList setAttributes(final ObjectName name, final AttributeList attributes) throws
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        try {
            return jmxServerConnection.setAttributes(name, attributes);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("AttributeList setAttributes(ObjectName, AttributeList) failed", ioe);
        }
    }

    /**
     * Returns the ListenerRegistration implementation object which allows the client to register
     * a event notification listener.
     * This method also creates a MBean
     * @return An instance of the class implementing the ListenerRegistration interface.
     * <code>null</code> is returned if
     */
    public ListenerRegistration getListenerRegistry() throws RemoteException {
        JMXServiceURL[] urls = jmxService.getConnectorServerURLs();
        return new ListenerRegistrationImpl(urls);
    }

    /*=============== The management methods are invoked on Remote MBeanServers ==========*/

    /**
     * Returns a connection for a server in the domain. Any error messages in getting
     * the server connection is thrown as a remote exception.
     * @param domainServerName    The server name.
     * @return                    A connection to the server.
     */
    private MBeanServerConnection getServerConnection (final String domainServerName) {
        //if the domainServerName is null, return the connection to the current server
        if (domainServerName == null || domainServerName.equals(serverName)) {
            return jmxServerConnection;
        }
        return DomainMonitor.getInstance().getConnection(domainServerName);
    }

    public Object getAttribute (final String domainServerName, final ObjectName name, final String attribute) throws
    AttributeNotFoundException,
    InstanceNotFoundException,
    MBeanException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.getAttribute(name, attribute);
        } catch (IOException ioe) {
            throw new RemoteException ("Object getAttribute(String, ObjectName, String) failed", ioe);
        }
    }


    public AttributeList getAttributes(final String domainServerName, final ObjectName name, final String[] attributes) throws
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.getAttributes(name, attributes);
        } catch (IOException ioe) {
            throw new RemoteException("AttributeList getAttributes(String, ObjectName, String[]) failed", ioe);
        }
    }



    public Integer getMBeanCount(final String domainServerName) throws RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
                throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.getMBeanCount();
        } catch (IOException ioe) {
            throw new RemoteException("Integer getMBeanCount(String) failed", ioe);
        }
    }


    public MBeanInfo getMBeanInfo(final String domainServerName, final ObjectName name) throws
    IntrospectionException,
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection (domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.getMBeanInfo(name);
        } catch (IOException ioe) {
            throw new RemoteException("MBeanInfo getMbeanInfo(String, ObjectName) failed", ioe);
        }
    }

    public Object invoke(final String domainServerName, final ObjectName name, final String operationName, final Object[] params, final String[] signature) throws
    MBeanException,
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection (domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.invoke (name, operationName, params, signature);
        } catch (IOException ioe) {
            throw new RemoteException("Object invoke(String, ObjectName, String, Object[], String[]) failed", ioe);
        }
    }

    public boolean isRegistered(final String domainServerName, final ObjectName name) throws RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.isRegistered(name);
        } catch (java.io.IOException ioe) {
            throw new RemoteException ("boolean isRegistered(String, ObjectName) failed", ioe);
        }
    }

    public Set queryNames(final String domainServerName, final ObjectName name, final QueryExp query) throws RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            return connection.queryNames (name, query);
        } catch (java.io.IOException ioe) {
            throw new RemoteException ("Set queryNames(String, ObjectName, QueryExp) failed", ioe);
        }
    }

    public void setAttribute(final String domainServerName, final ObjectName name, final Attribute attribute) throws
    MBeanException,
    AttributeNotFoundException,
    InstanceNotFoundException,
    InvalidAttributeValueException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException ("Could not connect to server " + domainServerName);
        }
        try {
            connection.setAttribute (name, attribute);
        } catch (java.io.IOException ioe) {
            throw new RemoteException ("void setAttribute(String, ObjectName, Attribute) failed", ioe);
        }
    }

    public AttributeList setAttributes(final String domainServerName, final ObjectName name, final AttributeList attributes) throws
    InstanceNotFoundException,
    ReflectionException,
    RemoteException {

        MBeanServerConnection connection = getServerConnection(domainServerName);
        if (connection == null) {
            throw new RemoteException("Could not connect to server " + domainServerName);
        }
        try {
            return connection.setAttributes(name, attributes);
        } catch (java.io.IOException ioe) {
            throw new RemoteException("AttributeList setAttributes(String, ObjectName, AttributeList) failed", ioe);
        }
    }


    /*================== ManagementBean Monitoring Endpoint implementation ===============*/
    /*=============== The management methods are invoked on Remote MBeanServers ==========*/

    /*
     * Implementation for the ManagementEndpoint interface begins here
     *
     * -the ManagementEndpoint allows monitoring of the server remotely using its web
     *  service.
     * -many of these methods look the same as the ones above but their
     *  data types have been changed to make them easier for a web service to use
     *  them.
     * -The only method used in the ManagementEndpoint interface that is
     *  not listed below is getMBeanCount which was already web service friendly
     * -Special expections are thrown when errors occur. These persist to the
     *  web service client. Exception Class:
     *  org.ow2.jonas.ee.mejb.ManagementEndpointException.
     */

    /**
     * Returns the names of the servers in the domain.
     * @return The names of the servers in the domain.
     * @throws ManagementEndpointException    If any errors occur.
     * @throws RemoteException                If a connection error occurs.
     */
    public String[] getServers() throws ManagementEndpointException, RemoteException {
        String j2eeDomainName = domainName + ":j2eeType=J2EEDomain,name=" + this.domainName;
        return getAttribute(serverName, j2eeDomainName, "serverNames");
    }


    /**
     * @see ManagementEndpoint#getAttribute(String, String, String)
     */
    public String[] getAttribute(final String domainServerName, final String objectName, final String attribute)
            throws ManagementEndpointException {
        try {
            return getObjectValue(getAttribute(domainServerName, new ObjectName(objectName), attribute));
        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in getAttribute service call for objectname: " + objectName
                    + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /**
     * @see ManagementEndpoint#isRegistered(String, String)
     */
    public boolean isRegistered(final String domainServerName, final String objectName) throws ManagementEndpointException {
        try {
            return isRegistered(domainServerName, new ObjectName(objectName));
        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in isRegistered service call for objectname: " + objectName
                    + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /**
     * @see ManagementEndpoint#queryNames(String, String, String)
     */
    public String[] queryNames(final String domainServerName, final String objectName, final String query) throws ManagementEndpointException {
        try {

            // TODO Determine how to use QueryExp. ATM query support disabled.
            return getObjectValue(queryNames(domainServerName, new ObjectName(objectName), null));

        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in queryNames service call for objectname: " + objectName
                    + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /**
     * @see ManagementEndpoint#getAttributesList(String, String)
     */
    public String[] getAttributesList(final String domainServerName, final String objectName) throws ManagementEndpointException {
        try {
            MBeanInfo info = getMBeanInfo(domainServerName, new ObjectName(objectName));
            MBeanAttributeInfo[] attrInfo = info.getAttributes();
            String[] attrs = new String[attrInfo.length];
            for (int i = 0; i < attrs.length; i++) {
                attrs[i] = attrInfo[i].getName();
            }
            return attrs;

        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in getAttributeList service call for objectname: " + objectName
                + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /**
     * @see ManagementEndpoint#getDescription(String, String)
     */
    public String getDescription(final String domainServerName, final String objectName)
        throws ManagementEndpointException {
        try {
            MBeanInfo info = getMBeanInfo(domainServerName, new ObjectName(objectName));
            return info.getDescription();
        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in getDescription service call for objectname: " + objectName
                + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }

    }

    /**
     * @see ManagementEndpoint#getOperations(String, String)
     */
    public String[] getOperations(final String domainServerName, final String objectName)
        throws ManagementEndpointException {
        try {
            MBeanInfo info = getMBeanInfo(domainServerName, new ObjectName(objectName));
            MBeanOperationInfo[] operationInfo = info.getOperations();
            String[] operations = new String[operationInfo.length];
            for (int i = 0; i < operationInfo.length; i++) {
                operations[i] = operationInfo[i].getName();
            }
            return operations;

        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in getOperations service call for objectname: " + objectName
                + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /**
     * @see ManagementEndpoint#invoke(String, String, String[])
     */
    public String[] invoke(final String domainServerName, final String objectName, final String operationName, final String[] params)
    throws ManagementEndpointException, RemoteException {
        try {
            String[] signature = new String[params.length];
            for (int i = 0; i < signature.length; i++) {
                signature[i] = "String";
            }
            return getObjectValue(invoke(domainServerName
                , new ObjectName(objectName)
                , operationName
                , params
                , signature));
        } catch (Exception e) {
            ManagementEndpointException mex = new ManagementEndpointException();
            mex.setExceptionType(e.getClass().toString());
            mex.setMessage("Problem in invoke service call for objectname: " + objectName
                    + " - the request was not completed: " + e.getMessage());
            logger.log(BasicLevel.ERROR, mex.getMessage(), e);
            throw (ManagementEndpointException) mex.initCause(e);
        }
    }

    /*
     *  Listed below are helper methods for the ManagementEndpoint
     *
     *  These methods are used to convert complex data types into more simpler
     *  types so that they work easier with web services. Most complex data types
     *  are converted into a string array
     */

    /**
     * Takes an arbitrary object and returns a string array representation
     * of the object. This methods is used to make passing values from a
     * web service endpoint to a client easier to handle.
     * @param objectValue   An object.
     * @return              A string array representation of the object.
     */
    private String[] getObjectValue(final Object objectValue) {
        String[] value = null;

        if (objectValue == null) {
            value = new String[1];
            value[0] = "null";
        } else {
            // Detect Array or Collection
            if (objectValue.getClass().isArray()) {
                // Array
                value = arrayToString((Object[]) objectValue);
            } else {
                try {
                    // Collection
                    value = collectionToString((Collection) objectValue);
                } catch (Exception e) {
                    // Default
                    value = new String[1];
                    value[0] = objectValue.toString();
                }
            }
        }
        return value;
    }

    /**
     * Takes an array of objects and returns the string array representation.
     * @param pArray    An array of objects.
     * @return          A string array representation of the object array.
     */
    private String[] arrayToString(final Object[] pArray) {
        String[] retStringArr = new String[pArray.length];
        for (int i = 0; i < pArray.length; i++) {
            if (pArray[i] == null) {
                retStringArr[i] = "null";
        } else {
                retStringArr[i] = pArray[i].toString();
            }

        }
        return retStringArr;
    }

    /**
     * Takes a collection and returns the string array representation of the object.
     * @param pCollection    A collection of objects.
     * @return               A string array representation of the object.
     */
    private String[] collectionToString(final Collection pCollection) {
        String[] retStringArr = new String[pCollection.size()];
        Iterator it = pCollection.iterator();
        int i = 0;
        while (it.hasNext()) {
            retStringArr[i++] = it.next().toString();
        }
        return retStringArr;
    }

}