blob: 085bb8c2108b54c6341f75261c9a3cbfac931f46 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.management.internal.cli;
import static org.apache.geode.internal.util.ProductVersionUtil.getDistributionVersion;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.apache.commons.lang3.StringUtils;
import org.springframework.shell.core.ExitShellRequest;
import org.apache.geode.internal.ExitCode;
import org.apache.geode.internal.util.ArgumentRedactor;
import org.apache.geode.internal.version.DistributionVersion;
import org.apache.geode.management.internal.cli.shell.Gfsh;
import org.apache.geode.management.internal.cli.shell.GfshConfig;
import org.apache.geode.management.internal.i18n.CliStrings;
/**
* Launcher class for :
* <ul>
* <li>gfsh 7.0
* <li>server
* <li>locator
* <li>Tools (Pulse, VSD, JConsole, JVisualVM)
* <li>Running Command Line Interface (CLI) Commands from OS prompt like
* <ol>
* <li>
* <ul>
* <li>compact offline-disk-store - Compact an offline disk store. If the disk store is large,
* additional memory may need to be allocated to the process using the --J=-Xmx??? parameter.
* <li>describe offline-disk-store - Display information about an offline disk store.
* <li>encrypt password - Encrypt a password for use in data source configuration.
* <li>run - Execute a set of GFSH commands. Commands that normally prompt for additional input will
* instead use default values.
* <li>start jconsole - Start the JDK's JConsole tool in a separate process. JConsole will be
* launched, but connecting to GemFire must be done manually.
* <li>start jvisualvm - Start the JDK's Java VisualVM (jvisualvm) tool in a separate process. Java
* VisualVM will be launched, but connecting to GemFire must be done manually.
* <li>start locator - Start a Locator.
* <li>start pulse - Open a new window in the default Web browser with the URL for the Pulse
* application.
* <li>start server - Start a GemFire Cache Server.
* <li>start vsd - Start VSD in a separate process.
* <li>status locator - Display the status of a Locator. Possible statuses are: started, online,
* offline or not responding.
* <li>status server - Display the status of a GemFire Cache Server.
* <li>stop locator - Stop a Locator.
* <li>stop server - Stop a GemFire Cache Server.
* <li>validate offline-disk-store - Scan the contents of a disk store to verify that it has no
* errors.
* <li>version - Display product version information.
* </ul>
* </li>
* <li>multiple commands specified using an option "-e"
* </ol>
* </ul>
*
* @since GemFire 7.0
*/
public class Launcher {
private static final String EXECUTE_OPTION = "execute";
private static final String HELP_OPTION = "help";
private static final String HELP = CliStrings.HELP;
private static final String MSG_INVALID_COMMAND_OR_OPTION = "Invalid command or option : {0}."
+ GfshParser.LINE_SEPARATOR + "Use 'gfsh help' to display additional information.";
public static final String SEPARATOR = ", ";
private final Set<String> allowedCommandLineCommands;
private final OptionParser commandLineParser;
private final StartupTimeLogHelper startupTimeLogHelper;
protected Launcher() {
startupTimeLogHelper = new StartupTimeLogHelper();
allowedCommandLineCommands = new HashSet<>();
allowedCommandLineCommands.add(CliStrings.RUN);
allowedCommandLineCommands.add(CliStrings.START_PULSE);
allowedCommandLineCommands.add(CliStrings.START_JCONSOLE);
allowedCommandLineCommands.add(CliStrings.START_JVISUALVM);
allowedCommandLineCommands.add(CliStrings.START_LOCATOR);
allowedCommandLineCommands.add(CliStrings.START_MANAGER);
allowedCommandLineCommands.add(CliStrings.START_SERVER);
allowedCommandLineCommands.add(CliStrings.START_VSD);
allowedCommandLineCommands.add(CliStrings.STATUS_LOCATOR);
allowedCommandLineCommands.add(CliStrings.STATUS_SERVER);
allowedCommandLineCommands.add(CliStrings.STOP_LOCATOR);
allowedCommandLineCommands.add(CliStrings.STOP_SERVER);
allowedCommandLineCommands.add(CliStrings.VERSION);
allowedCommandLineCommands.add(CliStrings.COMPACT_OFFLINE_DISK_STORE);
allowedCommandLineCommands.add(CliStrings.DESCRIBE_OFFLINE_DISK_STORE);
allowedCommandLineCommands.add(CliStrings.EXPORT_OFFLINE_DISK_STORE);
allowedCommandLineCommands.add(CliStrings.VALIDATE_DISK_STORE);
allowedCommandLineCommands.add(CliStrings.PDX_DELETE_FIELD);
allowedCommandLineCommands.add(CliStrings.PDX_RENAME);
commandLineParser = new OptionParser();
commandLineParser.accepts(EXECUTE_OPTION).withOptionalArg().ofType(String.class);
commandLineParser.accepts(HELP_OPTION).withOptionalArg().ofType(Boolean.class);
commandLineParser.posixlyCorrect(false);
}
public static void main(final String[] args) {
// first check whether required dependencies exist in the classpath
// should we start without tomcat/servlet jars?
String nonExistingDependency = CliUtils.cliDependenciesExist(true);
if (nonExistingDependency != null) {
System.err.println("Required (" + nonExistingDependency
+ ") libraries not found in the classpath. gfsh can't start.");
return;
}
Launcher launcher = new Launcher();
int exitValue = launcher.parseCommandLine(args);
ExitCode.fromValue(exitValue).doSystemExit();
}
private int parseCommandLineCommand(final String... args) {
Gfsh gfsh = null;
try {
gfsh = Gfsh.getInstance(false, args, new GfshConfig());
startupTimeLogHelper.logStartupTime();
} catch (IllegalStateException isex) {
System.err.println("ERROR : " + isex.getMessage());
}
ExitShellRequest exitRequest = ExitShellRequest.NORMAL_EXIT;
if (gfsh != null) {
final String commandLineCommand = combineStrings(args);
if (commandLineCommand.startsWith(HELP)) {
if (commandLineCommand.equals(HELP)) {
printUsage(gfsh, System.out);
} else {
// help is also available for commands which are not available under
// allowedCommandLineCommands
gfsh.executeCommand(commandLineCommand);
}
} else {
boolean commandIsAllowed = false;
for (String allowedCommandLineCommand : allowedCommandLineCommands) {
if (commandLineCommand.startsWith(allowedCommandLineCommand)) {
commandIsAllowed = true;
break;
}
}
if (!commandIsAllowed) {
System.err.println(
CliStrings.format(MSG_INVALID_COMMAND_OR_OPTION, StringUtils.join(args, SEPARATOR)));
exitRequest = ExitShellRequest.FATAL_EXIT;
} else {
if (!gfsh.executeScriptLine(commandLineCommand)) {
if (gfsh.getLastExecutionStatus() != 0) {
exitRequest = ExitShellRequest.FATAL_EXIT;
}
} else if (gfsh.getLastExecutionStatus() != 0) {
exitRequest = ExitShellRequest.FATAL_EXIT;
}
}
}
}
return exitRequest.getExitCode();
}
private int parseOptions(final String... args) {
OptionSet parsedOptions;
try {
parsedOptions = commandLineParser.parse(args);
} catch (OptionException e) {
System.err.println(
CliStrings.format(MSG_INVALID_COMMAND_OR_OPTION, StringUtils.join(args, SEPARATOR)));
return ExitShellRequest.FATAL_EXIT.getExitCode();
}
boolean launchShell = true;
boolean onlyPrintUsage = parsedOptions.has(HELP_OPTION);
if (parsedOptions.has(EXECUTE_OPTION) || onlyPrintUsage) {
launchShell = false;
}
Gfsh gfsh = null;
try {
gfsh = Gfsh.getInstance(launchShell, args, new GfshConfig());
startupTimeLogHelper.logStartupTime();
} catch (IllegalStateException isex) {
System.err.println("ERROR : " + isex.getMessage());
}
ExitShellRequest exitRequest = ExitShellRequest.NORMAL_EXIT;
if (gfsh != null) {
try {
if (launchShell) {
gfsh.start();
gfsh.waitForComplete();
exitRequest = gfsh.getExitShellRequest();
} else if (onlyPrintUsage) {
printUsage(gfsh, System.out);
} else {
@SuppressWarnings("unchecked")
List<String> commandsToExecute = (List<String>) parsedOptions.valuesOf(EXECUTE_OPTION);
// Execute all of the commands in the list, one at a time.
for (int i = 0; i < commandsToExecute.size()
&& exitRequest == ExitShellRequest.NORMAL_EXIT; i++) {
String command = commandsToExecute.get(i);
// sanitize the output string to not show the password
System.out.println(GfshParser.LINE_SEPARATOR + "(" + (i + 1) + ") Executing - "
+ ArgumentRedactor.redact(command) + GfshParser.LINE_SEPARATOR);
if (!gfsh.executeScriptLine(command) || gfsh.getLastExecutionStatus() != 0) {
exitRequest = ExitShellRequest.FATAL_EXIT;
}
}
}
} catch (InterruptedException iex) {
log(iex, gfsh);
}
}
return exitRequest.getExitCode();
}
private int parseCommandLine(final String... args) {
if (args.length > 0 && !args[0].startsWith(GfshParser.SHORT_OPTION_SPECIFIER)) {
return parseCommandLineCommand(args);
}
return parseOptions(args);
}
private void log(Throwable t, Gfsh gfsh) {
if (!(gfsh != null && gfsh.logToFile(t.getMessage(), t))) {
t.printStackTrace();
}
}
private String combineStrings(String... strings) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
stringBuilder.append(string).append(" ");
}
return stringBuilder.toString().trim();
}
private void printUsage(final Gfsh gfsh, final PrintStream stream) {
int terminalWidth = gfsh.getTerminalWidth();
final DistributionVersion distributionVersion = getDistributionVersion();
stream.print(distributionVersion.getName() + " v");
stream.print(distributionVersion.getVersion());
stream.println(" Command Line Shell" + GfshParser.LINE_SEPARATOR);
stream.println("USAGE");
stream.println(
"gfsh [ <command> [option]* | <help> [command] | [--help | -h] | [-e \"<command> [option]*\"]* ]"
+ GfshParser.LINE_SEPARATOR);
stream.println("OPTIONS");
stream.println("-e Execute a command");
stream.println(Gfsh.wrapText(
"Commands may be any that are available from the interactive gfsh prompt. "
+ "For commands that require a Manager to complete, the first command in the list must be \"connect\".",
1, terminalWidth));
stream.println("EXAMPLES");
stream.println("gfsh");
stream.println(Gfsh.wrapText("Start GFSH in interactive mode.", 1, terminalWidth));
stream.println("gfsh -h");
stream.println(Gfsh.wrapText(
"Displays 'this' help. ('gfsh --help' or 'gfsh help' is equivalent)", 1, terminalWidth));
stream.println("gfsh help start locator");
stream.println(
Gfsh.wrapText("Display help for the \"start locator\" command.", 1, terminalWidth));
stream.println("gfsh start locator --name=locator1");
stream.println(Gfsh.wrapText("Start a Locator with the name \"locator1\".", 1, terminalWidth));
stream.println("gfsh -e \"connect\" -e \"list members\"");
stream.println(Gfsh.wrapText(
"Connect to a running Locator using the default connection information and run the \"list members\" command.",
1, terminalWidth));
stream.println();
}
private static class StartupTimeLogHelper {
private final long start = System.currentTimeMillis();
private long done;
public void logStartupTime() {
done = System.currentTimeMillis();
LogWrapper.getInstance(null)
.info("Startup done in " + ((done - start) / 1000.0) + " seconds.");
}
@Override
public String toString() {
return StartupTimeLogHelper.class.getName() + " [start=" + start + ", done=" + done + "]";
}
}
}