org.rhq.server.control.RHQControl.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.server.control.RHQControl.java

Source

/*
 *
 *  * RHQ Management Platform
 *  * Copyright (C) 2005-2012 Red Hat, Inc.
 *  * All rights reserved.
 *  *
 *  * This program is free software; you can redistribute it and/or modify
 *  * it under the terms of the GNU General Public License, version 2, as
 *  * published by the Free Software Foundation, and/or the GNU Lesser
 *  * General Public License, version 2.1, also as published by the Free
 *  * Software Foundation.
 *  *
 *  * This program 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 General Public License and the GNU Lesser General Public License
 *  * for more details.
 *  *
 *  * You should have received a copy of the GNU General Public License
 *  * and the GNU Lesser General Public License along with this program;
 *  * if not, write to the Free Software Foundation, Inc.,
 *  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

package org.rhq.server.control;

import java.io.Console;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.jboss.crypto.CryptoUtil;

import org.rhq.core.util.PropertiesFileUpdate;
import org.rhq.core.util.StringUtil;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.core.util.obfuscation.Obfuscator;
import org.rhq.enterprise.server.installer.ServerProperties;
import org.rhq.storage.installer.StorageProperty;

/**
 * @author John Sanda
 */
public class RHQControl {

    private final Log log = LogFactory.getLog(RHQControl.class);

    public static final int EXIT_CODE_OK = 0;

    // These try to follow the LSB specification - status command
    public static final int EXIT_CODE_STATUS_NOT_RUNNING = 3;
    public static final int EXIT_CODE_STATUS_UNKNOWN = 4;

    // These try to follow the LSB specification - other commands
    public static final int EXIT_CODE_OPERATION_FAILED = 1;
    public static final int EXIT_CODE_INVALID_ARGUMENT = 2;
    public static final int EXIT_CODE_NOT_INSTALLED = 5;

    private Commands commands = new Commands();

    public void printUsage() {
        HelpFormatter helpFormatter = new HelpFormatter();
        String syntax = "rhqctl <cmd> [options]";
        String header = "\nwhere <cmd> is one of:";
        String footer = "\n* For help on a specific command: rhqctl <cmd> --help\n" //
                + "\n* Limit commands to a single component with one of: --storage, --server, --agent";

        helpFormatter.setOptPrefix("");
        helpFormatter.printHelp(syntax, header, commands.getOptions(), footer);
    }

    public int exec(String[] args) {
        int rValue = EXIT_CODE_OK;
        ControlCommand command = null;
        boolean undo = false;
        AbortHook abortHook = new AbortHook();

        try {
            if (args.length == 0) {
                printUsage();
                rValue = EXIT_CODE_INVALID_ARGUMENT;
            } else {
                String commandName = findCommand(commands, args);
                command = commands.get(commandName);

                if (!isHelp(args)) {
                    // don't wait for user to read the warning if this is just request for help
                    logWarningIfAgentRPMIsInstalled(command);
                }

                validateInstallCommand(command, args);

                // in case the installer gets killed, prepare the shutdown hook to try the undo
                abortHook.setCommand(command);
                Runtime.getRuntime().addShutdownHook(abortHook);

                // run the command
                rValue = command.exec(getCommandLine(commandName, args));
            }
        } catch (UsageException e) {
            printUsage();
            rValue = EXIT_CODE_INVALID_ARGUMENT;
        } catch (RHQControlException e) {
            undo = true;

            Throwable rootCause = ThrowableUtil.getRootCause(e);
            // Only show the messy stack trace if we're in debug mode. Otherwise keep it cleaner for the user...
            if (log.isDebugEnabled()) {
                log.error(rootCause.getMessage(), rootCause);
            } else {
                log.error(rootCause.getMessage());
            }
            rValue = EXIT_CODE_OPERATION_FAILED;
        } catch (Throwable t) {
            undo = true;
            log.error(t);
            rValue = EXIT_CODE_OPERATION_FAILED;
        } finally {
            abortHook.setCommand(null);
            Runtime.getRuntime().removeShutdownHook(abortHook);
        }

        if (undo && command != null) {
            try {
                if (!Boolean.getBoolean("rhqctl.skip.undo")) {
                    command.undo();
                } else {
                    throw new Exception("Was told by user to skip clean up attempt.");
                }
            } catch (Throwable t) {
                log.warn("Failed to clean up after the failed installation attempt. "
                        + "You may have to clean up some things before attempting to install again", t);
                rValue = EXIT_CODE_OPERATION_FAILED;
            }
        }

        return rValue;
    }

    private void logWarningIfAgentRPMIsInstalled(ControlCommand command) {
        // we only care about warning if the user is installing or upgrading; otherwise, just return silently
        if (!"install".equalsIgnoreCase(command.getName()) && (!"upgrade".equalsIgnoreCase(command.getName()))) {
            return;
        }

        // see if we can find an RPM installation somewhere.
        boolean rpmInstalled;
        File rpmParentLocation = new File("/usr/share");
        File[] rpms = rpmParentLocation.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (name.startsWith("jboss-on-")) {
                    File jonDir = new File(dir, name);
                    // technically, we should look for the agent in new File(jonDir, "agent") because that's the rpm install dir.
                    // but there are no other rpms other than the agent, so if we see this jboss-on-* location, we know the agent is here.
                    log.warn("An agent RPM installation was found in [" + jonDir
                            + "]!!! You will not be able to successfully run this older agent anymore. You should consult the "
                            + "install documentation about manually removing and/or merging the old and new agent.");
                    return true;
                } else {
                    return false;
                }
            }
        });

        // if there is an RPM install on this box, we need to pause for some time to give the user a chance to read the message
        rpmInstalled = (rpms != null && rpms.length > 0);
        if (rpmInstalled) {
            try {
                log.warn("Please read the above warnings about the existence of agent RPM installations. This "
                        + command.getName() + " will resume in a few seconds.");
                Thread.sleep(30000L);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }

        return;
    }

    private void validateInstallCommand(ControlCommand command, String[] args) {
        // just return if we're asking for help or if it is not an install command
        if (!"install".equalsIgnoreCase(command.getName()) || isHelp(args)) {
            return;
        }
        List<String> argsList = Arrays.asList(args);

        // don't perform validation for components not involved in the command
        boolean validateServer = argsList.contains("--server")
                || (!argsList.contains("--storage") && !argsList.contains("--agent"));
        boolean validateStorage = argsList.contains("--storage")
                || (!argsList.contains("--server") && !argsList.contains("--agent"));

        // perform any up front validation we can at this point.  Note that after this point we
        // lose stdin due to the use of ProcessExecutions.

        if (validateServer) {
            File serverPropertiesFile = new File("bin/rhq-server.properties");

            if (validateServer && !serverPropertiesFile.isFile()) {
                throw new RHQControlException(
                        "The required rhq-server.properties file can not be found in the expected location ["
                                + serverPropertiesFile.getAbsolutePath() + "]. Installation is canceled.");
            }

            // Prompt for critical required values, if not yet set.
            try {
                PropertiesFileUpdate pfu = new PropertiesFileUpdate(serverPropertiesFile);
                Properties props = pfu.loadExistingProperties();

                promptForProperty(pfu, props, serverPropertiesFile.getName(),
                        ServerProperties.PROP_AUTOINSTALL_ADMIN_PASSWORD, false, true, true);
                promptForProperty(pfu, props, serverPropertiesFile.getName(),
                        ServerProperties.PROP_DATABASE_PASSWORD, true, false, true);
                promptForProperty(pfu, props, serverPropertiesFile.getName(),
                        ServerProperties.PROP_JBOSS_BIND_ADDRESS, false, false, false);

            } catch (Throwable t) {
                throw new RHQControlException(
                        "The rhq-server.properties file is not valid. Installation is canceled: " + t.getMessage());
            }

            // Now, validate the property settings
            try {
                ServerProperties.validate(serverPropertiesFile);
            } catch (Throwable t) {
                throw new RHQControlException(
                        "The rhq-server.properties file is not valid. Installation is canceled: " + t.getMessage());
            }
        }

        if (validateStorage) {
            try {
                File storagePropertiesFile = new File("bin/rhq-storage.properties");

                if (validateStorage && !storagePropertiesFile.isFile()) {
                    throw new RHQControlException(
                            "The required rhq-storage.properties file can not be found in the expected location ["
                                    + storagePropertiesFile.getAbsolutePath() + "]. Installation is canceled.");
                }

                StorageProperty.validate(storagePropertiesFile);

            } catch (Throwable t) {
                throw new RHQControlException(
                        "The rhq-storage.properties file is not valid. Installation is canceled: "
                                + t.getMessage());
            }
        }
    }

    private void promptForProperty(PropertiesFileUpdate pfu, Properties props, String propertiesFileName,
            String propertyName, boolean obfuscate, boolean encode, boolean hideInput) throws Exception {

        String propertyValue = props.getProperty(propertyName);
        if (StringUtil.isBlank(propertyValue)) {

            // prompt for the property value
            Console console = System.console();
            console.format("\nThe [%s] property is required but not set in [%s].\n", propertyName,
                    propertiesFileName);
            console.format("Do you want to set [%s] value now?\n", propertyName);
            String response = "";
            while (!(response.startsWith("n") || response.startsWith("y"))) {
                response = String.valueOf(console.readLine("%s", "yes|no: ")).toLowerCase();
            }
            if (response.startsWith("n")) {
                throw new RHQControlException("Please update the [" + propertiesFileName + "] file as required.");
            }

            do {
                propertyValue = "";
                while (StringUtil.isBlank(propertyValue)) {
                    if (!hideInput) {
                        propertyValue = String.valueOf(console.readLine("%s",
                                propertyName + (((obfuscate || encode) ? " (enter as plain text): " : ": "))));
                    } else {
                        propertyValue = String.valueOf(console.readPassword("%s",
                                propertyName + (((obfuscate || encode) ? " (enter as plain text): " : ": "))));
                    }
                }

                if (!hideInput) {
                    console.format("Is [" + propertyValue + "] correct?\n");
                    response = "";
                    while (!(response.startsWith("n") || response.startsWith("y"))) {
                        response = String.valueOf(console.readLine("%s", "yes|no: ")).toLowerCase();
                    }
                } else {
                    console.format("Confirm:\n");
                    String confirmValue = String.valueOf(console.readPassword("%s",
                            propertyName + (((obfuscate || encode) ? " (enter as plain text): " : ": "))));
                    response = (propertyValue.equals(confirmValue) ? "yes" : "no");
                }
            } while (response.startsWith("n"));

            propertyValue = obfuscate ? Obfuscator.encode(propertyValue) : propertyValue;
            propertyValue = encode
                    ? CryptoUtil.createPasswordHash("MD5", CryptoUtil.BASE64_ENCODING, null, null, propertyValue)
                    : propertyValue;
            props.setProperty(propertyName, propertyValue);
            pfu.update(props);
        }
    }

    private String findCommand(Commands commands, String[] args) throws RHQControlException {
        List<String> commandNames = new LinkedList<String>();
        for (String arg : args) {
            if (commands.contains(arg)) {
                commandNames.add(arg);
            }
        }

        if (commandNames.size() != 1) {
            throw new UsageException();
        }

        return commandNames.get(0);
    }

    private boolean isHelp(String[] args) {
        for (String arg : args) {
            if (ControlCommand.HELP_OPTION_1.equals(arg) || ControlCommand.HELP_OPTION_2.equals(arg)) {
                return true;
            }
        }
        return false;
    }

    private String[] getCommandLine(String cmd, String[] args) {
        String[] cmdLine = new String[args.length - 1];
        int i = 0;
        for (String arg : args) {
            if (arg.equals(cmd)) {
                continue;
            }
            cmdLine[i++] = arg;
        }
        return cmdLine;
    }

    public static void main(String[] args) throws Exception {
        RHQControl control = new RHQControl();
        int rValue;
        try {
            rValue = control.exec(args);
        } catch (RHQControlException e) {
            Throwable rootCause = ThrowableUtil.getRootCause(e);
            // Only show the messy stack trace if we're in debug mode. Otherwise keep it cleaner for the user...
            if (control.log.isDebugEnabled()) {
                control.log.error("There was an unexpected error: " + rootCause.getMessage(), rootCause);
            } else {
                control.log.error("There was an unexpected error: " + rootCause.getMessage());
            }
            rValue = EXIT_CODE_OPERATION_FAILED;
        }
        System.exit(rValue);
    }

    private class AbortHook extends Thread {
        private ControlCommand command = null;

        public AbortHook() {
            super("Controller Abort Hook");
        }

        public void setCommand(ControlCommand cmd) {
            this.command = cmd;
        }

        @Override
        public void run() {
            try {
                if (this.command != null) {
                    this.command.undo();
                }
            } catch (Throwable t) {
                log.warn("An attempt to clean up after an aborted installation was unsuccessful. "
                        + "You may have to clean up some things before attempting to install again", t);
            }
        }
    }
}