| /* |
| * 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.shell; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.net.URL; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Scanner; |
| import java.util.TreeMap; |
| import java.util.logging.Level; |
| import java.util.logging.LogManager; |
| import java.util.logging.Logger; |
| |
| import jline.Terminal; |
| import jline.console.ConsoleReader; |
| import org.springframework.shell.core.AbstractShell; |
| import org.springframework.shell.core.ExecutionStrategy; |
| import org.springframework.shell.core.ExitShellRequest; |
| import org.springframework.shell.core.JLineLogHandler; |
| import org.springframework.shell.core.JLineShell; |
| import org.springframework.shell.core.Parser; |
| import org.springframework.shell.event.ShellStatus.Status; |
| |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| import org.apache.geode.annotations.internal.MutableForTesting; |
| import org.apache.geode.internal.GemFireVersion; |
| import org.apache.geode.internal.lang.ClassUtils; |
| import org.apache.geode.internal.logging.Banner; |
| import org.apache.geode.internal.process.signal.AbstractSignalNotificationHandler; |
| import org.apache.geode.internal.serialization.Version; |
| import org.apache.geode.internal.util.ArgumentRedactor; |
| import org.apache.geode.internal.util.HostName; |
| import org.apache.geode.internal.util.SunAPINotFoundException; |
| import org.apache.geode.logging.internal.executors.LoggingThread; |
| import org.apache.geode.management.cli.CommandProcessingException; |
| import org.apache.geode.management.cli.Result; |
| import org.apache.geode.management.internal.cli.CliUtil; |
| import org.apache.geode.management.internal.cli.CommandManager; |
| import org.apache.geode.management.internal.cli.GfshParser; |
| import org.apache.geode.management.internal.cli.LogWrapper; |
| import org.apache.geode.management.internal.cli.i18n.CliStrings; |
| import org.apache.geode.management.internal.cli.result.CommandResult; |
| import org.apache.geode.management.internal.cli.result.model.ResultModel; |
| import org.apache.geode.management.internal.cli.shell.jline.ANSIHandler; |
| import org.apache.geode.management.internal.cli.shell.jline.ANSIHandler.ANSIStyle; |
| import org.apache.geode.management.internal.cli.shell.jline.GfshHistory; |
| import org.apache.geode.management.internal.cli.shell.jline.GfshUnsupportedTerminal; |
| import org.apache.geode.management.internal.cli.shell.unsafe.GfshSignalHandler; |
| import org.apache.geode.management.internal.cli.util.CommentSkipHelper; |
| |
| /** |
| * Extends an interactive shell provided by |
| * <a href="https://github.com/SpringSource/spring-shell">Spring Shell</a> library. |
| * |
| * <p> |
| * This class is used to plug-in implementations of the following Spring (Roo) Shell components |
| * customized to suite GemFire Command Line Interface (CLI) requirements: |
| * <ul> |
| * <li><code>org.springframework.roo.shell.ExecutionStrategy</code> |
| * <li><code>org.springframework.roo.shell.Parser</code> |
| * </ul> |
| * <p /> |
| * Additionally, this class is used to maintain GemFire SHell (gfsh) specific information like: |
| * environment |
| * |
| * <p> |
| * Additionally, this class is used to maintain GemFire SHell (gfsh) specific information |
| * |
| * @since GemFire 7.0 |
| */ |
| public class Gfsh extends JLineShell { |
| public static final int DEFAULT_APP_FETCH_SIZE = 100; |
| public static final int DEFAULT_APP_LAST_EXIT_STATUS = 0; |
| public static final int DEFAULT_APP_COLLECTION_LIMIT = 20; |
| public static final boolean DEFAULT_APP_QUIET_EXECUTION = false; |
| public static final String DEFAULT_APP_QUERY_RESULTS_DISPLAY_MODE = "table"; |
| public static final String DEFAULT_APP_RESULT_VIEWER = "basic"; |
| public static final String EXTERNAL_RESULT_VIEWER = "external"; |
| |
| public static final String GFSH_APP_NAME = "gfsh"; |
| |
| public static final String LINE_INDENT = " "; |
| public static final String LINE_SEPARATOR = System.getProperty("line.separator"); |
| // Default Window dimensions |
| public static final int DEFAULT_WIDTH = 100; |
| public static final String ENV_APP_NAME = "APP_NAME"; |
| public static final String ENV_APP_CONTEXT_PATH = "APP_CONTEXT_PATH"; |
| public static final String ENV_APP_FETCH_SIZE = "APP_FETCH_SIZE"; |
| public static final String ENV_APP_LAST_EXIT_STATUS = "APP_LAST_EXIT_STATUS"; |
| public static final String ENV_APP_COLLECTION_LIMIT = "APP_COLLECTION_LIMIT"; |
| public static final String ENV_APP_QUERY_RESULTS_DISPLAY_MODE = "APP_QUERY_RESULTS_DISPLAY_MODE"; |
| public static final String ENV_APP_QUIET_EXECUTION = "APP_QUIET_EXECUTION"; |
| public static final String ENV_APP_LOGGING_ENABLED = "APP_LOGGING_ENABLED"; |
| public static final String ENV_APP_LOG_FILE = "APP_LOG_FILE"; |
| public static final String ENV_APP_PWD = "APP_PWD"; |
| public static final String ENV_APP_RESULT_VIEWER = "APP_RESULT_VIEWER"; |
| // Environment Properties taken from the OS |
| public static final String ENV_SYS_USER = "SYS_USER"; |
| public static final String ENV_SYS_USER_HOME = "SYS_USER_HOME"; |
| public static final String ENV_SYS_HOST_NAME = "SYS_HOST_NAME"; |
| public static final String ENV_SYS_CLASSPATH = "SYS_CLASSPATH"; |
| public static final String ENV_SYS_JAVA_VERSION = "SYS_JAVA_VERSION"; |
| public static final String ENV_SYS_OS = "SYS_OS"; |
| public static final String ENV_SYS_OS_LINE_SEPARATOR = "SYS_OS_LINE_SEPARATOR"; |
| public static final String ENV_SYS_GEODE_HOME_DIR = "SYS_GEODE_HOME_DIR"; |
| |
| |
| private static final String DEFAULT_SECONDARY_PROMPT = ">"; |
| private static final int DEFAULT_HEIGHT = 100; |
| private static final Object INSTANCE_LOCK = new Object(); |
| |
| @MutableForTesting |
| protected static PrintStream gfshout = System.out; |
| @MutableForTesting |
| protected static PrintStream gfsherr = System.err; |
| protected static final ThreadLocal<Gfsh> gfshThreadLocal = new ThreadLocal<>(); |
| @MakeNotStatic |
| private static volatile Gfsh instance; |
| // This flag is used to restrict column trimming to table only types |
| private static final ThreadLocal<Boolean> resultTypeTL = new ThreadLocal<>(); |
| private static final String OS = System.getProperty("os.name").toLowerCase(); |
| private final Map<String, String> env = new TreeMap<>(); |
| private final List<String> readonlyAppEnv = new ArrayList<>(); |
| // Map to keep reference to actual user specified Command String |
| // Should always have one value at the max |
| private final Map<String, String> expandedPropCommandsMap = new HashMap<>(); |
| private final ExecutionStrategy executionStrategy; |
| private final GfshParser parser; |
| private final LogWrapper gfshFileLogger; |
| private final GfshConfig gfshConfig; |
| private final GfshHistory gfshHistory; |
| private final ANSIHandler ansiHandler; |
| private final boolean isHeadlessMode; |
| private OperationInvoker operationInvoker; |
| private int lastExecutionStatus; |
| private Thread runner; |
| private boolean debugON; |
| private Terminal terminal; |
| private boolean suppressScriptCmdOutput; |
| private boolean isScriptRunning; |
| private AbstractSignalNotificationHandler signalHandler; |
| |
| public Gfsh() { |
| this(null); |
| } |
| |
| /** |
| * Create a GemFire shell with console using the specified arguments. |
| * |
| * @param args arguments to be used to create a GemFire shell instance |
| */ |
| protected Gfsh(String[] args) { |
| this(true, args, new GfshConfig()); |
| } |
| |
| /** |
| * Create a GemFire shell using the specified arguments. Console for user inputs is made available |
| * if <code>launchShell</code> is set to <code>true</code>. |
| * |
| * @param launchShell whether to make Console available |
| * @param args arguments to be used to create a GemFire shell instance or execute command |
| */ |
| protected Gfsh(boolean launchShell, String[] args, GfshConfig gfshConfig) { |
| // 1. Disable suppressing of duplicate messages |
| JLineLogHandler.setSuppressDuplicateMessages(false); |
| |
| // 2. set & use gfshConfig |
| this.gfshConfig = gfshConfig; |
| // The cache doesn't exist yet, since we are still setting up parsing. |
| this.gfshFileLogger = LogWrapper.getInstance(null); |
| this.gfshFileLogger.configure(this.gfshConfig); |
| this.ansiHandler = ANSIHandler.getInstance(this.gfshConfig.isANSISupported()); |
| |
| // 3. log system properties & gfsh environment TODO: change GFSH to use Geode logging |
| this.gfshFileLogger.info(new Banner().getString()); |
| |
| // 4. Customized History implementation |
| this.gfshHistory = new GfshHistory(); |
| |
| // 6. Set System Environment here |
| initializeEnvironment(); |
| // 7. Create Roo/SpringShell framework objects |
| this.executionStrategy = new GfshExecutionStrategy(this); |
| this.parser = new GfshParser(new CommandManager()); |
| // 8. Set max History file size |
| setHistorySize(gfshConfig.getHistorySize()); |
| |
| String envProps = env.toString(); |
| envProps = envProps.substring(1, envProps.length() - 1); |
| envProps = envProps.replaceAll(",", LINE_SEPARATOR); |
| this.gfshFileLogger.config("***** gfsh Environment ******" + LINE_SEPARATOR + envProps); |
| |
| if (this.gfshFileLogger.fineEnabled()) { |
| String gfshConfigStr = this.gfshConfig.toString(); |
| gfshConfigStr = gfshConfigStr.substring(0, gfshConfigStr.length() - 1); |
| gfshConfigStr = gfshConfigStr.replaceAll(",", LINE_SEPARATOR); |
| this.gfshFileLogger.fine("***** gfsh Configuration ******" + LINE_SEPARATOR + gfshConfigStr); |
| } |
| |
| // Setup signal handler for various signals (such as CTRL-C)... |
| try { |
| ClassUtils.forName("sun.misc.Signal", new SunAPINotFoundException( |
| "WARNING!!! Not running a Sun JVM. Could not find the sun.misc.Signal class; Signal handling disabled.")); |
| signalHandler = new GfshSignalHandler(); |
| } catch (SunAPINotFoundException e) { |
| signalHandler = new AbstractSignalNotificationHandler() {}; |
| this.gfshFileLogger.warning(e.getMessage()); |
| } |
| |
| // For test code only |
| if (this.gfshConfig.isTestConfig()) { |
| instance = this; |
| } |
| this.isHeadlessMode = !launchShell; |
| if (this.isHeadlessMode) { |
| this.gfshFileLogger.config("Running in headless mode"); |
| // disable jline terminal |
| System.setProperty("jline.terminal", GfshUnsupportedTerminal.class.getName()); |
| env.put(ENV_APP_QUIET_EXECUTION, String.valueOf(true)); |
| // Only in headless mode, we do not want Gfsh's logger logs on screen |
| this.gfshFileLogger.setParentFor(logger); |
| } |
| // we want to direct internal JDK logging to file in either mode |
| redirectInternalJavaLoggers(); |
| } |
| |
| public static Gfsh getInstance(boolean launchShell, String[] args, GfshConfig gfshConfig) { |
| Gfsh localGfshInstance = instance; |
| if (localGfshInstance == null) { |
| synchronized (INSTANCE_LOCK) { |
| localGfshInstance = instance; |
| if (localGfshInstance == null) { |
| localGfshInstance = new Gfsh(launchShell, args, gfshConfig); |
| localGfshInstance.executeInitFileIfPresent(); |
| instance = localGfshInstance; |
| } |
| } |
| } |
| |
| return instance; |
| } |
| |
| public static boolean isInfoResult() { |
| if (resultTypeTL.get() == null) { |
| return false; |
| } |
| return resultTypeTL.get(); |
| } |
| |
| public static void println() { |
| gfshout.println(); |
| } |
| |
| public static void println(Object toPrint) { |
| gfshout.println(toPrint); |
| } |
| |
| public static void print(Object toPrint) { |
| gfshout.print(toPrint); |
| } |
| |
| public static void printlnErr(Object toPrint) { |
| gfsherr.println(toPrint); |
| } |
| |
| // See 46369 |
| private static String readLine(ConsoleReader reader, String prompt) throws IOException { |
| String earlierLine = reader.getCursorBuffer().toString(); |
| String readLine; |
| try { |
| readLine = reader.readLine(prompt); |
| } catch (IndexOutOfBoundsException e) { |
| if (earlierLine.length() == 0) { |
| reader.println(); |
| readLine = LINE_SEPARATOR; |
| reader.getCursorBuffer().cursor = 0; |
| } else { |
| readLine = readLine(reader, prompt); |
| } |
| } |
| return readLine; |
| } |
| |
| private static String removeBackslash(String result) { |
| if (result.endsWith(GfshParser.CONTINUATION_CHARACTER)) { |
| result = result.substring(0, result.length() - 1); |
| } |
| return result; |
| } |
| |
| /** |
| * This method sets the parent of all loggers whose name starts with "java" or "javax" to |
| * LogWrapper. |
| * |
| * logWrapper disables any parents's log handler, and only logs to the file if specified. This |
| * would prevent JDK's logging show up in the console |
| */ |
| public void redirectInternalJavaLoggers() { |
| // Do we need to this on re-connect? |
| LogManager logManager = LogManager.getLogManager(); |
| |
| try { |
| Enumeration<String> loggerNames = logManager.getLoggerNames(); |
| |
| while (loggerNames.hasMoreElements()) { |
| String loggerName = loggerNames.nextElement(); |
| if (loggerName.startsWith("java.") || loggerName.startsWith("javax.")) { |
| Logger javaLogger = logManager.getLogger(loggerName); |
| /* |
| * From Java Docs: It is also important to note that the Logger associated with the String |
| * name may be garbage collected at any time if there is no strong reference to the |
| * Logger. The caller of this method must check the return value for null in order to |
| * properly handle the case where the Logger has been garbage collected. |
| */ |
| if (javaLogger != null) { |
| this.gfshFileLogger.setParentFor(javaLogger); |
| } |
| } |
| } |
| } catch (SecurityException e) { |
| this.gfshFileLogger.warning(e.getMessage(), e); |
| } |
| } |
| |
| public static Gfsh getCurrentInstance() { |
| return instance; |
| } |
| |
| private static String extractKey(String input) { |
| return input.substring("${".length(), input.length() - "}".length()); |
| } |
| |
| public static ConsoleReader getConsoleReader() { |
| Gfsh gfsh = Gfsh.getCurrentInstance(); |
| return (gfsh == null ? null : gfsh.reader); |
| } |
| |
| /** |
| * Take a string and wrap it into multiple lines separated by CliConstants.LINE_SEPARATOR. Lines |
| * are separated based upon the terminal width, separated on word boundaries and may have extra |
| * spaces added to provide indentation. |
| * |
| * For example: if the terminal width were 5 and the string "123 456789 01234" were passed in with |
| * an indentation level of 2, then the returned string would be: |
| * |
| * <pre> |
| * 123 |
| * 45678 |
| * 9 |
| * 01234 |
| * </pre> |
| * |
| * @param string String to wrap (add breakpoints and indent) |
| * @param indentationLevel The number of indentation levels to use. |
| * @return The wrapped string. |
| */ |
| public static String wrapText(final String string, final int indentationLevel, |
| final int terminalWidth) { |
| if (terminalWidth <= 1) { |
| return string; |
| } |
| |
| final int maxLineLength = terminalWidth - 1; |
| final StringBuffer stringBuf = new StringBuffer(); |
| int index = 0; |
| int startOfCurrentLine = 0; |
| while (index < string.length()) { |
| // Add the indentation |
| for (int i = 0; i < indentationLevel; i++) { |
| stringBuf.append(LINE_INDENT); |
| } |
| int currentLineLength = LINE_INDENT.length() * indentationLevel; |
| |
| // Find the end of a line: |
| // 1. If the end of string is reached |
| // 2. If the width of the terminal has been reached |
| // 3. If a newline character was found in the string |
| while (index < string.length() && currentLineLength < maxLineLength |
| && string.charAt(index) != '\n') { |
| index++; |
| currentLineLength++; |
| } |
| |
| // If the line was terminated with a newline character |
| if (index != string.length() && string.charAt(index) == '\n') { |
| stringBuf.append(string.substring(startOfCurrentLine, index)); |
| stringBuf.append(LINE_SEPARATOR); |
| index++; |
| startOfCurrentLine = index; |
| |
| // If the end of the string was reached or the last character just happened to be a space |
| // character |
| } else if (index == string.length() || string.charAt(index) == ' ') { |
| stringBuf.append(string.substring(startOfCurrentLine, index)); |
| if (index != string.length()) { |
| stringBuf.append(LINE_SEPARATOR); |
| index++; |
| } |
| |
| } else { |
| final int spaceCharIndex = string.lastIndexOf(" ", index); |
| |
| // If no spaces were found then there's no logical way to split the string |
| if (spaceCharIndex == -1 || spaceCharIndex < startOfCurrentLine) { |
| stringBuf.append(string.substring(startOfCurrentLine, index)).append(LINE_SEPARATOR); |
| |
| // Else split the string cleanly between words |
| } else { |
| stringBuf.append(string.substring(startOfCurrentLine, spaceCharIndex)) |
| .append(LINE_SEPARATOR); |
| index = spaceCharIndex + 1; |
| } |
| } |
| |
| startOfCurrentLine = index; |
| } |
| return stringBuf.toString(); |
| } |
| |
| /** |
| * Initializes default environment variables to default values |
| */ |
| private void initializeEnvironment() { |
| env.put(ENV_SYS_USER, System.getProperty("user.name")); |
| env.put(ENV_SYS_USER_HOME, System.getProperty("user.home")); |
| env.put(ENV_SYS_HOST_NAME, new HostName().determineHostName()); |
| env.put(ENV_SYS_CLASSPATH, System.getProperty("java.class.path")); |
| env.put(ENV_SYS_JAVA_VERSION, System.getProperty("java.version")); |
| env.put(ENV_SYS_OS, System.getProperty("os.name")); |
| env.put(ENV_SYS_OS_LINE_SEPARATOR, System.getProperty("line.separator")); |
| env.put(ENV_SYS_GEODE_HOME_DIR, System.getenv("GEODE_HOME")); |
| |
| env.put(ENV_APP_NAME, Gfsh.GFSH_APP_NAME); |
| readonlyAppEnv.add(ENV_APP_NAME); |
| env.put(ENV_APP_LOGGING_ENABLED, |
| String.valueOf(!Level.OFF.equals(this.gfshConfig.getLogLevel()))); |
| readonlyAppEnv.add(ENV_APP_LOGGING_ENABLED); |
| env.put(ENV_APP_LOG_FILE, this.gfshConfig.getLogFilePath()); |
| readonlyAppEnv.add(ENV_APP_LOG_FILE); |
| env.put(ENV_APP_PWD, System.getProperty("user.dir")); |
| readonlyAppEnv.add(ENV_APP_PWD); |
| env.put(ENV_APP_FETCH_SIZE, String.valueOf(DEFAULT_APP_FETCH_SIZE)); |
| env.put(ENV_APP_LAST_EXIT_STATUS, String.valueOf(DEFAULT_APP_LAST_EXIT_STATUS)); |
| readonlyAppEnv.add(ENV_APP_LAST_EXIT_STATUS); |
| env.put(ENV_APP_COLLECTION_LIMIT, String.valueOf(DEFAULT_APP_COLLECTION_LIMIT)); |
| env.put(ENV_APP_QUERY_RESULTS_DISPLAY_MODE, DEFAULT_APP_QUERY_RESULTS_DISPLAY_MODE); |
| env.put(ENV_APP_QUIET_EXECUTION, String.valueOf(DEFAULT_APP_QUIET_EXECUTION)); |
| env.put(ENV_APP_RESULT_VIEWER, String.valueOf(DEFAULT_APP_RESULT_VIEWER)); |
| } |
| |
| public AbstractSignalNotificationHandler getSignalHandler() { |
| return signalHandler; |
| } |
| |
| public String readPassword(String textToPrompt) { |
| if (isHeadlessMode && isQuietMode()) |
| return null; |
| |
| return readWithMask(textToPrompt, '*'); |
| } |
| |
| public String readText(String textToPrompt) { |
| if (isHeadlessMode && isQuietMode()) |
| return null; |
| |
| return interact(textToPrompt); |
| |
| } |
| |
| /** |
| * Starts this GemFire Shell with console. |
| */ |
| public void start() { |
| runner = new LoggingThread(getShellName(), false, this); |
| runner.start(); |
| } |
| |
| protected String getShellName() { |
| return "Gfsh Launcher"; |
| } |
| |
| /** |
| * Stops this GemFire Shell. |
| */ |
| public void stop() { |
| closeShell(); |
| LogWrapper.close(); |
| if (operationInvoker != null && operationInvoker.isConnected()) { |
| operationInvoker.stop(); |
| } |
| instance = null; |
| } |
| |
| public void waitForComplete() throws InterruptedException { |
| runner.join(); |
| } |
| |
| /* |
| * If an init file is provided, as a system property or in the default location, run it as a |
| * command script. |
| */ |
| private void executeInitFileIfPresent() { |
| |
| String initFileName = this.gfshConfig.getInitFileName(); |
| if (initFileName != null) { |
| this.gfshFileLogger.info("Using " + initFileName); |
| try { |
| File gfshInitFile = new File(initFileName); |
| boolean continueOnError = false; |
| this.executeScript(gfshInitFile, isQuietMode(), continueOnError); |
| } catch (Exception exception) { |
| this.gfshFileLogger.severe(initFileName, exception); |
| setLastExecutionStatus(-1); |
| } |
| } |
| |
| } |
| |
| /** |
| * See findResources in {@link AbstractShell} |
| */ |
| protected Collection<URL> findResources(String resourceName) { |
| return null; |
| } |
| |
| /** |
| * Returns the {@link ExecutionStrategy} implementation used by this implementation of |
| * {@link AbstractShell}. {@link Gfsh} uses {@link GfshExecutionStrategy}. |
| * |
| * @return ExecutionStrategy used by Gfsh |
| */ |
| @Override |
| protected ExecutionStrategy getExecutionStrategy() { |
| return executionStrategy; |
| } |
| |
| /** |
| * Returns the {@link Parser} implementation used by this implementation of |
| * {@link AbstractShell}.{@link Gfsh} uses {@link GfshParser}. |
| * |
| * @return Parser used by Gfsh |
| */ |
| @Override |
| public Parser getParser() { |
| return parser; |
| } |
| |
| public LogWrapper getGfshFileLogger() { |
| return gfshFileLogger; |
| } |
| |
| /** |
| * Executes a single command string. |
| * It substitutes the variables defined within the command, if any, and then delegates to the |
| * default execution. |
| * |
| * @param line command string to be executed |
| * @return command execution result. |
| */ |
| @Override |
| public org.springframework.shell.core.CommandResult executeCommand(String line) { |
| return super.executeCommand(!line.contains("$") ? line : expandProperties(line)); |
| } |
| |
| /** |
| * Executes the given command string. We have over-ridden the behavior to extend the original |
| * implementation to store the 'last command execution status'. |
| * |
| * @param line command string to be executed |
| * @return true if execution is successful; false otherwise |
| */ |
| @Override |
| public boolean executeScriptLine(final String line) { |
| boolean success = false; |
| String withPropsExpanded = line; |
| |
| try { |
| // expand env property if the string contains $ |
| if (line.contains("$")) { |
| withPropsExpanded = expandProperties(line); |
| } |
| String logMessage = "Command String to execute .. "; |
| if (!line.equals(withPropsExpanded)) { |
| if (!isQuietMode()) { |
| Gfsh.println("Post substitution: " + withPropsExpanded); |
| } |
| logMessage = "Command String after substitution : "; |
| expandedPropCommandsMap.put(withPropsExpanded, line); |
| } |
| if (gfshFileLogger.fineEnabled()) { |
| gfshFileLogger.fine(logMessage + ArgumentRedactor.redact(withPropsExpanded)); |
| } |
| success = super.executeScriptLine(withPropsExpanded); |
| } catch (Exception e) { |
| setLastExecutionStatus(-1); |
| } finally { // Add all commands to in-memory GfshHistory |
| gfshHistory.setAutoFlush(true); |
| gfshHistory.addToHistory(line); |
| gfshHistory.setAutoFlush(false); |
| |
| // clear the map |
| expandedPropCommandsMap.clear(); |
| } |
| return success; |
| } |
| |
| public String interact(String textToPrompt) { |
| try { |
| return reader.readLine(textToPrompt); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public String readWithMask(String textToPrompt, Character mask) { |
| try { |
| return reader.readLine(textToPrompt, mask); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void printBannerAndWelcome() { |
| printAsInfo(getBanner()); |
| printAsInfo(getWelcomeMessage()); |
| } |
| |
| public String getBanner() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(" _________________________ __").append(LINE_SEPARATOR); |
| sb.append(" / _____/ ______/ ______/ /____/ /").append(LINE_SEPARATOR); |
| sb.append(" / / __/ /___ /_____ / _____ / ").append(LINE_SEPARATOR); |
| sb.append(" / /__/ / ____/ _____/ / / / / ").append(LINE_SEPARATOR); |
| sb.append("/______/_/ /______/_/ /_/ ").append(" ").append(this.getVersion()) |
| .append(LINE_SEPARATOR); |
| return ansiHandler.decorateString(sb.toString(), ANSIStyle.BLUE); |
| } |
| |
| @Override |
| protected String getProductName() { |
| return "gfsh"; |
| } |
| |
| @Override |
| public String getVersion() { |
| return getVersion(false); |
| } |
| |
| public String getVersion(boolean full) { |
| if (full) { |
| return GemFireVersion.asString(); |
| } else { |
| |
| return GemFireVersion.getGemFireVersion(); |
| } |
| } |
| |
| public String getGeodeSerializationVersion() { |
| return Version.CURRENT.getName(); |
| } |
| |
| public String getWelcomeMessage() { |
| return ansiHandler.decorateString("Monitor and Manage " + GemFireVersion.getProductName(), |
| ANSIStyle.CYAN); |
| } |
| |
| // Over-ridden to avoid default behavior which is: |
| // For Iterable: go through all elements & call toString |
| // For others: call toString |
| @Override |
| protected void handleExecutionResult(Object result) { |
| try { |
| if (result instanceof Result) { |
| Result commandResult = (Result) result; |
| boolean isError = Result.Status.ERROR.equals(commandResult.getStatus()); |
| |
| if (isError) { |
| setLastExecutionStatus(-2); |
| } else { |
| setLastExecutionStatus(0); |
| } |
| |
| if (useExternalViewer(commandResult)) { |
| // - Save file and pass to less so that viewer can scroll through |
| // results |
| CliUtil.runLessCommandAsExternalViewer(commandResult); |
| } else { |
| if (!isScriptRunning) { |
| // Normal Command |
| while (commandResult.hasNextLine()) { |
| String nextLine = commandResult.nextLine(); |
| write(nextLine, isError); |
| } |
| } else if (!suppressScriptCmdOutput) { |
| // Command is part of script. Show output only when quite=false |
| while (commandResult.hasNextLine()) { |
| write(commandResult.nextLine(), isError); |
| } |
| } |
| commandResult.resetToFirstLine(); |
| } |
| |
| resultTypeTL.set(null); |
| } |
| if (result != null && !(result instanceof Result)) { |
| printAsInfo(result.toString()); |
| } |
| } catch (Exception e) { |
| printAsWarning(e.getMessage()); |
| logToFile(e.getMessage(), e); |
| } |
| } |
| |
| private boolean useExternalViewer(Result result) { |
| boolean flag = |
| EXTERNAL_RESULT_VIEWER.equals(getEnvProperty(Gfsh.ENV_APP_RESULT_VIEWER)) && isUnix(); |
| if (result instanceof CommandResult) { |
| CommandResult commandResult = (CommandResult) result; |
| resultTypeTL.set(commandResult.getType().equals("info")); |
| return flag && !commandResult.getType().equals("info"); |
| } else |
| return false; |
| } |
| |
| private boolean isUnix() { |
| return !(OS.contains("win")); |
| } |
| |
| private void write(String message, boolean isError) { |
| if (isError) { |
| printAsWarning(message); |
| } else { |
| Gfsh.println(message); |
| } |
| } |
| |
| @Override |
| protected ConsoleReader createConsoleReader() { |
| ConsoleReader consoleReader = super.createConsoleReader(); |
| consoleReader.setHistory(gfshHistory); |
| terminal = consoleReader.getTerminal(); |
| return consoleReader; |
| } |
| |
| @Override |
| protected void logCommandToOutput(String processedLine) { |
| String originalString = expandedPropCommandsMap.get(processedLine); |
| if (originalString != null) { |
| // In history log the original command string & expanded line as a comment |
| super.logCommandToOutput(ArgumentRedactor.redact(originalString)); |
| super.logCommandToOutput(ArgumentRedactor.redact("// Post substitution")); |
| super.logCommandToOutput(ArgumentRedactor.redact("//" + processedLine)); |
| } else { |
| super.logCommandToOutput(ArgumentRedactor.redact(processedLine)); |
| } |
| } |
| |
| @Override |
| public String versionInfo() { |
| return getVersion(); |
| } |
| |
| public int getTerminalHeight() { |
| return terminal != null ? terminal.getHeight() : DEFAULT_HEIGHT; |
| } |
| |
| public int getTerminalWidth() { |
| if (terminal != null) { |
| return terminal.getWidth(); |
| } |
| |
| Map<String, String> env = System.getenv(); |
| String columnsFromEnv = env.get("COLUMNS"); |
| if (columnsFromEnv != null) { |
| return Integer.parseInt(columnsFromEnv); |
| } |
| |
| return DEFAULT_WIDTH; |
| } |
| |
| /** |
| * @return the lastExecutionStatus |
| */ |
| public int getLastExecutionStatus() { |
| // APP_LAST_EXIT_STATUS |
| return lastExecutionStatus; |
| } |
| |
| /** |
| * Set the last command execution status |
| * |
| * @param lastExecutionStatus last command execution status |
| */ |
| public void setLastExecutionStatus(int lastExecutionStatus) { |
| this.lastExecutionStatus = lastExecutionStatus; |
| env.put(ENV_APP_LAST_EXIT_STATUS, String.valueOf(lastExecutionStatus)); |
| } |
| |
| public void printAsInfo(String message) { |
| if (isHeadlessMode) { |
| println(message); |
| } else { |
| logger.info(message); |
| } |
| } |
| |
| public void printAsWarning(String message) { |
| if (isHeadlessMode) { |
| printlnErr(message); |
| } else { |
| logger.warning(message); |
| } |
| } |
| |
| public void printAsSevere(String message) { |
| if (isHeadlessMode) { |
| printlnErr(message); |
| } else { |
| logger.severe(message); |
| } |
| } |
| |
| public void logInfo(String message, Throwable t) { |
| // No level enabled check for logger - it prints on console in colors as per level |
| if (debugON) { |
| logger.log(Level.INFO, message, t); |
| } else { |
| logger.info(message); |
| } |
| if (gfshFileLogger.infoEnabled()) { |
| gfshFileLogger.info(message, t); |
| } |
| } |
| |
| public void logWarning(String message, Throwable t) { |
| // No level enabled check for logger - it prints on console in colors as per level |
| if (debugON) { |
| logger.log(Level.WARNING, message, t); |
| } else { |
| logger.warning(message); |
| } |
| if (gfshFileLogger.warningEnabled()) { |
| gfshFileLogger.warning(message, t); |
| } |
| } |
| |
| public void logSevere(String message, Throwable t) { |
| // No level enabled check for logger - it prints on console in colors as per level |
| if (debugON) { |
| logger.log(Level.SEVERE, message, t); |
| } else { |
| logger.severe(message); |
| } |
| if (gfshFileLogger.severeEnabled()) { |
| gfshFileLogger.severe(message, t); |
| } |
| } |
| |
| public boolean logToFile(String message, Throwable t) { |
| boolean loggedMessage = false; |
| if (gfshFileLogger != null) { |
| gfshFileLogger.info(message, t); |
| loggedMessage = true; |
| } |
| return loggedMessage; |
| } |
| |
| public ResultModel executeScript(File scriptFile, boolean quiet, boolean continueOnError) { |
| ResultModel result = null; |
| String initialIsQuiet = getEnvProperty(ENV_APP_QUIET_EXECUTION); |
| try { |
| this.isScriptRunning = true; |
| if (scriptFile == null) { |
| throw new IllegalArgumentException("Given script file is null."); |
| } else if (!scriptFile.exists()) { |
| throw new IllegalArgumentException("Given script file does not exist."); |
| } else if (scriptFile.exists() && scriptFile.isDirectory()) { |
| throw new IllegalArgumentException(scriptFile.getPath() + " is a directory."); |
| } |
| |
| ScriptExecutionDetails scriptInfo = new ScriptExecutionDetails(scriptFile.getPath()); |
| if (scriptFile.exists()) { |
| setEnvProperty(ENV_APP_QUIET_EXECUTION, String.valueOf(quiet)); |
| this.suppressScriptCmdOutput = quiet; |
| BufferedReader reader = new BufferedReader(new FileReader(scriptFile)); |
| String lineRead = ""; |
| StringBuilder linesBuffer = new StringBuilder(); |
| String linesBufferString = ""; |
| int commandSrNum = 0; |
| CommentSkipHelper commentSkipper = new CommentSkipHelper(); |
| |
| LINEREAD_LOOP: while (exitShellRequest == null && (lineRead = reader.readLine()) != null) { |
| if (linesBuffer == null) { |
| linesBuffer = new StringBuilder(); |
| } |
| String lineWithoutComments = commentSkipper.skipComments(lineRead); |
| if (lineWithoutComments == null || lineWithoutComments.isEmpty()) { |
| continue; |
| } |
| |
| if (linesBuffer.length() != 0) {// add " " between lines |
| linesBuffer.append(" "); |
| } |
| linesBuffer.append(lineWithoutComments); |
| linesBufferString = linesBuffer.toString(); |
| // NOTE: Similar code is in promptLoop() |
| if (!linesBufferString.endsWith(GfshParser.CONTINUATION_CHARACTER)) { // see 45893 |
| |
| List<String> commandList = MultiCommandHelper.getMultipleCommands(linesBufferString); |
| for (String cmdLet : commandList) { |
| if (!cmdLet.isEmpty()) { |
| String redactedCmdLet = ArgumentRedactor.redact(cmdLet); |
| ++commandSrNum; |
| Gfsh.println(commandSrNum + ". Executing - " + redactedCmdLet); |
| Gfsh.println(); |
| boolean executeSuccess = executeScriptLine(cmdLet); |
| if (!executeSuccess) { |
| setLastExecutionStatus(-1); |
| } |
| scriptInfo.addCommandAndStatus(cmdLet, |
| getLastExecutionStatus() == -1 || getLastExecutionStatus() == -2 ? "FAILED" |
| : "PASSED"); |
| if ((getLastExecutionStatus() == -1 || getLastExecutionStatus() == -2) |
| && !continueOnError) { |
| break LINEREAD_LOOP; |
| } |
| } |
| } |
| |
| // reset buffer |
| linesBuffer = null; |
| linesBufferString = null; |
| } else { |
| linesBuffer.deleteCharAt(linesBuffer.length() - 1); |
| } |
| } |
| reader.close(); |
| } else { |
| throw new CommandProcessingException(scriptFile.getPath() + " doesn't exist.", |
| CommandProcessingException.ARGUMENT_INVALID, scriptFile); |
| } |
| result = scriptInfo.getResult(); |
| scriptInfo.logScriptExecutionInfo(gfshFileLogger, result); |
| if (quiet) { |
| // Create empty result when in quiet mode |
| result = ResultModel.createInfo(""); |
| } |
| } catch (IOException e) { |
| throw new CommandProcessingException("Error while reading file " + scriptFile, |
| CommandProcessingException.RESOURCE_ACCESS_ERROR, e); |
| } finally { |
| // reset to original Quiet Execution value |
| setEnvProperty(ENV_APP_QUIET_EXECUTION, initialIsQuiet); |
| this.isScriptRunning = false; |
| } |
| |
| return result; |
| } |
| |
| |
| public String setEnvProperty(String propertyName, String propertyValue) { |
| if (propertyName == null || propertyValue == null) { |
| throw new IllegalArgumentException( |
| "Environment Property name and/or value can not be set to null."); |
| } |
| if (propertyName.startsWith("SYS") || readonlyAppEnv.contains(propertyName)) { |
| throw new IllegalArgumentException("The Property " + propertyName + " can not be modified."); |
| } |
| return env.put(propertyName, propertyValue); |
| } |
| |
| public String getEnvProperty(String propertyName) { |
| return env.get(propertyName); |
| } |
| |
| public String getEnvAppContextPath() { |
| String path = getEnvProperty(Gfsh.ENV_APP_CONTEXT_PATH); |
| if (path == null) { |
| return ""; |
| } |
| return path; |
| } |
| |
| public Map<String, String> getEnv() { |
| Map<String, String> map = new TreeMap<>(env); |
| return map; |
| } |
| |
| public boolean isQuietMode() { |
| return Boolean.parseBoolean(env.get(ENV_APP_QUIET_EXECUTION)); |
| } |
| |
| @Override |
| public void promptLoop() { |
| String line = null; |
| String prompt = getPromptText(); |
| try { |
| gfshHistory.setAutoFlush(false); |
| // NOTE: Similar code is in executeScript() |
| while (exitShellRequest == null && (line = readLine(reader, prompt)) != null) { |
| if (!line.endsWith(GfshParser.CONTINUATION_CHARACTER)) { // see 45893 |
| List<String> commandList = MultiCommandHelper.getMultipleCommands(line); |
| for (String cmdLet : commandList) { |
| String trimmedCommand = cmdLet.trim(); |
| if (!trimmedCommand.isEmpty()) { |
| executeCommand(cmdLet); |
| } |
| } |
| prompt = getPromptText(); |
| } else { |
| prompt = getDefaultSecondaryPrompt(); |
| reader.getCursorBuffer().cursor = 0; |
| reader.getCursorBuffer().write(removeBackslash(line) + LINE_SEPARATOR); |
| } |
| } |
| if (line == null) { |
| // Possibly Ctrl-D was pressed on empty prompt. ConsoleReader.readLine |
| // returns null on Ctrl-D |
| this.exitShellRequest = ExitShellRequest.NORMAL_EXIT; |
| gfshFileLogger.info("Exiting gfsh, it seems Ctrl-D was pressed."); |
| } |
| } catch (IOException e) { |
| logSevere(e.getMessage(), e); |
| } |
| println((line == null ? LINE_SEPARATOR : "") + "Exiting... "); |
| setShellStatus(Status.SHUTTING_DOWN); |
| } |
| |
| String getDefaultSecondaryPrompt() { |
| return ansiHandler.decorateString(DEFAULT_SECONDARY_PROMPT, ANSIStyle.YELLOW); |
| } |
| |
| public boolean isConnectedAndReady() { |
| return operationInvoker != null && operationInvoker.isConnected() && operationInvoker.isReady(); |
| } |
| |
| public static boolean isCurrentInstanceConnectedAndReady() { |
| return (getCurrentInstance() != null && getCurrentInstance().isConnectedAndReady()); |
| } |
| |
| /** |
| * @return the operationInvoker |
| */ |
| public OperationInvoker getOperationInvoker() { |
| return operationInvoker; |
| } |
| |
| /** |
| * @param operationInvoker the operationInvoker to set |
| */ |
| public void setOperationInvoker(final OperationInvoker operationInvoker) { |
| this.operationInvoker = operationInvoker; |
| } |
| |
| public GfshConfig getGfshConfig() { |
| return this.gfshConfig; |
| } |
| |
| @Override |
| protected String getHistoryFileName() { |
| return gfshConfig.getHistoryFileName(); |
| } |
| |
| public void clearHistory() { |
| gfshHistory.clear(); |
| if (!gfshConfig.deleteHistoryFile()) { |
| printAsWarning("Gfsh history file is not deleted"); |
| } |
| } |
| |
| public String getLogFilePath() { |
| return gfshConfig.getLogFilePath(); |
| } |
| |
| public boolean isLoggingEnabled() { |
| return gfshConfig.isLoggingEnabled(); |
| } |
| |
| @Override |
| protected String getPromptText() { |
| String defaultPrompt = gfshConfig.getDefaultPrompt(); |
| String contextPath = ""; |
| String clusterString = ""; |
| |
| if (getOperationInvoker() != null && isConnectedAndReady()) { |
| int clusterId = getOperationInvoker().getClusterId(); |
| if (clusterId != OperationInvoker.CLUSTER_ID_WHEN_NOT_CONNECTED) { |
| clusterString = "Cluster-" + clusterId + " "; |
| } |
| } |
| |
| defaultPrompt = MessageFormat.format(defaultPrompt, clusterString, contextPath); |
| |
| return ansiHandler.decorateString(defaultPrompt, ANSIStyle.YELLOW); |
| } |
| |
| public void notifyDisconnect(String endPoints) { |
| String message = |
| CliStrings.format(CliStrings.GFSH__MSG__NO_LONGER_CONNECTED_TO_0, new Object[] {endPoints}); |
| printAsSevere(LINE_SEPARATOR + message); |
| if (gfshFileLogger.severeEnabled()) { |
| gfshFileLogger.severe(message); |
| } |
| setPromptPath(getEnvAppContextPath()); |
| } |
| |
| public boolean getDebug() { |
| return debugON; |
| } |
| |
| public void setDebug(boolean flag) { |
| debugON = flag; |
| } |
| |
| public boolean isHeadlessMode() { |
| return isHeadlessMode; |
| } |
| |
| public GfshHistory getGfshHistory() { |
| return gfshHistory; |
| } |
| |
| protected String expandProperties(final String input) { |
| String output = input; |
| Scanner s = new Scanner(output); |
| String foundInLine; |
| while ((foundInLine = s.findInLine("(\\$[\\{]\\w+[\\}])")) != null) { |
| String envProperty = getEnvProperty(extractKey(foundInLine)); |
| envProperty = envProperty != null ? envProperty : ""; |
| output = output.replace(foundInLine, envProperty); |
| } |
| return output; |
| } |
| } |