You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2020/01/16 16:27:57 UTC

[knox] branch master updated: KNOX-2128 - Custom DataSource and SQL Commands for KnoxShell and KnoxShellTable (#231)

This is an automated email from the ASF dual-hosted git repository.

lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 7e9a79d  KNOX-2128 - Custom DataSource and SQL Commands for KnoxShell and KnoxShellTable (#231)
7e9a79d is described below

commit 7e9a79dd85de235841ada1e7bee986d9a8c9631a
Author: lmccay <lm...@apache.org>
AuthorDate: Thu Jan 16 11:27:48 2020 -0500

    KNOX-2128 - Custom DataSource and SQL Commands for KnoxShell and KnoxShellTable (#231)
    
    * initial commit
    
    * Jackson annotations
    
    * fix select command
    
    * KNOX-2128 - Custom DataSource and SQL Commands for KnoxShell and KnoxShellTable
    
    * fix knoxshell.sh to use APP_JAR for shellcheck error
    
    * address review comments
    
    * fixed FileLock use
    
    * fix indentation in knoxshell.sh
    
    * address redundant swing workaround code
    
    * address review comments
    
    * address review comments and persistence when dirs don't exist
---
 .../main/resources/build-tools/spotbugs-filter.xml |  15 ++
 gateway-shell-release/home/bin/knoxshell.sh        |   2 +-
 .../home/conf/knoxshell-log4j.properties           |   2 +-
 .../apache/knox/gateway/shell/KnoxDataSource.java  |  73 ++++++++
 .../org/apache/knox/gateway/shell/KnoxSession.java | 132 +++++++++++++++
 .../java/org/apache/knox/gateway/shell/Shell.java  |   8 +
 .../shell/commands/AbstractKnoxShellCommand.java   |  46 +++++
 .../shell/commands/AbstractSQLCommandSupport.java  | 185 +++++++++++++++++++++
 .../knox/gateway/shell/commands/CSVCommand.java    |  66 ++++++++
 .../gateway/shell/commands/DataSourceCommand.java  | 108 ++++++++++++
 .../gateway/shell/commands/KnoxLoginDialog.java    | 111 +++++++++++++
 .../knox/gateway/shell/commands/LoginCommand.java  |  58 +++++++
 .../knox/gateway/shell/commands/SelectCommand.java | 178 ++++++++++++++++++++
 .../knox/gateway/shell/commands/SwingUtils.java    |  51 ++++++
 .../apache/knox/gateway/shell/jdbc/JDBCUtils.java  |  38 +++++
 .../shell/table/JDBCKnoxShellTableBuilder.java     |  23 +--
 .../knox/gateway/shell/table/KnoxShellTable.java   |   2 +-
 .../gateway/shell/table/KnoxShellTableTest.java    |   2 +-
 18 files changed, 1082 insertions(+), 18 deletions(-)

diff --git a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
index 239fb57..37f4e4f 100644
--- a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
+++ b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
@@ -52,6 +52,21 @@ limitations under the License.
   </Match>
 
   <Match>
+    <Class name="org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder" />
+    <Bug pattern="SQL_INJECTION_JDBC" />
+  </Match>
+  
+  <Match>
+    <Class name="org.apache.knox.gateway.shell.commands.SelectCommand" />
+    <Bug pattern="SQL_INJECTION_JDBC" />
+  </Match>
+  
+  <Match>
+    <Class name="org.apache.knox.gateway.shell.commands.DataSourceCommand" />
+    <Bug pattern="SQL_INJECTION_JDBC" />
+  </Match>
+  
+  <Match>
     <Class name="~org.apache.hadoop.gateway..*" />
     <Bug pattern="NM_SAME_SIMPLE_NAME_AS_SUPERCLASS" />
   </Match>
diff --git a/gateway-shell-release/home/bin/knoxshell.sh b/gateway-shell-release/home/bin/knoxshell.sh
index 52f7a98..6f8a8fe 100755
--- a/gateway-shell-release/home/bin/knoxshell.sh
+++ b/gateway-shell-release/home/bin/knoxshell.sh
@@ -81,7 +81,7 @@ function main {
 
    checkJava
    buildAppJavaOpts
-   $JAVA "${APP_JAVA_OPTS[@]}" -javaagent:"$APP_BIN_DIR"/../lib/aspectjweaver.jar -jar "$APP_JAR" "$@" || exit 1
+   $JAVA "${APP_JAVA_OPTS[@]}" -Dlog4j.configuration=conf/knoxshell-log4j.properties -javaagent:"$APP_BIN_DIR"/../lib/aspectjweaver.jar -cp "$APP_JAR":lib/* org.apache.knox.gateway.shell.Shell "$@" || exit 1
 
    return 0
 }
diff --git a/gateway-shell-release/home/conf/knoxshell-log4j.properties b/gateway-shell-release/home/conf/knoxshell-log4j.properties
index c7e1312..cac3d99 100644
--- a/gateway-shell-release/home/conf/knoxshell-log4j.properties
+++ b/gateway-shell-release/home/conf/knoxshell-log4j.properties
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-app.log.dir=${launcher.dir}/../logs
+app.log.dir=logs
 app.log.file=${launcher.name}.log
 
 log4j.rootLogger=ERROR, drfa
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxDataSource.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxDataSource.java
new file mode 100644
index 0000000..b5db74f
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxDataSource.java
@@ -0,0 +1,73 @@
+/*
+ * 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.knox.gateway.shell;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class KnoxDataSource {
+  private String name;
+  private String connectStr;
+  private String driver;
+  private String authnType;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getConnectStr() {
+    return connectStr;
+  }
+
+  public void setConnectStr(String connectStr) {
+    this.connectStr = connectStr;
+  }
+
+  public String getDriver() {
+    return driver;
+  }
+
+  public void setDriver(String driver) {
+    this.driver = driver;
+  }
+
+  public String getAuthnType() {
+    return authnType;
+  }
+
+  public void setAuthnType(String authnType) {
+    this.authnType = authnType;
+  }
+
+  public KnoxDataSource(@JsonProperty("name") String name,
+      @JsonProperty("contectStr") String connectStr,
+      @JsonProperty("driver") String driver,
+      @JsonProperty("authnType") String authnType) {
+    this.name = name;
+    this.connectStr = connectStr;
+    this.driver = driver;
+    this.authnType = authnType;
+  }
+
+  public KnoxDataSource(String name, String connectStr, String driver) {
+    this(name, connectStr, driver, "none");
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
index 5fa26c0..085a9a6 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
@@ -17,9 +17,13 @@
  */
 package org.apache.knox.gateway.shell;
 
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.sun.security.auth.callback.TextCallbackHandler;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
@@ -52,6 +56,7 @@ import org.apache.http.protocol.BasicHttpContext;
 import org.apache.http.ssl.SSLContexts;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.shell.util.ClientTrustStoreHelper;
+import org.apache.knox.gateway.util.JsonUtils;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
@@ -71,7 +76,12 @@ import java.lang.reflect.Constructor;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.channels.FileChannel;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
 import java.security.AccessController;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
@@ -83,6 +93,7 @@ import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -129,6 +140,9 @@ public class KnoxSession implements Closeable {
   ExecutorService executor;
   Map<String, String> headers = new HashMap<>();
 
+  private static final String KNOXSQLHISTORIES_JSON = "knoxsqlhistories.json";
+  private static final String KNOXDATASOURCES_JSON = "knoxdatasources.json";
+
   public Map<String, String> getHeaders() {
     return headers;
   }
@@ -570,6 +584,124 @@ public class KnoxSession implements Closeable {
     return String.format(Locale.ROOT, "KnoxSession{base='%s'}", base);
   }
 
+  /**
+   * Persist provided Map to a file within the {user.home}/.knoxshell directory
+   * @param <T>
+   * @param fileName of persisted file
+   * @param map to persist
+   */
+  public static <T> void persistToKnoxShell(String fileName, Map<String, List<T>> map) {
+    String s = JsonUtils.renderAsJsonString(map);
+    String home = System.getProperty("user.home");
+    try {
+      write(new File(
+          home + File.separator +
+          ".knoxshell" + File.separator + fileName),
+          s, StandardCharsets.UTF_8);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private static void write(File file, String s, Charset utf8) throws IOException {
+    synchronized(KnoxSession.class) {
+      // Ensure the parent directory exists...
+      // This will attempt to create all missing directories.  No failures will occur if the directories already exist.
+      Files.createDirectories(file.toPath().getParent());
+      try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE,
+          StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+        channel.tryLock();
+        FileUtils.write(file, s, utf8);
+      }
+      catch (OverlappingFileLockException e) {
+        System.out.println("Unable to acquire write lock for: " + file.getAbsolutePath());
+      }
+    }
+  }
+
+  public static void persistSQLHistory(Map<String, List<String>> sqlHistories) {
+    persistToKnoxShell(KNOXSQLHISTORIES_JSON, sqlHistories);
+  }
+
+  public static void persistDataSources(Map<String, List<KnoxDataSource>> datasources) {
+    persistToKnoxShell(KNOXDATASOURCES_JSON, datasources);
+  }
+
+  /**
+   * Load and return a map of datasource names to sql commands
+   * from the {user.home}/.knoxshell/knoxsqlhistories.json file.
+   * @return sqlHistory map
+   */
+  public static Map<String, List<String>> loadSQLHistories() throws IOException {
+    Map<String, List<String>> sqlHistories = null;
+    String home = System.getProperty("user.home");
+
+    File historyFile = new File(
+        home + File.separator +
+        ".knoxshell" + File.separator + KNOXSQLHISTORIES_JSON);
+    if (historyFile.exists()) {
+      String json = readFileToString(historyFile, "UTF8");
+      sqlHistories = (Map<String, List<String>>) getMapOfStringArrayListsFromJsonString(json);
+    }
+    return sqlHistories;
+  }
+
+  private static String readFileToString(File file, String s)
+      throws FileNotFoundException, IOException {
+    String content = null;
+
+    synchronized(KnoxSession.class) {
+      try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+        channel.tryLock(0L, Long.MAX_VALUE, true);
+        content = FileUtils.readFileToString(file, s);
+      }
+      catch (OverlappingFileLockException e) {
+        System.out.println("Unable to acquire write lock for: " + file.getAbsolutePath());
+      }
+    }
+
+    return content;
+  }
+
+  /**
+   * Load and return a map of datasource names to KnoxDataSource
+   * objects from the {user.home}/.knoxshell/knoxdatasources.json file.
+   * @return
+   */
+  public static Map<String, KnoxDataSource> loadDataSources() throws IOException {
+    Map<String, KnoxDataSource> datasources = null;
+    String home = System.getProperty("user.home");
+    String json = null;
+
+    File dsFile = new File(
+        home + File.separator +
+        ".knoxshell" + File.separator + KNOXDATASOURCES_JSON);
+    if (dsFile.exists()) {
+      json = readFileToString(dsFile, "UTF8");
+      datasources = getMapOfDataSourcesFromJsonString(json);
+    }
+
+    return datasources;
+  }
+
+  public static Map<String, List<String>> getMapOfStringArrayListsFromJsonString(String json) throws IOException {
+    Map<String, List<String>> obj = null;
+    JsonFactory factory = new JsonFactory();
+    ObjectMapper mapper = new ObjectMapper(factory);
+    TypeReference<Map<String, List<String>>> typeRef = new TypeReference<Map<String, List<String>>>() {};
+    obj = mapper.readValue(json, typeRef);
+    return obj;
+  }
+
+  public static Map<String, KnoxDataSource> getMapOfDataSourcesFromJsonString(String json) throws IOException {
+    Map<String, KnoxDataSource> obj = null;
+    JsonFactory factory = new JsonFactory();
+    ObjectMapper mapper = new ObjectMapper(factory);
+    TypeReference<Map<String, KnoxDataSource>> typeRef = new TypeReference<Map<String, KnoxDataSource>>() {};
+    obj = mapper.readValue(json, typeRef);
+    return obj;
+  }
+
   private static final class JAASClientConfig extends Configuration {
 
     private static final Configuration baseConfig = Configuration.getConfiguration();
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Shell.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Shell.java
index 62079c4..30c6fbb 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Shell.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Shell.java
@@ -18,6 +18,10 @@
 package org.apache.knox.gateway.shell;
 
 import groovy.ui.GroovyMain;
+
+import org.apache.knox.gateway.shell.commands.CSVCommand;
+import org.apache.knox.gateway.shell.commands.DataSourceCommand;
+import org.apache.knox.gateway.shell.commands.SelectCommand;
 import org.apache.knox.gateway.shell.hbase.HBase;
 import org.apache.knox.gateway.shell.hdfs.Hdfs;
 import org.apache.knox.gateway.shell.job.Job;
@@ -76,6 +80,10 @@ public class Shell {
       for( String name : IMPORTS ) {
         shell.execute( "import " + name );
       }
+      // register custom groovysh commands
+      shell.register(new SelectCommand(shell));
+      shell.register(new DataSourceCommand(shell));
+      shell.register(new CSVCommand(shell));
       shell.run( null );
     }
   }
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractKnoxShellCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractKnoxShellCommand.java
new file mode 100644
index 0000000..3fa400e
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractKnoxShellCommand.java
@@ -0,0 +1,46 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import java.util.List;
+import org.codehaus.groovy.tools.shell.CommandSupport;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public abstract class AbstractKnoxShellCommand extends CommandSupport {
+  static final String KNOXSQLHISTORY = "__knoxsqlhistory";
+  protected static final String KNOXDATASOURCES = "__knoxdatasources";
+
+  public AbstractKnoxShellCommand(Groovysh shell, String name, String shortcut) {
+    super(shell, name, shortcut);
+  }
+
+  protected String getBindingVariableNameForResultingTable(List<String> args) {
+    String variableName = null;
+    boolean nextOne = false;
+    for (String arg : args) {
+      if (nextOne) {
+        variableName = arg;
+        break;
+      }
+      if ("assign".equalsIgnoreCase(arg)) {
+        nextOne = true;
+      }
+    }
+    return variableName;
+  }
+}
\ No newline at end of file
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractSQLCommandSupport.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractSQLCommandSupport.java
new file mode 100644
index 0000000..0f7a5ab
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/AbstractSQLCommandSupport.java
@@ -0,0 +1,185 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.knox.gateway.shell.KnoxDataSource;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.apache.knox.gateway.shell.jdbc.JDBCUtils;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public abstract class AbstractSQLCommandSupport extends AbstractKnoxShellCommand {
+
+  protected static final String KNOXDATASOURCES = "__knoxdatasources";
+  protected static final String KNOXDATASOURCE = "__knoxdatasource";
+  private static final Object KNOXDATASOURCE_CONNECTIONS = "__knoxdatasourceconnections";
+
+  public AbstractSQLCommandSupport(Groovysh shell, String name, String shortcut) {
+    super(shell, name, shortcut);
+  }
+
+  @SuppressWarnings("unchecked")
+  protected Connection getConnectionFromSession(KnoxDataSource ds) {
+    HashMap<String, Connection> connections =
+        (HashMap<String, Connection>) getVariables()
+        .getOrDefault(KNOXDATASOURCE_CONNECTIONS,
+            new HashMap<String, Connection>());
+
+    Connection conn = connections.get(ds.getName());
+    return conn;
+  }
+
+  @SuppressWarnings("unchecked")
+  protected Connection getConnection(KnoxDataSource ds, String user, String pass) throws SQLException, Exception {
+    Connection conn = getConnectionFromSession(ds);
+    if (conn == null) {
+      if (user != null && pass != null) {
+        conn = JDBCUtils.createConnection(ds.getConnectStr(), user, pass);
+      }
+      else {
+        conn = JDBCUtils.createConnection(ds.getConnectStr(), null, null);
+
+      }
+      HashMap<String, Connection> connections =
+          (HashMap<String, Connection>) getVariables()
+          .getOrDefault(KNOXDATASOURCE_CONNECTIONS,
+              new HashMap<String, Connection>());
+      connections.put(ds.getName(), conn);
+      getVariables().put(KNOXDATASOURCE_CONNECTIONS, connections);
+    }
+    return conn;
+  }
+
+  protected void persistSQLHistory() {
+    Map<String, List<String>> sqlHistories =
+        (Map<String, List<String>>) getVariables().get(KNOXSQLHISTORY);
+    KnoxSession.persistSQLHistory(sqlHistories);
+  }
+
+  protected void persistDataSources() {
+    Map<String, List<KnoxDataSource>> datasources =
+        (Map<String, List<KnoxDataSource>>) getVariables().get(KNOXDATASOURCES);
+    KnoxSession.persistDataSources(datasources);
+  }
+
+  protected List<String> getSQLHistory(String dataSourceName) {
+    List<String> sqlHistory = null;
+    Map<String, List<String>> sqlHistories =
+        (Map<String, List<String>>) getVariables().get(KNOXSQLHISTORY);
+    if (sqlHistories == null) {
+      // check for persisted histories for known datasources
+      sqlHistories = loadSQLHistories();
+      if (sqlHistories == null || sqlHistories.isEmpty()) {
+        sqlHistories = new HashMap<>();
+        getVariables().put(KNOXSQLHISTORY, sqlHistories);
+      }
+    }
+    // get the history for the specific datasource
+    sqlHistory = sqlHistories.get(dataSourceName);
+    if (sqlHistory == null) {
+      sqlHistory = startSqlHistory(dataSourceName, sqlHistories);
+    }
+    return sqlHistory;
+  }
+
+  private List<String> startSqlHistory(String dataSourceName, Map<String, List<String>> sqlHistories) {
+    List<String> sqlHistory = new ArrayList<>();
+    sqlHistories.put(dataSourceName, sqlHistory);
+    return sqlHistory;
+  }
+
+  private Map<String, List<String>> loadSQLHistories() {
+    Map<String, List<String>> sqlHistories = null;
+    try {
+      sqlHistories = KnoxSession.loadSQLHistories();
+      if (sqlHistories != null) {
+        getVariables().put(KNOXSQLHISTORY, sqlHistories);
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return sqlHistories;
+  }
+
+  private Map<String, KnoxDataSource> loadDataSources() {
+    Map<String, KnoxDataSource> datasources = null;
+    try {
+      datasources = KnoxSession.loadDataSources();
+      if (datasources != null) {
+        getVariables().put(KNOXDATASOURCES, datasources);
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    return datasources;
+  }
+
+  protected void addToSQLHistory(String dsName, String sql) {
+    List<String> sqlHistory = null;
+    if (sql != null && !sql.isEmpty()) {
+      sqlHistory = getSQLHistory(dsName);
+      if (sqlHistory != null) {
+        sqlHistory.add(sql);
+      }
+    }
+
+    if (sqlHistory != null && sqlHistory.size() > 20) {
+      sqlHistory.remove(0);
+    }
+    persistSQLHistory();
+  }
+
+  protected void addToSQLHistory(List<String> sqlHistory, String sql) {
+    if (sql != null && !sql.isEmpty()) {
+      sqlHistory.add(sql);
+    }
+
+    if (sqlHistory.size() > 20) {
+      sqlHistory.remove(0);
+    }
+    persistSQLHistory();
+  }
+
+  protected void removeFromSQLHistory(String dsName) {
+    Map<String, List<String>> sqlHistories =
+        (Map<String, List<String>>) getVariables().get(KNOXSQLHISTORY);
+    sqlHistories.remove(dsName);
+    persistSQLHistory();
+  }
+
+  protected Map<String, KnoxDataSource> getDataSources() {
+    Map<String, KnoxDataSource> datasources = (Map<String, KnoxDataSource>) getVariables().get(KNOXDATASOURCES);
+    if (datasources == null) {
+      datasources = loadDataSources();
+      if (datasources != null) {
+        getVariables().put(KNOXDATASOURCES, datasources);
+      }
+      else {
+        datasources = new HashMap<>();
+      }
+    }
+    return datasources;
+  }
+}
\ No newline at end of file
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/CSVCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/CSVCommand.java
new file mode 100644
index 0000000..22aad2c
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/CSVCommand.java
@@ -0,0 +1,66 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public class CSVCommand extends AbstractKnoxShellCommand {
+  private boolean withHeaders;
+  private String url;
+
+  public CSVCommand(Groovysh shell) {
+    super(shell, ":CSV", ":csv");
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Object execute(List<String> args) {
+    KnoxShellTable table = null;
+    String bindVariableName = null;
+    if (!args.isEmpty()) {
+      bindVariableName = getBindingVariableNameForResultingTable(args);
+    }
+    if (args.get(0).contentEquals("withHeaders")) {
+      withHeaders = true;
+      url = args.get(1);
+    }
+    else {
+      url = args.get(0);
+    }
+
+    try {
+      if (withHeaders) {
+        table = KnoxShellTable.builder().csv().withHeaders().url(url);
+      }
+      else {
+        table = KnoxShellTable.builder().csv().url(url);
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    if (table != null && bindVariableName != null) {
+      getVariables().put(bindVariableName, table);
+    }
+    return table;
+  }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/DataSourceCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/DataSourceCommand.java
new file mode 100644
index 0000000..084aaa4
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/DataSourceCommand.java
@@ -0,0 +1,108 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.knox.gateway.shell.KnoxDataSource;
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public class DataSourceCommand extends AbstractSQLCommandSupport {
+
+  public DataSourceCommand(Groovysh shell) {
+    super(shell, ":datasources", ":ds");
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Object execute(List<String> args) {
+    Map<String, KnoxDataSource> dataSources =
+        getDataSources();
+    if (args.isEmpty()) {
+      args.add("list");
+    }
+    if (args.get(0).equalsIgnoreCase("add")) {
+      KnoxDataSource ds = new KnoxDataSource(args.get(1),
+          args.get(2),
+          args.get(3),
+          args.get(4));
+      dataSources.put(ds.getName(), ds);
+      getVariables().put(KNOXDATASOURCES, dataSources);
+      persistDataSources();
+    }
+    else if (args.get(0).equalsIgnoreCase("remove")) {
+      if (dataSources == null || dataSources.isEmpty()) {
+        return "No datasources to remove.";
+      }
+      // if the removed datasource is currently selected, unselect it
+      dataSources.remove(args.get(1));
+      if (((String)getVariables().get(KNOXDATASOURCE)).equals(args.get(1))) {
+        System.out.println("unselecting datasource.");
+        getVariables().put(KNOXDATASOURCE, "");
+        getVariables().put(KNOXDATASOURCES, dataSources);
+        persistDataSources();
+      }
+    }
+    else if (args.get(0).equalsIgnoreCase("list")) {
+      // valid command no additional work needed though
+    }
+    else if(args.get(0).equalsIgnoreCase("select")) {
+      if (dataSources == null || dataSources.isEmpty()) {
+        return "No datasources to select from.";
+      }
+      if (dataSources.containsKey(args.get(1))) {
+        getVariables().put(KNOXDATASOURCE, args.get(1));
+      }
+      KnoxDataSource dsValue = dataSources.get(args.get(1));
+      KnoxShellTable datasource = new KnoxShellTable();
+      datasource.title("Knox DataSource Selected");
+      datasource.header("Name").header("Connect String").header("Driver").header("Authn Type");
+      datasource.row().value(dsValue.getName()).value(dsValue.getConnectStr()).value(dsValue.getDriver()).value(dsValue.getAuthnType());
+      return datasource;
+    }
+    else {
+      return "ERROR: unknown datasources command.";
+    }
+
+    return buildTable();
+  }
+
+  private KnoxShellTable buildTable() {
+    KnoxShellTable datasource = new KnoxShellTable();
+    datasource.title("Knox DataSources");
+    datasource.header("Name").header("Connect String").header("Driver").header("Authn Type");
+    @SuppressWarnings("unchecked")
+    Map<String, KnoxDataSource> dataSources =
+        (Map<String, KnoxDataSource>) getVariables().get(KNOXDATASOURCES);
+    if (dataSources != null && !dataSources.isEmpty()) {
+      for(KnoxDataSource dsValue : dataSources.values()) {
+        datasource.row().value(dsValue.getName()).value(dsValue.getConnectStr()).value(dsValue.getDriver()).value(dsValue.getAuthnType());
+      }
+    }
+    return datasource;
+  }
+
+  public static void main(String[] args) {
+    DataSourceCommand cmd = new DataSourceCommand(new Groovysh());
+    List<String> args2 = new ArrayList<>();
+    cmd.execute(args2);
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/KnoxLoginDialog.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/KnoxLoginDialog.java
new file mode 100644
index 0000000..fba19ef
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/KnoxLoginDialog.java
@@ -0,0 +1,111 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPasswordField;
+import javax.swing.JTextField;
+import org.apache.knox.gateway.shell.CredentialCollectionException;
+import org.apache.knox.gateway.shell.CredentialCollector;
+
+public class KnoxLoginDialog implements CredentialCollector {
+  public static final String COLLECTOR_TYPE = "LoginDialog";
+
+  public char[] pass;
+  public String username;
+  String name;
+  public boolean ok;
+
+  @Override
+  public void collect() throws CredentialCollectionException {
+    JLabel jl = new JLabel("Enter Your username: ");
+    JTextField juf = new JTextField(24);
+    JLabel jl2 = new JLabel("Enter Your password:  ");
+    JPasswordField jpf = new JPasswordField(24);
+    Box box1 = Box.createHorizontalBox();
+    box1.add(jl);
+    box1.add(juf);
+    Box box2 = Box.createHorizontalBox();
+    box2.add(jl2);
+    box2.add(jpf);
+    Box box = Box.createVerticalBox();
+    box.add(box1);
+    box.add(box2);
+
+    // JDK-5018574 : Unable to set focus to another component in JOptionPane
+    SwingUtils.workAroundFocusIssue(juf);
+
+    int x = JOptionPane.showConfirmDialog(null, box,
+        "KnoxShell Login", JOptionPane.OK_CANCEL_OPTION);
+
+    if (x == JOptionPane.OK_OPTION) {
+      ok = true;
+      username = juf.getText();
+      pass = jpf.getPassword();
+    }
+  }
+
+  @Override
+  public String string() {
+    return new String(pass);
+  }
+
+  @Override
+  public char[] chars() {
+    return pass;
+  }
+
+  @Override
+  public byte[] bytes() {
+    return null;
+  }
+
+  @Override
+  public String type() {
+    return "dialog";
+  }
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public void setPrompt(String prompt) {
+  }
+
+  @Override
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public static void main(String[] args) {
+    KnoxLoginDialog dlg = new KnoxLoginDialog();
+    try {
+      dlg.collect();
+      if (dlg.ok) {
+        System.out.println("username: " + dlg.username);
+        System.out.println("password: " + new String(dlg.pass));
+      }
+    } catch (CredentialCollectionException e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/LoginCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/LoginCommand.java
new file mode 100644
index 0000000..e532948
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/LoginCommand.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.knox.gateway.shell.commands;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.knox.gateway.shell.CredentialCollectionException;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.codehaus.groovy.tools.shell.CommandSupport;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public class LoginCommand extends CommandSupport {
+
+  public LoginCommand(Groovysh shell) {
+    super(shell, ":login", ":lgn");
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Object execute(List<String> args) {
+    KnoxSession session = null;
+    KnoxLoginDialog dlg = new KnoxLoginDialog();
+    try {
+      dlg.collect();
+      if (dlg.ok) {
+        session = KnoxSession.login(args.get(0), dlg.username, new String(dlg.pass));
+        getVariables().put("__knoxsession", session);
+      }
+    } catch (CredentialCollectionException | URISyntaxException e) {
+      e.printStackTrace();
+    }
+    return "Session established for: " + args.get(0);
+  }
+
+  public static void main(String[] args) {
+    LoginCommand cmd = new LoginCommand(new Groovysh());
+    List<String> args2 = new ArrayList<>();
+    args2.add("https://localhost:8443/gateway");
+    cmd.execute(args2);
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SelectCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SelectCommand.java
new file mode 100644
index 0000000..d614cd1
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SelectCommand.java
@@ -0,0 +1,178 @@
+/*
+ * 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.knox.gateway.shell.commands;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import org.apache.knox.gateway.shell.CredentialCollectionException;
+import org.apache.knox.gateway.shell.KnoxDataSource;
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
+import org.codehaus.groovy.tools.shell.Groovysh;
+
+public class SelectCommand extends AbstractSQLCommandSupport implements KeyListener {
+
+  private static final String KNOXDATASOURCE = "__knoxdatasource";
+  private JTextArea sqlField;
+  private List<String> sqlHistory;
+  private int historyIndex = -1;
+
+  public SelectCommand(Groovysh shell) {
+    super(shell, ":SQL", ":sql");
+  }
+
+  @Override
+  public void keyPressed(KeyEvent event) {
+    int code = event.getKeyCode();
+    boolean setFromHistory = false;
+    if (sqlHistory != null && !sqlHistory.isEmpty()) {
+      if (historyIndex == -1) {
+        historyIndex = sqlHistory.size() + 1;
+      }
+      if (code == KeyEvent.VK_KP_UP ||
+          code == KeyEvent.VK_UP) {
+        if (historyIndex > 0) {
+          historyIndex -= 1;
+        }
+        setFromHistory = true;
+      }
+      else if (code == KeyEvent.VK_KP_DOWN ||
+          code == KeyEvent.VK_DOWN) {
+        if (historyIndex < sqlHistory.size() - 1) {
+          historyIndex += 1;
+          setFromHistory = true;
+        }
+      }
+      if (setFromHistory) {
+        sqlField.setText(sqlHistory.get(historyIndex));
+        sqlField.invalidate();
+      }
+    }
+  }
+
+  @Override
+  public void keyReleased(KeyEvent event) {
+  }
+
+  @Override
+  public void keyTyped(KeyEvent event) {
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Object execute(List<String> args) {
+    boolean ok = false;
+    String sql = "";
+    String bindVariableName = null;
+    KnoxShellTable table = null;
+
+    if (!args.isEmpty()) {
+      bindVariableName = getBindingVariableNameForResultingTable(args);
+    }
+
+    String dsName = (String) getVariables().get(KNOXDATASOURCE);
+    @SuppressWarnings("unchecked")
+    Map<String, KnoxDataSource> dataSources = getDataSources();
+    KnoxDataSource ds = null;
+    if (dsName == null || dsName.isEmpty()) {
+      if (dataSources == null || dataSources.isEmpty()) {
+        return "please configure a datasource with ':datasources add {name} {connectStr} {driver} {authntype: none|basic}'.";
+      }
+      else if (dataSources.size() == 1) {
+        dsName = (String) dataSources.keySet().toArray()[0];
+      }
+      else {
+        return "mulitple datasources configured. please disambiguate with ':datasources select {name}'.";
+      }
+    }
+
+    sqlHistory = getSQLHistory(dsName);
+    historyIndex = (sqlHistory != null && !sqlHistory.isEmpty()) ? sqlHistory.size() - 1 : -1;
+
+    ds = dataSources.get(dsName);
+    if (ds != null) {
+      JLabel jl = new JLabel("Query: ");
+      sqlField = new JTextArea(5,40);
+      sqlField.addKeyListener(this);
+      sqlField.setLineWrap(true);
+      JScrollPane scrollPane = new JScrollPane(sqlField);
+      Box box = Box.createHorizontalBox();
+      box.add(jl);
+      box.add(scrollPane);
+
+      // JDK-5018574 : Unable to set focus to another component in JOptionPane
+      SwingUtils.workAroundFocusIssue(sqlField);
+
+      int x = JOptionPane.showConfirmDialog(null, box,
+          "SQL Query Input", JOptionPane.OK_CANCEL_OPTION);
+
+      if (x == JOptionPane.OK_OPTION) {
+        ok = true;
+        sql = sqlField.getText();
+        addToSQLHistory(dsName, sql);
+        historyIndex = -1;
+      }
+
+      //KnoxShellTable.builder().jdbc().connect("jdbc:derby:codejava/webdb1").driver("org.apache.derby.jdbc.EmbeddedDriver").username("lmccay").pwd("xxxx").sql("SELECT * FROM book");
+      try {
+        if (ok) {
+          if (ds.getAuthnType().equalsIgnoreCase("none")) {
+            table = KnoxShellTable.builder().jdbc()
+                .connectTo(ds.getConnectStr())
+                .driver(ds.getDriver())
+                .sql(sql);
+          }
+          else if (ds.getAuthnType().equalsIgnoreCase("basic")) {
+            KnoxLoginDialog dlg = new KnoxLoginDialog();
+            try {
+              dlg.collect();
+              if (dlg.ok) {
+                table = KnoxShellTable.builder().jdbc()
+                    .connectTo(ds.getConnectStr())
+                    .driver(ds.getDriver())
+                    .username(dlg.username)
+                    .password(new String(dlg.pass))
+                    .sql(sql);
+              }
+            } catch (CredentialCollectionException | URISyntaxException e) {
+              e.printStackTrace();
+            }
+          }
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+    else {
+      return "please select a datasource via ':datasources select {name}'.";
+    }
+    if (table != null && bindVariableName != null) {
+      getVariables().put(bindVariableName, table);
+    }
+    return table;
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SwingUtils.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SwingUtils.java
new file mode 100644
index 0000000..99bc1d3
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/SwingUtils.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.knox.gateway.shell.commands;
+
+import java.awt.Component;
+import java.awt.Window;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.SwingUtilities;
+import javax.swing.text.JTextComponent;
+
+public class SwingUtils {
+
+  // JDK-5018574 : Unable to set focus to another component in JOptionPane
+  public static void workAroundFocusIssue(JTextComponent field) {
+    field.addHierarchyListener(new HierarchyListener() {
+      @Override
+      public void hierarchyChanged(HierarchyEvent e) {
+        final Component c = e.getComponent();
+        if (c.isShowing() && (e.getChangeFlags() &
+          HierarchyEvent.SHOWING_CHANGED) != 0) {
+          Window toplevel = SwingUtilities.getWindowAncestor(c);
+          toplevel.addWindowFocusListener(new WindowAdapter() {
+              @Override
+              public void windowGainedFocus(WindowEvent e) {
+                  c.requestFocus();
+              }
+          });
+        }
+      }
+    });
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/jdbc/JDBCUtils.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/jdbc/JDBCUtils.java
new file mode 100644
index 0000000..9b93553
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/jdbc/JDBCUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.knox.gateway.shell.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class JDBCUtils {
+  public static Connection createConnection(String connectionUrl,
+      String username, String pass) throws SQLException {
+    Connection con = null;
+    if (StringUtils.isNotBlank(username) && pass != null) {
+      con = DriverManager.getConnection(connectionUrl, username, pass);
+    }
+    else {
+      con = DriverManager.getConnection(connectionUrl);
+    }
+    return con;
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
index 0543044..3d1e69f 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
@@ -19,14 +19,13 @@ package org.apache.knox.gateway.shell.table;
 
 import java.io.IOException;
 import java.sql.Connection;
-import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Locale;
 
-import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.shell.jdbc.JDBCUtils;
 
 public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
 
@@ -42,15 +41,15 @@ public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
     return this;
   }
 
-  public String username() {
-    return username;
-  }
-
-  public JDBCKnoxShellTableBuilder pwd(String pass) {
+  public JDBCKnoxShellTableBuilder password(String pass) {
     this.pass = pass;
     return this;
   }
 
+  public String username() {
+    return username;
+  }
+
   public String password() {
     return pass;
   }
@@ -110,11 +109,7 @@ public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
   }
 
   private Connection createConnection() throws SQLException {
-    if (StringUtils.isNotBlank(username) && pass != null) {
-      return DriverManager.getConnection(connectionUrl, username, pass);
-    } else {
-      return DriverManager.getConnection(connectionUrl);
-    }
+    return JDBCUtils.createConnection(connectionUrl, username, pass);
   }
 
   // added this as a private method so that KnoxShellTableHistoryAspect will not
@@ -132,7 +127,7 @@ public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
       this.table.header(metadata.getColumnName(i));
     }
     while (resultSet.next()) {
-      table.row();
+      this.table.row();
       for (int i = 1; i < colCount + 1; i++) {
         try {
           table.value(resultSet.getObject(metadata.getColumnName(i), Comparable.class));
@@ -148,4 +143,4 @@ public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
     processResultSet(resultSet);
     return this.table;
   }
-}
+}
\ No newline at end of file
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
index 591f8aa..daaea31 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
@@ -55,7 +55,7 @@ public class KnoxShellTable {
   String title;
   long id;
 
-  KnoxShellTable() {
+  public KnoxShellTable() {
     this.id = getUniqueTableId();
   }
 
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
index 470cf4f..3d8c571 100644
--- a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
@@ -412,7 +412,7 @@ public class KnoxShellTableTest {
         return table;
       }
     };
-    builder.username("joe").pwd("pass").sql("select * from book");
+    builder.username("joe").password("pass").sql("select * from book");
   }
 
   @Test