Console.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.console;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.rdf4j.RDF4J;
import org.eclipse.rdf4j.common.app.AppConfiguration;
import org.eclipse.rdf4j.common.app.AppVersion;
import org.eclipse.rdf4j.console.command.Clear;
import org.eclipse.rdf4j.console.command.Close;
import org.eclipse.rdf4j.console.command.Connect;
import org.eclipse.rdf4j.console.command.ConsoleCommand;
import org.eclipse.rdf4j.console.command.Convert;
import org.eclipse.rdf4j.console.command.Create;
import org.eclipse.rdf4j.console.command.Disconnect;
import org.eclipse.rdf4j.console.command.Drop;
import org.eclipse.rdf4j.console.command.Export;
import org.eclipse.rdf4j.console.command.Federate;
import org.eclipse.rdf4j.console.command.Load;
import org.eclipse.rdf4j.console.command.Open;
import org.eclipse.rdf4j.console.command.PrintHelp;
import org.eclipse.rdf4j.console.command.PrintInfo;
import org.eclipse.rdf4j.console.command.QueryEvaluator;
import org.eclipse.rdf4j.console.command.SetParameters;
import org.eclipse.rdf4j.console.command.Show;
import org.eclipse.rdf4j.console.command.Sparql;
import org.eclipse.rdf4j.console.command.TupleAndGraphQueryEvaluator;
import org.eclipse.rdf4j.console.command.Verify;
import org.eclipse.rdf4j.console.setting.ConsoleSetting;
import org.eclipse.rdf4j.console.setting.ConsoleWidth;
import org.eclipse.rdf4j.console.setting.LogLevel;
import org.eclipse.rdf4j.console.setting.Prefixes;
import org.eclipse.rdf4j.console.setting.QueryPrefix;
import org.eclipse.rdf4j.console.setting.SaveHistory;
import org.eclipse.rdf4j.console.setting.ShowPrefix;
import org.eclipse.rdf4j.console.setting.WorkDir;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.UserInterruptException;
/**
* The RDF4J Console is a command-line application for interacting with RDF4J. 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 Broekstra
* @author Arjohn Kampman
* @author Bart Hanssens
*/
public class Console {
private final static AppVersion VERSION = AppVersion.parse(RDF4J.getVersion());
private final static String APP_NAME = "Console";
private final static AppConfiguration APP_CFG = new AppConfiguration(APP_NAME, VERSION);
private final static ConsoleState STATE = new DefaultConsoleState(APP_CFG);
private final static String PROP_PREFIX = "org.eclipse.rdf4j.console.setting.";
private static boolean exitOnError;
private final ConsoleIO consoleIO;
private final SortedMap<String, ConsoleCommand> commandMap = new TreeMap<>();
private final SortedMap<String, ConsoleSetting> settingMap = new TreeMap<>();
// "Core" commands
private final Connect connect;
private final Disconnect disconnect;
private final Open open;
private final Close close;
/**
* Get console state
*
* @return basic console state
*/
public ConsoleState getState() {
return STATE;
}
/**
* Get console IO
*
* @return console
*/
public ConsoleIO getConsoleIO() {
return this.consoleIO;
}
/**
* Set exit on error mode
*
* @param mode true when error should exit
*/
protected void setExitOnError(boolean mode) {
Console.exitOnError = mode;
}
/**
* Main
*
* @param args command line arguments
* @throws IOException
*/
public static void main(final String[] args) throws IOException {
final Console console = new Console();
CmdLineParser parser = new CmdLineParser(console);
if (parser.parse(args) == null) {
System.exit(-1);
}
if (!parser.handleInfoOptions()) {
System.exit(0);
}
parser.handleEchoOptions();
parser.handleExitOption();
String location = parser.handleLocationGroup();
if (!parser.handleCautionGroup()) {
System.exit(3);
}
String otherArg = parser.handleOtherArg();
connectAndOpen(console, parser.getSelectedLocation(), location, otherArg);
console.start();
}
/**
* Connect to (and open) a repository, exit when connection fails
*
* @param console
* @param selectedLocation s for server, d for local directory
* @param location
* @param otherArg last argument, if any
*/
private static void connectAndOpen(Console console, String selectedLocation, String location, String otherArg) {
boolean connected;
if ("s".equals(selectedLocation)) {
connected = console.connect.connectRemote(location);
} else if ("d".equals(selectedLocation)) {
connected = console.connect.connectLocal(location);
} else {
connected = console.connect.connectDefault();
}
if (!connected) {
System.exit(2);
}
if (!otherArg.isEmpty()) {
console.open.openRepository(otherArg);
}
}
/**
* Add command to register of known commands
*
* @param cmd command to be added
*/
public final void register(ConsoleCommand cmd) {
commandMap.put(cmd.getName(), cmd);
}
/**
* Add setting to register of known settings
*
* @param setting setting to be added
*/
public final void register(ConsoleSetting setting) {
settingMap.put(setting.getName(), setting);
}
/**
* Constructor
*
* @throws IOException
*/
public Console() throws IOException {
APP_CFG.init();
consoleIO = new ConsoleIO(STATE);
// propagate console setting to JLine
SaveHistory lineHistory = new SaveHistory() {
@Override
public void set(Boolean val) {
super.set(val);
consoleIO.getLineReader().setVariable(LineReader.DISABLE_HISTORY, !val);
}
};
// Basic console parameters
register(new ConsoleWidth());
register(new LogLevel());
register(new Prefixes());
register(new QueryPrefix());
register(lineHistory);
register(new ShowPrefix());
register(new WorkDir());
this.close = new Close(consoleIO, STATE);
this.disconnect = new Disconnect(consoleIO, STATE, close);
this.connect = new Connect(consoleIO, STATE, disconnect);
this.open = new Open(consoleIO, STATE, close);
// "core" commands for connnecting
register(open);
register(close);
register(connect);
register(disconnect);
// querying
TupleAndGraphQueryEvaluator eval = new TupleAndGraphQueryEvaluator(consoleIO, STATE, settingMap);
register(new Federate(consoleIO, STATE));
register(new Sparql(eval));
// information
register(new PrintHelp(consoleIO, commandMap));
register(new PrintInfo(consoleIO, STATE));
register(new Show(consoleIO, STATE));
// repository management
register(new Create(consoleIO, STATE));
register(new Drop(consoleIO, STATE, close));
// handling data
register(new Verify(consoleIO, settingMap));
register(new Load(consoleIO, STATE, settingMap));
register(new Clear(consoleIO, STATE));
register(new Export(consoleIO, STATE, settingMap));
register(new Convert(consoleIO, STATE, settingMap));
// parameters
register(new SetParameters(consoleIO, STATE, settingMap));
}
/**
* Load settings from properties file (application.properties)
*/
private void loadSettings() {
Properties props = APP_CFG.getProperties();
settingMap.forEach((k, v) -> {
String val = props.getProperty(PROP_PREFIX + k, "");
try {
if (!val.isEmpty()) {
v.setFromString(val);
}
} catch (IllegalArgumentException iae) {
consoleIO.writeError("Illegal value for property " + k);
}
});
}
/**
* Save settings to default properties file (application.properties)
*/
private void saveSettings() {
Properties props = APP_CFG.getProperties();
settingMap.forEach((k, v) -> {
String prop = PROP_PREFIX + k;
String oldval = props.getProperty(prop, "");
String newval = v.getAsString();
String val = (newval != null) ? newval : oldval;
if (!val.isEmpty()) {
props.setProperty(prop, val);
} else {
props.remove(prop);
}
});
try {
APP_CFG.save();
} catch (IOException ex) {
consoleIO.writeError("Could not save properties: " + ex.getMessage());
}
}
/**
* Load history from file
*/
private void loadHistory() {
try {
consoleIO.getLineReader().getHistory().load();
} catch (IOException ioe) {
consoleIO.writeError("Could not load history: " + ioe.getMessage());
}
}
/**
* Save JLine history to a file, unless the setting saveHistory is set to false
*/
private void saveHistory() {
try {
consoleIO.getLineReader().getHistory().save();
} catch (IOException ioe) {
consoleIO.writeError("Could not save history: " + ioe.getMessage());
}
}
/**
* Start the interactive console, return error code on exit
*
* @throws IOException
*/
public void start() throws IOException {
loadSettings();
loadHistory();
consoleIO.writeln(APP_CFG.getFullName());
consoleIO.writeln("Working dir: " + settingMap.get(WorkDir.NAME).getAsString());
consoleIO.writeln("Type 'help' for help.");
int exitCode = 0;
try {
boolean exitFlag = false;
while (!exitFlag) {
final String command = consoleIO.readCommand();
if (command == null) {
// EOF
break;
}
exitFlag = executeCommand(command);
if (exitOnError && consoleIO.wasErrorWritten()) {
exitCode = 2;
exitFlag = true;
}
}
} catch (UserInterruptException | EndOfFileException e) {
exitCode = 0;
} finally {
disconnect.execute(false);
}
saveSettings();
saveHistory();
if (exitCode != 0) {
System.exit(exitCode);
}
consoleIO.writeln("Bye");
consoleIO.getOutputStream().close();
}
/**
* Execute a command
*
* @param command
* @return true when exit/quit command is entered
*/
private boolean executeCommand(String command) {
boolean exit = false;
// only try to parse the command if non-empty.
if (!command.isEmpty()) {
final String[] tokens = parse(command);
final String operation = tokens[0].toLowerCase(Locale.ENGLISH);
exit = "quit".equals(operation) || "exit".equals(operation);
if (!exit) {
ConsoleCommand cmd = commandMap.getOrDefault(operation, commandMap.get("sparql"));
if (cmd instanceof QueryEvaluator) {
((QueryEvaluator) cmd).executeQuery(command, operation);
} else {
cmd.execute(tokens);
}
}
}
return exit;
}
/**
* Split a command into an array of tokens
*
* @param command command to parse
* @return array of strings
*/
private String[] parse(String command) {
final Pattern pattern = Pattern.compile("\"([^\"]*)\"|(\\S+)");
final Matcher matcher = pattern.matcher(command);
final List<String> tokens = new ArrayList<>();
while (matcher.find()) {
if (matcher.group(1) == null) {
tokens.add(matcher.group());
} else {
tokens.add(matcher.group(1));
}
}
return tokens.toArray(new String[tokens.size()]);
}
}