/*******************************************************************************
* Copyright (c) 2008 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cambridge Semantics Incorporated
*******************************************************************************/
package org.openanzo.client.cli;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.lang.ArrayUtils;
import org.openanzo.client.AnzoTrustManager;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.rdf.Constants;
import org.openanzo.rdf.RDFFormat;
import org.openanzo.rdf.URI;
import org.openanzo.services.UpdateServerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Command line interface for anzo.java.
*
* @author Joe Betz <jpbetz@cambridgesemantics.com>
*
*/
public class CommandLineInterface {
private static final Logger log = LoggerFactory.getLogger(CommandLineInterface.class);
static final URI CLIConfigURI = Constants.valueFactory.createURI("http://openanzo.org/cli/config");
// some defaults
static final String DEFAULT_HOST = "localhost";
static final String DEFAULT_PORT = "61616";
static final String DEFAULT_SSL_PORT = "61617";
// some defaults
static final boolean DEFAULT_USE_SSL = false;
static final String DEFAULT_RDF_FORMAT = "trig";
// global options
private static final Option HOST_OPTION = new Option("h", "host", true, "anzo server hostname");
private static final Option PORT_OPTION = new Option("p", "port", true, "anzo server port");
private static final Option USER_OPTION = new Option("u", "user", true, "username to connect with");
private static final Option PASSWORD_OPTION = new Option("w", "password", true, "user's password");
private static final Option SETTINGS_OPTION = new Option("z", "settings", true, "override the default settings file location");
private static final Option TIMEOUT_OPTION = new Option("t", "timeout", true, "override the default 30 second timeout for operations");
public static DefaultConsole DEFAULT_CONSOLE = new DefaultConsole();
static {
HOST_OPTION.setArgName("hostname");
PORT_OPTION.setArgName("int");
USER_OPTION.setArgName("string");
PASSWORD_OPTION.setArgName("string");
SETTINGS_OPTION.setArgName("file");
TIMEOUT_OPTION.setArgName("timeout");
}
private static final Option NO_PREFIXES_OPTION = new Option("x", "exclude-prefixes", false, "Do not use prefixes defined in user settings to expand options, arguments, or to write RDF.");
private static final Option USE_SSL_OPTION = new Option("ssl", "use-ssl", false, "Use SSL for connection.");
private static final Option SHOW_TRACE_OPTION = new Option("trace", "show-trace", false, "Show stack trace for errors.");
private static final Option PAUSE_EXIT_OPTION = new Option("pause", "pause-exit", false, "Wait for a user key entry before an abnormal exit.");
private static final Option TRUST_OPTION = new Option("trust", "trust-all", false, "Trust all certificates including invalid ones");
private static final Option BEEP_OPTION = new Option("beep", "beep", false, "beep when command is completed");
/**
* Add default options to options
*
* @param options
* options to augment
*/
public static void appendGlobalOptions(Options options) {
options.addOption(HOST_OPTION);
options.addOption(PORT_OPTION);
options.addOption(USER_OPTION);
options.addOption(PASSWORD_OPTION);
options.addOption(SETTINGS_OPTION);
options.addOption(TIMEOUT_OPTION);
options.addOption(NO_PREFIXES_OPTION);
options.addOption(USE_SSL_OPTION);
options.addOption(SHOW_TRACE_OPTION);
options.addOption(PAUSE_EXIT_OPTION);
options.addOption(TRUST_OPTION);
options.addOption(BEEP_OPTION);
}
public static Options getGlobalOptions() {
Options options = new Options();
appendGlobalOptions(options);
return options;
}
// sub commands
static final List<SubCommand> subcommands = new ArrayList<SubCommand>();
static {
// retrieval and storage
subcommands.add(new GetCommand());
subcommands.add(new FindCommand());
subcommands.add(new CreateCommand());
subcommands.add(new UpdateCommand());
subcommands.add(new ImportCommand());
subcommands.add(new ReplaceCommand());
subcommands.add(new RemoveCommand());
// reset and dump (backup and restore?)
subcommands.add(new ResetCommand());
// dump: glitter can query for all data, need more?
// query
subcommands.add(new QueryCommand());
// services and notification
subcommands.add(new WatchCommand());
subcommands.add(new CallCommand());
// simple rdf manipulation
subcommands.add(new ExpandCommand());
subcommands.add(new CollapseCommand());
subcommands.add(new ConvertCommand());
subcommands.add(new UnionCommand());
//subcommands.add(new DiffCommand());
// logging and analysis
subcommands.add(new PlayCommand());
subcommands.add(new AnalyzeCommand());
// jastor
subcommands.add(new GenCommand());
}
/**
* Run the Anzo command line interface
*
* @param arguments
* @throws Exception
*/
public static void main(String[] arguments) throws Exception {
if (arguments.length < 1) {
String header = generateVersionHeader();
System.out.println(header);
System.out.println("Type anzo help for usage");
System.exit(1);
}
int result = 0;
result = processCommand(null, true, arguments);
System.exit(result);
}
/**
*
* @return
*/
public static String generateVersionHeader() {
StringBuilder str = new StringBuilder("Anzo Command Line Client. \nCopyright (c) 2009 Cambridge Semantics Inc and others.\nAll rights reserved.");
String version;
try {
version = determineVersion();
} catch (URISyntaxException e) {
log.debug("Error obtaining version", e);
version = null;
} catch (IOException e) {
log.debug("Error obtaining version", e);
version = null;
}
str.append("\nVersion: ");
str.append((version == null) ? "Unknown" : version);
return str.toString();
}
private static String determineVersion() throws URISyntaxException, IOException {
String version = null;
if (CommandLineInterface.class.getProtectionDomain() != null && CommandLineInterface.class.getProtectionDomain().getCodeSource() != null && CommandLineInterface.class.getProtectionDomain().getCodeSource().getLocation() != null) {
ProtectionDomain domain = CommandLineInterface.class.getProtectionDomain();
CodeSource source = domain.getCodeSource();
URL location = source.getLocation();
if (location != null) {
File file = new File(location.toURI());
if (file.exists()) {
JarFile jar = new JarFile(file);
version = jar.getManifest().getMainAttributes().getValue("Bundle-Version");
if (version == null) {
version = jar.getManifest().getMainAttributes().getValue("Implementation-Build");
}
}
}
}
if (version == null) {
version = CommandLineInterface.class.getPackage().getImplementationVersion();
}
return version;
}
static CommandContext createContext(IConsole consoleWriter, CommandLine cl, Options options, String... arguments) throws AnzoException {
String hostname = cl.getOptionValue(HOST_OPTION.getOpt());
String port = cl.getOptionValue(PORT_OPTION.getOpt());
String username = cl.getOptionValue(USER_OPTION.getOpt());
String password = cl.getOptionValue(PASSWORD_OPTION.getOpt());
boolean noPrefixes = cl.hasOption(NO_PREFIXES_OPTION.getOpt());
Boolean useSsl = cl.hasOption(USE_SSL_OPTION.getOpt()) ? Boolean.TRUE : null;
String settingsPath = cl.getOptionValue(SETTINGS_OPTION.getOpt());
String timeout = cl.getOptionValue(TIMEOUT_OPTION.getOpt());
boolean showTrace = cl.hasOption(SHOW_TRACE_OPTION.getOpt());
return CommandContext.create(settingsPath, hostname, port, useSsl, username, password, timeout, noPrefixes, showTrace, consoleWriter);
}
static int processCommand(CommandContext context, boolean exitOnException, String... arguments) {
IConsole cw = context != null ? context.getConsoleWriter() : null;
if (cw == null) {
cw = DEFAULT_CONSOLE;
}
boolean beep = false;
String subcommand = arguments[0];
try {
for (SubCommand sc : subcommands) {
if (sc.getName().equals(subcommand)) {
boolean showStackTrace = false;
boolean pauseOnExit = false;
try {
Options options = sc.getOptions();
appendGlobalOptions(options);
CommandLineParser parser = new PosixParser();
String[] subcommandArgs = (String[]) ArrayUtils.subarray(arguments, 1, arguments.length);
CommandLine cl = parser.parse(options, subcommandArgs);
pauseOnExit = cl.hasOption(PAUSE_EXIT_OPTION.getOpt());
if (context == null) {
context = createContext(DEFAULT_CONSOLE, cl, options, arguments);
}
// set up the trust manager
boolean trustAll = cl.hasOption(TRUST_OPTION.getOpt());
showStackTrace = cl.hasOption(SHOW_TRACE_OPTION.getOpt());
beep = cl.hasOption(BEEP_OPTION.getOpt());
TrustManager[] myTMs = new TrustManager[] { new AnzoTrustManager(trustAll, showStackTrace) };
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, myTMs, new java.security.SecureRandom());
SSLContext.setDefault(ctx);
return sc.invoke(cl, context, context.getAnzoClient());
} catch (ParseException e) {
cw.writeError("error: ");
cw.printException(e, showStackTrace);
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("anzo", sc.getOptions());
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (InvalidArgumentException e) {
cw.printException(e, showStackTrace);
sc.printHelp(cw);
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (CommandException ae) {
if (ae.getCause() != null && ae.getCause() instanceof AnzoException) {
if (((AnzoException) ae.getCause()).getErrorCode() == ExceptionConstants.COMBUS.JMS_CONNECT_FAILED) {
cw.writeError("Connection failed.");
}
}
cw.printException(ae, showStackTrace);
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (UpdateServerException e) {
cw.writeError(e.getUserMessage());
for (List<AnzoException> exceptions : e.getErrors()) {
if (exceptions != null) {
for (AnzoException ae : exceptions) {
if (ae.getErrorCode() == ExceptionConstants.COMBUS.JMS_CONNECT_FAILED)
cw.writeError("Connection failed.");
cw.printException(ae, showStackTrace);
}
}
}
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (AnzoException e) {
if (e.getErrorCode() == ExceptionConstants.COMBUS.JMS_CONNECT_FAILED)
cw.writeError("Connection failed.");
cw.printException(e, showStackTrace);
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (AnzoRuntimeException e) {
cw.printException(e, showStackTrace);
sc.printHelp(cw);
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (NoSuchAlgorithmException e) {
cw.writeError("Error with ssl:");
cw.printException(e, showStackTrace);
if (exitOnException)
exitOnError(1, pauseOnExit);
} catch (KeyManagementException e) {
cw.writeError("Error with ssl:");
cw.printException(e, showStackTrace);
if (exitOnException)
exitOnError(1, pauseOnExit);
}
}
}
if (subcommand.startsWith("-")) {
cw.writeError("Option not allowed before subcommand: " + subcommand);
} else if (subcommand.equals("help")) {
if (arguments.length < 2) {
printHelp(cw);
} else {
String helpSubcommand = arguments[1];
for (SubCommand sc : subcommands) {
if (sc.getName().equals(helpSubcommand)) {
sc.printHelp(cw);
return 0;
}
}
cw.writeError("unrecognized subcommand: " + helpSubcommand);
}
} else {
cw.writeError("unrecognized subcommand: " + subcommand);
}
} finally {
if (cw != null && beep) {
cw.beep();
}
}
return 0;
}
/**
* If the PAUSE flag is set then the user must press enter before the program will exit
*
* @param errCode
* The errCode is the exit code for the program, 0 is normal and 1 is abnormal
* @param pauseOnExit
* If this is true then the user must press enter before the program will exit
*/
private static void exitOnError(int errCode, boolean pauseOnExit) {
if (pauseOnExit) {
try {
System.err.println("Press enter to exit");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
in.read();
in.close();
} catch (IOException e) {
System.err.println(e.getMessage());
System.exit(errCode);
}
}
System.exit(errCode);
}
/*
* Returns true if there is a javax.net.ssl.SSLException in the cause chain of the AnzoException argument.
*
* @param ae
* @return
private static boolean isSSLFailure(AnzoException ae) {
Throwable exception = ae;
while (exception != null) {
if (exception instanceof javax.net.ssl.SSLException)
return true;
if (exception == exception.getCause()) // prevents a possible infinite loop
return false;
exception = exception.getCause();
}
return false;
}*/
static void printHelp(IConsole consoleWriter) {
HelpFormatter formatter = new HelpFormatter();
formatter.setOptPrefix("");
String syntax = "anzo <subcommand> [options] [args]";
String header = generateVersionHeader();
header += "\n\nType 'anzo help <subcommand>' for help with a specific subcommand.";
header += "\n\nAvailable subcommands:";
String footer = "URI arguments to commands may either be fully qualified URIs (\"http://...\") or prefixed URIs (\"dc:title\"). ";
footer += "The prefix mapping is defined in the users settings file.";
footer += "\n\nUser settings are loaded from a user's \"~/.anzo/settings.trig\" file.";
footer += "\n\nRDF format options are: " + CommandLineInterface.getRDFFormatOptionsString();
footer += "See documentation for details.";
Options options = new Options();
for (SubCommand command : subcommands) {
options.addOption(new Option(command.getName(), ""));
}
formatter.printHelp(syntax, header, options, footer);
}
static String getRDFFormats() {
StringBuilder builder = new StringBuilder();
for (RDFFormat format : RDFFormat.values()) {
builder.append("'" + Arrays.toString(format.getFileExtensions()) + "' (" + format.name() + ")");
builder.append(" ");
}
return builder.toString();
}
static String getRDFFormatOptionsString() {
return getRDFFormats() + ". Filename arguments default to the file format matching their filename extension. STDIN and STDOUT default to '" + DEFAULT_RDF_FORMAT + "'.";
}
}