/**
* Copyright 2013-2014 Recruit Technologies Co., Ltd. and contributors
* (see CONTRIBUTORS.md)
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. A copy of the
* License is distributed with this work in the LICENSE.md file. You may
* also obtain a copy of the License from
*
* 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.gennai.gungnir.console;
import static org.gennai.gungnir.GungnirConfig.*;
import static org.gennai.gungnir.GungnirConst.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import jline.console.ConsoleReader;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.gennai.gungnir.GungnirConfig;
import org.gennai.gungnir.client.GungnirClient;
import org.gennai.gungnir.client.GungnirClient.Connection;
import org.gennai.gungnir.client.GungnirClient.Statement;
import org.gennai.gungnir.client.GungnirClientException;
import org.gennai.gungnir.console.hook.StartTopologyHook;
import org.gennai.gungnir.console.hook.StopTopologyHook;
import org.gennai.gungnir.console.hook.SubmitTopologyHook;
import org.gennai.gungnir.console.hook.UserCommandHook;
import org.gennai.gungnir.thrift.GungnirServerException;
import org.gennai.gungnir.utils.GungnirUtils;
import org.gennai.gungnir.utils.SLF4JHandler;
import org.jboss.netty.handler.codec.http.Cookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.net.server.ServerSocketReceiver;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
public final class Console {
private static final Logger LOG = LoggerFactory.getLogger(Console.class);
private static final String[] KEYWORDS = {
"FROM", "JOIN", "ON", "EMIT", "TO", "INTO", "EACH", "CAST", "FILTER", "EXPIRE", "STATE",
"SLIDE", "LENGTH", "SNAPSHOT", "EVERY", "LIMIT", "FIRST", "LAST", "GROUP", "BEGIN", "END",
"PARTITION", "BY", "STREAM", "AS", "USING", "PARALLELISM", "EXPLAIN", "EXTENDED", "CLEAR",
"CREATE", "TUPLE", "VIEW", "PARTITIONED", "COMMENT", "DESCRIBE", "DESC", "SHOW", "TUPLES",
"VIEWS", "FILE", "FILES", "FUNCTION", "FUNCTIONS", "ALTER", "DROP", "USER", "IDENTIFIED",
"USERS", "UPLOAD", "STATS",
"STRING", "TINYINT", "SMALLINT", "INT", "BIGINT", "FLOAT", "DOUBLE", "BOOLEAN", "TIMESTAMP",
"LIST", "MAP", "STRUCT",
"TOPOLOGY", "SUBMIT", "STOP", "START", "TOPOLOGIES", "CLUSTER",
"TRUE", "FALSE", "LIKE", "REGEXP", "IN", "ALL", "BETWEEN", "IS", "NULL", "AND", "OR", "NOT",
"SECONDS", "MINUTES", "HOURS", "DAYS", "SET", "POST", "LOG", "COOKIE", "QUIT", "EXIT"
};
private static final String PROMPT = "gungnir> ";
private static final String INPUTTING_PROMPT = " -> ";
private static final String HISTORY_FILE = ".gungnirhistory";
interface CommandHandler {
void prepare(ConsoleContext context);
boolean isMatch(String command);
void execute(String command);
}
private List<CommandHandler> commandHandlers;
private String userName;
private GungnirConfig config;
private Statement statement;
private ServerSocketReceiver receiver;
private ObjectMapper mapper = new ObjectMapper();
private Console() {
commandHandlers = Lists.newArrayList();
}
private void addHandler(CommandHandler handler) {
commandHandlers.add(handler);
}
private String getBuildTimeString() {
String buildTimeString = null;
try {
Properties properties = new Properties();
properties.load(Console.class.getResourceAsStream(BUILD_PROPERTIES));
buildTimeString = properties.getProperty(GUNGNIR_BUILD_TIME);
} catch (IOException e) {
return "-";
}
return buildTimeString;
}
private String getAccountId() throws IOException, GungnirServerException, GungnirClientException {
String res = statement.execute("DESC USER");
JsonNode descNode = mapper.readTree(res);
return descNode.get("id").asText();
}
private void startLogReceiver(Appender<ILoggingEvent> appender)
throws GungnirServerException, GungnirClientException {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
appender.setContext(lc);
appender.start();
ch.qos.logback.classic.Logger logger = lc.getLogger(LOGGER_NAME);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
String host = config.getString(LOG_RECEIVER_HOST);
if (host == null) {
host = GungnirUtils.getLocalAddress();
}
int port = config.getInteger(LOG_RECEIVER_PORT);
receiver = new ServerSocketReceiver();
receiver.setAddress(host);
receiver.setPort(port);
receiver.setContext(lc);
receiver.start();
statement.execute("SET " + LOG_RECEIVER_HOST + " = " + host);
statement.execute("SET " + LOG_RECEIVER_PORT + " = " + port);
}
private void processCommandString(String commandString) throws IOException,
GungnirServerException, GungnirClientException {
String accountId = getAccountId();
System.out.println("Gungnir version " + GUNGNIR_VERSION_STRING + " build at "
+ getBuildTimeString());
System.out.println("Welcome " + userName + " (Account ID: " + accountId + ")");
System.out.println(statement.execute(commandString));
}
private void processCommandFile(String commandFileName) throws IOException,
GungnirServerException, GungnirClientException {
File commandFile = new File(commandFileName);
if (commandFile.exists()) {
String accountId = getAccountId();
System.out.println("Gungnir version " + GUNGNIR_VERSION_STRING + " build at "
+ getBuildTimeString());
System.out.println("Welcome " + userName + " (Account ID: " + accountId + ")");
BufferedReader reader = null;
try {
reader = Files.newReader(commandFile, Charsets.UTF_8);
String line = null;
String command = null;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.endsWith(";")) {
sb.append(line.substring(0, line.length() - 1));
command = sb.toString().trim();
sb.setLength(0);
if (command.length() > 0) {
System.out.println(command);
System.out.println(statement.execute(command));
}
} else {
if (!line.isEmpty()) {
sb.append(line + "\n");
}
}
}
} catch (GungnirServerException e) {
System.err.println("FAILED: " + e.getMessage());
} catch (GungnirClientException e) {
System.err.println("FAILED: " + e.getMessage());
} finally {
if (reader != null) {
reader.close();
}
}
} else {
System.err.println("Command file doesn't exist");
}
}
private static final class ConsoleReaderAppender extends AppenderBase<ILoggingEvent> {
private ConsoleContext context;
private ObjectMapper mapper = new ObjectMapper();
private ConsoleReaderAppender(ConsoleContext context) {
this.context = context;
}
@Override
protected void append(ILoggingEvent eventObject) {
String marker = eventObject.getMarker().toString();
if (marker.startsWith(PATH_MARKER_NAME)) {
String msg = eventObject.getMessage();
try {
JsonNode pathNode = mapper.readTree(msg);
JsonNode tupleNode = pathNode.get("tuple");
StringBuilder sb = new StringBuilder().append(pathNode.get("source").asText())
.append(" -> ").append(pathNode.get("target").asText()).append(" ( ")
.append(tupleNode.get("tupleName").asText()).append(":")
.append(tupleNode.get("values")).append(" )");
context.addLogMessage("[" + marker.substring(PATH_MARKER_NAME.length() + 1) + "] "
+ sb.toString());
} catch (JsonProcessingException e) {
context.addLogMessage("[" + marker.substring(PATH_MARKER_NAME.length() + 1) + "] "
+ msg);
} catch (IOException e) {
context.addLogMessage("[" + marker.substring(PATH_MARKER_NAME.length() + 1) + "] "
+ msg);
}
} else {
context.addLogMessage("[" + eventObject.getMarker() + "] " + eventObject.getMessage());
}
}
}
private void processInteractive() throws IOException, GungnirServerException,
GungnirClientException {
ConsoleReader reader = new ConsoleReaderBuilder().prompt(PROMPT).history(HISTORY_FILE)
.completer(KEYWORDS).build();
String accountId = getAccountId();
reader.println("Gungnir version " + GUNGNIR_VERSION_STRING + " build at "
+ getBuildTimeString());
reader.println("Welcome " + userName + " (Account ID: " + accountId + ")");
ConsoleContext context = new ConsoleContext();
context.setConfig(config);
context.setStatement(statement);
context.setAccountId(accountId);
context.setReader(reader);
for (CommandHandler handler : commandHandlers) {
handler.prepare(context);
}
startLogReceiver(new ConsoleReaderAppender(context));
String line = null;
String command = null;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
if (!line.isEmpty()) {
reader.getHistory().removeLast();
}
line = line.trim();
if (line.trim().endsWith(";")) {
sb.append(line.substring(0, line.length() - 1));
command = sb.toString().trim();
sb.setLength(0);
if (command.length() > 0) {
if ("QUIT".equalsIgnoreCase(command) || "EXIT".equalsIgnoreCase(command)) {
break;
}
for (CommandHandler handler : commandHandlers) {
if (handler.isMatch(command)) {
handler.execute(command);
reader.setPrompt(PROMPT);
break;
}
}
}
} else {
if (!line.trim().isEmpty()) {
sb.append(line + "\n");
reader.setPrompt(INPUTTING_PROMPT);
}
}
}
}
public void execute(String[] args) throws IOException, GungnirServerException,
GungnirClientException {
Options options = new Options();
OptionBuilder.hasArg();
OptionBuilder.withArgName("username");
OptionBuilder.withDescription("Username to use when connecting to the gungnir server");
options.addOption(OptionBuilder.create('u'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("password");
OptionBuilder.withDescription("Password to use when connecting to the gungnir server");
options.addOption(OptionBuilder.create('p'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("quoted-command-string");
OptionBuilder.withDescription("Command from command line");
options.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("filename");
OptionBuilder.withDescription("Command from file");
options.addOption(OptionBuilder.create('f'));
OptionBuilder.withArgName("local-mode");
OptionBuilder.withDescription("Connect to cluster of local mode");
options.addOption(OptionBuilder.create('l'));
OptionBuilder.withArgName("help");
OptionBuilder.withDescription("Print help information");
options.addOption(OptionBuilder.create('h'));
String password = null;
String commandString = null;
String commandFileName = null;
boolean local = false;
try {
CommandLine commandLine = new GnuParser().parse(options, args);
if (commandLine.hasOption('h')) {
new HelpFormatter().printHelp("gungnir", options);
return;
}
if (commandLine.hasOption('u')) {
userName = commandLine.getOptionValue('u');
} else {
new HelpFormatter().printHelp("gungnir", options);
return;
}
if (commandLine.hasOption('p')) {
password = commandLine.getOptionValue('p');
} else {
new HelpFormatter().printHelp("gungnir", options);
return;
}
if (commandLine.hasOption('e')) {
commandString = commandLine.getOptionValue('e');
} else if (commandLine.hasOption('f')) {
commandFileName = commandLine.getOptionValue('f');
}
if (commandLine.hasOption('l')) {
local = true;
}
} catch (ParseException e) {
new HelpFormatter().printHelp("gungnir", options);
return;
}
System.out.println("Gungnir server connecting ...");
System.out.flush();
config = GungnirConfig.readGugnirConfig();
if (local) {
config.put(GungnirConfig.CLUSTER_MODE, GungnirConfig.LOCAL_CLUSTER);
}
Connection connection = null;
try {
connection = GungnirClient.getConnection(config, userName, password);
} catch (GungnirServerException e) {
System.err.println(e.getMessage());
return;
} catch (GungnirClientException e) {
System.err.println(e.getMessage());
return;
}
try {
statement = connection.createStatement();
if (commandString != null) {
try {
processCommandString(commandString);
} catch (GungnirServerException e) {
System.err.println(e.getMessage());
} catch (GungnirClientException e) {
System.err.println(e.getMessage());
}
} else if (commandFileName != null) {
processCommandFile(commandFileName);
} else {
processInteractive();
}
} finally {
if (connection != null) {
connection.close();
}
if (receiver != null) {
receiver.stop();
}
((LoggerContext) LoggerFactory.getILoggerFactory()).stop();
}
}
public static void main(String[] args) {
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.twitter");
logger.setUseParentHandlers(false);
logger.addHandler(new SLF4JHandler());
Map<String, Cookie> cookiesMap = Maps.newLinkedHashMap();
Console console = new Console();
console.addHandler(new PostCommandHandler(cookiesMap));
console.addHandler(new LogCommandHandler());
console.addHandler(new CookieCommandHandler(cookiesMap));
console.addHandler(new UploadCommandHandler());
StatementHandler statementHandler = new StatementHandler();
statementHandler.addHook(new UserCommandHook());
statementHandler.addHook(new SubmitTopologyHook());
statementHandler.addHook(new StopTopologyHook());
statementHandler.addHook(new StartTopologyHook());
console.addHandler(statementHandler);
try {
console.execute(args);
} catch (IOException e) {
LOG.error("An error occurred", e);
e.printStackTrace(System.err);
} catch (GungnirServerException e) {
LOG.error("An error occurred", e);
e.printStackTrace(System.err);
} catch (GungnirClientException e) {
LOG.error("An error occurred", e);
e.printStackTrace(System.err);
}
}
}