Package org.openrdf.console

Source Code of org.openrdf.console.Console

/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2009.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.console;

import static org.openrdf.query.QueryLanguage.SERQL;
import static org.openrdf.query.QueryLanguage.SPARQL;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import info.aduna.io.FileUtil;
import info.aduna.io.MavenUtil;
import info.aduna.text.StringUtil;

import org.openrdf.http.client.SesameClient;
import org.openrdf.http.client.connections.HTTPConnectionPool;
import org.openrdf.http.protocol.UnauthorizedException;
import org.openrdf.model.Literal;
import org.openrdf.model.Model;
import org.openrdf.model.Namespace;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.URIFactory;
import org.openrdf.model.Value;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.query.BindingSet;
import org.openrdf.query.EvaluationException;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryInterruptedException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.UnsupportedQueryLanguageException;
import org.openrdf.query.algebra.QueryModel;
import org.openrdf.query.parser.BooleanQueryModel;
import org.openrdf.query.parser.GraphQueryModel;
import org.openrdf.query.parser.QueryParserUtil;
import org.openrdf.query.parser.TupleQueryModel;
import org.openrdf.query.parser.serql.SeRQLUtil;
import org.openrdf.query.parser.sparql.SPARQLUtil;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.manager.LocalRepositoryManager;
import org.openrdf.repository.manager.RemoteRepositoryManager;
import org.openrdf.repository.manager.RepositoryInfo;
import org.openrdf.repository.manager.RepositoryManager;
import org.openrdf.repository.manager.templates.ConfigProperty;
import org.openrdf.repository.manager.templates.ConfigTemplate;
import org.openrdf.result.ContextResult;
import org.openrdf.result.GraphResult;
import org.openrdf.result.NamespaceResult;
import org.openrdf.result.TupleResult;
import org.openrdf.rio.ParseErrorListener;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.Rio;
import org.openrdf.rio.UnsupportedRDFormatException;
import org.openrdf.rio.helpers.RDFHandlerBase;
import org.openrdf.rio.ntriples.NTriplesUtil;
import org.openrdf.sail.LockManager;
import org.openrdf.sail.SailLockedException;
import org.openrdf.sail.SailReadOnlyException;
import org.openrdf.sail.helpers.DirectoryLockManager;
import org.openrdf.store.StoreConfigException;
import org.openrdf.store.StoreException;

/**
* The Sesame Console is a command-line application for interacting with Sesame.
* It reads commands from standard input and prints feedback to standard output.
* Available options include loading and querying of data in repositories,
* repository creation and verification of RDF files.
*
* @author jeen
* @author Arjohn Kampman
*/
public class Console {

  /*------------------*
   * Static constants *
   *------------------*/

  private static final String VERSION = MavenUtil.loadVersion("org.openrdf.sesame", "sesame-console",
      "devel");

  private static final String APP_NAME = "OpenRDF Sesame console";

  public static final Map<String, Level> LOG_LEVELS;

  static {
    Map<String, Level> logLevels = new LinkedHashMap<String, Level>();
    logLevels.put("none", Level.OFF);
    logLevels.put("error", Level.SEVERE);
    logLevels.put("warning", Level.WARNING);
    logLevels.put("info", Level.INFO);
    logLevels.put("debug", Level.FINE);
    LOG_LEVELS = Collections.unmodifiableMap(logLevels);
  }

  /*-----------*
   * Constants *
   *-----------*/

  private final java.util.logging.Logger jdkRootLogger = java.util.logging.Logger.getLogger("");

  private final Logger logger = LoggerFactory.getLogger(this.getClass());

  /*-----------*
   * Variables *
   *-----------*/

  private RepositoryManager manager;

  private String managerID;

  private Repository repository;

  private String repositoryID;

  private BufferedReader in;

  private PrintStream out;

  private int consoleWidth = 80;

  private boolean showPrefix = true;

  private boolean queryPrefix = false;

  /*---------------*
   * Static metods *
   *---------------*/

  public static void main(String[] args)
    throws Exception
  {
    Console console = new Console();

    // Parse command line options
    Options options = new Options();

    Option helpOption = new Option("h", "help", false, "print this help");
    Option versionOption = new Option("v", "version", false, "print version information");
    Option serverURLOption = new Option("s", "serverURL", true,
        "URL of Sesame server to connect to, e.g. http://localhost/openrdf-sesame/");
    Option dirOption = new Option("d", "dataDir", true, "Sesame data dir to 'connect' to");

    options.addOption(helpOption);
    options.addOption(versionOption);

    OptionGroup connectGroup = new OptionGroup();
    connectGroup.addOption(serverURLOption);
    connectGroup.addOption(dirOption);
    options.addOptionGroup(connectGroup);

    CommandLineParser argsParser = new PosixParser();

    try {
      CommandLine commandLine = argsParser.parse(options, args);

      if (commandLine.hasOption(helpOption.getOpt())) {
        printUsage(options);
        System.exit(0);
      }

      if (commandLine.hasOption(versionOption.getOpt())) {
        System.out.println(VERSION);
        System.exit(0);
      }

      String dir = commandLine.getOptionValue(dirOption.getOpt());
      String serverURL = commandLine.getOptionValue(serverURLOption.getOpt());
      String[] otherArgs = commandLine.getArgs();

      if (otherArgs.length > 1) {
        printUsage(options);
        System.exit(1);
      }

      boolean connected = false;
      if (dir != null) {
        connected = console.connectLocal(dir);
      }
      else if (serverURL != null) {
        connected = console.connectRemote(serverURL);
      }
      else {
        connected = console.connectDefault();
      }

      if (!connected) {
        System.exit(2);
      }

      if (otherArgs.length > 0) {
        console.openRepository(otherArgs[0]);
      }
    }
    catch (ParseException e) {
      System.err.println(e.getMessage());
      System.exit(1);
    }

    console.start();
  }

  private static void printUsage(Options options) {
    System.out.println("Sesame Console, an interactive shell based utility to communicate with Sesame repositories.");
    HelpFormatter formatter = new HelpFormatter();
    formatter.setWidth(80);
    formatter.printHelp("start-console [OPTION] [repositoryID]", options);
    // writeln("Usage: start-console [OPTION] [repositoryID]");
    // writeln();
    // writeln(" -h, --help print this help");
    // writeln(" -s, --serverURL=URL URL of Sesame server to connect to, e.g.
    // http://localhost/openrdf-sesame/");
    // writeln(" -d, --dataDir=DIR Sesame data dir to 'connect' to");
    System.out.println();
    System.out.println("For bug reports and suggestions, see http://www.openrdf.org/");
  }

  public Console()
    throws IOException
  {
    // Set log level to WARNING by default
    jdkRootLogger.setLevel(Level.WARNING);

    in = new BufferedReader(new InputStreamReader(System.in));
    out = System.out;
  }

  public void start()
    throws IOException
  {
    writeln();
    writeln("Commands end with '.' at the end of a line");
    writeln("Type 'help.' for help");

    try {
      boolean exitFlag = false;
      while (!exitFlag) {
        String command = readMultiLineInput();

        if (command == null) {
          // EOF
          break;
        }

        exitFlag = executeCommand(command);
      }
    }
    finally {
      disconnect(false);
    }

    writeln("Bye");
  }

  private boolean executeCommand(String command)
    throws IOException
  {
    String[] tokens = parse(command);

    if (tokens.length == 0) {
      return false;
    }

    String operation = tokens[0].toLowerCase(Locale.ENGLISH);
    boolean exit = false;

    if ("quit".equals(operation) || "exit".equals(operation)) {
      exit = true;
    }
    else if ("help".equals(operation)) {
      printHelp(tokens);
    }
    else if ("info".equals(operation)) {
      printInfo();
    }
    else if ("connect".equals(operation)) {
      connect(tokens);
    }
    else if ("disconnect".equals(operation)) {
      disconnect(true);
    }
    else if ("create".equals(operation)) {
      createRepository(tokens);
    }
    else if ("drop".equals(operation)) {
      dropRepository(tokens);
    }
    else if ("open".equals(operation)) {
      open(tokens);
    }
    else if ("close".equals(operation)) {
      close(tokens);
    }
    else if ("show".equals(operation)) {
      show(tokens);
    }
    else if ("load".equals(operation)) {
      load(tokens);
    }
    else if ("verify".equals(operation)) {
      verify(tokens);
    }
    else if ("clear".equals(operation)) {
      clear(tokens);
    }
    else if ("select".equals(operation)) {
      // TODO: should this be removed now that the 'serql' command is
      // supported?
      evaluateQuery(QueryLanguage.SERQL, command);
    }
    else if ("construct".equals(operation)) {
      // TODO: should this be removed now that the 'serql' command is
      // supported?
      evaluateQuery(QueryLanguage.SERQL, command);
    }
    else if ("serql".equals(operation)) {
      evaluateQuery(QueryLanguage.SERQL, command.substring("serql".length()));
    }
    else if ("sparql".equals(operation)) {
      evaluateQuery(QueryLanguage.SPARQL, command.substring("sparql".length()));
    }
    else if ("set".equals(operation)) {
      setParameter(tokens);
    }
    else if (command.length() == 0) {
      // empty line, ignore
    }
    else {
      writeError("Unknown command");
    }

    return exit;
  }

  private String[] parse(String command) {
    Pattern pattern = Pattern.compile("\"([^\"]*)\"|(\\S+)");
    Matcher matcher = pattern.matcher(command);
    List<String> tokens = new ArrayList<String>();
    while (matcher.find()) {
      if (matcher.group(1) != null) {
        tokens.add(matcher.group(1));
      }
      else {
        tokens.add(matcher.group());
      }
    }
    return tokens.toArray(new String[tokens.size()]);
  }

  private void printHelp(String[] tokens) {
    if (tokens.length < 2) {
      printCommandOverview();
    }
    else {
      String target = tokens[1].toLowerCase(Locale.ENGLISH);

      if ("connect".equals(target)) {
        printHelpConnect();
      }
      else if ("disconnect".equals(target)) {
        printHelpDisconnect();
      }
      else if ("create".equals(target)) {
        printHelpCreate();
      }
      else if ("drop".equals(target)) {
        printHelpDrop();
      }
      else if ("open".equals(target)) {
        printHelpOpen();
      }
      else if ("close".equals(target)) {
        printHelpClose();
      }
      else if ("show".equals(target)) {
        printHelpShow();
      }
      else if ("load".equals(target)) {
        printHelpLoad();
      }
      else if ("verify".equals(target)) {
        printHelpVerify();
      }
      else if ("clear".equals(target)) {
        printHelpClear();
      }
      else if ("set".equals(target)) {
        printHelpSet();
      }
      else {
        writeln("No info available for command " + tokens[1]);
      }
    }
  }

  private void printCommandOverview() {
    writeln("For more information on a specific command, try 'help <command>.'");
    writeln("List of all commands:");
    writeln("help        Displays this help message");
    writeln("info        Shows info about the console");
    writeln("connect     Connects to a (local or remote) set of repositories");
    writeln("disconnect  Disconnects from the current set of repositories");
    writeln("create      Creates a new repository");
    writeln("drop        Drops a repository");
    writeln("open        Opens a repository to work on, takes a repository ID as argument");
    writeln("close       Closes the current repository");
    writeln("show        Displays an overview of various resources");
    writeln("load        Loads a data file into a repository, takes a file path or URL as argument");
    writeln("verify      Verifies the syntax of an RDF data file, takes a file path or URL as argument");
    writeln("clear       Removes data from a repository");
    writeln("serql       Evaluates the SeRQL query, takes a query as argument");
    writeln("sparql      Evaluates the SPARQL query, takes a query as argument");
    writeln("set         Allows various console parameters to be set");
    writeln("exit, quit  Exit the console");
  }

  private void printInfo() {
    writeln(APP_NAME + " " + VERSION);
    writeln("Connected to: " + (managerID == null ? "-" : managerID));
    try {
      writeln("Data location: " + (manager == null ? "-" : manager.getLocation()));
    }
    catch (MalformedURLException e) {
      writeError("Unable to show data location (" + e.getMessage() + ")");
    }
  }

  private void printHelpConnect() {
    writeln("Usage:");
    writeln("connect default           Opens the default repository set for this console");
    writeln("connect <dataDirectory>   Opens the repository set in the specified data dir");
    writeln("connect <serverURL>       Connects to a Sesame server");
  }

  private void connect(String[] tokens)
    throws IOException
  {
    if (tokens.length != 2) {
      printHelpConnect();
      return;
    }

    String target = tokens[1];

    if ("default".equalsIgnoreCase(target)) {
      connectDefault();
    }
    else {
      try {
        new URL(target);
        // target is a valid URL
        connectRemote(target);
      }
      catch (MalformedURLException e) {
        // assume target is a directory path
        connectLocal(target);
      }
    }
  }

  private boolean connectDefault()
    throws IOException
  {
    return connectLocal(createTempDir().getPath());
  }

  private File createTempDir()
    throws IOException
  {
    final File dir = FileUtil.createTempDir("sesame");

    Runtime.getRuntime().addShutdownHook(new Thread() {

      @Override
      public void run() {
        try {
          FileUtil.deleteDir(dir);
        }
        catch (IOException e) {
          e.printStackTrace();
        }
      }
    });

    return dir;
  }

  private boolean connectLocal(String path) {
    File dir = new File(path);
    if (!dir.exists() || !dir.isDirectory()) {
      writeError("Specified path is not an (existing) directory: " + path);
      return false;
    }

    return installNewManager(new LocalRepositoryManager(dir), dir.toString());
  }

  private boolean connectRemote(String url) {
    try {
      // Ping server
      HTTPConnectionPool pool = new HTTPConnectionPool(url);
      try {
        new SesameClient(pool).protocol().get();
      }
      finally {
        pool.shutdown();
      }

      return installNewManager(new RemoteRepositoryManager(url), url);
    }
    catch (UnauthorizedException e) {
      // FIXME: handle authentication
      writeError("Not authorized to access the server");
    }
    catch (StoreException e) {
      writeError("Failed to access the server: " + e.getMessage());
      logger.warn("Failed to access the server", e);
    }

    return false;
  }

  private boolean installNewManager(RepositoryManager newManager, String newManagerID) {
    if (newManagerID.equals(managerID)) {
      writeln("Already connected to " + managerID);
      return true;
    }

    try {
      newManager.initialize();

      disconnect(false);
      manager = newManager;
      managerID = newManagerID;

      writeln("Connected to " + managerID);
      return true;
    }
    catch (StoreConfigException e) {
      writeError(e.getMessage());
      logger.error("Failed to install new manager", e);
      return false;
    }
  }

  private void printHelpDisconnect() {
    writeln("Usage:");
    writeln("disconnect   Disconnects from the current set of repositories or server");
  }

  private void disconnect(boolean verbose) {
    if (manager != null) {
      closeRepository(false);

      writeln("Disconnecting from " + managerID);
      manager.shutDown();
      manager = null;
      managerID = null;
    }
    else if (verbose) {
      writeln("Already disconnected");
    }
  }

  private void printHelpCreate() {
    writeln("Usage:");
    writeln("create <template-name>");
    writeln("  <template-name>   The name of a repository configuration template");
  }

  private void createRepository(String[] tokens)
    throws IOException
  {
    if (tokens.length < 2) {
      printHelpCreate();
    }
    else {
      createRepository(tokens[1]);
    }
  }

  private void createRepository(String templateName)
    throws IOException
  {
    try {
      ConfigTemplate configTemplate = manager.getConfigTemplateManager().getTemplate(templateName);
      if (configTemplate == null) {
        writeError("No template called " + templateName + " found");
        return;
      }

      List<ConfigProperty> properties = configTemplate.getProperties();

      if (!properties.isEmpty()) {
        writeln("Please specify values for the following variables:");
      }

      // prompt for repository ID
      write("Repository ID [" + templateName + "]: ");
      String repositoryID = in.readLine();
      if (repositoryID == null) {
        // EOF
        return;
      }
      else if (repositoryID.trim().length() < 1) {
        repositoryID = templateName;
      }

      // other properties
      for (ConfigProperty property : properties) {
        List<Literal> values = property.getPossibleLiterals();
        Literal defaultLiteral = property.getDefaultLiteral();

        write(property.getLabel());
        if (values.size() > 1) {
          write(" (");
          for (int i = 0; i < values.size(); i++) {
            if (i > 0) {
              write("|");
            }
            write(values.get(i).stringValue());
          }
          write(")");
        }
        if (defaultLiteral != null) {
          write(" [" + defaultLiteral.stringValue() + "]");
        }
        write(": ");

        String value = in.readLine();
        if (value == null) {
          // EOF
          return;
        }

        value = value.trim();
        if (value.length() > 0) {
          property.setValue(new LiteralImpl(value));
        }
      }

      Model repConfig = configTemplate.createConfig(properties);

      if (manager.hasRepositoryConfig(repositoryID)) {
        boolean proceed = askProceed(
            "WARNING: you are about to overwrite the configuration of an existing repository!", false);

        if (!proceed) {
          writeln("Create aborted");
          return;
        }
      }

      manager.addRepositoryConfig(repositoryID, repConfig);
      writeln("Repository created");
    }
    catch (Exception e) {
      writeError(e.getMessage());
      logger.error("Failed to create repository", e);
    }
  }

  private void printHelpDrop() {
    writeln("Usage:");
    writeln("drop <repositoryID>   Drops the repository with the specified id");
  }

  private void dropRepository(String[] tokens)
    throws IOException
  {
    if (tokens.length < 2) {
      printHelpDrop();
      return;
    }

    String id = tokens[1];

    try {
      boolean proceed = askProceed("WARNING: you are about to drop repository '" + id + "'.", true);
      if (proceed) {
        if (id.equals(repositoryID)) {
          closeRepository(false);
        }
        boolean isRemoved = manager.removeRepositoryConfig(id);

        if (isRemoved) {
          writeln("Dropped repository '" + id + "'");
        }
        else {
          writeln("Unknown repository '" + id + "'");
        }
      }
      else {
        writeln("Drop aborted");
      }
    }
    catch (StoreConfigException e) {
      writeError("Unable to drop repository '" + id + "': " + e.getMessage());
      logger.warn("Unable to drop repository '" + id + "'", e);
    }
    catch (StoreException e) {
      writeError("Unable to drop repository '" + id + "': " + e.getMessage());
      logger.warn("Unable to drop repository '" + id + "'", e);
    }
  }

  private void printHelpOpen() {
    writeln("Usage:");
    writeln("open <repositoryID>   Opens the repository with the specified ID");
  }

  private void open(String[] tokens) {
    if (tokens.length != 2) {
      printHelpOpen();
    }
    else {
      openRepository(tokens[1]);
    }
  }

  private void openRepository(String id) {
    try {
      Repository newRepository = manager.getRepository(id);

      if (newRepository != null) {
        // Close current repository, if any
        closeRepository(false);

        repository = newRepository;
        repositoryID = id;
        writeln("Opened repository '" + id + "'");
      }
      else {
        writeError("Unknown repository: '" + id + "'");
      }
    }
    catch (SailLockedException e) {
      try {
        if (tryToRemoveLock(e)) {
          openRepository(id);
        }
        else {
          writeError("Failed to open repository");
          logger.error("Failed to open repository", e);
        }
      }
      catch (IOException e1) {
        writeError("Unable to remove lock: " + e1.getMessage());
      }
    }
    catch (StoreConfigException e) {
      writeError(e.getMessage());
      logger.error("Failed to open repository", e);
    }
    catch (StoreException e) {
      writeError(e.getMessage());
      logger.error("Failed to open repository", e);
    }
  }

  private void printHelpClose() {
    writeln("Usage:");
    writeln("close   Closes the current repository");
  }

  private void close(String[] tokens) {
    if (tokens.length != 1) {
      printHelpClose();
    }
    else {
      closeRepository(true);
    }
  }

  private void closeRepository(boolean verbose) {
    if (repository != null) {
      writeln("Closing repository '" + repositoryID + "'...");
      repository = null;
      repositoryID = null;
    }
    else if (verbose) {
      writeln("There are no open repositories that can be closed");
    }
  }

  private void printHelpShow() {
    writeln("Usage:");
    writeln("show {r, repositories}   Shows all available repositories");
    writeln("show {n, namespaces}     Shows all namespaces");
    writeln("show {c, contexts}       Shows all context identifiers");
  }

  private void show(String[] tokens) {
    if (tokens.length != 2) {
      printHelpShow();
    }
    else {
      String target = tokens[1].toLowerCase(Locale.ENGLISH);

      if ("repositories".equals(target) || "r".equals(target)) {
        showRepositories();
      }
      else if ("namespaces".equals(target) || "n".equals(target)) {
        showNamespaces();
      }
      else if ("contexts".equals(target) || "c".equals(target)) {
        showContexts();
      }
      else {
        writeError("Unknown target '" + tokens[1] + "'");
      }
    }
  }

  private void showRepositories() {
    try {
      Set<String> repIDs = manager.getRepositoryIDs();

      if (repIDs.isEmpty()) {
        writeln("--no repositories found--");
      }
      else {
        writeln("+----------");
        for (String repID : repIDs) {
          write("|" + repID);

          try {
            RepositoryInfo repInfo = manager.getRepositoryInfo(repID);
            if (repInfo.getDescription() != null) {
              write(" (\"" + repInfo.getDescription() + "\")");
            }
          }
          catch (StoreConfigException e) {
            write(" [ERROR: " + e.getMessage() + "]");
          }
          writeln();
        }
        writeln("+----------");
      }
    }
    catch (StoreConfigException e) {
      writeError("Failed to get repository list: " + e.getMessage());
      logger.error("Failed to get repository list", e);
    }
  }

  private void showNamespaces() {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    RepositoryConnection con;
    try {
      con = repository.getConnection();

      try {
        NamespaceResult namespaces = con.getNamespaces();

        try {
          if (namespaces.hasNext()) {
            writeln("+----------");
            while (namespaces.hasNext()) {
              Namespace ns = namespaces.next();
              writeln("|" + ns.getPrefix() + "  " + ns.getName());
            }
            writeln("+----------");
          }
          else {
            writeln("--no namespaces found--");
          }
        }
        finally {
          namespaces.close();
        }
      }
      finally {
        con.close();
      }
    }
    catch (StoreException e) {
      writeError(e.getMessage());
      logger.error("Failed to show namespaces", e);
    }
  }

  private void showContexts() {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    RepositoryConnection con;
    try {
      con = repository.getConnection();

      try {
        ContextResult contexts = con.getContextIDs();

        try {
          if (contexts.hasNext()) {
            writeln("+----------");
            while (contexts.hasNext()) {
              Resource context = contexts.next();
              writeln("|" + context.toString());
            }
            writeln("+----------");
          }
          else {
            writeln("--no contexts found--");
          }
        }
        finally {
          contexts.close();
        }
      }
      finally {
        con.close();
      }
    }
    catch (StoreException e) {
      writeError(e.getMessage());
      logger.error("Failed to show contexts", e);
    }
  }

  private void printHelpLoad() {
    writeln("Usage:");
    writeln("load <file-or-url> [from <base-uri>] [into <context-id>]");
    writeln("  <file-or-url>   The path or URL identifying the data file");
    writeln("  <base-uri>      The base URI to use for resolving relative references, defaults to <file-or-url>");
    writeln("  <context-id>    The ID of the context to add the data to, e.g. foo:bar or _:n123");
    writeln("Loads the specified data file into the current repository");
  }

  private void load(String[] tokens) {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    if (tokens.length < 2) {
      printHelpLoad();
      return;
    }

    String dataPath = tokens[1];
    URL dataURL = null;
    File dataFile = null;
    String baseURI = null;
    String context = null;

    int i = 2;

    if (tokens.length >= i + 2 && tokens[i].equalsIgnoreCase("from")) {
      baseURI = tokens[i + 1];
      i += 2;
    }

    if (tokens.length >= i + 2 && tokens[i].equalsIgnoreCase("into")) {
      context = tokens[tokens.length - 1];
      i += 2;
    }

    if (i < tokens.length) {
      printHelpLoad();
      return;
    }

    try {
      dataURL = new URL(dataPath);
      // dataPath is a URI
    }
    catch (MalformedURLException e) {
      // dataPath is a file
      dataFile = new File(dataPath);
    }

    try {

      RDFFormat format = Rio.getParserFormatForFileName(dataPath, RDFFormat.RDFXML);

      writeln("Loading data...");
      long startTime = System.nanoTime();

      RepositoryConnection con = repository.getConnection();
      try {
        Resource[] contexts = new Resource[0];

        if (context != null) {
          Resource contextURI;
          if (context.startsWith("_:")) {
            contextURI = con.getValueFactory().createBNode(context.substring(2));
          }
          else {
            contextURI = con.getValueFactory().createURI(context);
          }

          contexts = new Resource[] { contextURI };
        }
        if (dataURL != null) {
          con.add(dataURL, baseURI, format, contexts);
        }
        else {
          con.add(dataFile, baseURI, format, contexts);
        }
      }
      finally {
        con.close();
      }

      long endTime = System.nanoTime();
      writeln("Data has been added to the repository (" + (endTime - startTime) / 1000000 + " ms)");
    }
    catch (SailReadOnlyException e) {
      try {
        if (tryToRemoveLock(e, repository)) {
          load(tokens);
        }
        else {
          writeError("Failed to load data");
          logger.error("Failed to load data", e);
        }
      }
      catch (StoreException e1) {
        writeError("Unable to restart repository: " + e1.getMessage());
        logger.error("Unable to restart repository", e1);
      }
      catch (IOException e1) {
        writeError("Unable to remove lock: " + e1.getMessage());
      }
    }
    catch (MalformedURLException e) {
      writeError("Malformed URL: " + dataPath);
    }
    catch (IllegalArgumentException e) {
      // Thrown when context URI is invalid
      writeError(e.getMessage());
    }
    catch (IOException e) {
      writeError("Failed to load data: " + e.getMessage());
    }
    catch (UnsupportedRDFormatException e) {
      writeError("No parser available for this RDF format");
    }
    catch (RDFParseException e) {
      writeError("Malformed document: " + e.getMessage());
    }
    catch (StoreException e) {
      writeError("Unable to add data to repository: " + e.getMessage());
      logger.error("Failed to add data to repository", e);
    }
  }

  private void printHelpVerify() {
    writeln("Usage:");
    writeln("verify <file-or-url>");
    writeln("  <file-or-url>   The path or URL identifying the data file");
    writeln("Verifies the validity of the specified data file");
  }

  private void verify(String[] tokens) {
    if (tokens.length != 2) {
      printHelpVerify();
      return;
    }

    String dataPath = tokens[1];

    try {
      new URL(dataPath);
      // dataPath is a URI
    }
    catch (MalformedURLException e) {
      // File path specified, convert to URL
      dataPath = "file:" + dataPath;
    }

    try {
      URL dataURL = new URL(dataPath);
      RDFFormat format = Rio.getParserFormatForFileName(dataPath, RDFFormat.RDFXML);

      writeln("RDF Format is " + format.getName());

      RDFParser parser = Rio.createParser(format);
      VerificationListener listener = new VerificationListener();
      parser.setDatatypeHandling(RDFParser.DatatypeHandling.VERIFY);
      parser.setVerifyData(true);
      parser.setParseErrorListener(listener);
      parser.setRDFHandler(listener);

      writeln("Verifying data...");
      InputStream in = dataURL.openStream();
      try {
        parser.parse(in, "urn://openrdf.org/RioVerifier/");
      }
      finally {
        in.close();
      }

      int warnings = listener.getWarnings();
      int errors = listener.getErrors();
      int statements = listener.getStatements();

      if (warnings + errors > 0) {
        writeln("Found " + warnings + " warnings and " + errors + " errors");
      }
      else {
        writeln("Data verified, no errors were found");
      }

      if (errors == 0) {
        writeln("File contains " + statements + " statements");
      }
    }
    catch (MalformedURLException e) {
      writeError("Malformed URL: " + dataPath);
    }
    catch (IOException e) {
      writeError("Failed to load data: " + e.getMessage());
    }
    catch (UnsupportedRDFormatException e) {
      writeError("No parser available for this RDF format");
    }
    catch (RDFParseException e) {
      // Any parse errors have already been reported by the
      // VerificationListener
    }
    catch (RDFHandlerException e) {
      writeError("Unable to verify : " + e.getMessage());
      logger.error("Unable to verify data file", e);
    }
  }

  private void printHelpClear() {
    writeln("Usage:");
    writeln("clear                   Clears the entire repository");
    writeln("clear (<uri>|null)...   Clears the specified context(s)");
  }

  private void clear(String[] tokens) {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    URIFactory valueFactory = repository.getURIFactory();

    Resource[] contexts = new Resource[tokens.length - 1];

    for (int i = 1; i < tokens.length; i++) {
      String contextID = tokens[i];

      if (contextID.equalsIgnoreCase("null")) {
        contexts[i - 1] = null;
      }
      else {
        try {
          contexts[i - 1] = valueFactory.createURI(contextID);
        }
        catch (IllegalArgumentException e) {
          writeError("illegal URI: " + contextID);
          printHelpClear();
          return;
        }
      }
    }

    if (contexts.length == 0) {
      writeln("Clearing repository...");
    }
    else {
      writeln("Removing specified contexts...");
    }

    try {
      RepositoryConnection con = repository.getConnection();
      try {
        con.clear(contexts);
      }
      finally {
        con.close();
      }
    }
    catch (SailReadOnlyException e) {
      try {
        if (tryToRemoveLock(e, repository)) {
          clear(tokens);
        }
        else {
          writeError("Failed to clear repository");
          logger.error("Failed to clear repository", e);
        }
      }
      catch (StoreException e1) {
        writeError("Unable to restart repository: " + e1.getMessage());
        logger.error("Unable to restart repository", e1);
      }
      catch (IOException e1) {
        writeError("Unable to remove lock: " + e1.getMessage());
      }
    }
    catch (StoreException e) {
      writeError("Failed to clear repository: " + e.getMessage());
      logger.error("Failed to clear repository", e);
    }
  }

  private void evaluateQuery(QueryLanguage ql, String queryString) {
    try {
      queryString = addQueryPrefixes(ql, queryString);
      QueryModel query = QueryParserUtil.parseQuery(ql, queryString, null);
      if (query instanceof TupleQueryModel) {
        evaluateTupleQuery(ql, queryString);
      }
      else if (query instanceof GraphQueryModel) {
        evaluateGraphQuery(ql, queryString);
      }
      else if (query instanceof BooleanQueryModel) {
        evaluateBooleanQuery(ql, queryString);
      }
      else {
        writeError("Unexpected query type");
      }
    }
    catch (UnsupportedQueryLanguageException e) {
      writeError("Unsupported query lanaguge: " + ql.getName());
    }
    catch (MalformedQueryException e) {
      writeError("Malformed query: " + e.getMessage());
    }
    catch (QueryInterruptedException e) {
      writeError("Query interrupted: " + e.getMessage());
      logger.error("Query interrupted", e);
    }
    catch (EvaluationException e) {
      writeError("Query evaluation error: " + e.getMessage());
      logger.error("Query evaluation error", e);
    }
    catch (StoreException e) {
      writeError("Failed to evaluate query: " + e.getMessage());
      logger.error("Failed to evaluate query", e);
    }
  }

  private String addQueryPrefixes(QueryLanguage ql, String queryString) {
    String result = queryString;

    if (repository != null && queryPrefix) {
      // FIXME this is a bit of a sloppy hack, a better way would be to
      // explicitly provide the query parser with namespace mappings in
      // advance.
      if ((SERQL.equals(ql) && queryString.toLowerCase().indexOf("using namespace ") == -1)
          || SPARQL.equals(ql) && !queryString.toLowerCase().startsWith("prefix"))
      {
        try {
          RepositoryConnection con = repository.getConnection();
          try {
            Collection<Namespace> namespaces = con.getNamespaces().asList();

            if (!namespaces.isEmpty()) {
              StringBuilder namespaceClause = new StringBuilder(512);

              if (SERQL.equals(ql)) {
                namespaceClause.append(" USING NAMESPACE ");

                for (Namespace namespace : namespaces) {
                  namespaceClause.append(namespace.getPrefix());
                  namespaceClause.append(" = ");
                  namespaceClause.append("<");
                  namespaceClause.append(SeRQLUtil.encodeString(namespace.getName()));
                  namespaceClause.append(">, ");
                }

                // Remove trailing ", "
                namespaceClause.setLength(namespaceClause.length() - 2);

                result += namespaceClause.toString();
              }
              else if (SPARQL.equals(ql)) {
                for (Namespace namespace : namespaces) {
                  namespaceClause.append("PREFIX ");
                  namespaceClause.append(namespace.getPrefix());
                  namespaceClause.append(": ");
                  namespaceClause.append("<");
                  namespaceClause.append(SPARQLUtil.encodeString(namespace.getName()));
                  namespaceClause.append("> ");
                }

                result = namespaceClause.toString() + result;
              }
            }
          }
          finally {
            con.close();
          }
        }
        catch (StoreException e) {
          writeError("Error connecting to repository: " + e.getMessage());
          logger.error("Error connecting to repository", e);
        }
      }
    }

    return result;
  }

  private void evaluateTupleQuery(QueryLanguage ql, String queryString)
    throws UnsupportedQueryLanguageException, MalformedQueryException, EvaluationException, StoreException
  {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    RepositoryConnection con = repository.getConnection();

    try {
      writeln("Evaluating query...");
      long startTime = System.nanoTime();

      Collection<Namespace> namespaces = con.getNamespaces().addTo(new ArrayList<Namespace>());

      TupleResult tupleQueryResult = con.prepareTupleQuery(ql, queryString).evaluate();

      try {
        int resultCount = 0;
        List<String> bindingNames = tupleQueryResult.getBindingNames();

        if (bindingNames.isEmpty()) {
          while (tupleQueryResult.hasNext()) {
            tupleQueryResult.next();
            resultCount++;
          }
        }
        else {
          int columnWidth = (consoleWidth - 1) / bindingNames.size() - 3;

          // Build table header
          StringBuilder sb = new StringBuilder(consoleWidth);
          for (String bindingName : bindingNames) {
            sb.append("| ").append(bindingName);
            StringUtil.appendN(' ', columnWidth - bindingName.length(), sb);
          }
          sb.append("|");
          String header = sb.toString();

          // Build separator line
          sb.setLength(0);
          for (int i = bindingNames.size(); i > 0; i--) {
            sb.append('+');
            StringUtil.appendN('-', columnWidth + 1, sb);
          }
          sb.append('+');
          String separatorLine = sb.toString();

          // Write table header
          writeln(separatorLine);
          writeln(header);
          writeln(separatorLine);

          // Write table rows

          while (tupleQueryResult.hasNext()) {
            BindingSet bindingSet = tupleQueryResult.next();
            resultCount++;

            sb.setLength(0);
            for (String bindingName : bindingNames) {
              Value value = bindingSet.getValue(bindingName);
              String valueStr = getStringRepForValue(value, namespaces);

              sb.append("| ").append(valueStr);
              StringUtil.appendN(' ', columnWidth - valueStr.length(), sb);
            }
            sb.append("|");
            writeln(sb.toString());
          }

          writeln(separatorLine);
        }

        long endTime = System.nanoTime();
        writeln(resultCount + " result(s) (" + (endTime - startTime) / 1000000 + " ms)");
      }
      finally {
        tupleQueryResult.close();
      }
    }
    finally {
      con.close();
    }
  }

  private void evaluateGraphQuery(QueryLanguage ql, String queryString)
    throws UnsupportedQueryLanguageException, MalformedQueryException, EvaluationException, StoreException
  {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    RepositoryConnection con = repository.getConnection();

    try {
      writeln("Evaluating query...");
      long startTime = System.nanoTime();

      Collection<Namespace> namespaces = con.getNamespaces().addTo(new ArrayList<Namespace>());

      GraphResult queryResult = con.prepareGraphQuery(ql, queryString).evaluate();

      try {
        int resultCount = 0;

        while (queryResult.hasNext()) {
          Statement st = queryResult.next();
          resultCount++;

          write(getStringRepForValue(st.getSubject(), namespaces));
          write("   ");
          write(getStringRepForValue(st.getPredicate(), namespaces));
          write("   ");
          write(getStringRepForValue(st.getObject(), namespaces));
          writeln();
        }

        long endTime = System.nanoTime();
        writeln(resultCount + " results (" + (endTime - startTime) / 1000000 + " ms)");
      }
      finally {
        queryResult.close();
      }
    }
    finally {
      con.close();
    }
  }

  private void evaluateBooleanQuery(QueryLanguage ql, String queryString)
    throws UnsupportedQueryLanguageException, MalformedQueryException, EvaluationException, StoreException
  {
    if (repository == null) {
      writeError("please open a repository first");
      return;
    }

    RepositoryConnection con = repository.getConnection();

    try {
      writeln("Evaluating query...");
      long startTime = System.nanoTime();

      boolean booleanQueryResult = con.prepareBooleanQuery(ql, queryString).ask();

      writeln("Answer: " + booleanQueryResult);

      long endTime = System.nanoTime();
      writeln("Query evaluated in " + (endTime - startTime) / 1000000 + " ms");
    }
    finally {
      con.close();
    }
  }

  /**
   * @param namespace
   * @param namespaces
   * @return
   */
  private String getPrefixForNamespace(String namespace, Collection<Namespace> namespaces) {
    for (Namespace ns : namespaces) {
      if (namespace.equals(ns.getName())) {
        return ns.getPrefix();
      }
    }
    return null;
  }

  private String getStringRepForValue(Value value, Collection<Namespace> namespaces) {
    if (value == null) {
      return "";
    }
    else if (showPrefix && value instanceof URI) {
      URI uri = (URI)value;

      String prefix = getPrefixForNamespace(uri.getNamespace(), namespaces);

      if (prefix != null) {
        return prefix + ":" + uri.getLocalName();
      }
      else {
        return NTriplesUtil.toNTriplesString(value);
      }
    }
    else {
      return NTriplesUtil.toNTriplesString(value);
    }
  }

  private void printHelpSet() {
    writeln("Usage:");
    writeln("set                            Shows all parameter values");
    writeln("set width=<number>             Set the width for query result tables");
    writeln("set log=<level>                Set the logging level (none, error, warning, info or debug)");
    writeln("set showPrefix=<true|false>    Toggles use of prefixed names in query results");
    writeln("set queryPrefix=<true|false>   Toggles automatic use of known namespace prefixes in queries (warning: buggy!)");
  }

  private void setParameter(String[] tokens) {
    if (tokens.length == 1) {
      showParameters();
    }
    else if (tokens.length == 2) {
      String param = tokens[1];

      String key, value;

      int eqIdx = param.indexOf('=');
      if (eqIdx == -1) {
        key = param;
        value = null;
      }
      else {
        key = param.substring(0, eqIdx);
        value = param.substring(eqIdx + 1);
      }

      setParameter(key, value);
    }
    else {
      printHelpSet();
    }
  }

  private void showParameters() {
    setLog(null);
    setWidth(null);
    setShowPrefix(null);
    setQueryPrefix(null);
  }

  private void setParameter(String key, String value) {
    key = key.toLowerCase(Locale.ENGLISH);

    if ("log".equals(key)) {
      setLog(value);
    }
    else if ("width".equals(key)) {
      setWidth(value);
    }
    else if ("showprefix".equals(key)) {
      setShowPrefix(value);
    }
    else if ("queryprefix".equals(key)) {
      setQueryPrefix(value);
    }
    else {
      writeError("unknown parameter: " + key);
    }
  }

  private void setWidth(String value) {
    if (value == null) {
      writeln("width: " + consoleWidth);
    }
    else {
      try {
        int width = Integer.parseInt(value);
        if (width > 0) {
          consoleWidth = width;
        }
        else {
          writeError("Width must be larger than 0");
        }
      }
      catch (NumberFormatException e) {
        writeError("Width must be a positive number");
      }
    }
  }

  private void setLog(String value) {
    if (value == null) {
      Level currentLevel = jdkRootLogger.getLevel();
      String levelString = currentLevel.getName();

      for (Map.Entry<String, Level> entry : LOG_LEVELS.entrySet()) {
        if (entry.getValue().equals(currentLevel)) {
          levelString = entry.getKey();
          break;
        }
      }

      writeln("log: " + levelString);
    }
    else {
      Level logLevel = LOG_LEVELS.get(value.toLowerCase());

      if (logLevel != null) {
        jdkRootLogger.setLevel(logLevel);
      }
      else {
        writeError("unknown logging level: " + value);
      }
    }
  }

  private void setShowPrefix(String value) {
    if (value == null) {
      writeln("showPrefix: " + showPrefix);
    }
    else {
      showPrefix = Boolean.parseBoolean(value);
    }
  }

  private void setQueryPrefix(String value) {
    if (value == null) {
      writeln("queryPrefix: " + queryPrefix);
    }
    else {
      queryPrefix = Boolean.parseBoolean(value);
    }
  }

  private boolean tryToRemoveLock(SailReadOnlyException e, Repository repo)
    throws IOException, StoreException
  {
    boolean lockRemoved = false;

    LockManager lockManager = new DirectoryLockManager(repo.getDataDir());

    if (lockManager.isLocked()) {
      if (askProceed("WARNING: The lock from another process on this repository needs to be removed", true))
      {
        repo.shutDown();
        lockRemoved = lockManager.revokeLock();
        repo.initialize();
      }
    }

    return lockRemoved;
  }

  private boolean tryToRemoveLock(SailLockedException e)
    throws IOException
  {
    boolean lockRemoved = false;

    LockManager lockManager = e.getLockManager();

    if (lockManager != null && lockManager.isLocked()) {
      if (askProceed("WARNING: The lock from process '" + e.getLockedBy()
          + "' on this repository needs to be removed", true))
      {
        lockRemoved = lockManager.revokeLock();
      }
    }

    return lockRemoved;
  }

  private boolean askProceed(String msg, boolean defaultValue)
    throws IOException
  {
    String defaultString = defaultValue ? "yes" : "no";

    while (true) {
      writeln(msg);
      write("Proceed? (yes|no) [" + defaultString + "]: ");
      String reply = in.readLine();

      if ("no".equalsIgnoreCase(reply) || "no.".equalsIgnoreCase(reply)) {
        return false;
      }
      else if ("yes".equalsIgnoreCase(reply) || "yes.".equalsIgnoreCase(reply)) {
        return true;
      }
      else if (reply.trim().length() == 0) {
        return defaultValue;
      }
    }
  }

  /**
   * Reads multiple lines from the input until a line that ends with a '.' is
   * read.
   */
  private String readMultiLineInput()
    throws IOException
  {
    if (repositoryID != null) {
      write(repositoryID);
    }
    write("> ");

    String line = in.readLine();
    if (line == null) {
      // EOF
      return null;
    }

    StringBuilder buf = new StringBuilder(256);
    buf.append(line);

    while (line != null && !line.endsWith(".")) {
      line = in.readLine();
      buf.append('\n');
      buf.append(line);
    }

    // Remove closing dot
    buf.setLength(buf.length() - 1);

    return buf.toString().trim();
  }

  private void write(String s) {
    out.print(s);
  }

  private void writeln() {
    out.println();
  }

  private void writeln(String s) {
    out.println(s);
  }

  private void writeError(String errMsg) {
    writeln("ERROR: " + errMsg);
  }

  private void writeParseError(String prefix, int lineNo, int colNo, String msg) {
    StringBuilder sb = new StringBuilder(256);

    sb.append(prefix);
    sb.append(": ");
    sb.append(msg);

    String locationString = RDFParseException.getLocationString(lineNo, colNo);
    if (locationString.length() > 0) {
      sb.append(" ").append(locationString);
    }

    writeln(sb.toString());
  }

  class VerificationListener extends RDFHandlerBase implements ParseErrorListener {

    private int warnings;

    private int errors;

    private int statements;

    public int getWarnings() {
      return warnings;
    }

    public int getErrors() {
      return errors;
    }

    public int getStatements() {
      return statements;
    }

    public void handleStatement(Statement st)
      throws RDFHandlerException
    {
      statements++;
    }

    public void warning(String msg, int lineNo, int colNo) {
      warnings++;
      writeParseError("WARNING", lineNo, colNo, msg);
    }

    public void error(String msg, int lineNo, int colNo) {
      errors++;
      writeParseError("ERROR", lineNo, colNo, msg);
    }

    public void fatalError(String msg, int lineNo, int colNo) {
      errors++;
      writeParseError("FATAL ERROR", lineNo, colNo, msg);
    }
  }
}
TOP

Related Classes of org.openrdf.console.Console

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.