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 <[email protected]> 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");
