/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.commands.admin;

import static org.ow2.jonas.commands.admin.CLIConstants.ADDFILE;
import static org.ow2.jonas.commands.admin.CLIConstants.CUSTOMOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.DEBUGOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.GCOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.HALTOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.ISDEPLOYOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTAPP;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTBEAN;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTENV;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTJNDI;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTMODULE;
import static org.ow2.jonas.commands.admin.CLIConstants.LISTTOPICS;
import static org.ow2.jonas.commands.admin.CLIConstants.PASSIVATEOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.REMOVEFILE;
import static org.ow2.jonas.commands.admin.CLIConstants.STARTOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.STOPOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.SYNCOPTION;
import static org.ow2.jonas.commands.admin.CLIConstants.TTOPTION;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

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

import org.ow2.jonas.commands.admin.util.JMXConnectionHelper;
import org.ow2.jonas.launcher.felix.JOnAS;

/**
 * This class implements a jmx remote client to administer a JOnAS server.
 * It may be run either in interactive mode or in command mode.
 * @author Adriana Danes and Philippe Coq
 * Contributor(s):
 *      Florent Benoit & Ludovic Bert : Methods for wars and ear files
 *      S. Ali Tokmen : added user name and password options
 */
public class ClientAdmin {

    /**
     * Default JOnAS name.
     */
    private static final String DEFAULT_NAME = "jonas";

    /**
     * Default JOnAS name.
     */
    private static final String JONAS_NAME = "jonas.name";

    /**
     * Default JMX URL prefix.
     */
    private final static String JMXSERVICE_URL_PREFIX = "service:jmx:rmi:///jndi/";

    /**
     * System property for JOnAS bootstrap.
     */
    private static final String JONAS_BOOTSTRAP = "jonas.bootstrap";

    /**
     * $JONAS_BASE system property.
     */
    private static final String JONAS_BASE = "jonas.base";

    /**
     * Name of the carol file.
     */
    private static final String CAROL_FILE = "carol.properties";

    /**
     * Default timeout (120 seconds)
     */
    private static final int DEFAULT_TIMEOUT = 120000;

    /**
     * Define one second by loop (sleep value) in milliseconds.
     */
    private static final int WAIT_LOOP_MSEC = 1000;

    /**
     * Current JOnAS name.
     */
    private String jonasName = null;

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

    /**
     * Server object name.
     */
    private ObjectName j2eeServerObjectName = null;

    /**
     * Domain object name.
     */
    private ObjectName domainOn = null;

    /**
     * True if server connection is null.
     */
    private boolean isError = false;

    /**
     * List of servers where deployment will be done.
     */
    private String[] target = null;

    /**
     * Quiet option.
     */
    private boolean qOption = false;

    /**
     * User name to use when connecting.
     */
    private String username = null;

    /**
     * Password to use when connecting.
     */
    private String password = null;

    /**
     * JMX Connection Helper.
     */
    private JMXConnectionHelper connection;

    public ClientAdmin(final String[] args) throws Exception {
        String fileName = null;
        String timeout = null;
        String topic = null;
        String pingTimeoutValue = null;
        String protocol = null;
        String registry = null;
        String manageableState = null;

        boolean pingOption = false;
        boolean startOption = false;

        boolean interactive = true;
        List<CLIArgument> lstArgs = new ArrayList<CLIArgument>();
        // Get command args
        for (int argn = 0; argn < args.length; argn++) {
            String arg = args[argn];
            boolean nextArgument = argn < args.length - 1;

            if (arg.equals("-h") || arg.equals("-?")) {
                usage();
                System.exit(0);
            }
            if (arg.equals("-a") && nextArgument) {
                fileName = args[++argn];
                CLIArgument ja = new CLIArgument(ADDFILE, fileName);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-ping")) {
                pingOption = true;
                continue;
            }
            if (arg.equals("-custom")) {
                CLIArgument ja = new CLIArgument(CUSTOMOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-e")) {
                CLIArgument ja = new CLIArgument(LISTENV);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-gc")) {
                CLIArgument ja = new CLIArgument(GCOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-j")) {
                CLIArgument ja = new CLIArgument(LISTJNDI);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-l")) {
                CLIArgument ja = new CLIArgument(LISTBEAN);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-lmodules")) {
                CLIArgument ja = new CLIArgument(LISTMODULE);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-lapps")) {
                CLIArgument ja = new CLIArgument(LISTAPP);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-registry") && nextArgument) {
                registry = args[++argn];
                continue;
            }
            if (arg.equals("-protocol") && nextArgument) {
                protocol = args[++argn];
                continue;
            }
            if (arg.equals("-timeout") && nextArgument) {
                pingTimeoutValue = args[++argn];
                continue;
            }
            if (arg.equals("-target") && nextArgument) {
                target = makeArrayFrom(args[++argn]);
                continue;
            }
            if (arg.equals("-r") && nextArgument) {
                fileName = args[++argn];
                CLIArgument ja = new CLIArgument(REMOVEFILE, fileName);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-stop")) {
                CLIArgument ja = new CLIArgument(STOPOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-halt")) {
                CLIArgument ja = new CLIArgument(HALTOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-start")) {
                CLIArgument ja = new CLIArgument(STARTOPTION);
                lstArgs.add(ja);
                interactive = false;
                startOption = true;
                continue;
            }
            if (arg.equals("-sync")) {
                CLIArgument ja = new CLIArgument(SYNCOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-passivate")) {
                CLIArgument ja = new CLIArgument(PASSIVATEOPTION);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("--debug-level") && nextArgument) {
                topic = args[++argn];
                CLIArgument ja = new CLIArgument(DEBUGOPTION, topic);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-t")) {
                CLIArgument ja = new CLIArgument(LISTTOPICS);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-tt") && nextArgument) {
                timeout = args[++argn];
                CLIArgument ja = new CLIArgument(TTOPTION, timeout);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-isdeployed") && nextArgument) {
                fileName = args[++argn];
                CLIArgument ja = new CLIArgument(ISDEPLOYOPTION, fileName);
                lstArgs.add(ja);
                interactive = false;
                continue;
            }
            if (arg.equals("-q")) {
                qOption = true;
                interactive = false;
                continue;
            }
            if (arg.equals("-username")) {
                username = args[++argn];
                continue;
            }
            if (arg.equals("-password")) {
                password = args[++argn];
                continue;
            }
            if (arg.equals("-manageable.state") && nextArgument) {
                manageableState = args[++argn];
                continue;
            }
            System.out.println("Bad option: " + arg);
            usage();
            System.exit(2);
        }

        if (System.getProperty(JONAS_NAME) != null) {
            jonasName = System.getProperty(JONAS_NAME);
        } else {
            jonasName = DEFAULT_NAME; // default value
        }

        JMXServiceURL jmxServiceUrl = initializeJMXConnectionHelper(registry, protocol);

        // Start JOnAS server if not already started
        if (startOption) {
            boolean isStarted = connection.connect();
            connection.close();

            if (!isStarted) {
                JOnAS jonas = new JOnAS(Boolean.getBoolean("jonas.cache.clean"));
                jonas.start();
                return;
            } else if (isStarted && Boolean.getBoolean(JONAS_BOOTSTRAP)) {
                System.err.println("JOnAS bootstrap is already started.");
                return;
            }
        }

        if (pingOption) {
            int pingTimeout = DEFAULT_TIMEOUT;
            // if a timeout is set by user, use it.
            if (pingTimeoutValue != null) {
                try {
                    pingTimeout = Integer.parseInt(pingTimeoutValue);
                } catch (NumberFormatException nfe) {
                    System.err.println("Incorrect timeout value for ping. Value is '" + pingTimeoutValue + "'.");
                }
            }
            // Wait JOnAS server is running
            System.exit(waitServerManageable(pingTimeout, manageableState));
        }

        // Now, we're in the administrative mode
        // We should try to contact the server
        if (!connection.connect()) {
            // Cannot connect
            System.err.println("Cannot connect to JOnAS using '" + jmxServiceUrl + "'");
            connection.getLastException().printStackTrace(System.err);
        } else {
            System.out.println("You are administering server named " + jonasName);
            System.out.println("  " + jmxServiceUrl);

            try {
                MBeanServerConnection conn = connection.getConnection();
                j2eeServerObjectName = getJ2EEServerObjectName(conn);
                domainName = j2eeServerObjectName.getDomain();
                domainOn = ObjectName.getInstance(domainName + ":j2eeType=J2EEDomain,name=" + domainName);
            } catch (Exception e) {
                if (!pingOption) {
                    System.err.println("Cannot admin server " + jonasName + ". Problem with JMX remote connection ("
                            + e.getMessage() + ")");
                    System.exit(2);
                }
            }

            // No option => interactive mode.
            if (interactive) {
                menu();
            } else {
                boolean first = true;
                boolean forQuiet = qOption;
                qOption = true;
                for (Iterator<CLIArgument> i = lstArgs.iterator(); i.hasNext();) {
                    CLIArgument argument = i.next();
                    switch (argument.type) {
                    case ADDFILE:
                        // Adds file into JOnAS container
                        addFile(argument.value);
                        break;
                    case REMOVEFILE:
                        // Remove file from JOnAS container
                        removeFile(argument.value);
                        break;
                    case STARTOPTION:
                        startServer();
                        break;
                    case STOPOPTION:
                        stopServer();
                        break;
                    case HALTOPTION:
                        haltServer();
                        break;
                    case LISTBEAN:
                        // List beans in JOnAS container
                        listBeans();
                        break;
                    case LISTMODULE:
                        // List beans in JOnAS container
                        listModules();
                        break;
                    case LISTAPP:
                        // List beans in JOnAS container
                        listApps();
                        break;
                    case LISTJNDI:
                        // List jndi names in JOnAS container
                        listJNDINames();
                        break;
                    case LISTENV:
                        // List JOnAS environment
                        listProperties();
                        break;
                    case LISTTOPICS:
                        // List Monolog topics
                        listTopics();
                        break;
                    case DEBUGOPTION:
                        // set debug for a topic
                        setTopic(argument.value, "DEBUG");
                        break;
                    case TTOPTION:
                        // set the default value for transaction timeout
                        setTTimeout(argument.value);
                        break;
                    case SYNCOPTION:
                        // sync all entity instances outside transactions
                        sync(false);
                        break;
                    case CUSTOMOPTION:
                        // Custom option
                        custom();
                        break;
                    case PASSIVATEOPTION:
                        // passivate all entity instances outside transactions
                        sync(true);
                        break;
                    case GCOPTION:
                        // run the garbage collector
                        runGC();
                        break;
                    case ISDEPLOYOPTION:
                        // is the file deployed ?
                        isDeployedFile(argument.value);
                        break;
                    }
                    // Keep original functionality w/ first option then add
                    // header
                    // lines for each additional command if -q not set.
                    if (first) {
                        first = false;
                        qOption = forQuiet;
                    }
                    // Exit if error
                    if (isError) {
                        System.exit(2);
                    }
                }
            }

            connection.close();
        }
    }

    /**
     * Main function, creates a new {@link ClientAdmin} instance.
     * @param args See {@link ClientAdmin#ClientAdmin(String[])}.
     * @throws Exception If any error occurs.
     */
    public static void main(final String[] args) throws Exception {
        try {
            new ClientAdmin(args);
        } catch (SecurityException e) {
            e.printStackTrace(System.err);
            System.err.println("This JOnAS server's JMX server requires credentials.");
            System.err.println("Please use the -username and -password options to provide them.");
        }
    }

    /**
     * Initialize the JMXConnectionHelper using provided parameters or the Carol properties by default.
     * @param registry User defined registry (can be null)
     * @param protocol User defined protocol (can be null)
     * @return The MBeanServer remote URL
     */
    private JMXServiceURL initializeJMXConnectionHelper(String registry, String protocol) {
        Properties carolProperties = new Properties();
        try {
            // Load Carol properties
            String jonasBase = System.getProperty(JONAS_BASE);
            FileInputStream fis = new FileInputStream(jonasBase +  File.separator + "conf" + File.separator + CAROL_FILE);
            carolProperties.load(fis);
        } catch (Exception e) {
            System.err.println("Cannot load Carol properties: " + e.getMessage());
        }

        if (protocol == null) {
            String protocols = carolProperties.getProperty("carol.protocols");
            // Take the first protocol of the list
            protocol = protocols.split(",")[0];
        }

        if (registry == null) {
            registry = carolProperties.getProperty("carol." + protocol + ".url");
        }
        String urlPrefix = JMXSERVICE_URL_PREFIX;
        if ("iiop".equals(protocol)) {
            urlPrefix = urlPrefix.replace("rmi", "iiop");
        }
        String serviceUrl = urlPrefix.concat(registry)
                                         .concat("/")
                                         .concat(protocol)
                                         .concat("connector_")
                                         .concat(jonasName);

        JMXServiceURL jmxServiceUrl = null;

        try {
            jmxServiceUrl = new JMXServiceURL(serviceUrl);
        } catch (MalformedURLException e) {
            System.err.println("Cannot administer server " + jonasName + " because of incorrect JMX URL (" + e.getMessage() + ")");
            System.exit(2);
        }

        connection = new JMXConnectionHelper(jmxServiceUrl, username, password);

        return jmxServiceUrl;
    }

    private void runGC() {
        if (!qOption) {
            System.out.println("Run GC:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        String operationName = "runGC";
        Object[] params = null;
        String[] signature = null;
        try {
            conn.invoke(j2eeServerObjectName, operationName, params, signature);
        } catch (Exception e) {
            System.err.println("Can't run GC in server " + jonasName + ". JMX Problem with MBean " + j2eeServerObjectName + " "
                    + e.getMessage() + ".");
        }
        System.out.println("");
    }

    private MBeanServerConnection getConnection() {
        try {
            return connection.getConnection();
        } catch (IOException e) {
            System.err.println("Unable to connect to " + connection.getURL());
            System.err.println("Latest Exception: " + e.getMessage());
        }
        return null;
    }

    private void custom() {
        if (!qOption) {
            System.out.println("Custom:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            System.out.println(UtilAdmin.dumpCustom(domainName, jonasName, conn));
        } catch (Exception e) {
            System.err.println(e);
            isError = true;
            return;
        }
        System.out.println("");
    }
    /**
     * Make an Array of OBJECT_NAMEs.
     * @param line A comma separated list of names
     * @return Array of String-ified object names
     */
    private String[] makeArrayFrom(final String line) {
        StringTokenizer stk = new StringTokenizer(line, ",");
        int nb  = stk.countTokens();
        String [] ret = new String[nb];
        for (int i = 0; i < nb; i++) {
            ret[i] = stk.nextToken();
        }
        return ret;
    }

    /**
     * Client usage description.
     */
    private void usage() {
        System.out.println("usage : jonas admin <options>");
        System.out.println("if no option(except -n), mode is interactive.");
        System.out.println("list of available options:");
        System.out.println("    -n name : to identify an JOnAS Server");
        System.out.println("    -username username : user name to use when connecting");
        System.out.println("    -password password : password to use when connecting");
        System.out.println("    -registry : to define registry address");
        System.out.println("    -protocol : to define protocol");
        System.out.println("    -s : stops the EJB server.");
        System.out.println("    -l        : lists beans currently in the JOnAS Server.");
        System.out.println("    -lmodules : lists J2EEModules currently in the JOnAS Server.");
        System.out.println("    -lapps    : lists J2EEApplications currently in the JOnAS Server.");
        System.out.println("    -j : lists registered JNDI names.");
        System.out.println("    -e : lists JOnAS properties currently used by the JOnAS Server.");
        System.out.println("    -a fileName : dynamically adds   : - beans from fileName in a new container");
        System.out.println("                                     : - servlets from a WAR file");
        System.out.println("                                     : - j2ee application from an EAR file");
        System.out.println("                                     : - resource adapter from a RAR file");
        System.out.println("    -r fileName : dynamically remove : - beans from container fileName");
        System.out.println("                                     : - servlets of a WAR file");
        System.out.println("                                     : - j2ee application of an EAR file");
        System.out.println("                                     : - resource adapter from a RAR file");
        System.out.println("    -sync: synchronize all entities");
        System.out.println("    -passivate: passivate all entities");
        System.out.println("    -gc: run the garbage collector");
        System.out.println("    -tt timeout: set default transaction timeout");
        System.out.println("    -start: Start servers designed by '-target' arg");
        System.out.println("    -target target: set target for commands (default is local server)");
        System.out.println("    -ping [-timeout <val (sec)>]: ping server for a given time.(default=100s)");
        System.out.println("    -t list monolog topics");
        System.out.println("    --debug-level topic : set DEBUG for a monolog topic");
        System.out.println("    -q : quiet mode, no processing header information.");
        System.out.println("    -h : help message.");
        System.out.println("    -? : help message.");
    }
    /**
     * interactive mode menu.
     */
    private void menu() throws IOException {
        // User interface
        BufferedReader inbuf = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.print("Admin (" + jonasName + ") > ");
            String command = inbuf.readLine();
            if (command.length() == 0) {
                continue;
            }
            if ("addfile".startsWith(command)) {
                String fName = null;
                System.out.print("file name ? > ");
                if ((fName = inbuf.readLine()).length() != 0) {
                    addFile(fName);
                }
                continue;
            }
            if ("start".startsWith(command)) {
                startServer();
                continue;
            }
            if ("env".startsWith(command)) {
                listProperties();
                continue;
            }
            if ("gc".startsWith(command)) {
                runGC();
                continue;
            }
            if ("help".startsWith(command) || command.equals("?")) {
                help();
                continue;
            }
            if ("jndinames".startsWith(command)) {
                listJNDINames();
                continue;
            }
            if ("listbeans".startsWith(command)) {
                listBeans();
                continue;
            }
            if ("listmodules".startsWith(command)) {
                listModules();
                continue;
            }
            if ("listapps".startsWith(command)) {
                listApps();
                continue;
            }
            if ("custom".startsWith(command)) {
                custom();
                continue;
            }
            if ("name".startsWith(command)) {
                System.out.println("Use the -n option to define the managed server name");
                continue;
            }
            if ("quit".startsWith(command) || "exit".startsWith(command)) {
                return;
            }
            if ("removefile".startsWith(command)) {
                String fName = null;
                System.out.print("file name ? > ");
                if ((fName = inbuf.readLine()).length() != 0) {
                    removeFile(fName);
                }
                continue;
            }
            if ("trace".startsWith(command)) {
                while (true) {
                    if (listTopics()) {
                        System.out.print("topic name ? > ");
                        String tname = inbuf.readLine().trim();
                        if (tname.length() == 0) {
                            break;
                        }
                        System.out.print("topic level ? (DEBUG | WARN | INFO | ERROR | INHERIT) > ");
                        String levstr = inbuf.readLine().trim();
                        setTopic(tname, levstr);
                    } else {
                        break;
                    }
                }
                continue;
            }
            if ("stop".startsWith(command)) {
                stopServer();
                continue;
            }
            if ("halt".startsWith(command)) {
                haltServer();
                continue;
            }
            if ("sync".startsWith(command)) {
                sync(false);
                continue;
            }
            if ("passivate".startsWith(command)) {
                sync(true);
                continue;
            }
            if ("ttimeout".startsWith(command)) {
                String tstr = null;
                System.out.print("transaction timeout in seconds ? > ");
                if ((tstr = inbuf.readLine()).length() != 0) {
                    setTTimeout(tstr);
                }
                continue;
            }
            if ("target".startsWith(command)) {
                System.out.print("Server or Cluster where to deploy the beans ? > ");
                target = makeArrayFrom(inbuf.readLine());
                continue;
            }
            System.out.println("Unknown command. Type help to get for the list of commands.");
        }
    }

    private void help() {
        System.out.println("addfile     adds beans/servlets/j2ee app/rars based upon the file extension");
        System.out.println("custom      dump jonas customization");
        System.out.println("env         JOnAS properties used by the server");
        System.out.println("gc          run the garbage collector");
        System.out.println("help        help");
        System.out.println("jndinames   lists registered JNDI names");
        System.out.println("listbeans   lists beans");
        System.out.println("listmodules lists modules");
        System.out.println("listapps    lists applications");
        System.out.println("name        to identify a current JOnAS server");
        System.out.println("quit        quit JonasAdmin");
        System.out.println("removefile  remove beans/servlets/j2ee app/rars (based upon the file extension)");
        System.out.println("start       stop target servers");
        System.out.println("stop        stop target servers");
        System.out.println("halt        halt target servers");
        System.out.println("sync        synchronize all entities");
        System.out.println("passivate   passivate all entities");
        System.out.println("trace       get/set monolog topics");
        System.out.println("ttimeout    set default transaction timeout");
        System.out.println("target      set list of servers where command must be applied");
    }

    /**
     * Check every second if the server is manageable and return when it's OK. The server becomes manageable when its state
     * reaches the state passed in parameter. Don't wait more than given timeout (DEFAULT_TIMEOUT if not set).
     * @param pingTimeout value to wait
     * @param manageableState the given state to reach
     */
    private int waitServerManageable(final int pingTimeout, final String manageableState) {
        if (pingTimeout <= 0) {
            throw new IllegalArgumentException("Timeout should be a value greater than 0");
        }
        // State provided by the J2EEServer MBean
        String serverState = null;
        // Define the number of loops (WAIT_LOOP_MSEC milliseconds by loop)
        int loopValue = pingTimeout / WAIT_LOOP_MSEC;
        for (int i = 0; i < loopValue; i++) {
            try {
                // Wait WAIT_LOOP_SEC seconds
                Thread.sleep(WAIT_LOOP_MSEC);
            } catch (InterruptedException e) {
                return 1;
            }

            // Try to connect to the server
            if (connection.connect()) {
                MBeanServerConnection conn = getConnection();

                // Hmm, we were never connected to the server
                // Try to ask for its name
                if (j2eeServerObjectName == null) {
                    j2eeServerObjectName = getJ2EEServerObjectName(conn);
                } else {
                    // If we have one, use it
                    // get server state
                    try {
                        serverState = (String) conn.getAttribute(j2eeServerObjectName, "state");
                        if (serverState.equalsIgnoreCase(manageableState)) {
                            return 0;
                        }
                    } catch (Exception e) {
                        System.err.println("Cannot administer server " + jonasName + ". Problem with MBean "
                                + j2eeServerObjectName + " (" + e.getMessage() + ")");
                        return 2;
                    }
                }
            }
        }
        return 1;
    }

    /**
     * @param conn
     */
    private ObjectName getJ2EEServerObjectName(final MBeanServerConnection conn) {
        if (j2eeServerObjectName == null) {
            try {
                Set<?> servers = conn.queryNames(UtilAdmin.J2EEServer(jonasName), null);
                if (!servers.isEmpty()) {
                    // Get the first one
                    return (ObjectName) servers.iterator().next();
                }
            } catch (Exception e) {
                // Continue
            }
        }
        return j2eeServerObjectName;
    }

    /**
     * This method can add a jar, war, ear or rar file depending on the
     * extension of the file.
     * @param fileName name of the .jar file (Case of ejb-jar file) name of the
     *        .war file (Case of WAR file) name of the .ear file (Case of EAR
     *        file) name of the .rar file (Case of RAR file)
     */
    private void addFile(final String fileName) {
        if (!qOption) {
            System.out.println("Add File:" + fileName);
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }

        String operationName = "deploy";
        String[] params = {fileName};
        String[] signature = {"java.lang.String"};
        try {
            conn.invoke(j2eeServerObjectName, operationName, params, signature);
            System.out.println("Check result with listmodules or listapps command.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("");
    }

    /**
     * This method tells if the given file is deployed or not.
     * @param fileName name of the .jar file (Case of ejb-jar file) name of the
     *        .war file (Case of WAR file) name of the .ear file (Case of EAR
     *        file) name of the .rar file (Case of RAR file)
     */
    private void isDeployedFile(final String fileName) {
        if (!qOption) {
            System.out.println("Is File Deployable:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        String operationName = "isDeployed";
        String[] params = {fileName};
        String[] signature = {"java.lang.String"};
        try {
            boolean res = (Boolean) conn.invoke(j2eeServerObjectName, operationName, params, signature);
            if (res) {
                System.out.println("File " + fileName + " deployed");
            } else {
                System.out.println("File " + fileName + " not deployed");
            }
            System.out.println("Check result with listmodules or listapps command.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("");
    }

    /**
     * This method can remove beans, war file or ear file depending on the
     * extension of the file.
     * @param fileName name of the .jar file (Case of ejb-jar file) name of the
     *        .war file (Case of WAR file) name of the .ear file (Case of EAR
     *        file) name of the .rar file (Case of RAR file)
     */
    private void removeFile(final String fileName) {
        if (!qOption) {
            System.out.println("Remove File: " + fileName);
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        String operationName = "undeploy";
        String[] params = {fileName};
        String[] signature = {"java.lang.String"};
        try {
            conn.invoke(j2eeServerObjectName, operationName, params, signature);
            System.out.println("Check result with listmodules or listapps command.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("");
    }

    private void stopServer() {
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            if (target == null) {
                // Stop managed server
                String operationName = "stop";
                Object[] params = null;
                String[] signature = null;
                try {
                    conn.invoke(j2eeServerObjectName, operationName, params, signature);
                } catch (Exception e) {
                    String message =
                        "ERROR: Can't stop server " + jonasName + " using MBean " + j2eeServerObjectName + ": " + e.getMessage();
                    if (e.getCause() != null) {
                        message += ", caused by " + e.getCause().getMessage();
                    }
                    message += ".";
                    System.err.println(message);
                }
            }
        } catch (Exception e) {
        }
    }

    private void haltServer() {
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            if (target == null) {
                // Halt managed server
                String operationName = "halt";
                Object[] params = null;
                String[] signature = null;
                try {
                    conn.invoke(j2eeServerObjectName, operationName, params, signature);
                } catch (Exception e) {
                    String message =
                        "ERROR: Can't halt server " + jonasName + " using MBean " + j2eeServerObjectName + ": " + e.getMessage();
                    if (e.getCause() != null) {
                        message += ", caused by " + e.getCause().getMessage();
                    }
                    message += ".";
                    System.err.println(message);
                }
            } else {
                // Halt remote servers via the Domain manager
                for (int i = 0; i < target.length; i++) {
                    String atarget = target[i];
                    String operationName = "stopRemoteTarget";
                    String[] params = {atarget};
                    String[] signature = {"java.lang.String"};
                    try {
                        conn.invoke(domainOn, operationName, params, signature);
                    } catch (Exception e) {
                        String message =
                            "ERROR: Can't halt remote server " + atarget + " using MBean " + domainOn + ": " + e.getMessage();
                        if (e.getCause() != null) {
                            message += ", caused by " + e.getCause().getMessage();
                        }
                        message += ".";
                        System.err.println(message);
                    }
                }
            }
        } catch (Exception e) {
            if (target != null) {
                System.err.println("Error while halting servers:" + e.getCause().getMessage());
            }
        }
    }

    private void startServer() {
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            if (target == null) {
                // Start managed server if stopped
                // TODO test server stopped
                String operationName = "start";
                Object[] params = null;
                String[] signature = null;
                try {
                    conn.invoke(j2eeServerObjectName, operationName, params, signature);
                } catch (Exception e) {
                    String message =
                        "ERROR: Can't start server " + jonasName + " using MBean " + j2eeServerObjectName + ": " + e.getMessage();
                    if (e.getCause() != null) {
                        message += ", caused by " + e.getCause().getMessage();
                    }
                    message += ".";
                    System.err.println(message);
                }
            } else {
                // Start remote server via the Domain manager
                for (int i = 0; i < target.length; i++) {
                    String atarget = target[i];
                    String operationName = "startRemoteTarget";
                    String[] params = {atarget};
                    String[] signature = {"java.lang.String"};
                    try {
                        conn.invoke(j2eeServerObjectName, operationName, params, signature);
                    } catch (Exception e) {
                        String message =
                            "ERROR: Can't start remote server " + jonasName + " using MBean " + j2eeServerObjectName + ": "
                            + e.getMessage();
                        if (e.getCause() != null) {
                            message += ", caused by " + e.getCause().getMessage();
                        }
                        message += ".";
                        System.err.println(message);
                    }
                }
            }
        } catch (Exception e) {
            if (target != null) {
                System.err.println("Error while starting servers:" + e.getCause().getMessage());
            }
        }
    }
    /**
     * Get EJB list
     */
    private void listBeans() {
        if (!qOption) {
            System.out.println("ListBeans:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            ArrayList<String> names = UtilAdmin.listBeans(domainName, jonasName, conn);
            if (names.size() == 0) {
                System.out.println("No beans in " + jonasName);
            } else {
                for (String name : names) {
                    System.out.println(name);
                }
            }
        } catch (Exception e) {
            System.err.println("ListBeans: " + e);
            isError = true;
            return;
        }
        System.out.println("");
    }

    /**
     * Get Modules list
     */
    private void listModules() {
        if (!qOption) {
            System.out.println("ListModules:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            ArrayList<String> names = UtilAdmin.listModules(domainName, jonasName, conn);
            if (names.size() == 0) {
                System.out.println("No modules in " + jonasName);
            } else {
                for (String name : names) {
                    System.out.println(name);
                }
            }
        } catch (Exception e) {
            System.err.println("ListModules: " + e);
            isError = true;
            return;
        }
        System.out.println("");
    }

    /**
     * Get Applications list
     */
    private void listApps() {
        if (!qOption) {
            System.out.println("ListApplications:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            ArrayList<String> names = UtilAdmin.listApps(domainName, jonasName, conn);
            if (names.size() == 0) {
                System.out.println("No applications in " + jonasName);
            } else {
                for (String name : names) {
                    System.out.println(name);
                }
            }
        } catch (Exception e) {
            System.err.println("ListApplications: " + e);
            isError = true;
            return;
        }
        System.out.println("");
    }

    private void listJNDINames() {
        if (!qOption) {
            System.out.println("List JndiNames:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
             ArrayList<ObjectName> ons = UtilAdmin.listJNDIResources(domainName, jonasName, conn);
             for (ObjectName on : ons) {
                 System.out.println("");try {
                     String name = (String) conn.getAttribute(on, "Name");
                     ArrayList names = (ArrayList) conn.getAttribute(on, "Names");
                     System.out.println(name + " names:");
                     for (int i = 0; i < names.size(); i++) {
                         System.out.println(names.get(i));
                     }
                } catch (javax.management.JMException me) {
                    System.out.println(">> JNDIResource: " + me);
                }
             }
        } catch (Exception e) {
            System.err.println("List JndiNames: " + e);
            isError = true;
            return;
        }
        System.out.println("");
    }

    private void listProperties() {
        if (!qOption) {
            System.out.println("ListProperties:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        String operationName = "getConfigFileEnv";
        Object[] params = null;
        String[] signature = null;
        try {
            Properties configProps = (Properties) (conn.invoke(j2eeServerObjectName, operationName, params, signature));
            for (Enumeration e = configProps.keys(); e.hasMoreElements();) {
                Object key = e.nextElement();
                Object value = configProps.get(key);
                System.out.println(key.toString() + "=" + value.toString());
            }
        } catch (Exception e) {
            System.err.println("Can't get properties of server " + jonasName + ". JMX Problem with MBean " + j2eeServerObjectName + " "
                    + e.getMessage() + ".");
        }
        System.out.println("");
    }
    /**
     *
     */
    private boolean listTopics() {
        if (!qOption) {
            System.out.println("List Monolog Topics:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return false;
        }
        try {
            String[] result = UtilAdmin.getTopics(domainName, jonasName, conn);
            if (result == null) {
                System.out.println("Can't list Monolog Topics. Didn't found MBean for JOnAS logging management");
                return false;
            }
            if (result.length == 0) {
                System.out.println("No topics in " + jonasName);
            }
            for (int i = 0; i < result.length; i++) {
                String level = UtilAdmin.getTopicLevel(domainName, jonasName, conn, result[i]);
                System.out.println(level + "\t" + result[i]);
            }
        } catch (Exception e) {
            System.err.println("List Monolog Topics : " + e);
            isError = true;
            return false;
        }
        return true;
    }

    private void setTopic(final String t, final String l) {
        if (!qOption) {
            System.out.println("Set Monolog Topic:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            UtilAdmin.setTopicLevel(domainName, jonasName, conn, t, l);
        } catch (Exception e) {
            System.err.println("Set Monolog Topic : " + e);
            isError = true;
        }
        System.out.println("");
    }

    private void setTTimeout(final String tstr) {
        if (!qOption) {
            System.out.println("Set TransactionTimeout:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        Integer t = new Integer(tstr);
        // Use JTA MBean
        try {
            ObjectName on = UtilAdmin.getJ2eeMBean(domainName, jonasName, "JTAResource", "JTAResource");
            if (conn.isRegistered(on)) {
                Attribute att = new Attribute("timeOut", t);
                conn.setAttribute(on, att);
                Integer val = (Integer) conn.getAttribute(on, "timeOut");
                System.out.println("TM timeout=" + val);
            }
        } catch (Exception e) {
            System.err.println("Can't set transaction time-out for server " + jonasName + ". "
                    + e.getMessage());
        }
        System.out.println("");
    }

    private void sync(final boolean passivate) {
        if (!qOption) {
            System.out.println("Sync:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            //ObjectName on = UtilAdmin.getJonasServiceMBean(domainName, jonasName, "ejbContainers");
            ObjectName on = getJonasServiceMBean(domainName, jonasName, "ejbContainers");
            String operationName = "syncAllEntities";
            Boolean[] params = {passivate};
            String[] signature = {"boolean"};
            try {
                conn.invoke(on, operationName, params, signature);
            } catch (Exception e) {
                System.err.println("Can't sync entity beans in server " + jonasName + ". JMX Problem with MBean " + on + " "
                        + e + ".");
            }
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NullPointerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("");
    }

    private ObjectName getJonasServiceMBean(final String domain, final String server, final String service)
        throws MalformedObjectNameException, NullPointerException {
        StringBuffer sb = new StringBuffer(domain);
        sb.append(":");
        sb.append(UtilAdmin.Type);
        sb.append("=");
        sb.append(UtilAdmin.ServiceType);
        sb.append(",");
        sb.append("name");
        sb.append("=");
        sb.append(service);
        return ObjectName.getInstance(new String(sb));
    }
}
