You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by kr...@apache.org on 2019/09/27 20:18:44 UTC

[knox] 01/02: KNOX-2022 - Splitting up KnoxShellTable and do minor cleanup

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

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

commit 8e7e9d93bceb09fb77fe0bb77fdf01ab5ef6dbf7
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Thu Sep 26 17:27:16 2019 +0200

    KNOX-2022 - Splitting up KnoxShellTable and do minor cleanup
---
 .../apache/knox/gateway/shell/KnoxShellTable.java  | 658 ---------------------
 .../java/org/apache/knox/gateway/shell/Shell.java  |   1 +
 .../shell/table/CSVKnoxShellTableBuilder.java      |  72 +++
 .../shell/table/JDBCKnoxShellTableBuilder.java     | 117 ++++
 .../shell/table/JSONKnoxShellTableBuilder.java     |  44 ++
 .../shell/table/JoinKnoxShellTableBuilder.java     |  75 +++
 .../knox/gateway/shell/table/KnoxShellTable.java   | 169 ++++++
 .../gateway/shell/table/KnoxShellTableBuilder.java |  44 ++
 .../gateway/shell/table/KnoxShellTableCell.java    |  61 ++
 .../gateway/shell/table/KnoxShellTableFilter.java  |  58 ++
 .../shell/table/KnoxShellTableRenderer.java        | 164 +++++
 .../shell/{ => table}/KnoxShellTableTest.java      |  22 +-
 12 files changed, 816 insertions(+), 669 deletions(-)

diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellTable.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellTable.java
deleted file mode 100644
index dfbbf09..0000000
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellTable.java
+++ /dev/null
@@ -1,658 +0,0 @@
-/*
- * 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 java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
-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.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.knox.gateway.util.JsonUtils;
-
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/**
-* Simple table representation and text based rendering of a table via toString().
-* Headers are optional but when used must have the same count as columns
-* within the rows.
-*/
-public class KnoxShellTable {
-  private static int CELL_PAD_SIZE = 2;
-  private static char CELL_CORNER_CHAR = '+';
-  private static char CELL_WALL_CHAR = '|';
-  private static char CELL_DASH_CHAR = '-';
-
-  private List<String> headers = new ArrayList<String>();
-  private List<List<String>> rows = new ArrayList<List<String>>();
-  private String title;
-
-  public KnoxShellTable title(String title) {
-    this.title = title;
-    return this;
-  }
-
-  public KnoxShellTable header(String header) {
-    headers.add(header);
-    return this;
-  }
-
-  public KnoxShellTable row() {
-    List<String> row = new ArrayList<String>();
-    rows.add(row);
-    return this;
-  }
-
-  public KnoxShellTable value(String value) {
-    int index = rows.size() - 1;
-    if (index == -1) {
-      index = 0;
-    }
-    List<String> row = rows.get(index);
-    row.add(value);
-    return this;
-  }
-
-  public KnoxShellTableCell cell(int colIndex, int rowIndex) {
-    return new KnoxShellTableCell(colIndex, rowIndex);
-  }
-
-  public List<String> values(int colIndex) {
-    ArrayList<String> col = new ArrayList<String>();
-    rows.forEach(row -> col.add(row.get(colIndex)));
-    return col;
-  }
-
-  public List<String> values(String colName) {
-    int colIndex = headers.indexOf(colName);
-    ArrayList<String> col = new ArrayList<String>();
-    rows.forEach(row -> col.add(row.get(colIndex)));
-    return col;
-  }
-
-  public KnoxShellTable apply(KnoxShellTableCell cell) {
-    if (!headers.isEmpty()) {
-      headers.set(cell.colIndex, cell.header);
-    }
-    if (!rows.isEmpty()) {
-      rows.get(cell.rowIndex).set(cell.colIndex, cell.value);
-    }
-    return this;
-  }
-
-  public class KnoxShellTableCell {
-    private int colIndex;
-    private int rowIndex;
-    private String header;
-    private String value;
-
-    KnoxShellTableCell(int colIndex, int rowIndex) {
-      this.colIndex = colIndex;
-      this.rowIndex = rowIndex;
-      if (!headers.isEmpty()) {
-        this.header = headers.get(colIndex);
-      }
-      if (!rows.isEmpty()) {
-        this.value = rows.get(rowIndex).get(colIndex);
-      }
-    }
-
-    KnoxShellTableCell(String name, int rowIndex) {
-      this.rowIndex = rowIndex;
-      if (!headers.isEmpty()) {
-        this.header = name;
-        this.colIndex = headers.indexOf(name);
-      }
-      if (!rows.isEmpty()) {
-        this.value = rows.get(rowIndex).get(colIndex);
-      }
-    }
-
-    public KnoxShellTableCell value(String value) {
-      this.value = value;
-      return this;
-    }
-
-    public KnoxShellTableCell header(String name) {
-      this.header = name;
-      return this;
-    }
-
-    public String value() {
-      return this.value;
-    }
-
-    public String header() {
-      return this.header;
-    }
-  }
-
-  public List<String> getHeaders() {
-    if (headers.isEmpty()) {
-      return null;
-    }
-    return headers;
-  }
-
-  public List<List<String>> getRows() {
-    return rows;
-  }
-
-  public String getTitle() {
-    return title;
-  }
-
-  @Override
-  public String toString() {
-    if (!headers.isEmpty() && !rows.isEmpty() && headers.size() != rows.get(0).size()) {
-      throw new IllegalStateException("Number of columns and headers must be the same.");
-    }
-    StringBuilder sb = new StringBuilder();
-    Map<Integer, Integer> widthMap = getWidthMap();
-
-    if (title != null && !title.isEmpty()) {
-      sb.append(this.title);
-      newLine(sb, 1);
-    }
-    int colCount = 0;
-    if (!rows.isEmpty()) {
-      colCount = rows.get(0).size();
-    }
-    if (!headers.isEmpty()) {
-      colCount = headers.size();
-      createBorder(sb, colCount, widthMap);
-      newLine(sb, 1);
-
-      sb.append(CELL_WALL_CHAR);
-      for (int i = 0; i < colCount; i++) {
-        sb.append(centerString(widthMap.get(i) + 4, headers.get(i))).append(CELL_WALL_CHAR);
-      }
-      newLine(sb, 1);
-    }
-    createBorder(sb, colCount, widthMap);
-
-    for (List<String> row : rows) {
-      newLine(sb, 1);
-      sb.append(CELL_WALL_CHAR);
-      for (int i = 0; i < row.size(); i++) {
-        sb.append(centerString(widthMap.get(i) + 4, row.get(i))).append(CELL_WALL_CHAR);
-      }
-    }
-
-    newLine(sb, 1);
-    createBorder(sb, colCount, widthMap);
-    newLine(sb, 1);
-
-    return sb.toString();
-  }
-
-  private void newLine(StringBuilder sb, int count) {
-    for (int i = 0; i < count; i++) {
-      sb.append('\n');
-    }
-  }
-
-  private String centerString(int width, String s) {
-    s = ensureEvenLength(s);
-    return String.format(Locale.ROOT, "%-" + width + "s", String.format(Locale.ROOT, "%" + (s.length() + (width - s.length()) / 2) + "s", s));
-  }
-
-  private String ensureEvenLength(String s) {
-    if (s.length() % 2 != 0) {
-      s = s + " ";
-    }
-    return s;
-  }
-
-  private void createBorder(StringBuilder sb, int headerCount, Map<Integer, Integer> widthMap) {
-    for (int i = 0; i < headerCount; i++) {
-      if (i == 0) {
-        sb.append(CELL_CORNER_CHAR);
-      }
-
-      for (int j = 0; j < widthMap.get(i) + CELL_PAD_SIZE * 2; j++) {
-        sb.append(CELL_DASH_CHAR);
-      }
-      sb.append(CELL_CORNER_CHAR);
-    }
-  }
-
-  private Map<Integer, Integer> getWidthMap() {
-    Map<Integer, Integer> map = new HashMap<>();
-    String cellValue = null;
-    String headerValue = null;
-
-    // set max's to header sizes for each col
-    for (int i = 0; i < headers.size(); i++) {
-      headerValue = ensureEvenLength(headers.get(i));
-      map.put(i, headerValue.length());
-    }
-    // if there are any cell values longer than the header length set max to longest
-    // cell value length
-    for (List<String> row : rows) {
-      for (int i = 0; i < row.size(); i++) {
-        cellValue = ensureEvenLength(row.get(i));
-        if (map.get(i) == null || cellValue.length() > map.get(i)) {
-          map.put(i, cellValue.length());
-        }
-      }
-    }
-    return map;
-  }
-
-  public static KnoxShellTableBuilder builder() {
-    return new KnoxShellTableBuilder();
-  }
-
-  public static class KnoxShellTableBuilder {
-    protected String title;
-
-    public KnoxShellTableBuilder title(String title) {
-      this.title = title;
-      return this;
-    }
-
-    public CSVKnoxShellTableBuilder csv() {
-      return new CSVKnoxShellTableBuilder();
-    }
-
-    public JSONKnoxShellTableBuilder json() {
-      return new JSONKnoxShellTableBuilder();
-    }
-
-    public JoinKnoxShellTableBuilder join() {
-      return new JoinKnoxShellTableBuilder();
-    }
-
-    public JDBCKnoxShellTableBuilder jdbc() {
-      return new JDBCKnoxShellTableBuilder();
-    }
-  }
-
-  public static class JoinKnoxShellTableBuilder extends KnoxShellTableBuilder {
-    private KnoxShellTable left;
-    private KnoxShellTable right;
-    private int leftIndex = -1;
-    private int rightIndex = -1;
-
-    public JoinKnoxShellTableBuilder() {
-    }
-
-    @Override
-    public JoinKnoxShellTableBuilder title(String title) {
-      this.title = title;
-      return this;
-    }
-
-    public JoinKnoxShellTableBuilder left(KnoxShellTable left) {
-      this.left = left;
-      return this;
-    }
-
-    public JoinKnoxShellTableBuilder right(KnoxShellTable right) {
-      this.right = right;
-      return this;
-    }
-
-    public KnoxShellTable on(int leftIndex, int rightIndex) {
-      KnoxShellTable joined = new KnoxShellTable();
-      if (title != null) {
-        joined.title(title);
-      }
-
-      this.leftIndex = leftIndex;
-      this.rightIndex = rightIndex;
-
-      joined.headers.addAll(new ArrayList<String>(left.headers));
-      for (List<String> row : left.rows) {
-        joined.rows.add(new ArrayList<String>(row));
-      }
-      List<String> col = right.values(rightIndex);
-      ArrayList<String> row;
-      String leftKey;
-      int matchedIndex;
-
-      joined.headers.addAll(new ArrayList<String>(right.headers));
-      for (Iterator<List<String>> it = joined.rows.iterator(); it.hasNext();) {
-        row = (ArrayList<String>) it.next();
-        leftKey = row.get(leftIndex);
-        if (leftKey != null) {
-          matchedIndex = col.indexOf(leftKey);
-          if (matchedIndex > -1) {
-            row.addAll(right.rows.get(matchedIndex));
-          }
-          else {
-            it.remove();
-          }
-        }
-      }
-      return joined;
-    }
-  }
-
-  public static class JSONKnoxShellTableBuilder extends KnoxShellTableBuilder {
-    boolean withHeaders;
-
-    @Override
-    public JSONKnoxShellTableBuilder title(String title) {
-      this.title = title;
-      return this;
-    }
-
-    public JSONKnoxShellTableBuilder withHeaders() {
-      withHeaders = true;
-      return this;
-    }
-
-    public KnoxShellTable string(String json) throws IOException {
-      KnoxShellTable table = getKnoxShellTableFromJsonString(json);
-      return table;
-    }
-
-    public KnoxShellTable path(String path) throws IOException {
-      String json = FileUtils.readFileToString(new File(path), StandardCharsets.UTF_8);
-      KnoxShellTable table = getKnoxShellTableFromJsonString(json);
-      return table;
-    }
-
-    public KnoxShellTable getKnoxShellTableFromJsonString(String json) throws IOException {
-      KnoxShellTable table = null;
-      JsonFactory factory = new JsonFactory();
-      ObjectMapper mapper = new ObjectMapper(factory);
-      TypeReference<KnoxShellTable> typeRef
-            = new TypeReference<KnoxShellTable>() {};
-      table = mapper.readValue(json, typeRef);
-      if (title != null) {
-        table.title(title);
-      }
-      return table;
-    }
-  }
-
-  public static class CSVKnoxShellTableBuilder extends KnoxShellTableBuilder {
-    boolean withHeaders;
-
-    @Override
-    public CSVKnoxShellTableBuilder title(String title) {
-      this.title = title;
-      return this;
-    }
-
-    public CSVKnoxShellTableBuilder withHeaders() {
-      withHeaders = true;
-      return this;
-    }
-
-    public KnoxShellTable url(String url) throws IOException {
-      int rowIndex = 0;
-      URLConnection connection;
-      BufferedReader csvReader = null;
-      KnoxShellTable table = null;
-      try {
-        URL urlToCsv = new URL(url);
-        connection = urlToCsv.openConnection();
-        csvReader = new BufferedReader(new InputStreamReader(
-            connection.getInputStream(), StandardCharsets.UTF_8));
-        table = new KnoxShellTable();
-        if (title != null) {
-          table.title(title);
-        }
-        String row = null;
-        while ((row = csvReader.readLine()) != null) {
-            boolean addingHeaders = (withHeaders && rowIndex == 0);
-            if (!addingHeaders) {
-              table.row();
-            }
-            String[] data = row.split(",");
-
-            for (String value : data) {
-              if (addingHeaders) {
-                table.header(value);
-              }
-              else {
-                table.value(value);
-              }
-            }
-            rowIndex++;
-        }
-      }
-      finally {
-        csvReader.close();
-      }
-      return table;
-    }
-  }
-
-  public static class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
-    private String connect;
-    private String username;
-    private String pwd;
-    private String driver;
-    private Connection conn;
-    private boolean tableManagedConnection = true;
-
-    @Override
-    public JDBCKnoxShellTableBuilder title(String title) {
-      this.title = title;
-      return this;
-    }
-
-    public JDBCKnoxShellTableBuilder connect(String connect) {
-      this.connect = connect;
-      return this;
-    }
-
-    public JDBCKnoxShellTableBuilder username(String username) {
-      this.username = username;
-      return this;
-    }
-
-    public JDBCKnoxShellTableBuilder pwd(String pwd) {
-      this.pwd = pwd;
-      return this;
-    }
-
-    public JDBCKnoxShellTableBuilder driver(String driver) {
-      this.driver = driver;
-      return this;
-    }
-
-    public JDBCKnoxShellTableBuilder connection(Connection connection) {
-      this.conn = connection;
-      this.tableManagedConnection = false;
-      return this;
-    }
-
-    public KnoxShellTable sql(String sql) throws IOException, SQLException {
-      KnoxShellTable table = null;
-      Statement statement = null;
-      ResultSet result = null;
-      if (conn == null) {
-        conn = DriverManager.getConnection(connect);
-      }
-      try {
-        if (conn != null) {
-          statement = conn.createStatement();
-  //table.builder().jdbc().connect("jdbc:derby:codejava/webdb1").username("lmccay").password("xxxx").sql("SELECT * FROM book");
-          result = statement.executeQuery(sql);
-          table = new KnoxShellTable();
-          ResultSetMetaData metadata = result.getMetaData();
-          table.title(metadata.getTableName(1));
-          int colcount = metadata.getColumnCount();
-          for(int i = 1; i < colcount + 1; i++) {
-            table.header(metadata.getColumnName(i));
-          }
-          while (result.next()) {
-            table.row();
-            for(int i = 1; i < colcount + 1; i++) {
-              table.value(result.getString(metadata.getColumnName(i)));
-            }
-          }
-        }
-      }
-      finally {
-        result.close();
-        if (conn != null && tableManagedConnection) {
-          conn.close();
-        }
-        if (statement != null) {
-          statement.close();
-        }
-        if (result != null && !result.isClosed()) {
-          result.close();
-        }
-      }
-      return table;
-    }
-  }
-
-  public String toJSON() {
-    return JsonUtils.renderAsJsonString(this);
-  }
-
-  public String toCSV() {
-    StringBuilder csv = new StringBuilder();
-    String header;
-    for(int i = 0; i < headers.size(); i++) {
-      header = headers.get(i);
-      csv.append(header);
-      if (i < headers.size() - 1) {
-        csv.append(',');
-      }
-      else {
-        csv.append('\n');
-      }
-    }
-    for(List<String> row : rows) {
-      for(int ii = 0; ii < row.size(); ii++) {
-        csv.append(row.get(ii));
-        if (ii < row.size() - 1) {
-          csv.append(',');
-        }
-        else {
-          csv.append('\n');
-        }
-      }
-    }
-
-    return csv.toString();
-  }
-
-  public KnoxShellTable select(String cols) {
-    KnoxShellTable table = new KnoxShellTable();
-    List<ArrayList<String>> columns = new ArrayList<ArrayList<String>>();
-    String[] colnames = cols.split(",");
-    for (String colName : colnames) {
-      table.header(colName);
-      columns.add((ArrayList<String>) values(headers.indexOf(colName)));
-    }
-    for (int i = 0; i < rows.size(); i ++) {
-      table.row();
-      for (List<String> col : columns) {
-        table.value(col.get(i));
-      }
-    }
-    return table;
-  }
-
-  public KnoxShellTable sort(String colName) {
-    KnoxShellTable table = new KnoxShellTable();
-
-    String value;
-    List<String> col = values(colName);
-    List<RowIndex> index = new ArrayList<RowIndex>();
-    for (int i = 0; i < col.size(); i++) {
-      value = col.get(i);
-      index.add(new RowIndex(value, i));
-    }
-    Collections.sort(index);
-    table.headers = new ArrayList<String>(headers);
-    for (RowIndex i : index) {
-      table.rows.add(new ArrayList<String>(this.rows.get(i.index)));
-    }
-    return table;
-  }
-
-  public static class RowIndex implements Comparable<RowIndex> {
-    String value;
-    int index;
-
-    public RowIndex(String value, int index) {
-      this.value = value;
-      this.index = index;
-    }
-
-    @Override
-    public int compareTo(RowIndex other) {
-      return (this.value.compareTo(other.value));
-    }
-  }
-
-  public KnoxShellTableFilter filter() {
-    return new KnoxShellTableFilter();
-  }
-
-  public class KnoxShellTableFilter {
-    String name;
-    int index;
-
-    public KnoxShellTableFilter name(String name) {
-      this.name = name;
-      index = headers.indexOf(name);
-      return this;
-    }
-
-    public KnoxShellTableFilter index(int index) {
-      this.index = index;
-      return this;
-    }
-
-    public KnoxShellTable regex(String regex) {
-      KnoxShellTable table = new KnoxShellTable();
-      table.headers.addAll(headers);
-      for (List<String> row : rows) {
-        if (Pattern.matches(regex, row.get(index))) {
-          table.row();
-          row.forEach(value -> {
-            table.value(value);
-          });
-        }
-      }
-      return table;
-    }
-  }
-}
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 f0aaf41..a674979 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
@@ -22,6 +22,7 @@ import org.apache.knox.gateway.shell.hbase.HBase;
 import org.apache.knox.gateway.shell.hdfs.Hdfs;
 import org.apache.knox.gateway.shell.job.Job;
 import org.apache.knox.gateway.shell.manager.Manager;
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
 import org.apache.knox.gateway.shell.workflow.Workflow;
 import org.apache.knox.gateway.shell.yarn.Yarn;
 import org.apache.log4j.PropertyConfigurator;
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/CSVKnoxShellTableBuilder.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/CSVKnoxShellTableBuilder.java
new file mode 100644
index 0000000..db5a9e7
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/CSVKnoxShellTableBuilder.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.knox.gateway.shell.table;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+
+public class CSVKnoxShellTableBuilder extends KnoxShellTableBuilder {
+
+  private boolean withHeaders;
+
+  public CSVKnoxShellTableBuilder withHeaders() {
+    withHeaders = true;
+    return this;
+  }
+
+  public KnoxShellTable url(String url) throws IOException {
+    int rowIndex = 0;
+    URLConnection connection;
+    BufferedReader csvReader = null;
+    KnoxShellTable table = null;
+    try {
+      URL urlToCsv = new URL(url);
+      connection = urlToCsv.openConnection();
+      csvReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
+      table = new KnoxShellTable();
+      if (title != null) {
+        table.title(title);
+      }
+      String row = null;
+      while ((row = csvReader.readLine()) != null) {
+        boolean addingHeaders = (withHeaders && rowIndex == 0);
+        if (!addingHeaders) {
+          table.row();
+        }
+        String[] data = row.split(",");
+
+        for (String value : data) {
+          if (addingHeaders) {
+            table.header(value);
+          } else {
+            table.value(value);
+          }
+        }
+        rowIndex++;
+      }
+    } finally {
+      csvReader.close();
+    }
+    return table;
+  }
+
+}
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
new file mode 100644
index 0000000..d8abaf1
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
@@ -0,0 +1,117 @@
+/*
+ * 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.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;
+
+public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
+
+  private String connectionUrl;
+  private String driver;
+  private Connection conn;
+  private boolean tableManagedConnection = true;
+
+  @Override
+  public JDBCKnoxShellTableBuilder title(String title) {
+    this.title = title;
+    return this;
+  }
+
+  public JDBCKnoxShellTableBuilder connectTo(String connectionUrl) {
+    this.connectionUrl = connectionUrl;
+    return this;
+  }
+
+  public JDBCKnoxShellTableBuilder driver(String driver) throws Exception {
+    this.driver = driver;
+    loadDriver();
+    return this;
+  }
+
+  private void loadDriver() throws Exception {
+    try {
+      Class.forName(driver).newInstance();
+    } catch (ClassNotFoundException e) {
+      System.out.println(String.format("Unable to load the JDBC driver %s. Check your CLASSPATH.", driver));
+      throw e;
+    } catch (InstantiationException e) {
+      System.out.println(String.format("Unable to instantiate the JDBC driver %s", driver));
+      throw e;
+    } catch (IllegalAccessException e) {
+      System.out.println(String.format("Not allowed to access the JDBC driver %s", driver));
+      throw e;
+    }
+  }
+
+  public JDBCKnoxShellTableBuilder connection(Connection connection) {
+    this.conn = connection;
+    this.tableManagedConnection = false;
+    return this;
+  }
+
+  public KnoxShellTable sql(String sql) throws IOException, SQLException {
+    KnoxShellTable table = null;
+    conn = conn == null ? DriverManager.getConnection(connectionUrl) : conn;
+    if (conn != null) {
+      try (Statement statement = conn.createStatement(); ResultSet result = statement.executeQuery(sql);) {
+        table = new KnoxShellTable();
+        final ResultSetMetaData metadata = result.getMetaData();
+        table.title(metadata.getTableName(1));
+        int colcount = metadata.getColumnCount();
+        for (int i = 1; i < colcount + 1; i++) {
+          table.header(metadata.getColumnName(i));
+        }
+        while (result.next()) {
+          table.row();
+          for (int i = 1; i < colcount + 1; i++) {
+            table.value(result.getString(metadata.getColumnName(i)));
+          }
+        }
+      } finally {
+        if (conn != null && tableManagedConnection) {
+          conn.close();
+        }
+      }
+    }
+    return table;
+  }
+
+  public KnoxShellTable build(ResultSet resultSet) throws SQLException {
+    KnoxShellTable table = new KnoxShellTable();
+    ResultSetMetaData metadata = resultSet.getMetaData();
+    table.title(metadata.getTableName(1));
+    int colcount = metadata.getColumnCount();
+    for (int i = 1; i < colcount + 1; i++) {
+      table.header(metadata.getColumnName(i));
+    }
+    while (resultSet.next()) {
+      table.row();
+      for (int i = 1; i < colcount + 1; i++) {
+        table.value(resultSet.getString(metadata.getColumnName(i)));
+      }
+    }
+    return table;
+  }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JSONKnoxShellTableBuilder.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JSONKnoxShellTableBuilder.java
new file mode 100644
index 0000000..1be5512
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JSONKnoxShellTableBuilder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.table;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.FileUtils;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JSONKnoxShellTableBuilder extends KnoxShellTableBuilder {
+
+  public KnoxShellTable fromJson(String json) throws IOException {
+    final KnoxShellTable table = new ObjectMapper(new JsonFactory()).readValue(json, new TypeReference<KnoxShellTable>() {
+    });
+    if (title != null) {
+      table.title(title);
+    }
+    return table;
+  }
+
+  public KnoxShellTable path(String path) throws IOException {
+    return fromJson(FileUtils.readFileToString(new File(path), StandardCharsets.UTF_8));
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JoinKnoxShellTableBuilder.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JoinKnoxShellTableBuilder.java
new file mode 100644
index 0000000..bc504dd
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JoinKnoxShellTableBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * 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.table;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class JoinKnoxShellTableBuilder extends KnoxShellTableBuilder {
+
+  private KnoxShellTable left;
+  private KnoxShellTable right;
+
+  @Override
+  public JoinKnoxShellTableBuilder title(String title) {
+    this.title = title;
+    return this;
+  }
+
+  public JoinKnoxShellTableBuilder left(KnoxShellTable left) {
+    this.left = left;
+    return this;
+  }
+
+  public JoinKnoxShellTableBuilder right(KnoxShellTable right) {
+    this.right = right;
+    return this;
+  }
+
+  public KnoxShellTable on(int leftIndex, int rightIndex) {
+    final KnoxShellTable joinedTable = new KnoxShellTable();
+    if (title != null) {
+      joinedTable.title(title);
+    }
+
+    joinedTable.headers.addAll(new ArrayList<String>(left.headers));
+    for (List<String> row : left.rows) {
+      joinedTable.rows.add(new ArrayList<String>(row));
+    }
+    ArrayList<String> row;
+    String leftKey;
+    int matchedIndex;
+
+    joinedTable.headers.addAll(new ArrayList<String>(right.headers));
+    for (Iterator<List<String>> it = joinedTable.rows.iterator(); it.hasNext();) {
+      row = (ArrayList<String>) it.next();
+      leftKey = row.get(leftIndex);
+      if (leftKey != null) {
+        matchedIndex = right.values(rightIndex).indexOf(leftKey);
+        if (matchedIndex > -1) {
+          row.addAll(right.rows.get(matchedIndex));
+        } else {
+          it.remove();
+        }
+      }
+    }
+    return joinedTable;
+  }
+
+}
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
new file mode 100644
index 0000000..cf40a09
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
@@ -0,0 +1,169 @@
+/*
+ * 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.table;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.knox.gateway.util.JsonUtils;
+
+/**
+ * Simple table representation and text based rendering of a table via
+ * toString(). Headers are optional but when used must have the same count as
+ * columns within the rows.
+ */
+public class KnoxShellTable {
+
+  List<String> headers = new ArrayList<String>();
+  List<List<String>> rows = new ArrayList<List<String>>();
+  String title;
+
+  public KnoxShellTable title(String title) {
+    this.title = title;
+    return this;
+  }
+
+  public KnoxShellTable header(String header) {
+    headers.add(header);
+    return this;
+  }
+
+  public KnoxShellTable row() {
+    rows.add(new ArrayList<String>());
+    return this;
+  }
+
+  public KnoxShellTable value(String value) {
+    final int index = rows.isEmpty() ? 0 : rows.size() - 1;
+    final List<String> row = rows.get(index);
+    row.add(value);
+    return this;
+  }
+
+  public KnoxShellTableCell cell(int colIndex, int rowIndex) {
+    return new KnoxShellTableCell(headers, rows, colIndex, rowIndex);
+  }
+
+  public List<String> values(int colIndex) {
+    ArrayList<String> col = new ArrayList<String>();
+    rows.forEach(row -> col.add(row.get(colIndex)));
+    return col;
+  }
+
+  public List<String> values(String colName) {
+    int colIndex = headers.indexOf(colName);
+    ArrayList<String> col = new ArrayList<String>();
+    rows.forEach(row -> col.add(row.get(colIndex)));
+    return col;
+  }
+
+  public KnoxShellTable apply(KnoxShellTableCell cell) {
+    if (!headers.isEmpty()) {
+      headers.set(cell.colIndex, cell.header);
+    }
+    if (!rows.isEmpty()) {
+      rows.get(cell.rowIndex).set(cell.colIndex, cell.value);
+    }
+    return this;
+  }
+
+  public List<String> getHeaders() {
+    return headers == null || headers.isEmpty() ? null : headers;
+  }
+
+  public List<List<String>> getRows() {
+    return rows;
+  }
+
+  public String getTitle() {
+    return title;
+  }
+
+  public static KnoxShellTableBuilder builder() {
+    return new KnoxShellTableBuilder();
+  }
+
+  public KnoxShellTableFilter filter() {
+    return new KnoxShellTableFilter().table(this);
+  }
+
+  public KnoxShellTable select(String cols) {
+    KnoxShellTable table = new KnoxShellTable();
+    List<List<String>> columns = new ArrayList<List<String>>();
+    String[] colnames = cols.split(",");
+    for (String colName : colnames) {
+      table.header(colName);
+      columns.add((ArrayList<String>) values(headers.indexOf(colName)));
+    }
+    for (int i = 0; i < rows.size(); i++) {
+      table.row();
+      for (List<String> col : columns) {
+        table.value(col.get(i));
+      }
+    }
+    return table;
+  }
+
+  public KnoxShellTable sort(String colName) {
+    KnoxShellTable table = new KnoxShellTable();
+
+    String value;
+    List<String> col = values(colName);
+    List<RowIndex> index = new ArrayList<RowIndex>();
+    for (int i = 0; i < col.size(); i++) {
+      value = col.get(i);
+      index.add(new RowIndex(value, i));
+    }
+    Collections.sort(index);
+    table.headers = new ArrayList<String>(headers);
+    for (RowIndex i : index) {
+      table.rows.add(new ArrayList<String>(this.rows.get(i.index)));
+    }
+    return table;
+  }
+
+  private static class RowIndex implements Comparable<RowIndex> {
+    String value;
+    int index;
+
+    public RowIndex(String value, int index) {
+      this.value = value;
+      this.index = index;
+    }
+
+    @Override
+    public int compareTo(RowIndex other) {
+      return (this.value.compareTo(other.value));
+    }
+  }
+
+  @Override
+  public String toString() {
+    return new KnoxShellTableRenderer(this).toString();
+  }
+
+  public String toJSON() {
+    return JsonUtils.renderAsJsonString(this);
+  }
+
+  public String toCSV() {
+    return new KnoxShellTableRenderer(this).toCSV();
+  }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableBuilder.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableBuilder.java
new file mode 100644
index 0000000..5dcc5af
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableBuilder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.table;
+
+
+public class KnoxShellTableBuilder {
+  protected String title;
+
+  public KnoxShellTableBuilder title(String title) {
+    this.title = title;
+    return this;
+  }
+
+  public CSVKnoxShellTableBuilder csv() {
+    return new CSVKnoxShellTableBuilder();
+  }
+
+  public JSONKnoxShellTableBuilder json() {
+    return new JSONKnoxShellTableBuilder();
+  }
+
+  public JoinKnoxShellTableBuilder join() {
+    return new JoinKnoxShellTableBuilder();
+  }
+
+  public JDBCKnoxShellTableBuilder jdbc() {
+    return new JDBCKnoxShellTableBuilder();
+  }
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCell.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCell.java
new file mode 100644
index 0000000..d2e885d
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCell.java
@@ -0,0 +1,61 @@
+/*
+ * 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.table;
+
+import java.util.List;
+
+class KnoxShellTableCell {
+  int rowIndex;
+  int colIndex;
+  String header;
+  String value;
+
+  KnoxShellTableCell(List<String> headers, List<List<String>> rows, int colIndex, int rowIndex) {
+    this.rowIndex = rowIndex;
+    this.colIndex = colIndex;
+    if (!headers.isEmpty()) {
+      this.header = headers.get(colIndex);
+    }
+    if (!rows.isEmpty()) {
+      this.value = rows.get(rowIndex).get(colIndex);
+    }
+  }
+
+  KnoxShellTableCell(List<String> headers, List<List<String>> rows, String name, int rowIndex) {
+    this.rowIndex = rowIndex;
+    if (!headers.isEmpty()) {
+      this.header = name;
+      this.colIndex = headers.indexOf(name);
+    }
+    if (!rows.isEmpty()) {
+      this.value = rows.get(rowIndex).get(colIndex);
+    }
+  }
+
+  KnoxShellTableCell value(String value) {
+    this.value = value;
+    return this;
+  }
+
+  KnoxShellTableCell header(String name) {
+    this.header = name;
+    return this;
+  }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableFilter.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableFilter.java
new file mode 100644
index 0000000..3a462cc
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableFilter.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.table;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class KnoxShellTableFilter {
+
+  private KnoxShellTable tableToFilter;
+  private int index;
+
+  public KnoxShellTableFilter table(KnoxShellTable table) {
+    this.tableToFilter = table;
+    return this;
+  }
+
+  public KnoxShellTableFilter name(String name) {
+    index = tableToFilter == null ? -1 : tableToFilter.headers.indexOf(name);
+    return this;
+  }
+
+  public KnoxShellTableFilter index(int index) {
+    this.index = index;
+    return this;
+  }
+
+  public KnoxShellTable regex(String regex) {
+    final Pattern pattern = Pattern.compile(regex);
+    final KnoxShellTable filteredTable = new KnoxShellTable();
+    filteredTable.headers.addAll(tableToFilter.headers);
+    for (List<String> row : tableToFilter.rows) {
+      if (pattern.matcher(row.get(index)).matches()) {
+        filteredTable.row();
+        row.forEach(value -> {
+          filteredTable.value(value);
+        });
+      }
+    }
+    return filteredTable;
+  }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableRenderer.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableRenderer.java
new file mode 100644
index 0000000..dd7295e
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableRenderer.java
@@ -0,0 +1,164 @@
+/*
+ * 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.table;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+class KnoxShellTableRenderer {
+
+  private static int CELL_PAD_SIZE = 2;
+  private static char CELL_CORNER_CHAR = '+';
+  private static char CELL_WALL_CHAR = '|';
+  private static char CELL_DASH_CHAR = '-';
+
+  private final KnoxShellTable tableToRender;
+
+  KnoxShellTableRenderer(KnoxShellTable tableToRender) {
+    this.tableToRender = tableToRender;
+  }
+
+  String toCSV() {
+    final StringBuilder csv = new StringBuilder();
+    String header;
+    for (int i = 0; i < tableToRender.headers.size(); i++) {
+      header = tableToRender.headers.get(i);
+      csv.append(header);
+      if (i < tableToRender.headers.size() - 1) {
+        csv.append(',');
+      } else {
+        csv.append('\n');
+      }
+    }
+    for (List<String> row : tableToRender.rows) {
+      for (int ii = 0; ii < row.size(); ii++) {
+        csv.append(row.get(ii));
+        if (ii < row.size() - 1) {
+          csv.append(',');
+        } else {
+          csv.append('\n');
+        }
+      }
+    }
+
+    return csv.toString();
+  }
+
+  @Override
+  public String toString() {
+    if (!tableToRender.headers.isEmpty() && !tableToRender.rows.isEmpty() && tableToRender.headers.size() != tableToRender.rows.get(0).size()) {
+      throw new IllegalStateException("Number of columns and headers must be the same.");
+    }
+    StringBuilder sb = new StringBuilder();
+    Map<Integer, Integer> widthMap = getWidthMap();
+
+    if (StringUtils.isNoneBlank(tableToRender.title)) {
+      sb.append(tableToRender.title);
+      newLine(sb, 1);
+    }
+    int colCount = 0;
+    if (!tableToRender.rows.isEmpty()) {
+      colCount = tableToRender.rows.get(0).size();
+    }
+    if (!tableToRender.headers.isEmpty()) {
+      colCount = tableToRender.headers.size();
+      createBorder(sb, colCount, widthMap);
+      newLine(sb, 1);
+
+      sb.append(CELL_WALL_CHAR);
+      for (int i = 0; i < colCount; i++) {
+        sb.append(centerString(widthMap.get(i) + 4, tableToRender.headers.get(i))).append(CELL_WALL_CHAR);
+      }
+      newLine(sb, 1);
+    }
+    createBorder(sb, colCount, widthMap);
+
+    for (List<String> row : tableToRender.rows) {
+      newLine(sb, 1);
+      sb.append(CELL_WALL_CHAR);
+      for (int i = 0; i < row.size(); i++) {
+        sb.append(centerString(widthMap.get(i) + 4, row.get(i))).append(CELL_WALL_CHAR);
+      }
+    }
+
+    newLine(sb, 1);
+    createBorder(sb, colCount, widthMap);
+    newLine(sb, 1);
+
+    return sb.toString();
+  }
+
+  private void newLine(StringBuilder sb, int count) {
+    for (int i = 0; i < count; i++) {
+      sb.append('\n');
+    }
+  }
+
+  private String centerString(int width, String s) {
+    s = ensureEvenLength(s);
+    return String.format(Locale.ROOT, "%-" + width + "s", String.format(Locale.ROOT, "%" + (s.length() + (width - s.length()) / 2) + "s", s));
+  }
+
+  private String ensureEvenLength(String s) {
+    if (s.length() % 2 != 0) {
+      s = s + " ";
+    }
+    return s;
+  }
+
+  private void createBorder(StringBuilder sb, int headerCount, Map<Integer, Integer> widthMap) {
+    for (int i = 0; i < headerCount; i++) {
+      if (i == 0) {
+        sb.append(CELL_CORNER_CHAR);
+      }
+
+      for (int j = 0; j < widthMap.get(i) + CELL_PAD_SIZE * 2; j++) {
+        sb.append(CELL_DASH_CHAR);
+      }
+      sb.append(CELL_CORNER_CHAR);
+    }
+  }
+
+  private Map<Integer, Integer> getWidthMap() {
+    Map<Integer, Integer> map = new HashMap<>();
+    String cellValue = null;
+    String headerValue = null;
+
+    // set max's to header sizes for each col
+    for (int i = 0; i < tableToRender.headers.size(); i++) {
+      headerValue = ensureEvenLength(tableToRender.headers.get(i));
+      map.put(i, headerValue.length());
+    }
+    // if there are any cell values longer than the header length set max to longest
+    // cell value length
+    for (List<String> row : tableToRender.rows) {
+      for (int i = 0; i < row.size(); i++) {
+        cellValue = ensureEvenLength(row.get(i));
+        if (map.get(i) == null || cellValue.length() > map.get(i)) {
+          map.put(i, cellValue.length());
+        }
+      }
+    }
+    return map;
+  }
+
+}
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxShellTableTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
similarity index 95%
rename from gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxShellTableTest.java
rename to gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
index a2c7e0f..766a0ad 100644
--- a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxShellTableTest.java
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.knox.gateway.shell;
+package org.apache.knox.gateway.shell.table;
 
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
@@ -35,7 +35,8 @@ import java.sql.ResultSetMetaData;
 import java.sql.Statement;
 
 import org.apache.commons.io.FileUtils;
-import org.apache.knox.gateway.shell.KnoxShellTable.KnoxShellTableCell;
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
+import org.apache.knox.gateway.shell.table.KnoxShellTableCell;
 import org.easymock.IAnswer;
 import org.junit.Test;
 
@@ -179,7 +180,7 @@ public class KnoxShellTableTest {
 
     String json = table.toJSON();
 
-    KnoxShellTable table2 = KnoxShellTable.builder().json().string(json);
+    KnoxShellTable table2 = KnoxShellTable.builder().json().fromJson(json);
     assertEquals(table.toString(), table2.toString());
   }
 
@@ -207,13 +208,13 @@ public class KnoxShellTableTest {
     table.row().value("789").value("012").value("844444444");
 
     KnoxShellTableCell cell = table.cell(1, 1);
-    assertEquals(cell.header(), "Column B");
-    assertEquals(cell.value(), "012");
+    assertEquals(cell.header, "Column B");
+    assertEquals(cell.value, "012");
     cell.header("Column Beeee");
     cell.value("234");
     table.apply(cell);
-    assertEquals(table.cell(1, 1).value(), "234");
-    assertEquals(table.cell(1, 1).header(), "Column Beeee");
+    assertEquals(table.cell(1, 1).value, "234");
+    assertEquals(table.cell(1, 1).header, "Column Beeee");
   }
 
   @Test
@@ -251,15 +252,15 @@ public class KnoxShellTableTest {
 
     assertEquals(joined.getRows().size(), 1);
     assertEquals(joined.getTitle(), "Joined Table");
-    assertEquals(joined.cell(0, 0).value(), "123");
+    assertEquals(joined.cell(0, 0).value, "123");
     String json = joined.toJSON();
 
-    KnoxShellTable zombie = KnoxShellTable.builder().json().string(json);
+    KnoxShellTable zombie = KnoxShellTable.builder().json().fromJson(json);
     zombie.title("Zombie Table");
 
     assertEquals(zombie.getRows().size(), 1);
     assertEquals(zombie.getTitle(), "Zombie Table");
-    assertEquals(zombie.cell(0, 0).value(), "123");
+    assertEquals(zombie.cell(0, 0).value, "123");
     KnoxShellTable joined2 = KnoxShellTable.builder().join().title("Joined Table 2").left(table).right(table2).on(1, 3);
     assertEquals(1, joined2.getRows().size());
   }
@@ -288,7 +289,6 @@ public class KnoxShellTableTest {
         return false;
       }
     }).times(2);
-    expect(resultSet.isClosed()).andReturn(true);
     expect(resultSet.getString("BOOK_ID")).andReturn("1").times(1);
     expect(resultSet.getString("TITLE")).andReturn("Apache Knox: The Definitive Guide").times(1);
     expect(metadata.getTableName(1)).andReturn("BOOK");