/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2010 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: ClientAdmin.java 20195 2010-08-20 07:51:16Z alitokmen $
 * --------------------------------------------------------------------------
 */

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.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.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.NotBoundException;
import java.util.ArrayList;
import java.util.Arrays;
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.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import org.ow2.jonas.commands.admin.util.ExceptionUtils;
import org.ow2.jonas.commands.admin.util.JMXConnectionHelper;
import org.ow2.jonas.commands.admin.util.PrintUtils;
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 static final String JMXSERVICE_URL_PREFIX = "service:jmx:rmi:///jndi/";

    /**
     * System property for standby mode.
     */
    private static final String JONAS_STANDBY = "jonas.standby";

    /**
     * $JONAS_ROOT system property.
     */
    private static final String JONAS_ROOT = "jonas.root";

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

    /**
     * Name of the JOnAS properties file.
     */
    private static final String JONAS_FILE = "jonas.properties";

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

    /**
     * The property in jonas.properties for the username parameter.
     */
    private static final String CLIENT_USERNAME = "jonas.adminClient.username";

    /**
     * The property in jonas.properties for the password parameter.
     */
    private static final String CLIENT_PASSWORD = "jonas.adminClient.password";

    /**
     * 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;

    /**
     * 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;

    /**
     * Is output verbose ?
     */
    private boolean verbose = false;

    /**
     * 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 = "j2ee.state.running";

        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;

            // Backward compliance for some tools like Cargo, etc that are using previous halt command
            if ("-halt".equals(arg)) {
                PrintUtils.warning("The -halt option is deprecated. Use -stop instead.");
                arg = "-stop";
            }

            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("-state") && nextArgument) {
                manageableState = 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("-start")) {
                CLIArgument ja = new CLIArgument(STARTOPTION);
                lstArgs.add(ja);
                startOption = true;
                interactive = false;
                continue;
            }
            if (arg.equals("-standby")) {
                System.setProperty(JONAS_STANDBY, "true");
                manageableState = "j2ee.state.stopped";
                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("--verbose") || arg.equals("-v")) {
                verbose = true;
                continue;
            }
            if (arg.equals("-username")) {
                username = args[++argn];
                continue;
            }
            if (arg.equals("-password")) {
                password = args[++argn];
                continue;
            }
            System.out.println("Bad option: " + arg);
            usage();
            System.exit(2);
        }

        Properties jonasProperties = new Properties();
        InputStream fis = null;
        try {
            // Load JOnAS properties
            String jonasBase = System.getProperty(JONAS_BASE);
            File base = new File(jonasBase);
            File conf = new File(base, "conf");
            File jonasPropertiesFile = new File(conf, JONAS_FILE);
            fis = new FileInputStream(jonasPropertiesFile);
            jonasProperties.load(fis);
        } catch (Exception e) {
            PrintUtils.failure("Cannot load jonas.properties",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
            // TODO no exit here ?
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    // Ignored
                }
            }
        }

        if (System.getProperty(JONAS_NAME) != null) {
            jonasName = System.getProperty(JONAS_NAME);
        } else {
            jonasName = jonasProperties.getProperty(JONAS_NAME);

            if (jonasName == null || jonasName.length() == 0) {
                jonasName = DEFAULT_NAME; // default value
            }
        }

        if (username == null) {
            username = jonasProperties.getProperty(CLIENT_USERNAME);
        }
        if (password == null) {
            password = jonasProperties.getProperty(CLIENT_PASSWORD);
        }

        JMXServiceURL jmxServiceUrl = initializeJMXConnectionHelper(registry, protocol);

        // Start JOnAS server if not already started
        if (startOption && (target == null)) {
            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_STANDBY)) {
                PrintUtils.error("JOnAS is already on standby.");
                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) {
                    PrintUtils.warning("Incorrect ping timeout value (" + pingTimeoutValue + "). " + pingTimeout + " will be used instead.");
                }
            }
            // 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
            Exception e = connection.getLastException();
            PrintUtils.failure("Cannot connect to JOnAS using '" + jmxServiceUrl + "'",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        } else {
            // Connection was a success
            PrintUtils.println("Connected on " + jmxServiceUrl);

            try {
                MBeanServerConnection conn = connection.getConnection();
                j2eeServerObjectName = getJ2EEServerObjectName(conn);
                domainName = j2eeServerObjectName.getDomain();
            } catch (Exception e) {
                if (!pingOption) {
                    PrintUtils.failure("Cannot administer server '" + jonasName + "'.",
                                       ExceptionUtils.getRootCause(e));
                    if (verbose) {
                        PrintUtils.println(e);
                    }

                    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 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 {
            // Add client.jar to the TCCL
            // See http://jira.ow2.org/browse/JONAS-35 for details
            String jonasRoot = System.getProperty(JONAS_ROOT);
            File clientJar = new File(jonasRoot, "lib/client.jar").getAbsoluteFile();

            if (clientJar.isFile()) {
                URL clientJarURL = clientJar.toURI().toURL();
                ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
                URLClassLoader clWithClientJar = new URLClassLoader(new URL[] { clientJarURL }, oldCL);
                try {
                    Thread.currentThread().setContextClassLoader(clWithClientJar);
                    new ClientAdmin(args);
                } finally {
                    Thread.currentThread().setContextClassLoader(oldCL);
                }
            } else {
                // Not an error: Micro JOnAS
                new ClientAdmin(args);
            }


        } catch (SecurityException e) {
            Throwable rootCause = ExceptionUtils.getRootCause(e);
            PrintUtils.error("This JOnAS server's JMX server requires credentials.");
            if (rootCause instanceof NotBoundException) {
                PrintUtils.error("JMX security seems not to be configured properly.");
                PrintUtils.error("Please check that in the jaas.config, the declaration for your JMX security");
                PrintUtils.error("authenticator (default name is jaas-jmx) has its serverName attribute with the");
                PrintUtils.error("same name as your server's name.", rootCause);
            } else {
                PrintUtils.error("Please use the -username and -password options to provide them.", rootCause);
            }

            // Log the trace if in verbose mode
            // As args are not parsed at this level, we have to
            // search the --verbose flag manually
            List<String> list = Arrays.asList(args);
            if (list.contains("-v") || list.contains("--verbose")) {
                PrintUtils.println(e);
            }

            System.exit(2);
        }
    }

    /**
     * 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();
        InputStream fis = null;
        try {
            // Load Carol properties
            String jonasBase = System.getProperty(JONAS_BASE);
            File base = new File(jonasBase);
            File conf = new File(base, "conf");
            File carolPropertiesFile = new File(conf, CAROL_FILE);
            fis = new FileInputStream(carolPropertiesFile);
            carolProperties.load(fis);
        } catch (Exception e) {
            PrintUtils.failure("Cannot load carol.properties",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
            // TODO no exit here ?
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    // Ignored
                }
            }
        }

        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) {
            PrintUtils.error("Cannot administer server " + jonasName + " because of incorrect JMX URL",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }

            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) {
            PrintUtils.error("Can't run GC in server " + jonasName + ". JMX Problem with MBean " + j2eeServerObjectName,
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        System.out.println("");
    }

    private MBeanServerConnection getConnection() {
        try {
            return connection.getConnection();
        } catch (IOException e) {
            PrintUtils.error("Unable to connect to " + connection.getURL(),
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        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) {
            PrintUtils.error("Cannot dump custom infos.",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.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("    -start : starts the current JOnAS server.");
        System.out.println("    -stop :  stops the current JOnAS 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("    -target target: set target for commands (default is local server)");
        System.out.println("    -start: Start servers designed by '-target' arg");
        System.out.println("    -stop:  Stop servers designed by '-target' arg");
        System.out.println("    -start [-standby] -target target: Start servers designed by '-target' arg");
        System.out.println("    -stop  [-standby] -target target: Stop servers designed by '-target' arg");
        System.out.println("    -ping [-timeout <t>] [-state <s>]: ping server for <t> milliseconds");
        System.out.println("                                       until it reaches state <s>");
        System.out.println("          Default timeout is 2 minutes = 120 seconds = 120000 milliseconds");
        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("    -v | --verbose : Verbose mode (Show stack traces).");
        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 == null || 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();
                        if (tname == null) {
                            break;
                        }
                        tname = tname.trim();
                        if (tname.length() == 0) {
                            break;
                        }
                        System.out.print("topic level ? (DEBUG | WARN | INFO | ERROR | INHERIT) > ");
                        String levstr = inbuf.readLine();
                        if (levstr == null) {
                            break;
                        }
                        levstr = levstr.trim();
                        setTopic(tname, levstr);
                    } else {
                        break;
                    }
                }
                continue;
            }
            if ("stop".startsWith(command)) {
                stopServer();
                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;
            }
            PrintUtils.error("Unknown command. Type 'help' to list 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       start target servers");
        System.out.println("stop        stop 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 {
                // Try to connect to the server
                if ("j2ee.state.stopped".equals(manageableState) && !Boolean.getBoolean(JONAS_STANDBY)) {
                    if (connection.connect()) {
                        // JOnAS didn't shutdown yet, close connection to retry
                        connection.close();
                    } else {
                        // The connection is down and we were waiting for JOnAS
                        // to shut down, we therefore "reached"
                        // j2ee.state.stopped
                        return 2;
                    }
                } else {
                    if (!connection.connect()) {
                        continue;
                    }

                    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) {
                            String message = "Cannot administer server " + jonasName + ". Problem with MBean "
                                    + j2eeServerObjectName;
                            PrintUtils.failure(message, ExceptionUtils.getRootCause(e));

                            if (verbose) {
                                PrintUtils.println(e);
                            }
                            return 2;
                        }
                    }
                }
            } catch (SecurityException e) {
                // If a SecurityException is thrown, be careful: when JOnAS is
                // starting, the cause of the SecurityException might be a
                // NotBoundException or ClassNotFoundException, temporarily. In
                // that case, keep on looping as these exceptions should
                // disappear as soon as the JOnAS security service is up.
                Throwable rootCause = ExceptionUtils.getRootCause(e);
                if ((rootCause instanceof NotBoundException) ||
                    (rootCause instanceof ClassNotFoundException)) {

                    if (i == loopValue - 1) {
                        throw e;
                    } else {
                        continue;
                    }
                }
            }
        }
        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;
    }

    /**
     * @param conn
     * @param targetName
     */
    private ObjectName getServerProxyObjectName(final MBeanServerConnection conn, final String targetName) {
        ObjectName result = null;
        try {
            Set<?> servers = conn.queryNames(UtilAdmin.ServerProxy(targetName), null);
            if (!servers.isEmpty()) {
                // Get the first one
                return (ObjectName) servers.iterator().next();
            }
        } catch (Exception e) {
            // TODO
        }
        return result;
    }
    /**
     * 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);
            PrintUtils.success("Deployment OK (Check result with listmodules or listapps command).");
        } catch (Exception e) {
            PrintUtils.error("Unable to deploy module " + fileName,
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        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) {
                PrintUtils.success("File '" + fileName + "' deployed.");
            } else {
                PrintUtils.success("File '" + fileName + "' not deployed.");
            }
            PrintUtils.success("Check result with listmodules or listapps command.");
        } catch (Exception e) {
            PrintUtils.error("Cannot determine if module '" + fileName + "' is deployed or not",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        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);
            PrintUtils.success("Undeployment OK (Check result with listmodules or listapps command).");
        } catch (Exception e) {
            PrintUtils.error("Cannot undeploy module '" + fileName + "'.",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        System.out.println("");
    }

    private void stopServer() {
        if (!Boolean.getBoolean(JONAS_STANDBY)) {
            haltServer();
            return;
        }

        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        if (target == null) {
            // Stop the managed server to the STOPPED state
            String operationName = "stop";
            Object[] params = null;
            String[] signature = null;
            try {
                System.out.println("Trying to stop and standby server named " + jonasName);
                conn.invoke(j2eeServerObjectName, operationName, params, signature);
                PrintUtils.success("JOnAS '" + jonasName + "' stopped (Standby mode).");
            } catch (Exception e) {
                PrintUtils.error("Can't stop server " + jonasName + " using MBean " + j2eeServerObjectName + ".",
                                 ExceptionUtils.getRootCause(e));
                if (verbose) {
                    PrintUtils.println(e);
                }
            }
        } else {
            // Stop remote servers via the Domain manager
            for (int i = 0; i < target.length; i++) {
                String atarget = target[i];
                // check if this target corresponds to a ServerProxy
                ObjectName on = getServerProxyObjectName(conn, atarget);
                if (on != null) {
                    // try to start the target via the ServerProxy MBean
                    String operationName = "stop";
                    boolean standby = true;
                    Boolean standbyObj = Boolean.valueOf(standby);
                    Object[] params = {
                            standbyObj
                    };
                    String[] signature = {
                            "boolean"
                    };
                    try {
                        System.out.println("Trying to stop and standby target server named " + atarget);
                        conn.invoke(on, operationName, params, signature);
                        PrintUtils.success("   Operation done.");
                    } catch (Exception e) {
                        PrintUtils.error("Can't stop target server " + atarget + " using MBean " + on + ".",
                                         ExceptionUtils.getRootCause(e));
                        if (verbose) {
                            PrintUtils.println(e);
                        }
                    }
                }
            }
        }
    }

    private void haltServer() {
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        if (target == null) {
            // Stop completely the managed server
            String operationName = "halt";
            Object[] params = null;
            String[] signature = null;
            try {
                System.out.println("Trying to stop server named " + jonasName);
                conn.invoke(j2eeServerObjectName, operationName, params, signature);
                PrintUtils.success("JOnAS '" + jonasName + "' stopped (JVM shutdown).");
            } catch (Exception e) {
                PrintUtils.error("Can't stop server " + jonasName + " using MBean " + j2eeServerObjectName + ".",
                                 ExceptionUtils.getRootCause(e));
                if (verbose) {
                    PrintUtils.println(e);
                }
            }
        } else {
            // Stop remote servers via the Domain manager
            for (int i = 0; i < target.length; i++) {
                String atarget = target[i];
                // check if this target corresponds to a ServerProxy
                ObjectName on = getServerProxyObjectName(conn, atarget);
                if (on != null) {
                    // try to stop the target via the ServerProxy MBean
                    String operationName = "stop";
                    boolean standby = false;
                    Boolean standbyObj = Boolean.valueOf(standby);
                    Object[] params = {standbyObj};
                    String[] signature = {"boolean"};
                    try {
                        System.out.println("You are trying to stop target server named " + atarget);
                        conn.invoke(on, operationName, params, signature);
                        PrintUtils.success("   Operation done.");
                    } catch (Exception e) {
                        PrintUtils.error("Can't stop target server " + atarget + " using MBean " + on + ".",
                                         ExceptionUtils.getRootCause(e));
                        if (verbose) {
                            PrintUtils.println(e);
                        }
                    }
                }
            }
        }
    }

    private void startServer() {
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        boolean standby = Boolean.getBoolean(JONAS_STANDBY);
        if (target == null) {
            // Start managed server if stopped
            // TODO test server stopped
            String operationName = "start";
            Object[] params = null;
            String[] signature = null;
            try {
                System.out.println("Trying to start server named " + jonasName);
                conn.invoke(j2eeServerObjectName, operationName, params, signature);
                PrintUtils.success("JOnAS '" + jonasName + "' started.");
            } catch (Exception e) {
                PrintUtils.error("Can't start server " + jonasName + " using MBean " + j2eeServerObjectName + ".",
                                 ExceptionUtils.getRootCause(e));
                if (verbose) {
                    PrintUtils.println(e);
                }
            }
        } else {
            // Start remote server via the Domain manager
            for (int i = 0; i < target.length; i++) {
                String atarget = target[i];
                // check if this target corresponds to a ServerProxy
                ObjectName on = getServerProxyObjectName(conn, atarget);
                if (on != null) {
                    // try to start the target via the ServerProxy MBean
                    String operationName = "start";
                    Boolean standbyObj = Boolean.valueOf(standby);
                    Object[] params = {standbyObj};
                    String[] signature = {"boolean"};
                    try {
                        System.out.println("Trying to start target server named " + atarget);
                        conn.invoke(on, operationName, params, signature);
                        PrintUtils.success("   Operation done.");
                    } catch (Exception e) {
                        PrintUtils.error("Can't start target server " + atarget + " using MBean " + on + ".",
                                         ExceptionUtils.getRootCause(e));
                        if (verbose) {
                            PrintUtils.println(e);
                        }
                    }
                }
            }
        }
    }

    /**
     * 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) {
            PrintUtils.error("Cannot list EJB components.",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(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) {
            PrintUtils.error("Cannot list deployed Java EE modules.",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(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) {
            PrintUtils.error("Cannot list Java EE applications (EAR).",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(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 (MBeanException me) {
                    PrintUtils.failure("Cannot access MBean attributes values (" + on + ").",
                                       ExceptionUtils.getRootCause(me));
                    if (verbose) {
                        PrintUtils.println(me);
                    }

                }
            }
        } catch (Exception e) {
            PrintUtils.error("Cannot list bound JNDI names.",
                             ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(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) {
            PrintUtils.failure("Can't get properties of server " + jonasName
                               + ". JMX Problem with MBean " + j2eeServerObjectName + ".",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        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) {
                PrintUtils.failure("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) {
            PrintUtils.failure("Cannot list Monolog topics.",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }

            isError = true;
            return false;
        }
        return true;
    }

    private void setTopic(final String topic, final String name) {
        if (!qOption) {
            System.out.println("Set Monolog Topic:");
        }
        MBeanServerConnection conn = getConnection();
        if (conn == null) {
            isError = true;
            return;
        }
        try {
            UtilAdmin.setTopicLevel(domainName, jonasName, conn, topic, name);
        } catch (Exception e) {
            PrintUtils.failure("Cannot set Monolog topic's level.",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(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;
        }
        try {
            Integer timeout = new Integer(tstr);
            // Use JTA MBean
            ObjectName on = UtilAdmin.getJ2eeMBean(domainName, jonasName, "JTAResource", "JTAResource");
            if (conn.isRegistered(on)) {
                Attribute att = new Attribute("timeOut", timeout);
                conn.setAttribute(on, att);
                Integer val = (Integer) conn.getAttribute(on, "timeOut");
                PrintUtils.success("New transaction timeout is " + val + " seconds.");
            }
        } catch (Exception e) {
            PrintUtils.failure("Can't set transaction timeout for server " + jonasName + ".",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        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", null);
            String operationName = "syncAllEntities";
            Boolean[] params = {passivate};
            String[] signature = {"boolean"};

            conn.invoke(on, operationName, params, signature);
        } catch (Exception e) {
            PrintUtils.failure("Can't synchronize entity beans in server " + jonasName + ".",
                               ExceptionUtils.getRootCause(e));
            if (verbose) {
                PrintUtils.println(e);
            }
        }
        System.out.println("");
    }
}
