package org.sdnplatform.sync.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map.Entry;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.core.JsonParser;
import org.kohsuke.args4j.Option;
import org.sdnplatform.sync.IClosableIterator;
import org.sdnplatform.sync.IStoreClient;
import org.sdnplatform.sync.Versioned;
import org.sdnplatform.sync.ISyncService.Scope;
import org.sdnplatform.sync.error.UnknownStoreException;
/**
* A command-line tool for interacting with a remote sync service
* @author readams
*/
public class SyncClient extends SyncClientBase {
/**
* Shell commands
*/
protected HashMap<String, ShellCommand> commands;
/**
* Command-line settings
*/
protected SyncClientSettings syncClientSettings;
/**
* Store client for currently-active store
*/
protected IStoreClient<JsonNode, JsonNode> storeClient;
protected static class SyncClientSettings
extends SyncClientBaseSettings {
@Option(name="--store", aliases="-s",
usage="Store name to access")
protected String storeName;
@Option(name="--command", aliases="-c",
usage="If set, execute a command")
protected String command;
@Option(name="--debug",
usage="Show full error information")
protected boolean debug;
}
public SyncClient(SyncClientSettings syncClientSettings) {
super(syncClientSettings);
this.syncClientSettings = syncClientSettings;
commands = new HashMap<String, ShellCommand>();
commands.put("quit", new QuitCommand());
commands.put("help", new HelpCommand());
commands.put("put", new PutCommand());
commands.put("delete", new DeleteCommand());
commands.put("get", new GetCommand());
commands.put("getfull", new GetFullCommand());
commands.put("entries", new EntriesCommand());
commands.put("store", new StoreCommand());
commands.put("register", new RegisterCommand());
}
// **************
// SyncClientBase
// **************
@Override
protected void connect() throws Exception {
super.connect();
if (syncClientSettings.storeName != null)
getStoreClient();
}
// **************
// Shell commands
// **************
/**
* Quit command
* @author readams
*
*/
protected static class QuitCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line) {
return true;
}
@Override
public String syntaxString() {
return "quit";
}
}
/**
* Help command
* @author readams
*
*/
protected class HelpCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line) {
out.println("Commands: ");
for (Entry<String, ShellCommand> entry : commands.entrySet()) {
out.println(entry.getValue().syntaxString());
}
return false;
}
@Override
public String syntaxString() {
return "help";
}
}
/**
* Get command
* @author readams
*
*/
protected class GetCommand extends ShellCommand {
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
@Override
public boolean execute(String[] tokens, String line) throws Exception {
if (tokens.length < 2) {
err.println("Usage: " + syntaxString());
return false;
}
if (!checkStoreSettings()) return false;
StringReader sr = new StringReader(line);
while (sr.read() != ' ');
JsonParser jp = mjf.createJsonParser(sr);
JsonNode keyNode = validateJson(jp);
if (keyNode == null) return false;
out.println("Getting Key:");
out.println(writer.writeValueAsString(keyNode));
out.println("");
Versioned<JsonNode> value = storeClient.get(keyNode);
display(value);
return false;
}
protected void display(Versioned<JsonNode> value) throws Exception {
if (value.getValue() == null) {
out.println("Not found");
} else {
out.println("Value:");
out.println(writer.writeValueAsString(value.getValue()));
}
}
@Override
public String syntaxString() {
return "get [key]";
}
}
protected class GetFullCommand extends GetCommand {
@Override
protected void display(Versioned<JsonNode> value) throws Exception {
if (value.getValue() == null) {
out.println("Not found");
} else {
out.println("Version:");
out.println(value.getVersion());
out.println("Value:");
out.println(writer.writeValueAsString(value.getValue()));
}
}
@Override
public String syntaxString() {
return "getfull [key]";
}
}
protected class EntriesCommand extends ShellCommand {
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
@Override
public boolean execute(String[] tokens, String line) throws Exception {
if (!checkStoreSettings()) return false;
IClosableIterator<Entry<JsonNode, Versioned<JsonNode>>> iter =
storeClient.entries();
try {
while (iter.hasNext()) {
Entry<JsonNode, Versioned<JsonNode>> e = iter.next();
display(e.getKey(), e.getValue());
}
} finally {
iter.close();
}
return false;
}
protected void display(JsonNode keyNode,
Versioned<JsonNode> value) throws Exception {
if (value.getValue() == null) return;
ObjectNode n = mapper.createObjectNode();
n.put("key", keyNode);
n.put("value", value.getValue());
out.println(writer.writeValueAsString(n));
}
@Override
public String syntaxString() {
return "entries";
}
}
/**
* Put command
* @author readams
*
*/
protected class PutCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line) throws Exception {
if (tokens.length < 3) {
err.println("Usage: " + syntaxString());
return false;
}
if (!checkStoreSettings()) return false;
StringReader sr = new StringReader(line);
while (sr.read() != ' ');
JsonParser jp = mjf.createJsonParser(sr);
JsonNode keyNode = validateJson(jp);
if (keyNode == null) return false;
JsonNode valueNode = validateJson(jp);
if (valueNode == null) return false;
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
out.println("Putting Key:");
out.println(writer.writeValueAsString(keyNode));
out.println("\nValue:");
out.println(writer.writeValueAsString(valueNode));
out.flush();
storeClient.put(keyNode, valueNode);
out.println("Success");
return false;
}
@Override
public String syntaxString() {
return "put [key] [value]";
}
}
/**
* Delete command
* @author readams
*
*/
protected class DeleteCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line) throws Exception {
if (tokens.length < 2) {
err.println("Usage: " + syntaxString());
return false;
}
if (!checkStoreSettings()) return false;
StringReader sr = new StringReader(line);
while (sr.read() != ' ');
JsonParser jp = mjf.createJsonParser(sr);
JsonNode keyNode = validateJson(jp);
if (keyNode == null) return false;
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
out.println("Deleting Key:");
out.println(writer.writeValueAsString(keyNode));
out.println("");
storeClient.delete(keyNode);
out.println("Success");
return false;
}
@Override
public String syntaxString() {
return "delete [key]";
}
}
/**
* Choose the store
* @author readams
*/
protected class StoreCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line)
throws Exception {
if (tokens.length < 2) {
err.println("Usage: " + syntaxString());
return false;
}
syncClientSettings.storeName = tokens[1];
getStoreClient();
return false;
}
@Override
public String syntaxString() {
return "store [storeName]";
}
}
/**
* Register a new store
* @author readams
*/
protected class RegisterCommand extends ShellCommand {
@Override
public boolean execute(String[] tokens, String line)
throws Exception {
if (tokens.length < 3) {
err.println("Usage: " + syntaxString());
return false;
}
Scope scope = Scope.LOCAL;
if ("global".equals(tokens[2]))
scope = Scope.GLOBAL;
syncClientSettings.storeName = tokens[1];
syncManager.registerStore(syncClientSettings.storeName, scope);
getStoreClient();
return false;
}
@Override
public String syntaxString() {
return "register [storeName] [local|global]";
}
}
// *************
// Local methods
// *************
protected boolean checkStoreSettings() {
if (syncClientSettings.storeName == null) {
err.println("No store selected. Select using \"store\" command.");
return false;
}
return true;
}
protected void getStoreClient() throws UnknownStoreException {
storeClient = syncManager.getStoreClient(syncClientSettings.storeName,
JsonNode.class,
JsonNode.class);
}
protected boolean executeCommandLine(String line) {
String[] tokens = line.split("\\s+");
if (tokens.length > 0) {
ShellCommand command = commands.get(tokens[0]);
if (command != null) {
try {
if (command.execute(tokens, line))
return true;
} catch (Exception e) {
err.println("Failed to execute command: " +
line);
if (syncClientSettings.debug)
e.printStackTrace(err);
else
err.println(e.getClass().getSimpleName() +
": " + e.getMessage());
}
} else {
err.println("Unrecognized command: \"" +
tokens[0] + "\"");
}
}
return false;
}
protected void startShell(SyncClientBaseSettings settings)
throws InterruptedException {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String line;
try {
while (true) {
err.print("> ");
line = br.readLine();
if (line == null) break;
if (executeCommandLine(line)) break;
}
} catch (IOException e) {
err.println("Could not read input: " + e.getMessage());
}
}
// ****
// Main
// ****
public static void main(String[] args) throws Exception {
SyncClientSettings settings = new SyncClientSettings();
settings.init(args);
SyncClient client = new SyncClient(settings);
try {
client.connect();
if (settings.command == null) {
client.startShell(settings);
} else {
client.executeCommandLine(settings.command);
}
} finally {
client.cleanup();
}
}
}