/*******************************************************************************
* Copyright (c) 2008 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse 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.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;
import org.openanzo.client.AnzoClient;
import org.openanzo.client.AnzoClientConfigurationFactory;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.glitter.util.CURIE;
import org.openanzo.ontologies.system.Credentials;
import org.openanzo.ontologies.system.NetworkConnection;
import org.openanzo.ontologies.system.SystemFactory;
import org.openanzo.rdf.IDataset;
import org.openanzo.rdf.INamedGraph;
import org.openanzo.rdf.MemURI;
import org.openanzo.rdf.NamedGraph;
import org.openanzo.rdf.RDFFormat;
import org.openanzo.rdf.Resource;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.TypedLiteral;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.adapter.RioToAnzoWriterAdapter;
import org.openanzo.rdf.utils.ReadWriteUtils;
import org.openanzo.rdf.utils.ReadWriteUtils.StatementsAndPrefixes;
import org.openanzo.services.ServicesProperties;
/**
* Loads context for an anzo command line invocation.
*
* Loads user settings if present and applies hostname, port and user, password options.
*
* Also keeps track of prefixes found in user setting file if they have one and if these prefixes should be applied to inputs and outputs of the command.
*
* @author Joe Betz <jpbetz@cambridgesemantics.com>
*
*/
public class CommandContext {
final AnzoClient client;
final INamedGraph settings;
final Map<String, String> prefixes;
final boolean noPrefixes;
final String user;
final String password;
final String host;
final String port;
final boolean useSsl;
final String timeout;
boolean showTrace;
final boolean requireLogin;
final IConsole consoleWriter;
/**
* Sets things up.
*
* Loads user settings if the user has them.
*
* If non-null, overrides the hostname, port, user and password fields.
*
*/
static CommandContext create(String settingsPath, String hostname, String port, Boolean useSsl, String user, String password, String timeout, boolean noPrefixes, boolean showTrace, IConsole consoleWriter) throws AnzoException {
if (consoleWriter == null) {
consoleWriter = CommandLineInterface.DEFAULT_CONSOLE;
}
INamedGraph settingsGraph = new NamedGraph(MemURI.create("http://openanzo.org/reserved/user-config"));
Map<String, String> prefixes = new HashMap<String, String>();
boolean hasSettings = true;
File clientFile = null;
if (settingsPath != null) {
File file = new File(settingsPath);
if (!file.exists()) {
throw new CommandException("requested user settings file does not exist:" + file.getAbsolutePath());
}
clientFile = file;
} else {
String userHome = System.getProperty("user.home");
File anzoDirectory = new File(new File(userHome), ".anzo");
if (!anzoDirectory.isDirectory()) {
hasSettings = false;
} else {
clientFile = new File(anzoDirectory, "settings.trig");
if (!clientFile.isFile()) {
hasSettings = false;
}
}
}
if (hasSettings && clientFile != null) {
try {
StatementsAndPrefixes sAndP = ReadWriteUtils.loadStatementsAndPrefixes(ReadWriteUtils.createSmartFileReader(clientFile), RDFFormat.forFileName(clientFile.getName()), "", settingsGraph.getNamedGraphUri());
settingsGraph.add(sAndP.getStatements());
prefixes.putAll(sAndP.getPrefixes());
stripCommonSchemesFromPrefix(consoleWriter, prefixes);
} catch (Exception ae) {
throw new CommandException(ae, "Error loading configuration file");
}
}
Credentials credentials = SystemFactory.getCredentials(CommandLineInterface.CLIConfigURI, settingsGraph);
if (credentials != null) {
if (user == null) {
user = credentials.getUser();
}
if (password == null) {
password = credentials.getPassword();
}
}
boolean reqLogin = user == null || password == null;
if (reqLogin) {
consoleWriter.writeOutput(CommandLineInterface.generateVersionHeader() + "\n");
try {
if (user == null) {
user = consoleWriter.readString("Username required. Please enter a username: ", false);
}
if (password == null) {
password = consoleWriter.readString("Password required. Please enter the password for user '" + user + "': ", true);
}
} catch (IOException ie) {
throw new CommandException(ie, "Unable to read username or password from the command line");
}
}
AnzoClient client = null;
NetworkConnection connection = SystemFactory.getNetworkConnection(CommandLineInterface.CLIConfigURI, settingsGraph);
if (connection != null) {
if (hostname == null) {
hostname = connection.getHost();
}
if (port == null && connection.getPort() != null) {
port = connection.getPort().toString();
}
if (useSsl == null && connection.getUseSsl() != null) {
useSsl = connection.getUseSsl();
}
Long time = connection.getTimeout();
if (time != null && timeout == null) {
timeout = time.toString();
}
String keystoreFile = connection.getKeystoreFile();
if (keystoreFile != null) {
System.setProperty("javax.net.ssl.keyStore", preprocessString(keystoreFile));
}
String keystoreType = connection.getKeystoreType();
if (keystoreType != null) {
System.setProperty("javax.net.ssl.keyStoreType", keystoreType);
}
String keystorePassword = connection.getKeystorePassword();
if (keystorePassword != null) {
System.setProperty("javax.net.ssl.keyStorePassword", keystorePassword);
}
String truststoreFile = connection.getTruststoreFile();
if (truststoreFile != null) {
System.setProperty("javax.net.ssl.trustStore", preprocessString(truststoreFile));
}
String truststoreType = connection.getTruststoreType();
if (truststoreType != null) {
System.setProperty("javax.net.ssl.trustStoreType", truststoreType);
}
String truststorePassword = connection.getTruststorePassword();
if (truststorePassword != null) {
System.setProperty("javax.net.ssl.trustStorePassword", truststorePassword);
}
}
hostname = hostname == null ? CommandLineInterface.DEFAULT_HOST : hostname;
useSsl = useSsl == null ? CommandLineInterface.DEFAULT_USE_SSL : useSsl;
port = port == null ? (useSsl ? CommandLineInterface.DEFAULT_SSL_PORT : CommandLineInterface.DEFAULT_PORT) : port;
Properties conf = AnzoClientConfigurationFactory.createJMSConfiguration(user, password, hostname, Integer.valueOf(port), useSsl);
if (timeout == null) {
timeout = "0";
}
ServicesProperties.setTimeout(conf, Integer.parseInt(timeout) * 1000);
//File store = new File(anzoDirectory, ".store");
//AnzoClientConfigurationFactory.configurePersistedClient(conf,"localstore", "HSQL","jdbc:hsqldb:file:" + store,"sa","");
client = new AnzoClient(conf);
CommandContext ctx = new CommandContext(client, settingsGraph, prefixes, noPrefixes, user, password, hostname, port, useSsl, timeout, showTrace, reqLogin, consoleWriter);
return ctx;
}
/** Convert system properties to values */
public static String preprocessString(String value) {
if (value == null)
return null;
String result = value;
while ((result).contains("${")) {
int index = (result).indexOf("${");
String val = (result).substring(0, index);
int endIndex = (result).indexOf("}", index);
if (endIndex < 0)
endIndex = (result).length();
String replacement = (result).substring(index + 2, endIndex);
if (System.getProperty(replacement) != null) {
val = val.concat(System.getProperty(replacement));
} else if (System.getenv(replacement) != null) {
val = val.concat(System.getenv(replacement));
}
if (endIndex < (result).length()) {
val = val.concat((result).substring(endIndex + 1));
}
result = val;
}
return result;
}
private CommandContext(AnzoClient client, INamedGraph settings, Map<String, String> prefixes, boolean noPrefixes, String user, String password, String host, String port, boolean useSsl, String timeout, boolean showTrace, boolean requireLogin, IConsole consoleWriter) {
this.client = client;
this.settings = settings;
this.prefixes = prefixes;
this.noPrefixes = noPrefixes;
this.user = user;
this.password = password;
this.host = host;
this.port = port;
this.useSsl = useSsl;
this.timeout = timeout;
this.showTrace = showTrace;
this.requireLogin = requireLogin;
this.consoleWriter = consoleWriter;
}
/**
* @return the consoleWriter
*/
public IConsole getConsoleWriter() {
return consoleWriter;
}
/**
* Gets the user provided settings.
*/
INamedGraph getSettings() {
return settings;
}
/**
* Gets the prefix map defined in the user setting.
*/
Map<String, String> getPrefixes() {
return prefixes;
}
/**
* Gets the anzo client configured for this command line execution.
*/
AnzoClient getAnzoClient() {
return client;
}
String getUser() {
return user;
}
String getPassword() {
return password;
}
String getHost() {
return host;
}
int getPort() {
return Integer.parseInt(port);
}
boolean getUseSsl() {
return useSsl;
}
String getTimeout() {
return timeout;
}
boolean getExcludePrefixes() {
return noPrefixes;
}
boolean getRequireLogin() {
return requireLogin;
}
/**
* @return the showTrace
*/
public boolean getShowTrace() {
return showTrace;
}
/**
* Creates a CURIE from the provided string, which may be either a CURIE, or a URI that is map-able to a CURIE using the user provided prefix map.
*/
CURIE getCURIE(String in) {
if (hasCommonScheme(in)) {
for (Map.Entry<String, String> entry : prefixes.entrySet()) {
String prefix = entry.getValue();
if (in.startsWith(prefix)) {
return new CURIE(entry.getKey() + ":" + in.substring(prefix.length()));
}
}
return null;
} else {
return new CURIE(in);
}
}
/**
* Checks that the input string is either a CURIE, or a URI that is map-able to a CURIE using the user provided prefix map.
*/
boolean isCURIE(String in) {
boolean ret = false;
if (in.indexOf(':') != -1) {
if (hasCommonScheme(in)) {
// It's likely a URI so check that it's map-able to a CURIE using the user provided prefix map.
for (Map.Entry<String, String> entry : prefixes.entrySet()) {
String prefix = entry.getValue();
if (in.startsWith(prefix)) {
ret = true;
}
}
} else {
// It's a regular CURIE.
ret = true;
}
}
return ret;
}
static final Set<String> commonUriSchemeSet = new HashSet<String>();
static int longestCommonSchemeLength = -1;
static {
commonUriSchemeSet.add("jdbc");
commonUriSchemeSet.add("data");
commonUriSchemeSet.add("file");
commonUriSchemeSet.add("ftp");
commonUriSchemeSet.add("gopher");
commonUriSchemeSet.add("http");
commonUriSchemeSet.add("https");
commonUriSchemeSet.add("im");
commonUriSchemeSet.add("imap");
commonUriSchemeSet.add("info");
commonUriSchemeSet.add("ldap");
commonUriSchemeSet.add("mailto");
commonUriSchemeSet.add("mid");
commonUriSchemeSet.add("news");
commonUriSchemeSet.add("nntp");
commonUriSchemeSet.add("rtsp");
commonUriSchemeSet.add("snmp");
commonUriSchemeSet.add("tag");
commonUriSchemeSet.add("tel");
commonUriSchemeSet.add("telnet");
commonUriSchemeSet.add("xmpp");
commonUriSchemeSet.add("tcp");
commonUriSchemeSet.add("lsid");
commonUriSchemeSet.add("urn");
commonUriSchemeSet.add("feed");
commonUriSchemeSet.add("ldaps");
commonUriSchemeSet.add("skype");
commonUriSchemeSet.add("userdata");
for (String prefix : commonUriSchemeSet) {
int len = prefix.length();
if (len > longestCommonSchemeLength) {
longestCommonSchemeLength = len;
}
}
}
static String extractPrefix(String in) {
return extractPrefix(in, -1);
}
static String extractPrefix(String in, int maxLength) {
String prefix = null;
int colonIndex = -1;
int len = maxLength == -1 ? in.length() : Math.min(in.length(), maxLength);
for (int i = 0; i < len; i++) {
if (in.charAt(i) == ':') {
colonIndex = i;
break;
}
}
if (colonIndex != -1) {
prefix = in.substring(0, colonIndex);
}
return prefix;
}
static boolean hasCommonScheme(String in) {
boolean ret = false;
String prefix = extractPrefix(in, longestCommonSchemeLength + 1);
if (prefix != null) {
ret = commonUriSchemeSet.contains(prefix);
}
return ret;
}
/**
* Creates a URI from the provided string, which may be either a URI, or a CURIE that is map-able to a URI using the user provided prefix map.
*/
URI getURI(String in) throws URISyntaxException {
if (hasCommonScheme(in)) {
return MemURI.create(new java.net.URI(in));
} else {
String prefix = extractPrefix(in);
if (prefix != null && prefixes.containsKey(prefix)) {
return MemURI.create(prefixes.get(prefix) + in.substring(prefix.length() + 1));
}
}
throw new URINotCURIEOrKnownScheme(in, "String does not have a recognized scheme for an absolute URI and does not have a registered prefix. So the URI cannot be parsed");
}
/**
* Checks that the input string is either a URI, or a CURIE that is map-able to a URI using the user provided prefix map.
*/
boolean isURI(String in) {
if (hasCommonScheme(in)) {
try {
MemURI.create(new java.net.URI(in));
return true;
} catch (Exception e) {
writeError("Invalid URI:(" + in + ")");
consoleWriter.printException(e, showTrace);
return false;
}
} else {
for (Map.Entry<String, String> entry : prefixes.entrySet()) {
String prefix = entry.getKey();
if (in.startsWith(prefix + ":")) {
return true;
}
}
}
return false;
}
/**
* Writes the dataset to STDOUT in the format provided. If usePrefixes is set, included either all the prefixes, or if minimizePrefixes is false, just
* include the prefixes required by the RDF being output.
*/
void outputRdf(IDataset dataset, RDFFormat format) throws AnzoException {
this.outputRdf(dataset, format, new OutputStreamWriter(System.out));
}
/**
* Writes the dataset to the provided output stream in the format provided. If usePrefixes is set, included either all the prefixes, or if minimizePrefixes
* is false, just include the prefixes required by the RDF being output.
*/
void outputRdf(IDataset dataset, RDFFormat format, Writer writer) throws AnzoException {
outputRdf(dataset.getStatements(), format, writer, Collections.<String, String> emptyMap());
}
/**
* Writes the statements to the provided output stream in the format provided. If usePrefixes is set, included either all the prefixes, or if
* minimizePrefixes is false, just include the prefixes required by the RDF being output.
*/
void outputRdf(Collection<Statement> itr, RDFFormat format, Writer writer) throws AnzoException {
outputRdf(itr, format, writer, Collections.<String, String> emptyMap());
}
/**
* Writes the statements to the provided output stream in the format provided. If usePrefixes is set, included either all the prefixes, or if
* minimizePrefixes is false, just include the prefixes required by the RDF being output.
*/
void outputRdf(Collection<Statement> itr, RDFFormat format, Writer writer, Map<String, String> inputPrefixes) throws AnzoException {
RioToAnzoWriterAdapter w = new RioToAnzoWriterAdapter(writer, format);
w.startRDF();
if (format.supportsNamespaces()) {
Map<String, String> tempPrefixes = combinePrefixes(this.prefixes, inputPrefixes);
tempPrefixes = findMinimumPrefixMap(tempPrefixes, itr);
if (!noPrefixes) {
for (Map.Entry<String, String> prefix : tempPrefixes.entrySet()) {
w.handleNamespace(prefix.getKey(), prefix.getValue());
}
}
}
for (Statement stmt : StatementComparator.sort(itr)) {
try {
if (stmt.getNamedGraphUri() != null && stmt.getNamedGraphUri().equals(defaultNamedGraph)) {
w.handleStatement(new Statement(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(), null));
} else {
w.handleStatement(stmt);
}
} catch (AnzoException e) {
throw new AnzoRuntimeException(e);
}
}
w.endRDF();
}
Map<String, String> combinePrefixes(Map<String, String> userPrefixes, Map<String, String> inputPrefixes) {
Map<String, String> results = new HashMap<String, String>(userPrefixes);
for (Map.Entry<String, String> prefixEntry : inputPrefixes.entrySet()) {
if (!userPrefixes.containsValue(prefixEntry.getValue()) && !userPrefixes.containsKey(prefixEntry.getKey())) {
results.put(prefixEntry.getKey(), prefixEntry.getValue());
}
}
return results;
}
static final URI defaultNamedGraph = MemURI.create("http://cambridgesemantics.com/reserved/placeholder");
void populateDataset(IDataset dataset, Collection<Statement> resultSet) {
dataset.addNamedGraph(defaultNamedGraph);
dataset.addDefaultGraph(defaultNamedGraph);
for (Statement stmt : resultSet) {
if (stmt.getNamedGraphUri() == null) {
dataset.add(new Statement(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(), defaultNamedGraph));
} else {
dataset.addNamedGraph(stmt.getNamedGraphUri());
dataset.add(stmt);
}
}
}
// perform an 'suffix array' search against the nodes of the statements to determine which prefixes are used.
// for a method outside the writer this will be near optimum. If the writer could be modified to calculate prefixes
// as it ran, that could be much faster.
private static Map<String, String> findMinimumPrefixMap(Map<String, String> prefixes, Collection<Statement> stmts) {
String[] values = new String[stmts.size() * 5];
int i = 0;
for (Statement stmt : stmts) {
Resource subject = stmt.getSubject();
if (subject instanceof URI) {
values[i++] = subject.toString();
}
values[i++] = stmt.getPredicate().toString();
Value object = stmt.getObject();
if (object instanceof TypedLiteral) {
values[i++] = ((TypedLiteral) object).getDatatypeURI().toString();
} else if (object instanceof URI) {
values[i++] = object.toString();
}
if (stmt.getNamedGraphUri() != null)
values[i++] = stmt.getNamedGraphUri().toString();
}
values = (String[]) ArrayUtils.subarray(values, 0, i);
Arrays.sort(values);
HashMap<String, String> results = new HashMap<String, String>();
for (Map.Entry<String, String> entry : prefixes.entrySet()) {
if (isUsed(entry.getValue(), values, i)) {
results.put(entry.getKey(), entry.getValue());
}
}
return results;
}
/**
* Writes the statement to a string using the user provided prefix map to convert URIs to CURIEs where possible.
*/
String applyPrefixes(Statement stmt) {
return applyPrefixes(stmt.getSubject()) + " " + applyPrefixes(stmt.getPredicate()) + " " + applyPrefixes(stmt.getObject()) + " " + applyPrefixes(stmt.getNamedGraphUri());
}
private static final String curieTypeFormat = "\"\"\"{0}\"\"\"^^{1}";
String applyPrefixes(Value val) {
if (val instanceof URI) {
CURIE curie = getCURIE(val.toString());
if (curie == null) {
return val.toString();
} else {
return curie.toString();
}
} else if (val instanceof TypedLiteral) {
String label = ((TypedLiteral) val).getLabel();
URI datatype = ((TypedLiteral) val).getDatatypeURI();
if (isCURIE(datatype.toString())) {
return MessageFormat.format(curieTypeFormat, label, applyPrefixes(datatype));
} else {
return val.toString();
}
} else {
return val.toString();
}
}
private static boolean isUsed(String prefix, String[] sortedNodes, int length) {
int low = Arrays.binarySearch(sortedNodes, prefix + (char) 0);
int high = Arrays.binarySearch(sortedNodes, prefix + Character.MAX_VALUE);
if (low < 0 && low == high)
return false;
return true;
}
/*
private static class PrefixCollector extends StatementCollector {
Map<String, String> prefixes = new HashMap<String, String>();
@Override
void handleNamespace(String prefix, String uri) throws AnzoException {
prefixes.put(prefix, uri);
}
Map<String, String> getPrefixes() {
return prefixes;
}
}
*/
private static void stripCommonSchemesFromPrefix(IConsole consoleWriter, Map<String, String> prefixes) {
Set<String> toRemove = new HashSet<String>();
for (String key : prefixes.keySet()) {
if (commonUriSchemeSet.contains(key)) {
toRemove.add(key);
}
}
for (String key : toRemove) {
prefixes.remove(key);
consoleWriter.writeError("Warning, ignoring common uri scheme prefix: " + key);
}
}
static class URINotCURIEOrKnownScheme extends URISyntaxException {
private static final long serialVersionUID = 1L;
URINotCURIEOrKnownScheme(String input, String reason) {
super(input, reason);
}
}
public void writeOutput(String output) {
if (consoleWriter != null) {
consoleWriter.writeOutput(output);
} else {
CommandLineInterface.DEFAULT_CONSOLE.writeOutput(output);
}
}
public void writeError(String error) {
if (consoleWriter != null) {
consoleWriter.writeError(error);
} else {
CommandLineInterface.DEFAULT_CONSOLE.writeError(error);
}
}
public void beep() {
if (consoleWriter != null) {
consoleWriter.beep();
} else {
CommandLineInterface.DEFAULT_CONSOLE.beep();
}
}
public String readString(String prompt, boolean masked) throws IOException {
if (consoleWriter != null) {
return consoleWriter.readString(prompt, masked);
} else {
return CommandLineInterface.DEFAULT_CONSOLE.readString(prompt, masked);
}
}
}