You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rya.apache.org by mi...@apache.org on 2017/08/04 13:37:48 UTC

[4/7] incubator-rya git commit: RYA-325 Renamed rya.console to rya.shell. Closes #194

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/RyaBannerProvider.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/RyaBannerProvider.java b/extras/shell/src/main/java/org/apache/rya/shell/RyaBannerProvider.java
new file mode 100644
index 0000000..42581ed
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/RyaBannerProvider.java
@@ -0,0 +1,97 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.plugin.support.DefaultBannerProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * Customizes the Rya Shell's banner.
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class RyaBannerProvider extends DefaultBannerProvider implements CommandMarker {
+    private final Logger log = LoggerFactory.getLogger(RyaBannerProvider.class);
+
+    private static final String BANNER =
+    " _____                _____ _          _ _ \n" +
+    "|  __ \\              / ____| |        | | |\n" +
+    "| |__) |   _  __ _  | (___ | |__   ___| | |\n" +
+    "|  _  / | | |/ _` |  \\___ \\| '_ \\ / _ \\ | |\n" +
+    "| | \\ \\ |_| | (_| |  ____) | | | |  __/ | |\n" +
+    "|_|  \\_\\__, |\\__,_| |_____/|_| |_|\\___|_|_|\n" +
+    "        __/ |                              \n" +
+    "       |___/                               ";
+
+    private String version = null;
+
+    @Override
+    public String getBanner() {
+        return BANNER + "\n" + getVersion() + "\n";
+    }
+
+    @Override
+    public String getWelcomeMessage() {
+        return "Welcome to the Rya Shell.\n" +
+                "\n" +
+                "Execute one of the connect commands to start interacting with an instance of Rya.\n" +
+                "You may press tab at any time to see which of the commands are available.";
+    }
+
+    @Override
+    public String getVersion() {
+        if(version == null) {
+            version = loadVersion();
+        }
+        return version;
+    }
+
+    /**
+     * Loads the version number from the Rya Shell's MANIFEST.MF file.
+     *
+     * @return The version number of the Rya Shell.
+     */
+    private String loadVersion() {
+        final String className = getClass().getSimpleName() + ".class";
+        final String classPath = getClass().getResource( className ).toString();
+
+        try {
+            final URL classUrl = new URL(classPath);
+            final JarURLConnection jarConnection = (JarURLConnection) classUrl.openConnection();
+            final Manifest manifest = jarConnection.getManifest();
+            final Attributes attributes = manifest.getMainAttributes();
+            return attributes.getValue("Implementation-Version");
+        } catch (final IOException e) {
+            log.error("Could not load the application's version from it's manifest.", e);
+        }
+
+        return "UNKNOWN";
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/RyaCommands.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/RyaCommands.java b/extras/shell/src/main/java/org/apache/rya/shell/RyaCommands.java
new file mode 100644
index 0000000..09ee410
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/RyaCommands.java
@@ -0,0 +1,166 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.text.DecimalFormat;
+import java.util.Objects;
+
+import org.apache.rya.api.client.RyaClient;
+import org.apache.rya.api.client.RyaClientException;
+import org.apache.rya.shell.SharedShellState.ShellState;
+import org.apache.rya.shell.util.ConsolePrinter;
+import org.apache.rya.shell.util.SparqlPrompt;
+import org.openrdf.rio.RDFFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+import com.google.common.base.Optional;
+
+/**
+ * Rya Shell commands that have to do with common tasks (loading and querying data)
+ */
+@Component
+public class RyaCommands implements CommandMarker {
+
+    private static final Logger log = LoggerFactory.getLogger(RyaCommands.class);
+
+    public static final String LOAD_DATA_CMD = "load-data";
+    public static final String SPARQL_QUERY_CMD = "sparql-query";
+
+    private final SharedShellState state;
+    private final SparqlPrompt sparqlPrompt;
+    private final ConsolePrinter consolePrinter;
+
+    /**
+     * Constructs an instance of {@link RyaCommands}.
+     *
+     * @param state - Holds shared state between all of the command classes. (not null)
+     * @param sparqlPrompt - Prompts a user for a SPARQL query. (not null)
+     * @param consolePrinter - Allows the command to print feedback to the user. (not null)
+     */
+    @Autowired
+    public RyaCommands(final SharedShellState state, final SparqlPrompt sparqlPrompt,
+            final ConsolePrinter consolePrinter) {
+        this.state = Objects.requireNonNull(state);
+        this.sparqlPrompt = requireNonNull(sparqlPrompt);
+        this.consolePrinter = Objects.requireNonNull(consolePrinter);
+    }
+
+    /**
+     * Enables commands that are always available once the Shell is connected to a Rya Instance.
+     */
+    @CliAvailabilityIndicator({ LOAD_DATA_CMD, SPARQL_QUERY_CMD })
+    public boolean areInstanceCommandsAvailable() {
+        switch (state.getShellState().getConnectionState()) {
+        case CONNECTED_TO_INSTANCE:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    @CliCommand(value = LOAD_DATA_CMD, help = "Loads RDF Statement data from a local file to the connected Rya instance.")
+    public String loadData(
+            @CliOption(key = { "file" }, mandatory = true, help = "A local file containing RDF Statements that is to be loaded.")
+            final String file,
+            @CliOption(key = { "format" }, mandatory = false, help = "The format of the supplied RDF Statements file. [RDF/XML, N-Triples, Turtle, N3, TriX, TriG, BinaryRDF, N-Quads, JSON-LD, RDF/JSON, RDFa]")
+            final String format
+            ) {
+        // Fetch the command that is connected to the store.
+        final ShellState shellState = state.getShellState();
+        final RyaClient commands = shellState.getConnectedCommands().get();
+        final Optional<String> ryaInstanceName = shellState.getRyaInstanceName();
+        try {
+            final long start = System.currentTimeMillis();
+            final File rdfInputFile = new File(file);
+
+            RDFFormat rdfFormat = null;
+            if (format != null) {
+                rdfFormat = RDFFormat.valueOf(format);
+                if (rdfFormat == null) {
+                    throw new RuntimeException("Unsupported RDF Statement data input format: " + format);
+                }
+            }
+            if (rdfFormat == null) {
+                rdfFormat = RDFFormat.forFileName(rdfInputFile.getName());
+                if (rdfFormat == null) {
+                    throw new RuntimeException("Unable to detect RDF Statement data input format for file: " + rdfInputFile);
+                } else {
+                    consolePrinter.println("Detected RDF Format: " + rdfFormat);
+                    consolePrinter.flush();
+                }
+            }
+            commands.getLoadStatementsFile().loadStatements(ryaInstanceName.get(), rdfInputFile.toPath(), rdfFormat);
+
+            final String seconds = new DecimalFormat("0.0##").format((System.currentTimeMillis() - start) / 1000.0);
+            return "Loaded the file: '" + file + "' successfully in " + seconds + " seconds.";
+
+        } catch (final RyaClientException | IOException e) {
+            log.error("Error", e);
+            throw new RuntimeException("Can not load the RDF Statement data. Reason: " + e.getMessage(), e);
+        }
+    }
+
+    @CliCommand(value = SPARQL_QUERY_CMD, help = "Executes the provided SPARQL Query on the connected Rya instance.")
+    public String sparqlQuery(
+            @CliOption(key = { "file" }, mandatory = false, help = "A local file containing the SPARQL Query that is to be read and executed.")
+            final String file) {
+        // Fetch the command that is connected to the store.
+        final ShellState shellState = state.getShellState();
+        final RyaClient commands = shellState.getConnectedCommands().get();
+        final Optional<String> ryaInstanceName = shellState.getRyaInstanceName();
+
+        try {
+            // file option specified
+            String sparqlQuery;
+            if (file != null) {
+                sparqlQuery = new String(Files.readAllBytes(new File(file).toPath()), StandardCharsets.UTF_8);
+                consolePrinter.println("Loaded Query:");
+                consolePrinter.println(sparqlQuery);
+            } else {
+                // No Options specified. Show the user the SPARQL Prompt
+                final Optional<String> sparqlQueryOpt = sparqlPrompt.getSparql();
+                if (sparqlQueryOpt.isPresent()) {
+                    sparqlQuery = sparqlQueryOpt.get();
+                } else {
+                    return ""; // user aborted the SPARQL prompt.
+                }
+            }
+
+            consolePrinter.println("Executing Query...");
+            consolePrinter.flush();
+            return commands.getExecuteSparqlQuery().executeSparqlQuery(ryaInstanceName.get(), sparqlQuery);
+        } catch (final RyaClientException | IOException e) {
+            log.error("Error", e);
+            throw new RuntimeException("Can not execute the SPARQL Query. Reason: " + e.getMessage(), e);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/RyaConnectionCommands.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/RyaConnectionCommands.java b/extras/shell/src/main/java/org/apache/rya/shell/RyaConnectionCommands.java
new file mode 100644
index 0000000..f5ba451
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/RyaConnectionCommands.java
@@ -0,0 +1,166 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.CharBuffer;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Connector;
+import org.apache.rya.api.client.InstanceExists;
+import org.apache.rya.api.client.RyaClient;
+import org.apache.rya.api.client.RyaClientException;
+import org.apache.rya.api.client.accumulo.AccumuloConnectionDetails;
+import org.apache.rya.api.client.accumulo.AccumuloRyaClientFactory;
+import org.apache.rya.shell.SharedShellState.ConnectionState;
+import org.apache.rya.shell.util.ConnectorFactory;
+import org.apache.rya.shell.util.PasswordPrompt;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+import com.google.common.base.Optional;
+
+/**
+ * Spring Shell commands that manage the connection that is used by the shell.
+ */
+@Component
+public class RyaConnectionCommands implements CommandMarker {
+
+    // Command line commands.
+    public static final String PRINT_CONNECTION_DETAILS_CMD = "print-connection-details";
+    public static final String CONNECT_ACCUMULO_CMD = "connect-accumulo";
+    public static final String CONNECT_INSTANCE_CMD = "connect-rya";
+    public static final String DISCONNECT_COMMAND_NAME_CMD = "disconnect";
+
+    private final SharedShellState sharedState;
+    private final PasswordPrompt passwordPrompt;
+
+    /**
+     * Constructs an instance of {@link RyaConnectionCommands}.
+     *
+     * @param state - Holds shared state between all of the command classes. (not null)
+     * @param passwordPrompt - Prompts the user for their password when connecting to a Rya store. (not null)
+     */
+    @Autowired
+    public RyaConnectionCommands(final SharedShellState state, final PasswordPrompt passwordPrompt) {
+        this.sharedState = requireNonNull( state );
+        this.passwordPrompt = requireNonNull(passwordPrompt);
+    }
+
+    @CliAvailabilityIndicator({PRINT_CONNECTION_DETAILS_CMD})
+    public boolean isPrintConnectionDetailsAvailable() {
+        return true;
+    }
+
+    @CliAvailabilityIndicator({CONNECT_ACCUMULO_CMD})
+    public boolean areConnectCommandsAvailable() {
+        return sharedState.getShellState().getConnectionState() == ConnectionState.DISCONNECTED;
+    }
+
+    @CliAvailabilityIndicator({CONNECT_INSTANCE_CMD})
+    public boolean isConnectToInstanceAvailable() {
+        switch(sharedState.getShellState().getConnectionState()) {
+            case CONNECTED_TO_STORAGE:
+            case CONNECTED_TO_INSTANCE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @CliAvailabilityIndicator({DISCONNECT_COMMAND_NAME_CMD})
+    public boolean isDisconnectAvailable() {
+        return sharedState.getShellState().getConnectionState() != ConnectionState.DISCONNECTED;
+    }
+
+    @CliCommand(value = PRINT_CONNECTION_DETAILS_CMD, help = "Print information about the Shell's Rya storage connection.")
+    public String printConnectionDetails() {
+        final Optional<AccumuloConnectionDetails> detailsHolder = sharedState.getShellState().getConnectionDetails();
+
+        if(detailsHolder.isPresent()) {
+            final AccumuloConnectionDetails details = detailsHolder.get();
+            return "The shell is connected to an instance of Accumulo using the following parameters:\n" +
+                    "    Username: " + details.getUsername() + "\n" +
+                    "    Instance Name: " + details.getInstanceName() + "\n" +
+                    "    Zookeepers: " + details.getZookeepers();
+        } else {
+            return "The shell is not connected to anything.";
+        }
+    }
+
+    @CliCommand(value = CONNECT_ACCUMULO_CMD, help = "Connect the shell to an instance of Accumulo.")
+    public String connectToAccumulo(
+            @CliOption(key = {"username"}, mandatory = true, help = "The username that will be used to connect to Accummulo.")
+            final String username,
+            @CliOption(key = {"instanceName"}, mandatory = true, help = "The name of the Accumulo instance that will be connected to.")
+            final String instanceName,
+            @CliOption(key = {"zookeepers"}, mandatory = true, help = "A comma delimited list of zookeeper server hostnames.")
+            final String zookeepers
+            ) {
+
+        try {
+            // Prompt the user for their password.
+            final char[] password = passwordPrompt.getPassword();
+            final Connector connector= new ConnectorFactory().connect(username, CharBuffer.wrap(password), instanceName, zookeepers);
+
+            // Initialize the connected to Accumulo shared state.
+            final AccumuloConnectionDetails accumuloDetails = new AccumuloConnectionDetails(username, password, instanceName, zookeepers);
+            final RyaClient commands = AccumuloRyaClientFactory.build(accumuloDetails, connector);
+            sharedState.connectedToAccumulo(accumuloDetails, commands);
+
+        } catch(IOException | AccumuloException | AccumuloSecurityException e) {
+            throw new RuntimeException("Could not connect to Accumulo. Reason: " + e.getMessage(), e);
+        }
+
+        return "Connected. You must select a Rya instance to interact with next.";
+    }
+
+    @CliCommand(value = CONNECT_INSTANCE_CMD, help = "Connect to a specific Rya instance")
+    public void connectToInstance(
+            @CliOption(key = {"instance"}, mandatory = true, help = "The name of the Rya instance the shell will interact with.")
+            final String instance) {
+        try {
+            final InstanceExists instanceExists = sharedState.getShellState().getConnectedCommands().get().getInstanceExists();
+
+            // TODO gracefully fail if that version doen't support it. maybe the list command should go ahead
+
+            // Make sure the requested instance exists.
+            if(!instanceExists.exists(instance)) {
+                throw new RuntimeException(String.format("'%s' does not match an existing Rya instance.", instance));
+            }
+        } catch(final RyaClientException e) {
+            throw new RuntimeException("Could not connect to Rya instance. Reason: " + e.getMessage(), e);
+        }
+
+        // Store the instance name in the shared state.
+        sharedState.connectedToInstance(instance);
+    }
+
+    @CliCommand(value = DISCONNECT_COMMAND_NAME_CMD, help = "Disconnect the shell's Rya storage connection (Accumulo).")
+    public void disconnect() {
+        sharedState.disconnected();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/RyaPromptProvider.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/RyaPromptProvider.java b/extras/shell/src/main/java/org/apache/rya/shell/RyaPromptProvider.java
new file mode 100644
index 0000000..ed5f261
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/RyaPromptProvider.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import static java.util.Objects.requireNonNull;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.shell.plugin.support.DefaultPromptProvider;
+import org.springframework.stereotype.Component;
+
+import org.apache.rya.shell.SharedShellState.ShellState;
+
+/**
+ * Customizes the Rya Shell's prompt.
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class RyaPromptProvider extends DefaultPromptProvider {
+
+    private final SharedShellState sharedState;
+
+    @Autowired
+    public RyaPromptProvider(final SharedShellState sharedState) {
+        this.sharedState = requireNonNull(sharedState);
+    }
+
+    @Override
+    public String getPrompt() {
+        final ShellState state = sharedState.getShellState();
+
+        switch(state.getConnectionState()) {
+            case DISCONNECTED:
+                return "rya> ";
+            case CONNECTED_TO_STORAGE:
+                return String.format("rya/%s> ", state.getConnectionDetails().get().getInstanceName());
+            case CONNECTED_TO_INSTANCE:
+                return String.format("rya/%s:%s> ",
+                        state.getConnectionDetails().get().getInstanceName(),
+                        state.getRyaInstanceName().get());
+            default:
+                return "rya> ";
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/RyaShellHistoryProvider.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/RyaShellHistoryProvider.java b/extras/shell/src/main/java/org/apache/rya/shell/RyaShellHistoryProvider.java
new file mode 100644
index 0000000..b4ade8f
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/RyaShellHistoryProvider.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import java.io.File;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.shell.plugin.HistoryFileNameProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * Customizes the Rya Shell's history file.
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class RyaShellHistoryProvider implements HistoryFileNameProvider {
+
+    public static final String RYA_SHELL_HISTORY_FILENAME = ".rya_shell_history";
+
+    @Override
+    public String getHistoryFileName() {
+        final String userHome = System.getProperty("user.home");
+        if(userHome == null) {
+            return RYA_SHELL_HISTORY_FILENAME;
+        } else {
+            return new File(userHome, RYA_SHELL_HISTORY_FILENAME).getAbsolutePath();
+        }
+    }
+
+    @Override
+    public String getProviderName() {
+        return this.getClass().getSimpleName();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/SharedShellState.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/SharedShellState.java b/extras/shell/src/main/java/org/apache/rya/shell/SharedShellState.java
new file mode 100644
index 0000000..526b031
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/SharedShellState.java
@@ -0,0 +1,339 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.ThreadSafe;
+
+import com.google.common.base.Optional;
+
+import org.apache.rya.api.client.RyaClient;
+import org.apache.rya.api.client.accumulo.AccumuloConnectionDetails;
+
+/**
+ * Holds values that are shared between the various Rya command classes.
+ */
+@ThreadSafe
+@DefaultAnnotation(NonNull.class)
+public class SharedShellState {
+    // The shared nature of this object means we shouldn't assume only a single thread is accessing it.
+    private final ReentrantLock lock = new ReentrantLock();
+
+    // The current state.
+    private ShellState shellState = ShellState.builder()
+            .setConnectionState( ConnectionState.DISCONNECTED )
+            .build();
+
+    /**
+     * @return The values that define the state of the Rya Shell.
+     */
+    public ShellState getShellState() {
+        lock.lock();
+        try {
+            return shellState;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * This method indicates a shift into the {@link ConnectionState#CONNECTED_TO_STORAGE} state.
+     * <p/>
+     * Store the values used by an Accumulo Rya Storage connection. This may
+     * only be called when the shell is disconnected.
+     *
+     * @param connectionDetails - Metadata about the Accumulo connection. (not null)
+     * @param connectedCommands - Rya Commands that will execute against the Accumulo instance. (not null)
+     * @throws IllegalStateException Thrown if the shell is already connected to a Rya storage.
+     */
+    public void connectedToAccumulo(
+            final AccumuloConnectionDetails connectionDetails,
+            final RyaClient connectedCommands) throws IllegalStateException {
+        requireNonNull(connectionDetails);
+        requireNonNull(connectedCommands);
+
+        lock.lock();
+        try {
+            // Ensure the Rya Shell is disconnected.
+            if(shellState.getConnectionState() != ConnectionState.DISCONNECTED) {
+                throw new IllegalStateException("You must clear the old connection state before you may set a new connection state.");
+            }
+
+            // Store the connection details.
+            shellState = ShellState.builder()
+                .setConnectionState( ConnectionState.CONNECTED_TO_STORAGE )
+                .setAccumuloConnectionDetails( connectionDetails )
+                .setConnectedCommands( connectedCommands )
+                .build();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * This method indicates a shift into the {@link ConnectionState#CONNECTED_TO_INSTANCE} state.
+     * <p/>
+     * Store the name of the Rya instance all commands will be executed against.
+     *
+     * @param instanceName - The name of the Rya instance. (not null)
+     * @throws IllegalStateException Thrown if the shell is disconnected.
+     */
+    public void connectedToInstance(final String instanceName) throws IllegalStateException {
+        requireNonNull(instanceName);
+
+        lock.lock();
+        try {
+            // Verify the Rya Shell is connected to a storage.
+            if(shellState.getConnectionState() == ConnectionState.DISCONNECTED) {
+                throw new IllegalStateException("You can not set a Rya Instance Name before connecting to a Rya Storage.");
+            }
+
+            // Set the instance name.
+            shellState = ShellState.builder( shellState )
+                    .setConnectionState(ConnectionState.CONNECTED_TO_INSTANCE)
+                    .setRyaInstanceName( instanceName )
+                    .build();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * This method indicates a shift into the {@link DISCONNECTED} state.
+     * <p/>
+     * Clears all of the values associated with a Rya Storage/Instance connection.
+     * If the shell is already disconnected, then this method does not do anything.
+     */
+    public void disconnected() {
+        lock.lock();
+        try {
+            shellState = ShellState.builder()
+                .setConnectionState(ConnectionState.DISCONNECTED)
+                .build();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Enumerates the various states a Rya Shell may be in.
+     */
+    public static enum ConnectionState {
+        /**
+         * The shell is not connected to a Rya Storage.
+         */
+        DISCONNECTED,
+
+        /**
+         * The shell is connected to a Rya Storage, but a specific instance hasn't been set.
+         */
+        CONNECTED_TO_STORAGE,
+
+        /**
+         * The shell is connected to Rya Storage and a specific Rya Instance.
+         */
+        CONNECTED_TO_INSTANCE;
+    }
+
+    /**
+     * Values that define the state of a Rya Shell.
+     */
+    @Immutable
+    @DefaultAnnotation(NonNull.class)
+    public static final class ShellState {
+        // Indicates the state of the shell.
+        private final ConnectionState connectionState;
+
+        // Connection specific values.
+        private final Optional<AccumuloConnectionDetails> connectionDetails;
+        private final Optional<RyaClient> connectedCommands;
+
+        // Instance specific values.
+        private final Optional<String> instanceName;
+
+        private ShellState(
+                final ConnectionState connectionState,
+                final Optional<AccumuloConnectionDetails> connectionDetails,
+                final Optional<RyaClient> connectedCommands,
+                final Optional<String> instanceName) {
+            this.connectionState = requireNonNull(connectionState);
+            this.connectionDetails = requireNonNull(connectionDetails);
+            this.connectedCommands = requireNonNull(connectedCommands);
+            this.instanceName = requireNonNull(instanceName);
+        }
+
+        /**
+         * @return The {@link ConnectionState} of the Rya Shell.
+         */
+        public ConnectionState getConnectionState() {
+            return connectionState;
+        }
+
+        /**
+         * @return Metadata about the Accumulo connection. The value will not be present
+         *   if the Rya Shell is not connected to a storage.
+         */
+        public Optional<AccumuloConnectionDetails> getConnectionDetails() {
+            return connectionDetails;
+        }
+
+        /**
+         * @return The {@link RyaClient} to use when a command on the shell is issued.
+         *   The value will not be present if the Rya Shell is not connected to a storage.
+         */
+        public Optional<RyaClient> getConnectedCommands() {
+            return connectedCommands;
+        }
+
+        /**
+         * @return The name of the Rya Instance the Rya Shell is issuing commands to.
+         *   The value will not be present if the Rya Shell is not connected to a
+         *   storage or if a target instance has not been set yet.
+         */
+        public Optional<String> getRyaInstanceName() {
+            return instanceName;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(connectionState, connectionDetails, connectedCommands, instanceName);
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if(this == obj) {
+                return true;
+            }
+            if(obj instanceof ShellState) {
+                final ShellState state = (ShellState)obj;
+                return Objects.equals(connectionState, state.connectionState) &&
+                        Objects.equals(connectionDetails, state.connectionDetails) &&
+                        Objects.equals(connectedCommands, state.connectedCommands) &&
+                        Objects.equals(instanceName, state.instanceName);
+            }
+            return false;
+        }
+
+        /**
+         * @return An empty instance of {@link Builder}.
+         */
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        /**
+         * Create an instance of {@link Builder} populated with the values of {code shellState}.
+         *
+         * @param shellState - The initial state of the Builder.
+         * @return An instance of {@link Builder} populated with the values
+         *   of {code shellState}.
+         */
+        public static Builder builder(final ShellState shellState) {
+            return new Builder(shellState);
+        }
+
+        /**
+         * Builds instances of {@link ShellState}.
+         */
+        @DefaultAnnotation(NonNull.class)
+        public static class Builder {
+            private ConnectionState connectionState;
+
+            // Connection specific values.
+            private AccumuloConnectionDetails connectionDetails;
+            private RyaClient connectedCommands;
+
+            // Instance specific values.
+            private String instanceName;
+
+            /**
+             * Constructs an empty instance of {@link Builder}.
+             */
+            public Builder() { }
+
+            /**
+             * Constructs an instance of {@builder} initialized with the values
+             * of a {@link ShellState}.
+             *
+             * @param shellState - The initial state of the builder. (not null)
+             */
+            public Builder(final ShellState shellState) {
+                this.connectionState = shellState.getConnectionState();
+                this.connectionDetails = shellState.getConnectionDetails().orNull();
+                this.connectedCommands = shellState.getConnectedCommands().orNull();
+                this.instanceName = shellState.getRyaInstanceName().orNull();
+            }
+
+            /**
+             * @param connectionState - The {@link ConnectionState} of the Rya Shell.
+             * @return This {@link Builder} so that method invocations may be chained.
+             */
+            public Builder setConnectionState(@Nullable final ConnectionState connectionState) {
+                this.connectionState = connectionState;
+                return this;
+            }
+
+            /**
+             * @param connectionDetails - Metadata about the Accumulo connection.
+             * @return This {@link Builder} so that method invocations may be chained.
+             */
+            public Builder setAccumuloConnectionDetails(@Nullable final AccumuloConnectionDetails connectionDetails) {
+                this.connectionDetails = connectionDetails;
+                return this;
+            }
+
+            /**
+             * @param connectedCommands - The {@link RyaClient} to use when a command on the shell is issued.
+             * @return This {@link Builder} so that method invocations may be chained.
+             */
+            public Builder setConnectedCommands(@Nullable final RyaClient connectedCommands) {
+                this.connectedCommands = connectedCommands;
+                return this;
+            }
+
+            /**
+             * @param instanceName - The name of the Rya Instance the Rya Shell is issuing commands to.
+             * @return This {@link Builder} so that method invocations may be chained.
+             */
+            public Builder setRyaInstanceName(@Nullable final String instanceName) {
+                this.instanceName = instanceName;
+                return this;
+            }
+
+            /**
+             * @return An instance of {@link ShellState} built using this builder's values.
+             */
+            public ShellState build() {
+                return new ShellState(
+                        connectionState,
+                        Optional.fromNullable(connectionDetails),
+                        Optional.fromNullable(connectedCommands),
+                        Optional.fromNullable(instanceName));
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/ConnectorFactory.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/ConnectorFactory.java b/extras/shell/src/main/java/org/apache/rya/shell/util/ConnectorFactory.java
new file mode 100644
index 0000000..d18c5c8
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/ConnectorFactory.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Connector;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.ZooKeeperInstance;
+import org.apache.accumulo.core.client.security.tokens.PasswordToken;
+
+/**
+ * Creates {@link Connector}s that are linked to an instance of Accumulo.
+ */
+@DefaultAnnotation(NonNull.class)
+public class ConnectorFactory {
+
+    /**
+     * Create a {@link Connector} that uses the provided connection details.
+     *
+     * @param username - The username the connection will use. (not null)
+     * @param password - The password the connection will use. (not null)
+     * @param instanceName - The name of the Accumulo instance. (not null)
+     * @param zookeeperHostnames - A comma delimited list of the Zookeeper server hostnames. (not null)
+     * @return A {@link Connector} that may be used to access the instance of Accumulo.
+     * @throws AccumuloSecurityException Could not connect for security reasons.
+     * @throws AccumuloException Could not connect for other reasons.
+     */
+    public Connector connect(
+            final String username,
+            final CharSequence password,
+            final String instanceName,
+            final String zookeeperHostnames) throws AccumuloException, AccumuloSecurityException {
+        requireNonNull(username);
+        requireNonNull(password);
+        requireNonNull(instanceName);
+        requireNonNull(zookeeperHostnames);
+
+        // Setup the password token that will be used.
+        final PasswordToken token = new PasswordToken( password );
+
+        // Connect to the instance of Accumulo.
+        final Instance instance = new ZooKeeperInstance(instanceName, zookeeperHostnames);
+        return instance.getConnector(username, token);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/ConsolePrinter.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/ConsolePrinter.java b/extras/shell/src/main/java/org/apache/rya/shell/util/ConsolePrinter.java
new file mode 100644
index 0000000..4492a87
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/ConsolePrinter.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import java.io.IOException;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jline.console.ConsoleReader;
+
+/**
+ * A mechanism for printing content to the console.
+ */
+@DefaultAnnotation(NonNull.class)
+public interface ConsolePrinter {
+
+    /**
+     * Prints the provided content to the console.
+     * @param cs - Output the specified String to the console.
+     * @throws IOException There was a problem reading the user's input.
+     */
+    public void print(CharSequence cs) throws IOException;
+
+    /**
+     * Prints the provided content to the console with a newline.
+     * @param cs - Output the specified String to the console.
+     * @throws IOException There was a problem reading the user's input.
+     */
+    public void println(CharSequence cs) throws IOException;
+
+    /**
+     * Prints a newline.
+     * @throws IOException There was a problem reading the user's input.
+     */
+    public void println() throws IOException;
+
+    /**
+     * Flush any pending console updates to the console output stream.
+     * @throws IOException
+     */
+    public void flush() throws IOException;
+
+    /**
+     * Prints to the console using a JLine {@link ConsoleReader}.
+     */
+    @DefaultAnnotation(NonNull.class)
+    public static class JLineConsolePrinter extends JLinePrompt implements ConsolePrinter {
+
+        @Override
+        public void print(final CharSequence cs) throws IOException {
+            getReader().print(cs);
+        }
+
+        @Override
+        public void println(final CharSequence cs) throws IOException {
+            getReader().println(cs);
+        }
+
+        @Override
+        public void println() throws IOException {
+            getReader().println();
+        }
+
+        @Override
+        public void flush() throws IOException {
+            getReader().flush();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/InstallPrompt.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/InstallPrompt.java b/extras/shell/src/main/java/org/apache/rya/shell/util/InstallPrompt.java
new file mode 100644
index 0000000..5d9d48b
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/InstallPrompt.java
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import java.io.IOException;
+
+import org.apache.rya.api.client.Install.InstallConfiguration;
+
+import com.google.common.base.Optional;
+
+import jline.console.ConsoleReader;
+
+/**
+ * A mechanism for prompting a user of the application for a the parameters
+ * that will be used when installing an instance of Rya.
+ */
+public interface InstallPrompt {
+
+    /**
+     * Prompt the user for the name of the Rya instance that will be created.
+     *
+     * @return The value they entered.
+     * @throws IOException There was a problem reading the value.
+     */
+    public String promptInstanceName() throws IOException;
+
+    /**
+     * Prompt the user for which features of Rya they want enabled.
+     *
+     * @param instanceName - The Rya instance name.
+     * @return The value they entered.
+     * @throws IOException There was a problem reading the values.
+     */
+    public InstallConfiguration promptInstallConfiguration(String instanceName) throws IOException;
+
+    /**
+     * Prompt the user asking them if they are sure they would like to do the
+     * install.
+     *
+     * @return The value they entered.
+     * @throws IOException There was a problem reading the value.
+     */
+    public boolean promptVerified(String instanceName, InstallConfiguration installConfig) throws IOException;
+
+    /**
+     * Prompts a user for install information using a JLine {@link ConsoleReader}.
+     */
+    public static class JLineAccumuloInstallPrompt extends JLinePrompt implements InstallPrompt {
+
+        @Override
+        public String promptInstanceName() throws IOException {
+            final String prompt = makeFieldPrompt("Rya Instance Name", "rya_");
+            final String instanceName = promptString(prompt, Optional.of("rya_"));
+            return instanceName;
+        }
+
+        @Override
+        public InstallConfiguration promptInstallConfiguration(final String instanceName) throws IOException {
+            final InstallConfiguration.Builder builder = InstallConfiguration.builder();
+
+            String prompt = makeFieldPrompt("Use Shard Balancing (improves streamed input write speeds)", false);
+            final boolean enableTableHashPrefix = promptBoolean(prompt, Optional.of(false));
+            builder.setEnableTableHashPrefix( enableTableHashPrefix );
+
+            prompt = makeFieldPrompt("Use Entity Centric Indexing", true);
+            final boolean enableEntityCentricIndexing = promptBoolean(prompt, Optional.of(true));
+            builder.setEnableEntityCentricIndex( enableEntityCentricIndexing );
+
+            prompt = makeFieldPrompt("Use Free Text Indexing", true);
+            final boolean enableFreeTextIndexing = promptBoolean(prompt, Optional.of(true));
+            builder.setEnableFreeTextIndex( enableFreeTextIndexing );
+
+            prompt = makeFieldPrompt("Use Geospatial Indexing", true);
+            final boolean enableGeoIndexing = promptBoolean(prompt, Optional.of(true));
+            builder.setEnableGeoIndex( enableGeoIndexing );
+
+            prompt = makeFieldPrompt("Use Temporal Indexing", true);
+            final boolean useTemporalIndexing = promptBoolean(prompt, Optional.of(true));
+            builder.setEnableTemporalIndex( useTemporalIndexing );
+
+            prompt = makeFieldPrompt("Use Precomputed Join Indexing", true);
+            final boolean enablePCJIndexing = promptBoolean(prompt, Optional.of(true));
+            builder.setEnablePcjIndex( enablePCJIndexing );
+
+            if(enablePCJIndexing) {
+                final boolean useFluoApp = promptBoolean("Use a Fluo application to update the PCJ Index? (y/n) ", Optional.absent());
+
+                if(useFluoApp) {
+                    prompt = makeFieldPrompt("PCJ Updater Fluo Application Name (must be initialized)", instanceName + "pcj_updater");
+                    final String fluoAppName = promptString(prompt, Optional.of(instanceName + "pcj_updater"));
+                    builder.setFluoPcjAppName(fluoAppName);
+                }
+            }
+
+            return builder.build();
+        }
+
+        @Override
+        public boolean promptVerified(final String instanceName, final InstallConfiguration installConfig) throws IOException {
+            final ConsoleReader reader = getReader();
+            reader.println();
+            reader.println("A Rya instance will be installed using the following values:");
+            reader.println("   Instance Name: " + instanceName);
+            reader.println("   Use Shard Balancing: " + installConfig.isTableHashPrefixEnabled());
+            reader.println("   Use Entity Centric Indexing: " + installConfig.isEntityCentrixIndexEnabled());
+            reader.println("   Use Free Text Indexing: " + installConfig.isFreeTextIndexEnabled());
+            reader.println("   Use Geospatial Indexing: " + installConfig.isGeoIndexEnabled());
+            reader.println("   Use Temporal Indexing: " + installConfig.isTemporalIndexEnabled());
+            reader.println("   Use Precomputed Join Indexing: " + installConfig.isPcjIndexEnabled());
+            if(installConfig.isPcjIndexEnabled()) {
+                if(installConfig.getFluoPcjAppName().isPresent()) {
+                    reader.println("   PCJ Updater Fluo Application Name: " + installConfig.getFluoPcjAppName().get());
+                } else {
+                    reader.println("   Not using a PCJ Updater Fluo Application");
+                }
+            }
+
+            reader.println("");
+
+            return promptBoolean("Continue with the install? (y/n) ", Optional.absent());
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/InstanceNamesFormatter.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/InstanceNamesFormatter.java b/extras/shell/src/main/java/org/apache/rya/shell/util/InstanceNamesFormatter.java
new file mode 100644
index 0000000..f50164b
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/InstanceNamesFormatter.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Pretty formats a list of Rya instance names.
+ */
+@DefaultAnnotation(NonNull.class)
+public class InstanceNamesFormatter {
+
+    /**
+     * Formats the list of Rya instance names with a '*' next to whichever entry
+     * matches the connected name.
+     *
+     * @param names - The Rya instance names. (not null)
+     * @param connectedName - The instance name that will have a '*' next to it. (not null)
+     * @return A string holding the pretty formatted list.
+     */
+    public String format(final List<String> names, final String connectedName) {
+        requireNonNull(names);
+        requireNonNull(connectedName);
+
+        // Will be -1 if the connected name isn't in the list of names, so none will be starred.
+        final int connectedIndex = names.indexOf( connectedName );
+
+        final StringBuilder formatted = new StringBuilder("Rya instance names:\n");
+        for(int i = 0; i < names.size(); i++) {
+            if(i == connectedIndex) {
+                formatted.append(" * ");
+            } else {
+                formatted.append("   ");
+            }
+            formatted.append( names.get(i) ).append("\n");
+        }
+
+        return formatted.toString();
+    }
+
+    /**
+     * Formats the list of Rya instance names.
+     *
+     * @param names - The Rya instance names. (not null)
+     * @return A string holding the pretty formatted list.
+     */
+    public String format(final List<String> names) {
+        requireNonNull(names);
+
+        final StringBuilder formatted = new StringBuilder("Rya instance names:\n");
+        for(int i = 0; i < names.size(); i++) {
+            formatted.append("   ").append( names.get(i) ).append("\n");
+        }
+
+        return formatted.toString();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/JLinePrompt.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/JLinePrompt.java b/extras/shell/src/main/java/org/apache/rya/shell/util/JLinePrompt.java
new file mode 100644
index 0000000..a259593
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/JLinePrompt.java
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.util.FieldUtils;
+import org.springframework.shell.core.Shell;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jline.console.ConsoleReader;
+
+/**
+ * Provides access to the host {@link Shell}'s {@link ConsoleReader} and some
+ * utility functions for using it.
+ */
+@DefaultAnnotation(NonNull.class)
+public abstract class JLinePrompt {
+
+    /**
+     * Defines things that may be typed in response to a boolean prompt that evaluate to true.
+     */
+    private static final Set<String> affirmativeStrings = Sets.newHashSet("true", "t", "yes", "y");
+
+    /**
+     * Defines things that may be typed in response to a boolean prompt that evaluate to false.
+     */
+    private static final Set<String> negativeStrings = Sets.newHashSet("false", "f", "no", "n");
+
+    @Autowired
+    private Shell shell;
+
+    /**
+     * @return The shell's {@link ConsoleReader}.
+     */
+    public ConsoleReader getReader() {
+        // XXX Spring Shell doesn't expose the reader that we need to use to
+        //     read values from a terminal, so use reflection to pull it out.
+        return (ConsoleReader) FieldUtils.getProtectedFieldValue("reader", shell);
+    }
+
+    /**
+     * Formats a prompt that shows a default value.
+     *
+     * @param fieldName - The text portion that appears before the default. (not null)
+     * @param defaultValue - The default value that will be shown in the prompt.
+     * @return A prompt that shows the default value for a field.
+     */
+    public String makeFieldPrompt(final String fieldName, final boolean defaultValue) {
+    	return makeFieldPrompt(fieldName, Boolean.toString(defaultValue));
+    }
+
+    /**
+     * Formats a prompt that shows a default value.
+     *
+     * @param fieldName - The text portion that appears before the default. (not null)
+     * @param defaultValue - The default value that will be shown in the prompt.
+     * @return A prompt that shows the default value for a field.
+     */
+    public String makeFieldPrompt(final String fieldName, final String defaultValue) {
+        return String.format("%s [default: %s]: ", fieldName, defaultValue);
+    }
+
+    /**
+     * Checks if a user's input matches one of the affirmative strings.
+     *
+     * @param input - The user's input. (not null)
+     * @return {@code true} if the input is one of the affirmative values; otherwise {@code false}.
+     */
+    private boolean isAffirmative(final String input) {
+        requireNonNull(input);
+        for(final String affirmativeString : affirmativeStrings) {
+            if( input.equalsIgnoreCase(affirmativeString) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if a user's input matches one of the negative strings.
+     *
+     * @param input - The user's input. (not null)
+     * @return {@code true} if the input is one of the negative values; otherwise {@code false}.
+     */
+    private boolean isNegative(final String input) {
+        requireNonNull(input);
+        for(final String negativeString : negativeStrings) {
+            if( input.equalsIgnoreCase(negativeString) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Prompts a user for a boolean value. The prompt will be repeated until
+     * a value true/false string has been submitted. If a default value is
+     * provided, then it will be used if the user doens't enter anything.
+     *
+     * @param prompt - The prompt for the input. (not null)
+     * @param defaultValue - The default value for the input if one is provided. (not null)
+     * @return The value the user entered.
+     * @throws IOException There was a problem reading values from the user.
+     */
+    protected boolean promptBoolean(final String prompt, final Optional<Boolean> defaultValue) throws IOException {
+        requireNonNull(prompt);
+        requireNonNull(defaultValue);
+
+        final ConsoleReader reader = getReader();
+        reader.setPrompt(prompt);
+
+        Boolean value = null;
+        boolean prompting = true;
+
+        while(prompting) {
+            // An empty input means to use the default value.
+            final String input = reader.readLine();
+            if(input.isEmpty() && defaultValue.isPresent()) {
+                value = defaultValue.get();
+                prompting = false;
+            }
+
+            // Check if it is one of the affirmative answers.
+            if(isAffirmative(input)) {
+                value = true;
+                prompting = false;
+            }
+
+            // Check if it is one of the negative answers.
+            if(isNegative(input)) {
+                value = false;
+                prompting = false;
+            }
+
+            // If we are still prompting, the input was invalid.
+            if(prompting) {
+                reader.println("Invalid response (true/false)");
+            }
+        }
+
+        return value;
+    }
+
+    /**
+     * Prompts a user for a String value. The prompt will be repeated until a
+     * value has been submitted. If a default value is provided, then it will be
+     * used if the user doesn't enter anything.
+     *
+     * @param prompt - The prompt for the input. (not null)
+     * @param defaultValue - The default value for the input if one is provided. (not null)
+     * @return The value the user entered.
+     * @throws IOException There was a problem reading values from the user.
+     */
+    protected String promptString(final String prompt, final Optional<String> defaultValue) throws IOException {
+        requireNonNull(prompt);
+        requireNonNull(defaultValue);
+
+        final ConsoleReader reader = getReader();
+        reader.setPrompt(prompt);
+
+        String value = null;
+        boolean prompting = true;
+
+        while(prompting) {
+            // Read a line of input.
+            final String input = reader.readLine();
+
+            if(!input.isEmpty()) {
+                // If a value was provided, return it.
+                value = input;
+                prompting = false;
+            } else {
+                // Otherwise, if a default value was provided, return it;
+                if(defaultValue.isPresent()) {
+                    value = defaultValue.get();
+                    prompting = false;
+                } else {
+                    // Otherwise, the user must provide a value.
+                    reader.println("Invalid response. Must provide a value.");
+                }
+            }
+        }
+
+        return value;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/PasswordPrompt.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/PasswordPrompt.java b/extras/shell/src/main/java/org/apache/rya/shell/util/PasswordPrompt.java
new file mode 100644
index 0000000..b61faf9
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/PasswordPrompt.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import java.io.IOException;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import jline.console.ConsoleReader;
+
+/**
+ * A mechanism for prompting a user of the application for a password.
+ */
+@DefaultAnnotation(NonNull.class)
+public interface PasswordPrompt {
+
+    /**
+     * Prompt the user for a password, wait for their input, and then get the
+     * value they entered.
+     *
+     * @return A character array holding the entered password.
+     * @throws IOEXception There was a problem reading the password.
+     */
+    public char[] getPassword() throws IOException;
+
+    /**
+     * Prompts a user for their password using a JLine {@link ConsoleReader}.
+     * <p>
+     * This prompt has a known security issue. ConsoleReader only reads passwords
+     * into Strings, so they can't easily be cleared out. We many an attempt to
+     * garbage collect the String after converting it to a character array, but
+     * this could be improved.
+     */
+    public static class JLinePasswordPrompt extends JLinePrompt implements PasswordPrompt {
+
+        @Override
+        public char[] getPassword() throws IOException {
+            char[] password = new char[0];
+
+            final ConsoleReader reader = getReader();
+            reader.setPrompt("Password: ");
+            String passwordStr = reader.readLine('*');
+            password = passwordStr.toCharArray();
+
+            // Reading the password into memory as a String is less safe than a char[]
+            // because the String is immutable. We can't clear it out. At best, we can
+            // remove all references to it and suggest the GC clean it up. There are no
+            // guarantees though.
+            passwordStr = null;
+            System.gc();
+
+            return password;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/RyaDetailsFormatter.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/RyaDetailsFormatter.java b/extras/shell/src/main/java/org/apache/rya/shell/util/RyaDetailsFormatter.java
new file mode 100644
index 0000000..dace754
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/RyaDetailsFormatter.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import org.apache.rya.api.instance.RyaDetails;
+import org.apache.rya.api.instance.RyaDetails.PCJIndexDetails;
+import org.apache.rya.api.instance.RyaDetails.PCJIndexDetails.PCJDetails;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Formats an instance of {@link RyaDetails}.
+ */
+@DefaultAnnotation(NonNull.class)
+public class RyaDetailsFormatter {
+
+    /**
+     * Pretty formats an instance of {@link RyaDetails}.
+     *
+     * @param details - The object to format. (not null)
+     * @return A pretty render of the object.
+     */
+    public String format(final RyaDetails details) {
+        requireNonNull(details);
+
+        final StringBuilder report = new StringBuilder();
+
+        report.append("General Metadata:\n");
+        report.append("  Instance Name: ").append(details.getRyaInstanceName()).append("\n");
+        report.append("  RYA Version: ").append( details.getRyaVersion() ).append("\n");
+        report.append("  Users: ").append( Joiner.on(", ").join(details.getUsers()) ).append("\n");
+
+        report.append("Secondary Indicies:\n");
+        report.append("  Entity Centric Index:\n");
+        report.append("    Enabled: ").append( details.getEntityCentricIndexDetails().isEnabled() ).append("\n");
+      //RYA-215        report.append("  Geospatial Index:\n");
+      //RYA-215        report.append("    Enabled: ").append( details.getGeoIndexDetails().isEnabled() ).append("\n");
+        report.append("  Free Text Index:\n");
+        report.append("    Enabled: ").append( details.getFreeTextIndexDetails().isEnabled() ).append("\n");
+        report.append("  Temporal Index:\n");
+        report.append("    Enabled: ").append( details.getTemporalIndexDetails().isEnabled() ).append("\n");
+
+        report.append("  PCJ Index:\n");
+        final PCJIndexDetails pcjDetails = details.getPCJIndexDetails();
+        report.append("    Enabled: ").append( pcjDetails.isEnabled() ).append("\n");
+        if(pcjDetails.isEnabled()) {
+            if(pcjDetails.getFluoDetails().isPresent()) {
+                final String fluoAppName = pcjDetails.getFluoDetails().get().getUpdateAppName();
+                report.append("    Fluo App Name: ").append(fluoAppName).append("\n");
+            }
+
+            final ImmutableMap<String, PCJDetails> pcjs = pcjDetails.getPCJDetails();
+            report.append("    PCJs:\n");
+            if(pcjs.isEmpty()) {
+                report.append("      No PCJs have been added yet.\n");
+            } else {
+                for(final PCJDetails pcj : pcjs.values()) {
+                    report.append("      ID: ").append(pcj.getId()).append("\n");
+
+                    final String updateStrategy = format( pcj.getUpdateStrategy(), "None" );
+                    report.append("        Update Strategy: ").append(updateStrategy).append("\n");
+
+                    final String lastUpdateTime = format( pcj.getLastUpdateTime(), "unavailable");
+                    report.append("        Last Update Time: ").append(lastUpdateTime).append("\n");
+                }
+            }
+        }
+
+        report.append("Statistics:\n");
+        report.append("  Prospector:\n");
+        final String prospectorLastUpdateTime = format(details.getProspectorDetails().getLastUpdated(), "unavailable");
+        report.append("    Last Update Time: ").append( prospectorLastUpdateTime).append("\n");
+
+        report.append("  Join Selectivity:\n");
+        final String jsLastUpdateTime = format(details.getJoinSelectivityDetails().getLastUpdated(), "unavailable");
+        report.append("    Last Updated Time: ").append( jsLastUpdateTime ).append("\n");
+
+        return report.toString();
+    }
+
+    /**
+     * Formats an optional value using the value's toString() value.
+     *
+     * @param value - The optional value that will be formatted. (not null)
+     * @param absentValue - The String that will be returned if the optional is absent. (not null)
+     * @return The String representation of the optional value.
+     */
+    private <T> String format(final Optional<T> value, final String absentValue) {
+        requireNonNull(value);
+
+        String formatted = "unavailable";
+        if(value.isPresent()) {
+            formatted = value.get().toString();
+        }
+        return formatted;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/SparqlPrompt.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/SparqlPrompt.java b/extras/shell/src/main/java/org/apache/rya/shell/util/SparqlPrompt.java
new file mode 100644
index 0000000..2574eea
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/SparqlPrompt.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import java.io.IOException;
+
+import com.google.common.base.Optional;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jline.console.ConsoleReader;
+
+/**
+ * A mechanism for prompting a user of the application for a SPARQL string.
+ */
+@DefaultAnnotation(NonNull.class)
+public interface SparqlPrompt {
+
+    /**
+     * Prompt the user for a SPARQL query, wait for their input, and then get the value they entered.
+     *
+     * @return The user entered SPARQL query, or an empty string if the user aborts.
+     * @throws IOException There was a problem reading the user's input.
+     */
+    public Optional<String> getSparql() throws IOException;
+
+    /**
+     * Prompts a user for a SPARQL query using a JLine {@link ConsoleReader}.
+     */
+    @DefaultAnnotation(NonNull.class)
+    public static class JLineSparqlPrompt extends JLinePrompt implements SparqlPrompt {
+
+        private final String EXECUTE_COMMAND = "\\e";
+        private final String CLEAR_COMMAND = "\\c";
+
+        @Override
+        public Optional<String> getSparql() throws IOException {
+            final ConsoleReader reader = getReader();
+            reader.setCopyPasteDetection(true); // disable tab completion from activating
+            reader.setHistoryEnabled(false);    // don't store SPARQL fragments in the command history
+            try {
+                reader.println("Enter a SPARQL Query.");
+                reader.println("Type '" + EXECUTE_COMMAND + "' to execute the current query.");
+                reader.println("Type '" + CLEAR_COMMAND + "' to clear the current query.");
+                reader.flush();
+
+                final StringBuilder sb = new StringBuilder();
+                String line = reader.readLine("SPARQL> ");
+                while (!line.endsWith(CLEAR_COMMAND) && !line.endsWith(EXECUTE_COMMAND)) {
+                    sb.append(line).append("\n");
+                    line = reader.readLine("     -> ");
+                }
+
+                if (line.endsWith(EXECUTE_COMMAND)) {
+                    sb.append(line.substring(0, line.length() - EXECUTE_COMMAND.length()));
+                    return Optional.of(sb.toString());
+                }
+                return Optional.absent();
+            } finally {
+                reader.setHistoryEnabled(true);      // restore the ConsoleReader's settings
+                reader.setCopyPasteDetection(false); // restore tab completion
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/java/org/apache/rya/shell/util/UninstallPrompt.java
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/java/org/apache/rya/shell/util/UninstallPrompt.java b/extras/shell/src/main/java/org/apache/rya/shell/util/UninstallPrompt.java
new file mode 100644
index 0000000..628727c
--- /dev/null
+++ b/extras/shell/src/main/java/org/apache/rya/shell/util/UninstallPrompt.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rya.shell.util;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+
+import com.google.common.base.Optional;
+
+import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import jline.console.ConsoleReader;
+
+/**
+ * A mechanism for prompting a user of the application to ensure they want to
+ * uninstall an instance of Rya.
+ */
+@DefaultAnnotation(NonNull.class)
+public interface UninstallPrompt {
+
+    /**
+     * Prompt the user to make sure they want to uninstall the instance of Rya.
+     *
+     * @param ryaInstanceName - The name of the Rya instance being prompted for. (not null)
+     * @return The value they entered.
+     * @throws IOException There was a problem reading the values.
+     */
+    public boolean promptAreYouSure(final String ryaInstanceName) throws IOException;
+
+    /**
+     * Prompts a user for uninstall information using a JLine {@link ConsoleReader}.
+     */
+    public static class JLineUninstallPrompt extends JLinePrompt implements UninstallPrompt {
+        @Override
+        public boolean promptAreYouSure(final String ryaInstanceName) throws IOException {
+            requireNonNull(ryaInstanceName);
+            return promptBoolean("Are you sure you want to uninstall this instance of Rya named '" +
+                    ryaInstanceName + "'? ", Optional.<Boolean>absent());
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/resources/LICENSE.txt
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/resources/LICENSE.txt b/extras/shell/src/main/resources/LICENSE.txt
new file mode 100644
index 0000000..4a9fe83
--- /dev/null
+++ b/extras/shell/src/main/resources/LICENSE.txt
@@ -0,0 +1,16 @@
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+ 
+    http://www.apache.org/licenses/LICENSE-2.0
+ 
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/resources/META-INF/spring/spring-shell-plugin.xml
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/resources/META-INF/spring/spring-shell-plugin.xml b/extras/shell/src/main/resources/META-INF/spring/spring-shell-plugin.xml
new file mode 100644
index 0000000..48c4846
--- /dev/null
+++ b/extras/shell/src/main/resources/META-INF/spring/spring-shell-plugin.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
+
+    <!-- Tell Spring where it can find all of the Command components. -->
+    <context:component-scan base-package="org.apache.rya.shell"/>
+
+    <!-- Define the shell state bean that will be shared across all of the commands. -->
+    <bean id="sharedShellState" class="org.apache.rya.shell.SharedShellState" />
+    <bean id="passwordPrompt" class="org.apache.rya.shell.util.PasswordPrompt.JLinePasswordPrompt" />
+    <bean id="installPrompt" class="org.apache.rya.shell.util.InstallPrompt.JLineAccumuloInstallPrompt" />
+    <bean id="uninstallPrompt" class="org.apache.rya.shell.util.UninstallPrompt.JLineUninstallPrompt" />
+    <bean id="sparqlPrompt" class="org.apache.rya.shell.util.SparqlPrompt.JLineSparqlPrompt" />
+    <bean id="consolePrinter" class="org.apache.rya.shell.util.ConsolePrinter.JLineConsolePrinter" />
+    
+    <!-- Define each of the beans that hold onto commands used by the shell. -->
+    <bean id="ryaConnectionCommands" class="org.apache.rya.shell.RyaConnectionCommands" />
+    <bean id="ryaAdminCommands" class="org.apache.rya.shell.RyaAdminCommands" />
+    <bean id="ryaCommands" class="org.apache.rya.shell.RyaCommands" />
+    
+    <!--  
+    <bean id="springHelpCommands" class="org.springframework.shell.commands.HelpCommands" />
+    <bean id="springScriptCommands" class="org.springframework.shell.commands.ScriptCommands" />
+    <bean id="springExitCommands" class="org.springframework.shell.commands.ExitCommands" />
+    -->
+    
+</beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2564ac0a/extras/shell/src/main/scripts/rya
----------------------------------------------------------------------
diff --git a/extras/shell/src/main/scripts/rya b/extras/shell/src/main/scripts/rya
new file mode 100644
index 0000000..5280286
--- /dev/null
+++ b/extras/shell/src/main/scripts/rya
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+PROJECT_HOME=$(dirname $(cd $(dirname $0) && pwd))
+#cd $PROJECT_HOME
+
+java -cp $PROJECT_HOME/lib/${project.artifactId}-${project.version}-shaded.jar \
+  -Drya.shell.home="${PROJECT_HOME}/" \
+  -Dlog4j.configuration="file://$PROJECT_HOME/conf/log4j.properties" \
+  org.springframework.shell.Bootstrap "$@"
+  
+# --profiles - Specifies values for the system property spring.profiles.active so that Spring 3.1 and greater profile support is enabled.
+# --cmdfile - Specifies a file to read that contains shell commands
+# --histsize - Specifies the maximum number of lines to store in the command history file. Default value is 3000.
+# --disableInternalCommands - Flag that disables all commands that would be pre-registered with the shell. There is no argument to this option. You can selectively add back any internal commands by referencing them in your shell plugin file. Look at the Spring Shell javadocs for specific commands located in the org.springframework.shell.commands package as well as the section in this documentation of Built in commands.