/* * 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.samza.sql.client.cli; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import org.apache.samza.sql.client.exceptions.CommandHandlerException; import org.apache.samza.sql.client.impl.CliCommandHandler; import org.apache.samza.sql.client.impl.CliCommandType; import org.apache.samza.sql.client.interfaces.CommandHandler; import org.apache.samza.sql.client.interfaces.ExecutionContext; import org.apache.samza.sql.client.exceptions.ExecutorException; import org.apache.samza.sql.client.interfaces.SqlExecutor; import org.apache.samza.sql.client.exceptions.CliException; import org.apache.samza.sql.client.util.CliUtil; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; import org.jline.reader.UserInterruptException; import org.jline.reader.impl.DefaultParser; import org.jline.reader.impl.completer.StringsCompleter; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; import org.jline.utils.InfoCmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The shell UI. */ public class CliShell { private static final Logger LOG = LoggerFactory.getLogger(CliShell.class); private final Terminal terminal; private final PrintWriter writer; private final LineReader lineReader; private final String firstPrompt; private SqlExecutor executor; private List<CommandHandler> commandHandlers; private final ExecutionContext exeContext; private boolean keepRunning = true; CliShell(CliEnvironment environment) throws ExecutorException { if (environment == null) { throw new IllegalArgumentException(); } // Terminal try { terminal = TerminalBuilder.builder().name(CliConstants.WINDOW_TITLE).build(); } catch (IOException e) { throw new CliException("Error when creating terminal", e); } // Terminal writer writer = terminal.writer(); // LineReader final DefaultParser parser = new DefaultParser().eofOnEscapedNewLine(true).eofOnUnclosedQuote(true); lineReader = LineReaderBuilder.builder() .appName(CliConstants.APP_NAME) .terminal(terminal) .parser(parser) .highlighter(new CliHighlighter()) .completer(new StringsCompleter(CliCommandType.getAllCommands())) .build(); // Command Prompt firstPrompt = new AttributedStringBuilder().style(AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)) .append(CliConstants.PROMPT_1ST + CliConstants.PROMPT_1ST_END) .toAnsi(); // Execution context and executor executor = environment.getExecutor(); exeContext = new ExecutionContext(); executor.start(exeContext); // Command handlers if (commandHandlers == null) { commandHandlers = new ArrayList<>(); } commandHandlers.add(new CliCommandHandler()); commandHandlers.addAll(environment.getCommandHandlers()); for (CommandHandler commandHandler : commandHandlers) { LOG.info("init commandHandler {}", commandHandler.getClass().getName()); commandHandler.init(this, environment, terminal, exeContext); } } Terminal getTerminal() { return terminal; } SqlExecutor getExecutor() { return executor; } ExecutionContext getExeContext() { return exeContext; } /** * Actually run the shell. Does not return until user choose to exit. */ void open(String message) { // Remember we cannot enter alternate screen mode here as there is only one alternate // screen and we need it to show streaming results. Clear the screen instead. clearScreen(); writer.write(CliConstants.WELCOME_MESSAGE); printVersion(); if (!CliUtil.isNullOrEmpty(message)) { writer.println(message); } writer.println(); try { // Check if jna.jar exists in class path try { ClassLoader.getSystemClassLoader().loadClass("com.sun.jna.NativeLibrary"); } catch (ClassNotFoundException e) { // Something's wrong. It could be a dumb terminal if neither jna nor jansi lib is there writer.write("Warning: jna.jar does NOT exist. It may lead to a dumb shell or a performance hit.\n"); } while (keepRunning) { String line; try { line = lineReader.readLine(firstPrompt); } catch (UserInterruptException e) { continue; } catch (EndOfFileException e) { keepRunning = false; break; } if (CliUtil.isNullOrEmpty(line)) continue; String helpCmdText = CliCommandType.HELP.getCommandName(); if (line.toUpperCase().startsWith(helpCmdText)) { if (line.toLowerCase().trim().equals(helpCmdText)) { printHelpMessage(); } else { CommandAndHandler commandAndHandler = findHandlerForCommand(line.substring(helpCmdText.length())); if (commandAndHandler.handler != null) { commandAndHandler.handler.handleCommand(commandAndHandler.handler.parseLine(line)); } else { printHelpMessage(); } } continue; } CommandAndHandler commandAndHandler = null; try { commandAndHandler = findHandlerForCommand(line); if (commandAndHandler.handler == null) { LOG.info("no commandHandler found for command {}", line); printHelpMessage(); continue; } keepRunning = commandAndHandler.handler.handleCommand(commandAndHandler.command); } catch (CommandHandlerException e) { writer.println("Error: " + e); LOG.error("Error in {}: ", commandAndHandler.command.getCommandType(), e); writer.flush(); } } } catch (Exception e) { writer.print(e.getClass().getSimpleName()); writer.print(". "); writer.println(e.getMessage()); e.printStackTrace(writer); writer.println(); writer.println("We are sorry but SamzaSqlShell has encountered a problem and must stop."); } writer.write("Cleaning up... "); writer.flush(); try { executor.stop(exeContext); writer.write("Done.\nBye.\n\n"); writer.flush(); terminal.close(); } catch (IOException | ExecutorException e) { // Doesn't matter } } private void printVersion() { String version = String.format("Shell version %s, Executor is %s, version %s", this.getClass().getPackage().getImplementationVersion(), executor.getClass().getName(), executor.getVersion()); writer.println(version); } private void clearScreen() { terminal.puts(InfoCmp.Capability.clear_screen); } private void printHelpMessage() { for (CommandHandler commandHandler : commandHandlers) { commandHandler.printHelpMessage(); } writer.println("HELP <COMMAND> to get help for a specific command."); writer.flush(); } private CommandAndHandler findHandlerForCommand(String line) { CommandHandler commandHandler = null; CliCommand parsedCommand = null; for (CommandHandler curCommandHandler : commandHandlers) { parsedCommand = curCommandHandler.parseLine(line); if (parsedCommand != null && !parsedCommand.getCommandType().getCommandName().equals(CliCommandType.INVALID_COMMAND.getCommandName())) { commandHandler = curCommandHandler; LOG.info("Found commandHandler {} to handle command {}", commandHandler.getClass().getName(), parsedCommand.getFullCommand()); break; } } return new CommandAndHandler(parsedCommand, commandHandler); } class CommandAndHandler { CliCommand command; CommandHandler handler; CommandAndHandler(CliCommand aCommand, CommandHandler itsHandler) { command = aCommand; handler = itsHandler; } } }