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