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

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import javax.management.JMException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.deployment.common.xml.JLinkedList;
import org.ow2.jonas.deployment.domain.DomainMap;
import org.ow2.jonas.deployment.domain.DomainMapException;
import org.ow2.jonas.deployment.domain.lib.DomainMapManager;
import org.ow2.jonas.deployment.domain.xml.Cluster;
import org.ow2.jonas.deployment.domain.xml.ClusterDaemon;
import org.ow2.jonas.deployment.domain.xml.Location;
import org.ow2.jonas.deployment.domain.xml.Server;
import org.ow2.jonas.discovery.DiscoveryEvent;
import org.ow2.jonas.discovery.DiscoveryState;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.management.domain.cluster.BaseCluster;
import org.ow2.jonas.lib.management.domain.cluster.ClusterFactory;
import org.ow2.jonas.lib.management.domain.cluster.cmi.CmiClusterFactory;
import org.ow2.jonas.lib.management.domain.cluster.ha.EjbHaClusterFactory;
import org.ow2.jonas.lib.management.domain.cluster.jk.JkClusterFactory;
import org.ow2.jonas.lib.management.domain.cluster.logical.LogicalCluster;
import org.ow2.jonas.lib.management.domain.cluster.logical.LogicalClusterFactory;
import org.ow2.jonas.lib.management.domain.cluster.tomcat.TomcatClusterFactory;
import org.ow2.jonas.lib.management.domain.proxy.clusterd.ClusterDaemonProxy;
import org.ow2.jonas.lib.management.domain.proxy.server.ServerProxy;
import org.ow2.jonas.lib.management.javaee.J2EEServerState;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;

/**
 * A DomainMonitor instance is created for each Master Server in the domain. It
 * manages the servers and the clusters in the domain.
 * @author danesa
 * @author durieuxp refactoring
 * @author S. Ali Tokmen
 */
public class DomainMonitor {

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

    /**
     * List of known ClusterFactory.
     */
    private ArrayList<ClusterFactory> clusterFactoryList = new ArrayList<ClusterFactory>(5);

    /**
     * List of known ClusterDaemon.
     */
    private HashMap<String, ClusterDaemonProxy> clusterDaemons = new HashMap<String, ClusterDaemonProxy>();

    /**
     * Authentication information for the domain
     */
    private HashMap<String, AuthenticationInformation> authInfo = new HashMap<String, AuthenticationInformation>();

    /**
     * Default authentication information for the domain.
     */
    private AuthenticationInformation defaultAuthInfo;

    /**
     * Reference to the Jmx Service.
     */
    private JmxService jmx = null;

    /**
     * Thread object allowing to check the proxys state in order to detect if
     * they can be reached using the established connection.
     */
    private StateMonitor stateMonitor = null;

    /**
     * My Domain Name
     */
    private String domainName;

    /**
     * Local Server Name
     */
    private String jonasServerName;

    /**
     * The local server name (name of the master)
     */
    private String masterName;

    /**
     * Domain description string (from domain.xml)
     */
    private String description;

    /**
     * list of deployement operations
     */
    @SuppressWarnings("unchecked")
    private ArrayList<DeployAction> deployList = new ArrayList<DeployAction>();

    /**
     * removed server by cluster daemons
     */
    @SuppressWarnings("unused")
    private HashMap<String, ArrayList<String>> removedServerByClusterd = new HashMap<String, ArrayList<String>>();

    /**
     * The LogicalClusterFactory is created by the setMaster() method.
     */
    private LogicalClusterFactory lclFactory = null;

    /**
     * Set to true by setMaster() method.
     */
    private boolean ismaster = false;

    /**
     * Singleton.
     */
    private static DomainMonitor unique;

    /**
     * used to store servers taht might be associated to a cluster daemon when
     * they will start.
     */
    private HashMap<String, String> cd4srvAssociation = null;

    /**
     * Constructor
     */
    public DomainMonitor(final String domain, final String serverName) {
        domainName = domain;
        unique = this;
        jonasServerName = serverName;
    }

    /**
     * Singleton: Each server (master or slave) must have 1 unique DomainMonitor
     * object.
     * @return the unique instance of the DomainMonitor
     */
    public static DomainMonitor getInstance() {
        return unique;
    }

    /**
     *
     */
    public void setMaster() {
        ismaster = true;
        // Create the ClusterFactory objects
        lclFactory = new LogicalClusterFactory(this);
        clusterFactoryList.add(lclFactory);
        clusterFactoryList.add(new JkClusterFactory(this));
        clusterFactoryList.add(new TomcatClusterFactory(this));
        clusterFactoryList.add(new CmiClusterFactory(this));
        clusterFactoryList.add(new EjbHaClusterFactory(this));
        // Add a ServerProxy for the local server
        ArrayList<String> theUrls = new ArrayList<String>();
        JMXServiceURL[] urls = jmx.getConnectorServerURLs();
        for (int i = 0; i < urls.length; i++) {
            theUrls.add(urls[i].toString());
        }
        ServerProxy sp = new ServerProxy(this, jonasServerName, theUrls, null);
        // Register ServerProxy MBean
        ObjectName on = null;
        try {
            on = JonasObjectName.serverProxy(domainName, jonasServerName);
            sp.setObjectName(on.toString());
        } catch (MalformedObjectNameException e) {
            logger.log(BasicLevel.ERROR, "MalformedObjectName: " + jonasServerName);
        }
        // Get connection with the local server's MBean server
        MBeanServerConnection connection = getConnection(jonasServerName);
        sp.setConnection(connection);
        // add ServerProxy to the list
        lclFactory.notifyServer(sp);
        // put monitoring info in the ServerProxy
        sp.getMonitoringInfo();
        // Detect eventual cluster.
        notifyServerProxyRunning(sp);
        readDomainConfig();
    }

    public boolean isMaster() {
        return ismaster;
    }

    public void readDomainConfig() {

        // Create domain map based on the info contained in domain.xml file.
        try {
            createDomainMap();
        } catch (Exception e) {
            logger.log(BasicLevel.INFO, "Cannot read domain.xml");
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Exception raised:" + e);
            }
        }

        // Create state monitor thread
        stateMonitor = new StateMonitor(this);
        stateMonitor.start();
        logger.log(BasicLevel.DEBUG, "State monitor created");
    }

    public String getDescription() {
        return description;
    }

    public String getDomainName() {
        return domainName;
    }

    public void setMonitoringPeriod(final int sec) {
        stateMonitor.setSamplingPeriod(sec);
    }

    public int getMonitoringPeriod() {
        return stateMonitor.getSamplingPeriod();
    }

    /**
     * Create the domain representation found in domain.xml
     * @throws DomainMapException Error reading domain.xml
     * @throws JMException
     */
    @SuppressWarnings("unchecked")
    private void createDomainMap() throws DomainMapException, JMException {

        // Parse the domain.xml file.
        ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
        DomainMap domainMap = null;

        try {
            domainMap = DomainMapManager.getDomainMap(null, currentLoader);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Domain Map :" + domainMap);
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }

        description = domainMap.getDescription();
        // Set the default authentication information for the domain
        String username = domainMap.getUsername();
        String password = domainMap.getPassword();
        if (username != null && password != null) {
            try {
                defaultAuthInfo = new AuthenticationInformation(username, password);
            } catch (UnsupportedEncodingException e) {
                logger.log(BasicLevel.ERROR, e);
            }
        } else {
            defaultAuthInfo = null;
        }

        // Get list of clusterdaemons
        Vector daemons = domainMap.getClusterDaemons();
        for (int i = 0; i < daemons.size(); i++) {
            ClusterDaemon cd = (ClusterDaemon) daemons.elementAt(i);
            String cdname = cd.getName();
            // Add the authentication information for this cluster daemon
            username = cd.getUsername();
            password = cd.getPassword();
            if (username != null && password != null) {
                try {
                    authInfo.put(cdname, new AuthenticationInformation(username, password));
                } catch (UnsupportedEncodingException e) {
                    logger.log(BasicLevel.ERROR, e);
                }
            }
            // Create the proxy and add it to the list
            Location loc = cd.getLocation();
            ClusterDaemonProxy cdp = new ClusterDaemonProxy(this, cdname, loc.getUrlList());
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Adding clusterdaemon : " + cdname);
            }
            // give a name and register mbean
            ObjectName on = JonasObjectName.clusterDaemonProxy(domainName, cdname);
            cdp.setObjectName(on.toString());
            // add it to the list
            clusterDaemons.put(cdname, cdp);
        }

        // Get list of servers
        Vector servers = domainMap.getServers();
        for (int i = 0; i < servers.size(); i++) {
            Server sv = (Server) servers.elementAt(i);
            String svname = sv.getName();
            // Check ClusterDaemon attached to this server
            String cdn = sv.getClusterDaemon();
            ClusterDaemonProxy cdp = null;
            if (cdn != null) {
                cdp = clusterDaemons.get(cdn);
                if (cdp == null) {
                    logger.log(BasicLevel.WARN, "Undeclared ClusterDaemon :" + cdn);
                }
            }
            // Add the authentication information for this server
            username = sv.getUsername();
            password = sv.getPassword();
            if (username != null && password != null) {
                try {
                    authInfo.put(svname, new AuthenticationInformation(username, password));
                } catch (UnsupportedEncodingException e) {
                    logger.log(BasicLevel.ERROR, e);
                }
            }
            // Create the proxy and add it to the list
            Location loc = sv.getLocation();
            ServerProxy sp = findServerProxy(svname);
            if (sp == null) {
                sp = new ServerProxy(this, svname, loc.getUrlList(), cdp);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Adding server : " + svname);
                }
                // give a name and register mbean
                ObjectName on = JonasObjectName.serverProxy(domainName, svname);
                sp.setObjectName(on.toString());
                // add it to the list
                lclFactory.notifyServer(sp);
            } else {
                logger.log(BasicLevel.WARN, "Server already declared: " + svname);
            }
        }

        // Get list of logical clusters
        Vector clusters = domainMap.getClusters();
        for (int i = 0; i < clusters.size(); i++) {
            Cluster cl = (Cluster) clusters.elementAt(i);
            String clname = cl.getName();
            LogicalCluster locl = (LogicalCluster) lclFactory.getCluster(clname);
            if (locl == null) {
                // Create a Logical Cluster
                locl = createLogicalCluster(clname);
            }
            if (locl != null) {
                // Add servers to the new cluster
                LinkedList serverList = cl.getServerList();
                for (int j = 0; j < serverList.size(); j++) {
                    Server sv = (Server) serverList.get(j);
                    String svname = sv.getName();
                    ServerProxy sp = findServerProxy(svname);
                    if (sp == null) {
                        // Server is defined directly in the Cluster
                        String cdn = sv.getClusterDaemon();
                        ClusterDaemonProxy cdp = null;
                        if (cdn != null) {
                            cdp = clusterDaemons.get(cdn);
                            if (cdp == null) {
                                logger.log(BasicLevel.WARN, "Undeclared ClusterDaemon :" + cdn);
                            }
                        }
                        // Create the proxy and add it to the list
                        Location loc = sv.getLocation();
                        sp = new ServerProxy(this, svname, loc.getUrlList(), cdp);
                        // give a name and register mbean
                        ObjectName on = JonasObjectName.serverProxy(domainName, svname);
                        sp.setObjectName(on.toString());
                    }
                    locl.addServer(svname, sp);
                    // Notify the cluster factories
                    notifyServerProxyRunning(sp);
                }
            }
        }

    }

    /**
     * Get a Cluster by its name. Look in all cluster factories.
     * @param name The name of the cluster
     * @return the Cluster or null if unknown by the domain.
     */
    public BaseCluster findCluster(final String name) {
        logger.log(BasicLevel.DEBUG, name);
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        BaseCluster cluster = null;
        // Look in each cluster factory to find if this cluster exists somewhere
        for (Iterator<ClusterFactory> it = clusterFactoryList.iterator(); it.hasNext();) {
            ClusterFactory cf = it.next();
            cluster = cf.getCluster(name);
            if (cluster != null) {
                break;
            }
        }
        return cluster;
    }

    /**
     * Get a ServerProxy by its name.
     * @param name The name of the server
     * @return the ServerProxy or null if unknown by the domain.
     */
    public ServerProxy findServerProxy(final String name) {
        logger.log(BasicLevel.DEBUG, name);
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        // Look in the domain cluster, holding all servers in the domain.
        BaseCluster cluster = lclFactory.getCluster(domainName);
        if (cluster == null) {
            // Not initialized yet: no server known at this time.
            return null;
        }
        return cluster.getServerProxy(name);
    }

    /**
     * Get a ClusterDaemon by its name
     * @param name The name of the clusterdaemon
     * @return the ClusterDaemonProxy or null if unknown by the domain.
     */
    public ClusterDaemonProxy findClusterDaemonProxy(final String name) {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        // the return value will be null if the clusterd si not yet registered
        return clusterDaemons.get(name);
    }

    /**
     * Register a cluster daemon proxy in the domain.
     * @param cdName the cluster daemon's name
     * @param serverName name of the current jonas server (master instance)
     * @param urls connection urls for the cluster daemon
     * @throws MalformedObjectNameException can't create ObjectName for the
     *         ClusterDaemon's MBean
     */
    @SuppressWarnings( {"unchecked", "unchecked"})
    public void registerClusterDaemonProxy(final String cdName, final String serverName, final Collection urls)
            throws MalformedObjectNameException {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        // this cluster daemon is not registered
        // give a name and register mbean
        ObjectName on = JonasObjectName.clusterDaemonProxy(domainName, cdName);
        ClusterDaemonProxy cdp = new ClusterDaemonProxy(this, cdName, urls);
        cdp.setObjectName(on.toString());
        if (clusterDaemons.get(cdName) != null) {
            // the clusterd is already registered.
            // remove it first
            clusterDaemons.remove(cdName);
        }
        clusterDaemons.put(cdName, cdp);
    }

    /**
     * Gets the authentication information for a given server or cluster daemon
     * name defined in the domain.xml file.
     * @param name Name of the server, null to get default.
     * @return Authentication information for server or cluster daemon name, if
     *         no authentication information defined for that name the default
     *         information for this domain, finally null if no default specified
     *         either.
     */
    public AuthenticationInformation getAuthenticationInformation(final String name) {
        if (name == null) {
            return defaultAuthInfo;
        }
        AuthenticationInformation info = authInfo.get(name);
        if (info == null) {
            return defaultAuthInfo;
        } else {
            return info;
        }
    }

    /**
     * Adds authentication information for a given server or the domain.
     * @param name Name of the server, null to change default.
     * @param username User name to use when connecting.
     * @param password Password to use when connecting. Must be passed in clear
     *        format, will be encoded by this method. throws
     *        UnsupportedEncodingException If encoding the password fails.
     */
    public void addAuthenticationInformation(final String name, final String username, final String password)
            throws UnsupportedEncodingException {
        // Password may contain special characters: encode
        AuthenticationInformation info = new AuthenticationInformation(username, AuthenticationInformation
                .encodePassword(password));
        if (name == null) {
            defaultAuthInfo = info;
        } else {
            authInfo.put(name, info);
        }
    }

    /**
     * Removes authentication information for a given server or the domain.
     * @param name Name of the server, null to change domain's default.
     */
    public void removeAuthenticationInformation(final String name) {
        if (name == null) {
            defaultAuthInfo = null;
        } else {
            authInfo.remove(name);
        }
    }

    /**
     * Get the list of ServerProxy.
     * @return Collection of all ServerProxy in the Domain
     */
    @SuppressWarnings("unchecked")
    public Collection getServerList() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        // All servers are set in the Domain Cluster
        LogicalCluster domaincluster = (LogicalCluster) lclFactory.getCluster(domainName);
        if (domaincluster == null) {
            return new ArrayList();
        }
        return domaincluster.getServerProxyList();
    }

    /**
     * @return Get the list of all ClusterDaemonProxy.
     */
    public Collection<ClusterDaemonProxy> getClusterDaemonList() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        return clusterDaemons.values();
    }

    /**
     * @return Get the list of all clusters of any type
     * @return Collection of all Clusters in the domain
     */
    @SuppressWarnings("unchecked")
    public Collection getTotalClusterList() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        ArrayList ret = new ArrayList();
        for (Iterator<ClusterFactory> it = clusterFactoryList.iterator(); it.hasNext();) {
            ClusterFactory cf = it.next();
            ret.addAll(cf.getClusterList());
        }
        return ret;
    }

    /**
     * Create a logical cluster
     * @return LogicalCluster instance
     * @param name cluster name
     */
    private LogicalCluster createLogicalCluster(final String name) {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        return lclFactory.createLogicalCluster(name);
    }

    /**
     * Get the list of logical clusters.
     * @return the list of the logical clusters
     */
    public Collection getLogicalClusterList() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        return lclFactory.getClusterList();
    }

    /**
     * MBean method To be replaced by J2EEDomain.getServers()
     * @return Array of OBJECT_NAMEs of all ServerProxy
     */
    public String[] getProxys() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        // Look in the domain cluster, holding all servers in the domain.
        BaseCluster cluster = lclFactory.getCluster(domainName);
        Collection col = cluster.getServerProxyList();
        String[] ret = new String[col.size()];
        int i = 0;
        for (Iterator it = col.iterator(); it.hasNext();) {
            ServerProxy proxy = (ServerProxy) it.next();
            ret[i++] = proxy.getObjectName();
        }
        return ret;
    }

    /**
     * MBean method.
     * @return Array of OBJECT_NAMEs of all cluster MBeans
     */
    public String[] getClusters() {
        if (!ismaster) {
            throw new RuntimeException("This is not the master.");
        }
        ArrayList<String> al = new ArrayList<String>();
        for (int i = 0; i < clusterFactoryList.size(); i++) {
            ClusterFactory clFactory = clusterFactoryList.get(i);
            Iterator clIt = clFactory.getClusterList().iterator();
            for (; clIt.hasNext();) {
                BaseCluster cl = (BaseCluster) clIt.next();
                al.add(cl.getObjectName());
            }
        }
        String[] ret = new String[al.size()];
        for (int i = 0; i < al.size(); i++) {
            ret[i] = al.get(i);
        }
        return ret;
    }

    /**
     * Handle notifications from Discovery Service.
     * @param event received DiscoveryEvent.
     */
    public void discoveryNotification(final DiscoveryEvent event) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Get notification:" + event);
        }
        // Ignore events from a different domain
        if (!event.getDomainName().equals(domainName)) {
            logger.log(BasicLevel.WARN, "My domain is: '" + domainName + "'. Discovery notification from another domain '"
                    + event.getDomainName() + "'. Forget it.");
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Notification Domain = " + event.getDomainName());
            }
            return;
        }
        String state = event.getState();
        String svname = event.getServerName();

        // Look if the server is known
        ServerProxy proxy = findServerProxy(svname);

        if (state.equals(DiscoveryState.RUNNING) || state.equals(DiscoveryState.STARTUP)) {
            // If this is myself, just forget it
            if (svname.equals(masterName)) {
                logger.log(BasicLevel.DEBUG, "Yes, I'm already running, I knew that.");
                return;
            }
            // Get urls to establish the connection
            ArrayList<String> urls = new ArrayList<String>();
            String[] urlstr = event.getConnectorURL();
            for (int i = 0; i < urlstr.length; i++) {
                urls.add(urlstr[i]);
            }
            if (proxy == null) {
                proxy = new ServerProxy(this, svname, urls, null);
                // Get a name and register mbean
                ObjectName on;
                try {
                    on = JonasObjectName.serverProxy(domainName, svname);
                    proxy.setObjectName(on.toString());
                } catch (MalformedObjectNameException e) {
                    logger.log(BasicLevel.ERROR, "MalformedObjectName - Cannot register MBEAN:" + svname);
                }
            } else {
                proxy.connect(urls);
            }
            notifyServerProxyRunning(proxy);
            /**
             * Associate a cluster daemon to thsi server if needed.
             */
            if (cd4srvAssociation != null) {
                String cdName = cd4srvAssociation.get(svname);
                if (cdName != null) {
                    ClusterDaemonProxy cdProxy = findClusterDaemonProxy(cdName);
                    if (cdProxy != null) {
                        proxy.setClusterdaemon(cdProxy);
                    }
                    /**
                     * Remove server entry.
                     */
                    cd4srvAssociation.remove(svname);
                }
            }
        } else if (state.equals(DiscoveryState.STOPPING)) {
            if (proxy != null) {
                proxy.notifyStopping();
                // TODO Notify ClusterFactory's
            }
        }
    }

    /**
     * Handles Notifications from Discovery Service for clusterd daemons
     * @param event
     */
    @SuppressWarnings("unchecked")
    public void discoveryNotificationForClusterd(final DiscoveryEvent event) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Get notification:" + event + " from cluster daemon \n");
        }
        if (!event.getDomainName().equals(domainName)) {
            logger.log(BasicLevel.WARN, "I'm a master, my domain is " + domainName + ".Discovery notification from domain "
                    + event.getDomainName() + ". Forget it.");
            return;
        }
        String state = event.getState();
        String clusterdName = event.getServerName();
        ClusterDaemonProxy cdp = findClusterDaemonProxy(clusterdName);
        String[] urls = null;
        JLinkedList jUrls = null;
        if (cdp == null) {
            // Create an entry for this server
            urls = event.getConnectorURL();
            jUrls = new JLinkedList("urls");
            for (int i = 0; i < urls.length; i++) {
                jUrls.add(urls[i]);
            }
            cdp = new ClusterDaemonProxy(this, clusterdName, jUrls);
            // give a name and register mbean
            ObjectName on;
            try {
                on = JonasObjectName.clusterDaemonProxy(domainName, clusterdName);
                cdp.setObjectName(on.toString());
                clusterDaemons.put(clusterdName, cdp);
                logger.log(BasicLevel.DEBUG, "Adding clusterdaemon : " + clusterdName);
            } catch (MalformedObjectNameException e) {
                logger.log(BasicLevel.ERROR, "MalformedObjectName - Cannot register MBean:" + clusterdName);
            }
        }

        // If server is running, get its urls to establish a connection.
        if (state.equals(DiscoveryState.STARTUP) || state.equals(DiscoveryState.RUNNING)) {
            cdp.notifyStarting(jUrls);
            // Affect the cluster daemon to his controlled server, that has
            // already been registered.
            List<String> controlledservers = cdp.getControlledServersNames();
            if (controlledservers != null && !controlledservers.isEmpty()) {
                for (Iterator<String> it = controlledservers.iterator(); it.hasNext();) {
                    String servername = it.next();
                    ServerProxy srvProxy = findServerProxy(servername);
                    if (srvProxy != null) {
                        srvProxy.setClusterdaemon(cdp);
                    } else {
                        if (cd4srvAssociation == null) {
                            cd4srvAssociation = new HashMap<String, String>();
                        }
                        /**
                         * The given server might be associated to the given
                         * cluster daemon.
                         */
                        cd4srvAssociation.put(servername, clusterdName);
                    }
                }
            }

            // retrieve jonasBase, jonasRoot, javaHome to avoid retrieving on
            // user request.
        } else if (state.equals(DiscoveryState.STOPPING)) {
            // TODO Notify ClusterFactory's
        }

    }

    /**
     * Notify to all the cluster factories that a server started in the domain
     * @param sp the server's proxy
     */
    @SuppressWarnings("unchecked")
    public void notifyServerProxyRunning(final ServerProxy sp) {
        for (Iterator<ClusterFactory> it = clusterFactoryList.iterator(); it.hasNext();) {
            ClusterFactory cf = it.next();
            cf.notifyServer(sp);
        }
    }

    /**
     * Register a DeployAction
     * @return true if correctly aded in the list
     */
    @SuppressWarnings("unchecked")
    public synchronized boolean registerDeployAction(final DeployAction action) {
        // Don't add if already another similar action
        for (Iterator<DeployAction> it = deployList.iterator(); it.hasNext();) {
            DeployAction act = it.next();
            if (act.getFileName().equals(action.getFileName()) && act.getServerName().equals(action.getServerName())) {
                logger.log(BasicLevel.DEBUG, "Already a similar action");
                return false;
            }
        }
        return deployList.add(action);
    }

    /**
     * Get the list of server where a file is being deployed
     * @param filename file to deploy
     * @return server name
     */
    public synchronized String[] getDeployServers(final String filename) {
        ArrayList<String> slist = new ArrayList<String>();
        for (Iterator<DeployAction> it = deployList.iterator(); it.hasNext();) {
            DeployAction act = it.next();
            if (act.getFileName().equals(filename)) {
                slist.add(act.getServerName());
            }
        }
        return slist.toArray(new String[0]);
    }

    /**
     * Get the current state (string form) of deployment operation
     * @param filename file to deploy
     * @param servername server where deployment is done
     * @return one of "progress","ok","fail"
     */
    public synchronized String getDeployState(final String filename, final String servername) {
        String ret = "error";
        for (Iterator<DeployAction> it = deployList.iterator(); it.hasNext();) {
            DeployAction act = it.next();
            if (act.getFileName().equals(filename) && act.getServerName().equals(servername)) {
                ret = act.getStateAsString();
                break;
            }
        }
        return ret;
    }

    /**
     * Get the error message associated to the error state
     * @param filename file to deploy
     * @param servername server where deployment is done
     * @return error message
     */
    public synchronized String getErrorMessage(final String filename, final String servername) {
        String ret = "";
        for (Iterator<DeployAction> it = deployList.iterator(); it.hasNext();) {
            DeployAction act = it.next();
            if (act.getFileName().equals(filename) && act.getServerName().equals(servername)) {
                ret = act.getErrorMessage();
            }
        }
        return ret;
    }

    /**
     * Forget all deploy information
     */
    public synchronized void forgetAllDeploy() {
        deployList.clear();
    }

    /**
     * Try to refresh the server states TODO notify thread only.
     */
    public void refreshStates() {
        try {
            checkServerStates(false);
        } catch (JMException e) {
            logger.log(BasicLevel.DEBUG, "Cannot refresh states:", e);
        }
    }

    // ------------------------------------------------------------------------------
    // Thread associated to the DomainMonitor
    // ------------------------------------------------------------------------------

    /**
     * Check state of all remote servers in the domain.
     * @throws JMException
     */
    private synchronized void checkServerStates(final boolean readall) throws JMException {
        for (Iterator i = getServerList().iterator(); i.hasNext();) {
            ServerProxy proxy = (ServerProxy) i.next();
            proxy.checkit(readall);
        }
        for (Iterator<ClusterDaemonProxy> i = getClusterDaemonList().iterator(); i.hasNext();) {
            ClusterDaemonProxy proxy = i.next();
            proxy.checkit();
        }
        for (Iterator<ClusterFactory> i = clusterFactoryList.iterator(); i.hasNext();) {
            ClusterFactory cf = i.next();
            cf.getMonitoringInfo();
        }
    }

    /**
     * Monitoring thread that checks managed servers periodically
     */
    class StateMonitor extends Thread {

        /**
         * My DomainMonitor
         */
        private DomainMonitor domainMonitor;

        /**
         * Logger object to log events
         */
        private Logger logger = Log.getLogger("org.ow2.jonas.domain.management");

        /**
         * Default sampling period of 60 seconds
         */
        private long samplingPeriod = 60000L;

        /**
         * To stop the thread
         */
        private boolean stopped = false;

        /**
         * Constructor
         * @param domainMonitor reference on the DomainMonitor instance
         * @param log the logger object
         */
        public StateMonitor(final DomainMonitor domainMonitor) {
            super("DomainStateMonitor");
            setDaemon(true);
            this.domainMonitor = domainMonitor;
        }

        /**
         * Set the sampling period. Allows reconfiguration.
         * @param sec sampling period in sec.
         */
        public void setSamplingPeriod(final int sec) {
            samplingPeriod = sec * 1000L;
        }

        /**
         * @return Sampling period in seconds
         * @return
         */
        public int getSamplingPeriod() {
            return new Long(samplingPeriod / 1000L).intValue();
        }

        /**
         * Stop this thread.
         */
        public void stopit() {
            stopped = true;
        }

        /**
         * Start the state monitor thread
         */
        @Override
        public void run() {
            while (!stopped) {
                try {
                    sleep(samplingPeriod);
                    domainMonitor.checkServerStates(true);
                } catch (JMException me) {
                    logger.log(BasicLevel.WARN, "Exception while checking server states", me);
                } catch (InterruptedException e) {
                    logger.log(BasicLevel.ERROR, "Interrupted", e);
                    break;
                }
            }
        }
    }

    /**
     * Return a JMX connection to a given server in the domain. Use for that,
     * the ServerProxy associated to the server or the current
     * MBeanServerConnection if server is the current server.
     * @param serverName the name of the server
     * @return the connection provided by its ServerProxy if there is a
     *         ServerProxy object in this domain that corresponds to the server
     *         named serverName. Null if there is no such serverProxy or if the
     *         serverName is null.
     */
    public MBeanServerConnection getConnection(final String serverName) {
        // We want a connection to the local server
        if (serverName == null || jonasServerName.equals(serverName)) {
            return jmx.getJmxServerConnection();
        }

        // A connection to a remote server is possible only on the master
        if (!ismaster) {
            logger.log(BasicLevel.WARN, "This server is not the master");
            return null;
        }

        ServerProxy serverProxy = findServerProxy(serverName);
        if (serverProxy != null) {
            return serverProxy.getConnection();
        } else {
            return null;
        }
    }

    /**
     * Return the JMX ConnectorServerURLs of a given server in the domain. Use
     * for that, the ServerProxy associated to the server.
     * @param serverName the name of the server
     * @return the ConnectorServerURLs provided by its ServerProxy if there is a
     *         ServerProxy object in this domain that corresponds to the server
     *         named serverName. Null if thger is no such serverProxy or if the
     *         serverName is null. Alse null if the ConnectorServerURLs list is
     *         null.
     */
    /*
     * public String[] getConnectorServerURLs(String serverName) { if
     * (serverName == null) { return null; } ServerProxy serverProxy =
     * findServerProxy(serverName); if (serverProxy != null) { ArrayList urlList
     * = null; urlList = serverProxy.getUrls(); if (urlList == null) { return
     * null; } String[] ret = new String[urlList.size()]; Iterator it =
     * urlList.iterator(); for (int i = 0; i < urlList.size(); i++) { ret[i] =
     * (String) it.next(); } return ret; } else { return null; } }
     */

    /**
     * @param started if true, return only the servers which are in RUNNING
     *        state anyway, don't pat attention to the servers state
     * @return an array containing the OBJECT_NAMEs of the J2EEServer MBeans
     *         associated to the managed servers in this domain.
     */
    public String[] getServers(final boolean started) {
        Collection col = getServerList(); // not null, maybe empty
        ArrayList<String> al = new ArrayList<String>();
        for (Iterator it = col.iterator(); it.hasNext();) {
            ServerProxy proxy = (ServerProxy) it.next();
            boolean adit = true;
            if (started) {
                if (!J2EEServerState.RUNNING.toString().equals(proxy.getState())) {
                    adit = false;
                }
            }
            if (adit) {
                al.add(proxy.getJ2eeObjectName());
            }
        }
        return al.toArray(new String[al.size()]);
    }

    /**
     * @return an array containing the names of the managed servers in this
     *         domain.
     */
    public String[] getServerNames() {
        Collection col = getServerList();
        String[] names = new String[col.size()];
        int i = 0;
        for (Iterator it = col.iterator(); it.hasNext();) {
            ServerProxy proxy = (ServerProxy) it.next();
            names[i++] = proxy.getName();
        }
        return names;
    }

    /**
     * @param clusterName the cluster name
     * @return an array containing the names of the managed servers in the
     *         cluster.
     */
    public String[] getServerNames(final String clusterName) {
        String[] names = null;
        BaseCluster cl = findCluster(clusterName);
        if (cl != null) {
            int nbServers = cl.getNbMembers();
            names = new String[nbServers];
            if (nbServers == 0) {
                return names;
            }
            Iterator sps = cl.getServerProxyList().iterator();
            int i = 0;
            while (sps.hasNext()) {
                ServerProxy sp = (ServerProxy) sps.next();
                names[i++] = sp.getServerName();
            }
        }
        return names;
    }

    /**
     * Return the state of a server in the domain. Used by JonasAdmin.
     * @param serverName the server name
     * @return Get the state of a server in the domain
     */
    public String getServerState(final String serverName) {
        ServerProxy proxy = findServerProxy(serverName);
        if (proxy != null) {
            return proxy.getState();
        }
        return null;
    }

    /**
     * Return the state of a cluster in the domain. Used by JonasAdmin.
     * @param clusterName the cluster name
     * @return Get the state of a cluster in the domain
     */
    public String getClusterState(final String clusterName) {
        BaseCluster cl = findCluster(clusterName);
        if (cl != null) {
            return cl.getState();
        }
        return null;
    }

    /**
     * Return the state of a cluster daemon in the domain. Used by JonasAdmin.
     * @param clusterdaemonName the cluster daemon name
     * @return Get the state of a cluster daemon in the domain
     */
    public String getClusterdaemonState(final String clusterdaemonName) {
        ClusterDaemonProxy cdProxy = findClusterDaemonProxy(clusterdaemonName);
        if (cdProxy != null) {
            return cdProxy.getState();
        }
        return null;
    }

    /**
     * Return the type of a cluster in the domain. Used by JonasAdmin.
     * @param clusterName the cluster name
     * @return Get the type of a cluster in the domain
     */
    public String getClusterType(final String clusterName) {
        BaseCluster cl = findCluster(clusterName);
        if (cl != null) {
            return cl.getType();
        }
        return null;
    }

    /**
     * @return an array containing the OBJECT_NAMEs of the ClusterDaemons in
     *         this domain
     */
    @SuppressWarnings("unchecked")
    public String[] getClusterDaemons() {
        String[] sb = null;
        Collection<ClusterDaemonProxy> col = getClusterDaemonList();
        sb = new String[col.size()];
        int i = 0;
        for (Iterator<ClusterDaemonProxy> it = col.iterator(); it.hasNext();) {
            ClusterDaemonProxy cdp = it.next();
            sb[i++] = cdp.getObjectName();
        }
        return sb;
    }

    /**
     * Create a logical cluster.
     * @param name the name of the cluster
     * @return the OBJECT_NAME of the associated MBean
     */
    public String createCluster(final String name) {
        BaseCluster cl = createLogicalCluster(name);
        return cl.getObjectName();
    }

    /**
     * Start a managed JOnAS Server
     * @param serverName name of the server
     */
    public void startServer(final String serverName) {
        ServerProxy proxy = findServerProxy(serverName);
        // TODO: pass standby mode as parameter.
        proxy.start(false);
    }

    /**
     * Halt a managed JOnAS Server
     * @param serverName name of the server
     */
    public void haltServer(final String serverName) {
        ServerProxy proxy = findServerProxy(serverName);
        // stop the jvm
        proxy.stop(false);
    }

    /**
     * Stop a managed JOnAS Server
     * @param serverName name of the server
     */
    public void stopServer(final String serverName) {
        ServerProxy proxy = findServerProxy(serverName);
        // Do not stop the JVM.
        proxy.stop(true);
    }

    /**
     * Deploy a module on a target which may be a server or a cluster
     * @param target target name
     * @param fileName file containing the module
     */
    public void deployOnTarget(final String target, final String fileName) {
        ServerProxy sp = findServerProxy(target);
        if (sp != null) {
            // The target is a known server in the domain
            sp.deployModule(fileName);
        } else {
            // maybe the target is a cluster
            BaseCluster cl = findCluster(target);
            if (cl != null) {
                cl.deployModule(fileName);
            } else {
                logger.log(BasicLevel.ERROR, "Can't deploy on unknown target " + target);
            }
        }
    }

    /**
     * Upload a module on a target which may be a server or a cluster
     * @param target target name
     * @param fileName file containing the module
     * @param replaceExisting true if the file can replace an existing one (same
     *        name)
     */
    public void uploadDeployOnTarget(final String target, final String fileName, final boolean replaceExisting) {
        ServerProxy sp = findServerProxy(target);
        if (sp != null) {
            // The target is a known server in the domain
            sp.uploadDeployModule(fileName, replaceExisting);
        } else {
            // maybe the target is a cluster
            BaseCluster cl = findCluster(target);
            if (cl != null) {
                cl.uploadDeployModule(fileName, replaceExisting);
            } else {
                logger.log(BasicLevel.ERROR, "Can't get deploy on unknown target " + target);
            }
        }
    }

    /**
     * Undeploy a module on a target which may be a server or a cluster
     * @param target target name
     * @param fileName file containing the module
     */
    public void unDeployOnTarget(final String target, final String fileName) {
        ServerProxy sp = findServerProxy(target);
        if (sp != null) {
            // The target is a known server in the domain
            sp.undeployModule(fileName);
        } else {
            // maybe the target is a cluster
            BaseCluster cl = findCluster(target);
            if (cl != null) {
                cl.undeployModule(fileName);
            } else {
                logger.log(BasicLevel.ERROR, "Can't get undeploy on unknown target " + target);
            }
        }
    }

    /**
     * Stop a server or a cluster
     * @param target the name of the target to be stoped
     * @return true is success, false otherwise
     * @throws JMException problem when trying to stop
     */
    public boolean stopRemoteTarget(final String target) throws JMException {
        BaseCluster cluster = findCluster(target);
        if (cluster != null) {
            cluster.stopit();
            return true;
        }
        ServerProxy server = findServerProxy(target);
        if (server != null) {
            // this target is a server
            // Do not halt, the same was done on former invocation.
            server.stop(true);
            return true;
        }
        return false;
    }

    /**
     * Start a server or a cluster
     * @param target the name of the target to be started
     * @return true is success, false otherwise
     * @throws JMException problem when trying to start
     */
    public boolean startRemoteTarget(final String target) throws JMException {
        BaseCluster cluster = findCluster(target);
        if (cluster != null) {
            cluster.startit();
            return true;
        }
        ServerProxy server = findServerProxy(target);
        if (server != null) {
            // this target is a server
            // TODO:pass standby mode as parameter.
            server.start(false);
            return true;
        }
        return false;
    }

    /**
     * Upload and deploy a module on a target.
     * @param target the target name
     * @param filename the file containing the module
     * @return true is success, false otherwise
     */
    public boolean uploadDeployFileOn(final String target, final String filename) {
        BaseCluster cluster = findCluster(target);
        if (cluster != null) {
            // this target is a cluster
            cluster.uploadDeployModule(filename, true);
            return true;
        }
        ServerProxy server = findServerProxy(target);
        if (server != null) {
            // this target is a server
            server.uploadDeployModule(filename, true);
            return true;
        }
        return false;
    }

    /**
     * Check if a cluster exists.
     * @param clusterName the name of the cluster to check
     * @return true if a cluster with this name exists
     */
    public boolean isCluster(final String clusterName) {
        BaseCluster cluster = findCluster(clusterName);
        if (cluster != null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param clusterName Name of the cluster
     * @return the servers in the domain that doesn't belong to the cluster
     */
    public String[] getServersNotInCluster(final String clusterName) {
        String[] allServers = getServerNames();
        BaseCluster clust = findCluster(clusterName);
        if (clust != null) {
            String[] allServersInCluster = getServerNames(clusterName);
            if (allServersInCluster.length == 0) {
                return allServers;
            } else {
                int nbServers = allServers.length - allServersInCluster.length;
                String[] allServersNotInCluster = new String[nbServers];
                int j = 0;
                for (int i = 0; i < allServers.length; i++) {
                    String server = allServers[i];
                    if (!clust.isMember(server)) {
                        try {
                            allServersNotInCluster[j++] = server;
                        } catch (IndexOutOfBoundsException be) {
                            logger.log(BasicLevel.DEBUG, "Can't get servers not in cluster " + clusterName + " correctly");
                            break;
                        }
                    }
                }
                return allServersNotInCluster;
            }
        } else {
            return allServers;
        }
    }

    /**
     * @param clusterName Name of the cluster
     * @return the servers in the domain that belong to the cluster
     */
    public String[] getServersInCluster(final String clusterName) {
        return getServerNames(clusterName);
    }

    /**
     * Return the cluster daemon name of a server in the domain. Used by
     * JonasAdmin.
     * @param serverName the server name
     * @return Get the cluster daemon name of a server in the domain
     */
    public String getServerClusterdaemon(final String serverName) {
        ServerProxy proxy = findServerProxy(serverName);
        if (proxy != null) {
            return proxy.getClusterDaemonName();
        }
        return null;
    }

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmx = jmxService;
    }

    /**
     * @return reference to the jmxService
     */
    public JmxService getJmxService() {
        return this.jmx;
    }

    /**
     * @return reference to the jmxService
     */
    public JMXServiceURL getJmxServiceURL() {
        JMXServiceURL[] urls = this.jmx.getConnectorServerURLs();
        return urls[0];
    }
}
