/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008 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.lib.management.extensions.domain;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.lib.management.extensions.base.BaseManagement;
import org.ow2.jonas.lib.management.extensions.base.BaseObjectName;
import org.ow2.jonas.lib.management.extensions.base.JonasManagementRepr;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.management.extensions.domain.api.IDomain;

/**
 * Implements domain management functions.
 * @author Adriana Danes
 * @author Oualaa Hani
 */
public class DomainManagement extends BaseManagement implements IDomain {
    /* ======================= J2EEServer MBean attributes ==================== */
    private final static String javaVMsAttribute = "javaVMs";

    private final static String nodeAttribute = "node";

    /* ======================= J2EEDomain MBean attributes ==================== */
    private final static String masterAttribute = "master";

    private final static String serverNamesAttribute = "serverNames";

    private final static String clustersAttribute = "clusters";

    private final static String clusterDaemonsAttribute = "clusterDaemons";

    /* ======================= Connector MBean key ==================== */
    private final static String portKey = "port";

    /* ======================= Connector MBean attribute ==================== */
    private final static String protocolAttribute = "protocol";

    /*
     * ======== Connector MBean attribute values ============
     */
    private final static String httProtocol = "HTTP";

    /**
     * The J2EEDomain MBean's ObjectName. All the servers in the domain have a
     * J2EEDomain MBean registered in their JMX server with the same ObjectName:
     * domainName:j2eeType=J2EEDomain,name=domainName
     */
    private ObjectName domainOn = null;

    /**
     * The J2EEServer MBean's ObjectName. This is the current server's MBean.
     */
    private ObjectName serverOn = null;

    /**
     * Singleton.
     */
    private static DomainManagement domainManagement = null;

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

    /**
     * Constructor.
     */
    public DomainManagement() {
        super();
        domainOn = J2eeObjectName.J2EEDomain(getDomainName());
        serverOn = J2eeObjectName.J2EEServer(getDomainName(), getServerName());
    }

    /**
     * Gets singleton instance.
     * @return singleton instance of domain management.
     */
    public static DomainManagement getInstance() {
        if (domainManagement == null) {
            domainManagement = new DomainManagement();
        }
        return domainManagement;
    }

    /*
     * ======== Operations concerning the current server ==========
     */

    /**
     * @return the current server's host.
     */
    public String getServerHost() {
        String[] jvms = (String[]) JonasManagementRepr.getAttribute(serverOn, javaVMsAttribute, getServerName());
        // JVM standard MBean
        ObjectName jvmOn = null;
        try {
            jvmOn = ObjectName.getInstance(jvms[0]);
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return null;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            logger.log(BasicLevel.ERROR, "Following error occured: " + e);
            return null;
        }
        return ((String) JonasManagementRepr.getAttribute(jvmOn, nodeAttribute, getServerName()));
    }

    /**
     * @return the current server's port if the web service activated.
     */
    @SuppressWarnings("unchecked")
    public String getServerPort() {
        try {
            ObjectName connectorOns = BaseObjectName.catalinaConnectors(getDomainName());
            Set<ObjectName> connectorOnSet = JonasManagementRepr.queryNames(connectorOns, getServerName());
            if (connectorOnSet.isEmpty()) {
                // This case is not possible normally
                // TODO - treat error case
            } else {
                Iterator<ObjectName> it = connectorOnSet.iterator();
                while (it.hasNext()) {
                    ObjectName connectorOn = it.next();
                    if (connectorOnSet.size() == 1) {
                        // most common case on a master or admin server
                        // nothing to check, just return port key
                        return connectorOn.getKeyProperty(portKey);
                    }
                    String protocol = (String) JonasManagementRepr
                            .getAttribute(connectorOn, protocolAttribute, getServerName());
                    if (protocol.contains(httProtocol)) {
                        // Maybe we should check the scheme attribute
                        // (http/https)
                        return connectorOn.getKeyProperty(portKey);
                    }
                }
            }
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return null;
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "following exception occured: " + e);
            return null;
        }
        // didn't found a HTTP protocol, just return default port number for a
        // master
        return null;
    }

    /*
     * ======== Operations which are directly relied to the domain ===========
     */

    /**
     * Return True if the managed server is a master.
     * @param pServer managed server name
     * @return the "master" attribute of the corresponding J2EEDomain MBean
     */
    public boolean isMaster(final String pServer) {
        return ((Boolean) JonasManagementRepr.getAttribute(domainOn, masterAttribute, pServer)).booleanValue();
    }

    /**
     * Return True if the current server is a master.
     * @return the "master" attribute of the current J2EEDomain MBean
     */
    public boolean isMaster() {
        return ((Boolean) JonasManagementRepr.getAttribute(domainOn, masterAttribute, getServerName())).booleanValue();
    }

    /**
     * @return the names of servers in the domain.
     */
    public String[] getServerNames() {
        return (String[]) JonasManagementRepr.getAttribute(domainOn, serverNamesAttribute, getServerName());
    }

    /**
     * @return the names of clusters in the domain.
     */
    public String[] getClustersNames() {
        String[] clusters = getClusters();
        return getKeyValues(clusters, "name");
    }

    /**
     * @return the names of clustersDaemons in the domain.
     */
    public String[] getClusterDaemonNames() {
        String[] cdm = getclusterDaemons();
        return getKeyValues(cdm, "name");
    }

    /**
     * Return the clusters in the domain. A cluster in the domain has associated
     * a cluster MBean having in its ObjectName a key property 'type' with
     * values in {LogicalCluster, TomcatCluster, JkCluster, EjbHa}. Remove from
     * the clusters list the LogicalCluster which represents the domain.
     * @return the OBJECT_NAMES of the Cluster MBeans.
     */
    public String[] getClusters() {
        // Get all clusters, event the LogicalCluster representing the domain
        String[] allClusters = (String[]) JonasManagementRepr.getAttribute(domainOn, clustersAttribute, getServerName());
        if (allClusters == null) {
            // called in a slave context, after a context switch
            return null;
        }
        String[] allClusterNames = getKeyValues(allClusters, "name");
        String[] clusters = new String[allClusters.length - 1];
        int j = 0;
        for (int i = 0; i < allClusters.length; i++) {
            if (!getDomainName().equals(allClusterNames[i])) {
                clusters[j++] = allClusters[i];
            }
        }
        return clusters;
    }

    /**
     * Return the clusterDaemons in the domain. A clusterDaemon in the domain
     * has associated a cluster daemon MBean having in its ObjectName a key
     * property 'type' with value equal to 'ClusterDaemon'.
     * @return the OBJECT_NAMES of the Cluster Daemon MBeans.
     */
    public String[] getclusterDaemons() {
        return (String[]) JonasManagementRepr.getAttribute(domainOn, clusterDaemonsAttribute, getServerName());
    }

    /**
     * Add a new server in the domain. Use LogicalCluster MBean corresponding to
     * the domain.
     * @param serverName the server's name.
     * @param connector the server's connector it can be JRMP, IRMI or IIOP.
     * @param userName the user of the new server.
     * @param password the password of the new server.
     * @param clusterDaemon the name of clusterDaemon if this server is
     *        controlled by a clusterDaemon.
     */
    public void addServer(final String serverName, final String connector, String userName, String password,
            String clusterDaemon) {
        if (clusterDaemon == null || clusterDaemon.length() < 1) {
            clusterDaemon = null;
        }
        if (userName == null || password == null || userName.length() < 1 || password.length() < 1) {
            userName = null;
            password = null;
        }
        String currentServerName = getServerName();
        String domainName = getDomainName();
        String clusterType = "LogicalCluster";

        ObjectName on = null;
        try {
            on = JonasObjectName.cluster(domainName, domainName, clusterType);
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return;
        }
        String[] signature = {"java.lang.String", "[Ljava.lang.String;", "java.lang.String", "java.lang.String",
                "java.lang.String"};
        Object[] params = new Object[5];
        if (serverName != null) {
            params[0] = serverName;
            String[] urls = {connector};
            params[1] = urls;
            params[2] = clusterDaemon;
            params[3] = userName;
            params[4] = password;
            JonasManagementRepr.invoke(on, "addServer", params, signature, currentServerName);
        }
    }

    /**
     * Remove a list of servers from the domain.
     * @param serversToRemove the list of servers to remove.
     */
    public void removeServers(final String[] serversToRemove) {
        String currentServerName = getServerName();
        String domainName = getDomainName();
        // Remove the servers from all the LogicalClusters.
        // Construct the list of the logical clusters, then add the domain's
        // cluster
        List<ObjectName> removeClOns = new ArrayList<ObjectName>();
        String[] clusters = getClusters();
        for (int i = 0; i < clusters.length; i++) {
            String type = getKeyValue(clusters[i], "type");
            String name = getKeyValue(clusters[i], "name");
            if (!"LogicalCluster".equals(type)) {
                continue;
            }
            if (domainName.equals(name)) {
                continue;
            }
            ObjectName clOn = null;
            try {
                clOn = ObjectName.getInstance(clusters[i]);
            } catch (MalformedObjectNameException e) {
                // TODO Auto-generated catch block
                logger.log(BasicLevel.ERROR, "bad objectName: " + e);
                continue;
            } catch (NullPointerException e) {
                // why continuing will something turned wrong ?
                logger.log(BasicLevel.ERROR, "following exception occured: " + e);
                continue;
            }
            removeClOns.add(clOn);
        }
        try {
            ObjectName on = JonasObjectName.cluster(domainName, domainName, "LogicalCluster");
            removeClOns.add(on);
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return;
        }

        // Remove all the servers from all the clusters in removeClOns
        for (int j = 0; j < serversToRemove.length; j++) {
            String serverName = serversToRemove[j];
            for (int i = 0; i < removeClOns.size(); i++) {
                ObjectName clOn = removeClOns.get(i);
                String[] signature = {"java.lang.String"};
                String[] params = {serverName};
                JonasManagementRepr.invoke(clOn, "removeServer", params, signature, currentServerName);
            }
        }
    }

    /**
     * Add a new cluster in the domain.
     * @param clusterName the new cluster's name.
     */
    public void addCluster(final String clusterName) {
        ObjectName domainOn = J2eeObjectName.J2EEDomain(getDomainName());
        String serverName = getServerName();
        String[] signature = {"java.lang.String"};
        String[] params = {clusterName};
        signature[0] = "java.lang.String";
        JonasManagementRepr.invoke(domainOn, "createCluster", params, signature, serverName);
    }

    /*
     * ========== Operations concerning a given server ============
     */

    /**
     * Return the state of a server in the domain
     * @param serverName the server's name
     * @return the state of a server in the domain
     */
    public String getServerState(final String serverName) {
        String operation = "getServerState";
        Object[] param = {serverName};
        String[] signature = {"java.lang.String"};
        return (String) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /**
     * Return the clusterDaemon of a server in the domain.
     * @param serverName the server's name
     * @return the name of the server's cluster daemon
     */
    public String getServerClusterdaemon(final String serverName) {
        String operation = "getServerClusterdaemon";
        Object[] param = {serverName};
        String[] signature = {"java.lang.String"};
        return (String) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /**
     * Start the server.
     * @param serverName the name of the server to start
     * @param standby <code>true</code> to enter standby mode.
     */
    public void startServer(final String serverName, final boolean standby) {
        String currentServerName = getServerName();
        String domainName = getDomainName();
        ObjectName on = null;
        try {
            on = JonasObjectName.serverProxy(domainName, serverName);
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return;
        }
        String opName = "start";
        Object[] pstandby = {standby};
        String[] signature = {boolean.class.toString()};
        JonasManagementRepr.invoke(on, opName, pstandby, signature, currentServerName);
    }

    /**
     * Stop the server.
     * @param serverName the name of the server to stop
     * @param standby <code>true</code> to enter standby mode.
     */
    public void stopServer(final String serverName, final boolean standby) {
        String currentServerName = getServerName();
        String domainName = getDomainName();
        ObjectName on = null;
        try {
            on = JonasObjectName.serverProxy(domainName, serverName);
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.ERROR, "bad objectName: " + e);
            return;
        }
        String opName = "stop";
        Object[] pstandby = {standby};
        String[] signature = {boolean.class.toString()};
        JonasManagementRepr.invoke(on, opName, pstandby, signature, currentServerName);
    }

    /*
     * ======== Operations concerning a given cluster ===========
     */

    /**
     * Return the state of a cluster in the domain.
     * @param clusterName the cluster's name
     * @return the state of a cluster in the domain
     */
    public String getClusterState(final String clusterName) {
        String operation = "getClusterState";
        Object[] param = {clusterName};
        String[] signature = {"java.lang.String"};
        return (String) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /**
     * Return the type of a cluster in the current domain.
     * @param clusterName the cluster's name
     * @return the result of the getClusterType operation
     */
    public String getClusterType(final String clusterName) {
        String operation = "getClusterType";
        Object[] param = {clusterName};
        String[] signature = {"java.lang.String"};
        return (String) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /**
     * @param clusterName the cluster name
     * @return the server names that are belonging to a cluster
     */
    public String[] getServerNames(final String clusterName) {
        String operation = "getServersInCluster";
        Object[] param = {clusterName};
        String[] signature = {"java.lang.String"};
        return (String[]) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /*
     * ======= Operations concerning a given clusterDaemon ========
     */

    /**
     * Return the state of a cluster daemon in the domain.
     * @param clusterDaemonName the cluster daemon's name
     * @return the state of a cluster daemon in the domain
     */
    public String getClusterdaemonState(final String clusterDaemonName) {
        String operation = "getClusterdaemonState";
        Object[] param = {clusterDaemonName};
        String[] signature = {"java.lang.String"};
        return (String) JonasManagementRepr.invoke(domainOn, operation, param, signature, getServerName());
    }

    /**
     * Get the name of the servers in the domain except the ones that are
     * belonging to this cluster.
     * @param clusterName the cluster name
     * @return name of the servers that can be attached to the cluster
     */
    public String[] getServersNotInCluster(final String clusterName) {
        Object[] asParam = {clusterName};
        String[] asSignature = {"java.lang.String"};
        String[] serverNames = (String[]) JonasManagementRepr.invoke(domainOn, "getServersNotInCluster", asParam, asSignature,
                getServerName());
        return serverNames;
    }

}
