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


import javax.management.JMException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.management.javaee.J2EEManagedObject;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.util.Log;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
/**
 * Implements a managed object which represents a management domain.
 * The complete interface is described in mbeans-descriptors.xml
 * @author Adriana Danes
 * @author Vivek Lakshmanan
 */
public class J2EEDomain extends J2EEManagedObject implements MBeanRegistration {

    /**
     * Management domain name.
     */
    private String domainName = null;

    /**
     * J2EE Management logger.
     */
    private static Logger logger = Log.getLogger("org.ow2.jonas.management.j2eemanagement");

    /**
     * OBJECT_NAME of the current server's J2EEServer MBean.
     * Used in MBean operations such as getServers() or getStartedServers(),
     * when the managed server is not master.
     */
    private String myJ2EEServerOn = null;

    /**
     * The associated DomainMonitor, in case this is the master node.
     */
    private DomainMonitor domainMonitor = null;

    private static J2EEDomain unique;

    /**
     * MBean constructor.
     * @param objectName object name of the managed object
     * @throws JMException ..
     */
    public J2EEDomain(final String objectName) throws JMException {
        super(objectName, false, false, false);
        ObjectName myObjectName = ObjectName.getInstance(objectName);
        domainName = myObjectName.getKeyProperty("name");
        unique = this;
    }

    // -------------- public management methods (cf. mbean-descriptors.xml) -------------

    /**
     * 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 String getErrorMessage(final String filename, final String servername) {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation allowed on master only");
            return null;
        }
        return domainMonitor.getErrorMessage(filename, servername);
    }

    /**
     * Forget all deploy information.
     */
    public void forgetAllDeploy() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation allowed on master only");
            return;
        }
        domainMonitor.forgetAllDeploy();
    }

    /**
     * Implement <code>clusterDaemons</code> management attribute.
     * @return Array contining the ClusterDaemon MBeans OBJECT_NAMEs.
     */
    public String[] getClusterDaemons() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation allowed on master only");
            return null;
        }
        return domainMonitor.getClusterDaemons();
    }

    /**
     * Implement <code>clusters</code> management attribute.
     * Return the MBeans OBJECT_NAMEs of the clusters in this domain
     * @return The list of object names corresponding to the clusters in this domain
     */
    public String[] getClusters() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation allowed on master only");
            return null;
        }
        return domainMonitor.getClusters();
    }

    /**
     * Implement <code>description</code> management attribute.
     * @return Returns the domain description.
     */
    public String getDescription() {
        if (!isMaster()) {
            return domainMonitor.getDescription();
        } else {
            return "Unknown";
        }
    }

    /**
     * Implement <code>master</code> management attribute.
     * @return Returns the master.
     */
    public boolean isMaster() {
        return domainMonitor.isMaster();
    }

    /**
     * Implement <code>myName</code> management attribute.
     * @return domain name
     */
    public String getMyName() {
        return domainName;
    }

    /**
     * Implement <code>serverNames</code> management attribute.
     * @return The list of names corresponding to the servers belonging to this domain
     */
    public String[] getServerNames() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            // Slave: Only itself
            return getServerName();
        }
        return domainMonitor.getServerNames();
    }

    /**
     * Implement <code>servers</code> JSR77 management attribute.
     * Return the MBean OBJECT_NAMEs of the servers belonging to this domain
     * @return The list of object names corresponding to the servers belonging to  this domain
     */
    public String[] getServers() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            return getServer();
        }
        // we want all the servers, not only the started ones
        boolean started = false;
        return domainMonitor.getServers(started);
    }

    /**
     * Implement <code>startedServers</code> management attribute.
     * @return The list of MBean OBJECT_NAMEs of the servers belonging to this domain
     * but only if they are running.
     */
    public String[] getStartedServers() {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            return getServer();
        }
        // we want only the started servers
        boolean started = true;
        return domainMonitor.getServers(started);
    }

    /**
     * Create a logical cluster (used by jonasAdmin).
     * @param name the cluster's name
     * @return the OBJECT_NAME corresponding to the created MBean
     */
    public String createCluster(final String name) {
        logger.log(BasicLevel.DEBUG, "");
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation allowed on master only");
            return null;
        }
        return domainMonitor.createCluster(name);
    }

    /**
     * Start a Remote JOnAS Server.
     * @param serverName Name of the jonas server
     */
    public void startServer(final String serverName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.startServer(serverName);
    }

    /**
     * Halt a Remote JOnAS Server.
     * @param serverName Name of the jonas server
     */
    public void haltServer(final String serverName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.haltServer(serverName);
    }

    /**
     * Stop a Remote JOnAS Server.
     * @param serverName Name of the jonas server
     */
    public void stopServer(final String serverName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.stopServer(serverName);
    }

    /**
     * Deploy a JAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     */
    public void deployJar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            deployOnTarget(target[i], fileName);
        }
    }

    /**
     * Deploy a EAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     */
    public void deployEar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            deployOnTarget(target[i], fileName);
        }
    }

    /**
     * Deploy a WAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     */
    public void deployWar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            deployOnTarget(target[i], fileName);
        }
    }

    /**
     * Deploy a RAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     */
    public void deployRar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            deployOnTarget(target[i], fileName);
        }
    }

    /**
     * Upload and deploy a JAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     * @param replaceExisting true if the uploaded file can replace a file with the same name in the jars directory
     */
    public void uploadDeployJar(final String[] target, final String fileName, final boolean replaceExisting) {
        for (int i = 0; i < target.length; i++) {
            uploadDeployOnTarget(target[i], fileName, replaceExisting);
        }
    }

    /**
     * Upload and deploy a WAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     * @param replaceExisting true if the uploaded file can replace a file with the same name in the wars directory
     */
    public void uploadDeployWar(final String[] target, final String fileName, final boolean replaceExisting) {
        for (int i = 0; i < target.length; i++) {
            uploadDeployOnTarget(target[i], fileName, replaceExisting);
        }
    }

    /**
     * Upload and deploy an EAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     * @param replaceExisting true if the uploaded file can replace a file with the same name in the apps directory
     */
    public void uploadDeployEar(final String[] target, final String fileName, final boolean replaceExisting) {
        for (int i = 0; i < target.length; i++) {
            uploadDeployOnTarget(target[i], fileName, replaceExisting);
        }
    }

    /**
     * Upload and deploy a RAR file on a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be deployed
     * @param replaceExisting true if the uploaded file can replace a file with the same name in the rars directory
     */
    public void uploadDeployRar(final String[] target, final String fileName, final boolean replaceExisting) {
        for (int i = 0; i < target.length; i++) {
            uploadDeployOnTarget(target[i], fileName, replaceExisting);
        }
    }

    /**
     * Undeploy a JAR file of a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be undeployed
     */
    public void unDeployJar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            unDeployOnTarget(target[i], fileName);
        }
    }

    /**
     * Undeploy a WAR file of a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be undeployed
     */
    public void unDeployWar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            unDeployOnTarget(target[i], fileName);
        }
    }

    /**
     * Undeploy a EAR file of a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be undeployed
     */
    public void unDeployEar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            unDeployOnTarget(target[i], fileName);
        }
    }

    /**
     * Undeploy a RAR file of a multiple management target composed of servers and/or clusters.
     * @param target list of server and/or cluster names
     * @param fileName file to be undeployed
     */
    public void unDeployRar(final String[] target, final String fileName) {
        for (int i = 0; i < target.length; i++) {
            unDeployOnTarget(target[i], fileName);
        }
    }
    /**
     * Get the list of servers where a file is being deployed.
     * @param fileName file in deployment
     * @return target servers name
     */
    public String [] getDeployServers(final String fileName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getDeployServers(fileName);
    }
    /**
     * Get the current state of deployment operation.
     * @param fileName file to deploy
     * @param serverName server where deployment is done
     * @return one of "progress","ok","fail"
     */
    public String getDeployState(final String fileName, final String serverName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getDeployState(fileName, serverName);
    }
    /**
     * Get the name of the servers in the domain except the ones that
     * are already belonging to this cluster. This method is used to
     * attach a server u=in the domain to a cluster.
     * @param clusterName the cluster name
     * @return name of the servers that can be attached to the cluster
     */
    public String [] getServersNotInCluster(final String clusterName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getServersNotInCluster(clusterName);
    }
    /**
     * Get the name of the servers in the domain that
     * are belonging to a cluster.
     * @param clusterName the cluster name
     * @return name of the servers that are attached to the cluster
     */
    public String [] getServersInCluster(final String clusterName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getServersInCluster(clusterName);
    }

    /**
     * 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) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getServerState(serverName);
    }

    /**
     * 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) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getClusterState(clusterName);
    }

    /**
     * Return the state of a cluster daemon in the domain. Used by JonasAdmin.
     * @param clusterdaemonName the cluster name
     * @return Get the state of a cluster daemon in the domain
     */
    public String getClusterdaemonState(final String clusterdaemonName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getClusterdaemonState(clusterdaemonName);
    }

    /**
     * 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) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getClusterType(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) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return null;
        }
        return domainMonitor.getServerClusterdaemon(serverName);
    }
    // -------------- public methods for JOnAS -------------

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

    /**
     * Get the domainMonitor, or null if slave server.
     * @return the domainMonitor, for the master server.
     */
    public DomainMonitor getDomainMonitor() {
        return domainMonitor;
    }

    /**
     * Set this server as the master. Used by the J2EEDomain creator.
     * @param ismaster true if the current server is master
     * @param serverName current server name
     */
    public void setMaster(final boolean ismaster, final String serverName) {
        try {
            domainMonitor = new DomainMonitor(domainName, serverName);
            domainMonitor.setJmxService(jmxService);
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot create DomainMonitor", e);
        }
        if (ismaster) {
            domainMonitor.setMaster();
        }
    }

    /**
     * Set the local server's OBJECT_NAME. Used by the J2EEDomain creator.
     * @param myJ2EEServerOn the local server's OBJECT_NAME
     */
    public void setMyJ2EEServerOn(final String myJ2EEServerOn) {
        this.myJ2EEServerOn = myJ2EEServerOn;
    }

    /**
     * Start a remote target on the behalf of ClientAdmin. Already checked the
     * current server is a master.
     * @param target the target to start
     * @return true is success, false otherwise
     * @throws JMException problem when trying to start
     */
    public boolean startRemoteTarget(final String target) throws JMException {
        logger.log(BasicLevel.DEBUG, "");
        return domainMonitor.startRemoteTarget(target);
    }
    /**
     * Stop a remote target on the behalf of ClientAdmin. Already checked the
     * current server is a master.
     * @param target the target to stop
     * @return true is success, false otherwise
     * @throws JMException problem when trying to stop
     */
    public boolean stopRemoteTarget(final String target) throws JMException {
        logger.log(BasicLevel.DEBUG, "");
        return domainMonitor.stopRemoteTarget(target);
    }

    /**
     * Upload and deploy a module to a target on the behalf of Adm. Already checked the
     * current server is a master.
     * @param target the target to deploy on
     * @param filename the file containing the module
     * @return true is success, false otherwise
     */
    public boolean uploadDeployFileOn(final String target, final String filename) {
        logger.log(BasicLevel.DEBUG, "");
        return domainMonitor.uploadDeployFileOn(target, filename);
    }

    // -------------- MBeanRegistration interface --------------------------

    /**
     * @param mbeanServer The MBean server in which the MBean will be registered.
     * @param name The object name of the MBean
     * @return The object name of the MBean
     * @throws Exception This exception will be caught by the MBean server and re-thrown as an MBeanRegistrationException
     */
    public ObjectName preRegister(final MBeanServer mbeanServer, final ObjectName name) throws Exception {
        logger.log(BasicLevel.DEBUG, "");
        if (name == null) {
            return J2eeObjectName.J2EEDomain(domainName);
        }
        //this.mbeanServer = mbeanServer;
        String myName = getObjectName().toString();
        String argName = name.toString();
        if (myName.equals(argName)) {
            return name;
        } else {
            if (logger.isLoggable(BasicLevel.WARN)) {
                logger.log(BasicLevel.WARN, "Trying to register J2EEDomain MBean with the following name: "
                        + argName + " when the expected name is: " + myName);
            }
            return ObjectName.getInstance(domainName);
        }
    }

    /**
     * Add J2EEDomain MBean (myself) as listener to registration/unregistration notifications of
     * JOnAS management MBeans.
     * @param registrationDone Indicates whether or not the MBean has been successfully registered
     */
    public void postRegister(final Boolean registrationDone) {
        logger.log(BasicLevel.DEBUG, "");
    }

    /**
     * @see javax.management.MBeanRegistration#preDeregister()
     */
    public void preDeregister() throws Exception {
        logger.log(BasicLevel.DEBUG, "");
    }

    /**
     * @see javax.management.MBeanRegistration#postDeregister()
     */
    public void postDeregister() {
        logger.log(BasicLevel.DEBUG, "");
    }



    // -------------- private methods -------------
    /**
     * @return an one element array containing the current J2EEServer's OBJECT_NAME
     */
    private String[] getServer() {
        String[] sb = new String[1];
        sb[0] = myJ2EEServerOn;
        return sb;
    }
    /**
     * @return an one element array containing the current server's name
     */
    private String[] getServerName() {
        String[] names = new String[1];
        try {
            ObjectName on = ObjectName.getInstance(myJ2EEServerOn);
            names[0] = on.getKeyProperty("name");
        } catch (MalformedObjectNameException e) {
            // Can't happen
            e.printStackTrace();
        }
        return names;
    }
    /**
     * Deploy a file on a management target which may be a server or a cluster.
     * @param target server or cluster name
     * @param fileName file to be deployed
     */
    private void deployOnTarget(final String target, final String fileName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.deployOnTarget(target, fileName);
    }

    /**
     * Deploy a file on a management target which may be a server or a cluster.
     * @param target server or cluster name
     * @param fileName file to be deployed
     * @param replaceExisting true if the uploaded file can replace a file with the same name in the jars directory
     */
    private void uploadDeployOnTarget(final String target, final String fileName, final boolean replaceExisting) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.uploadDeployOnTarget(target, fileName, replaceExisting);
    }

    /**
     * Undeploy a file of a management target which may be a server or a cluster.
     * @param target server or cluster name
     * @param fileName file to be deployed
     */
    private void unDeployOnTarget(final String target, final String fileName) {
        if (!isMaster()) {
            logger.log(BasicLevel.ERROR, "Operation only possible on master");
            return;
        }
        domainMonitor.unDeployOnTarget(target, fileName);
    }

    /**
     * ref to the Jmx Service.
     */
    private JmxService jmxService = null;

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

}
