/*
* Copyright (c) 2012-2015 iWave Software LLC
* All Rights Reserved
*/
package com.iwave.ext.command;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.log4j.Logger;
/**
* Utility class for executing a command. This handles building the command line and resolving
* variable references contained within. Variables are of the form <code>${variable}</code>, which
* will be replaced in {@link #resolveCommandLine()}.
*
* @author jonnymiller
*/
public class Command {
/** The command logger. */
protected final Logger log = Logger.getLogger(getClass());
/** The implementation that handles the actual execution of the command. */
private CommandExecutor commandExecutor;
/** The resolved command line. */
private String commandLine;
/** The resolved command line values. */
private List<String> commandLineValues;
/** The command output. */
private CommandOutput output;
/** The base command string. */
private String command;
/** The list of arguments. */
private List<String> arguments = new ArrayList<String>();
/** The map of variable values. */
private Map<String, String> variables = new HashMap<String, String>();
/** Flag to indicate the command must be run as root. */
private boolean runAsRoot = false;
public Command() {
}
public Command(String command, String... args) {
setCommand(command);
addArguments(args);
}
public boolean isRunAsRoot() {
return runAsRoot;
}
public void setRunAsRoot(boolean runAsRoot) {
this.runAsRoot = runAsRoot;
}
/**
* Gets the command executor.
*
* @return the command executor.
*/
public CommandExecutor getCommandExecutor() {
return commandExecutor;
}
/**
* Sets the command executor.
*
* @param commandExecutor the command executor.
*/
public void setCommandExecutor(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
/**
* Gets the command-line. This is only available after {@link #resolveCommandLine()} is called
* during command execution.
*
* @return the command-line.
*/
public String getCommandLine() {
return commandLine;
}
/**
* Gets the command-line values. This is only available after {@link #resolveCommandLine()} is
* called during command execution.
*
* @return the command-line values.
*/
public List<String> getCommandLineValues() {
return commandLineValues;
}
/**
* Gets the command-line that's safe for logging purposes.
*
* @return the loggable command line.
*/
protected String getLoggableCommandLine() {
return commandLine;
}
/**
* Gets the output of the command.
*
* @return the command output.
*/
public CommandOutput getOutput() {
return output;
}
/**
* Gets the base command to be executed.
*
* @return the command to be executed.
*/
public String getCommand() {
return command;
}
/**
* Sets the base command to be executed.
*
* @param command the base command.
*/
public void setCommand(String command) {
this.command = command;
}
/**
* Gets the arguments for the command.
*
* @return the arguments.
*/
protected List<String> getArguments() {
return arguments;
}
/**
* Adds an argument to the command.
*
* @param arg the argument to add.
* @return this for chaining.
*/
public Command addArgument(String arg) {
arguments.add(arg);
return this;
}
/**
* Adds multiple arguments to the command.
*
* @param args the arguments to add.
* @return this for chaining.
*/
public Command addArguments(String... args) {
for (String arg : args) {
addArgument(arg);
}
return this;
}
/**
* Removes an argument from the command.
*
* @param arg the argument to remove.
* @return this for chaining.
*/
public Command removeArgument(String arg) {
arguments.remove(arg);
return this;
}
/**
* Determines if the command has the given argument.
*
* @param arg the argument.
* @return true if the command contains the given argument.
*/
public boolean hasArgument(String arg) {
return arguments.contains(arg);
}
/**
* Adds a variable to the list of arguments.
*
* @param name the variable name.
* @return this for chaining.
*/
public Command addVariable(String name) {
return addArgument("${" + name + "}");
}
/**
* Removes a variable from the list of arguments and removes its value.
*
* @param name the variable name.
* @return this for chaining.
*/
public Command removeVariable(String name) {
removeArgument("${" + name + "}");
removeVariableValue(name);
return this;
}
/**
* Determines if the command contains the given variable.
*
* @param name the variable name.
* @return true if the command contains the given variable.
*/
public boolean hasVariable(String name) {
return hasArgument("${" + name + "}");
}
/**
* Gets the variable value.
*
* @param name the variable name.
* @return the variable value.
*/
public String getVariableValue(String name) {
return variables.get(name);
}
/**
* Sets a variable's value.
*
* @param name the name of the variable.
* @param value the variable value.
*/
public void setVariableValue(String name, String value) {
variables.put(name, value);
}
/**
* Removes the specified variable value.
*
* @param name the variable name.
*/
public void removeVariableValue(String name) {
variables.remove(name);
}
/**
* Determines if a variable's value is set.
*
* @param name the name of the variable.
* @return the variable value.
*/
public boolean hasVariableValue(String name) {
return variables.containsKey(name);
}
/**
* Requires that the specified variable values are set.
*
* @param names the names of the variable values.
*
* @throws CommandException if any of the required variable values are not set.
*/
protected void requireVariableValues(String... names) {
StringBuilder missing = new StringBuilder();
for (String name : names) {
if (!hasVariableValue(name)) {
if (missing.length() > 0) {
missing.append(", ");
}
missing.append(name);
}
}
if (missing.length() > 0) {
throw new CommandException("Missing required value(s): " + missing);
}
}
/**
* Removes any of the specified variables is they have no value set.
*
* @param names the names of the variables.
*/
protected void removeUnsetVariables(String... names) {
for (String name : names) {
if (!hasVariableValue(name)) {
removeVariable(name);
}
}
}
/**
* Requires that the specified arguments are present.
*
* @param names the names of the arguments.
*
* @throws CommandException if any of the required arguments are not present.
*/
protected void requireArguments(String... names) {
StringBuilder missing = new StringBuilder();
for (String name : names) {
if (!hasArgument(name)) {
if (missing.length() > 0) {
missing.append(", ");
}
missing.append(name);
}
}
if (missing.length() > 0) {
throw new CommandException("Missing required argument(s): " + missing);
}
}
/**
* Quotes the string and escapes any quotes within.
*
* @param str the string to quote.
* @return the quoted string.
*
* @see #escapeQuotes(String)
*/
public String quoteString(String str) {
StringBuilder sb = new StringBuilder();
sb.append('"');
if (StringUtils.isNotBlank(str)) {
sb.append(escapeQuotes(str));
}
sb.append('"');
return sb.toString();
}
/**
* Escapes the quotes in the given string.
*
* @param str the string to escape.
* @return the escaped string.
*/
public String escapeQuotes(String str) {
return StringUtils.replace(str, "\"", "\\\"");
}
/**
* Evaluates the given string for variable references.
*
* @param str the string.
* @return the evaluated string.
*/
protected String evaluate(String str) {
StrSubstitutor substitutor = new StrSubstitutor(variables);
return substitutor.replace(str);
}
/**
* Resolves the given variable value to a string.
*
* @param name the name of the variable to resolve.
* @return the string value.
*
* @see #getVariableValue(String)
*/
protected String resolveValue(String name) {
return getVariableValue(name);
}
/**
* Hook method for validating the command line arguments.
*
* @throws CommandException if the command line is not valid.
*/
protected void validateCommandLine() throws CommandException {
}
/**
* Builds the command line and resolves all variable references.
*/
protected void resolveCommandLine() {
commandLineValues = new ArrayList<String>();
commandLineValues.add(evaluate(getCommand()));
for (String argument : getArguments()) {
commandLineValues.add(evaluate(argument));
}
commandLine = StringUtils.join(commandLineValues, " ");
}
/**
* Gets the resolved command line.
*
* @return the resolved command line.
*/
public String getResolvedCommandLine() {
validateCommandLine();
resolveCommandLine();
return commandLine;
}
/**
* Executes the resolved command line.
*/
protected void executeCommandLine() throws CommandException {
if (log.isDebugEnabled()) {
log.debug("Executing: " + getLoggableCommandLine());
}
output = commandExecutor.executeCommand(this);
if (log.isTraceEnabled()) {
if (output.getExitValue() != 0) {
log.trace("ExitValue: " + output.getExitValue());
}
if (StringUtils.isNotEmpty(output.getStdout())) {
log.trace("Stdout: " + output.getStdout());
}
if (StringUtils.isNotEmpty(output.getStderr())) {
log.trace("Stderr: " + output.getStderr());
}
}
}
/**
* Processes the command output.
*
* @throws CommandException if an error occurs.
*/
protected void processOutput() throws CommandException {
}
/**
* Processes the command output when an error occurs (non-zero exit value).
*
* @throws CommandException if an error occurs.
*/
protected void processError() throws CommandException {
String errorMessage = getErrorMessage();
throw new CommandException(errorMessage, output);
}
/**
* Get an error message from the CommandOutput object
*/
protected String getErrorMessage() {
String errorMessage = StringUtils.trimToNull(output.getStderr());
if (errorMessage == null) {
errorMessage = StringUtils.trimToNull(output.getStdout());
}
return errorMessage;
}
/**
* Executes the command.
*/
public void execute() throws CommandException {
if (commandExecutor == null) {
throw new CommandException("commandExecutor is not set");
}
validateCommandLine();
resolveCommandLine();
executeCommandLine();
if (output.getExitValue() == 0) {
processOutput();
}
else {
processError();
}
}
/**
* Determines if the string is contained in either STDOUT or STDERR, case-sensitive.
*
* @param find the string to look for.
* @return true if the string is found in either output stream (case-sensitive).
*/
protected boolean containsInOutput(String find) {
return containsInOutput(find, true);
}
/**
* Determines if the string is contained in either STDOUT or STDERR, case-insensitive.
*
* @param find the string to look for.
* @return true if the string is found in either output stream (case-insensitive).
*/
protected boolean containsInOutputIgnoreCase(String find) {
return containsInOutput(find, false);
}
private boolean containsInOutput(String find, boolean caseSensitive) {
if (output != null) {
String stdout = output.getStdout();
String stderr = output.getStderr();
if (caseSensitive) {
return StringUtils.contains(stdout, find) || StringUtils.contains(stderr, find);
}
else {
return StringUtils.containsIgnoreCase(stdout, find) || StringUtils.containsIgnoreCase(stderr, find);
}
}
return false;
}
}