blob: fb3e09a0a7765b0d2fe42fd90bcac05712ab90c1 [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.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;
}
}
}