org.openanzo.client.cli.CommandContext.java Source code

Java tutorial

Introduction

Here is the source code for org.openanzo.client.cli.CommandContext.java

Source

/*******************************************************************************
 * 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);
        }
    }
}