args2 = new ArrayList<>();
+ Object res = cmd.execute(args2);
+ if (res != null) {
+ terminal.writer().println(res);
+ terminal.writer().flush();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ImportCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ImportCommand.java
new file mode 100644
index 0000000000..a0145af3a5
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ImportCommand.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.terminal.Terminal;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Manages Groovy imports in the current shell session.
+ *
+ * Usage:
+ * :import - lists active imports
+ * :import org.apache.knox.gateway.shell.KnoxSession - adds a single import
+ * :import org.apache.knox.gateway.shell.* - wildcard import
+ */
+public class ImportCommand extends AbstractKnoxShellCommand {
+
+ private static final String NAME = ":import";
+ private static final String SHORTCUT = ":i";
+ private static final String DESC = "Import a class into the namespace";
+ private static final String USAGE = "Usage: :import []\n"
+ + " :import - list active imports\n"
+ + " :import - add a new import\n"
+ + " :import static - add a static import\n"
+ + " :import .* - wildcard import";
+ private static final String HELP = USAGE;
+
+ // Groovysh 4.x validation: chars, digits, underscore, dot, star, optional semicolon
+ private static final Pattern IMPORTED_ITEM_PATTERN = Pattern.compile("^[a-zA-Z0-9_. *]+;?$");
+
+
+ public ImportCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, NAME, SHORTCUT, DESC, USAGE, HELP);
+ }
+
+ @Override
+ public Object execute(List args) {
+ if (args == null || args.isEmpty()) {
+ // List mode
+ Map imports = engine.getImports();
+ if (imports.isEmpty()) {
+ terminal.writer().println("No imports registered.");
+ } else {
+ terminal.writer().println("Active imports:");
+ imports.values().stream()
+ .sorted()
+ .forEach(value -> terminal.writer().println(value)); }
+ terminal.writer().flush();
+ return null;
+ }
+
+ // Join with spaces to preserve "static" keyword
+ String target = String.join(" ", args).trim();
+
+ if (!IMPORTED_ITEM_PATTERN.matcher(target).matches()) {
+ terminal.writer().println("Invalid import definition: '" + target + "'");
+ terminal.writer().flush();
+ return null;
+ }
+
+ // Strip Java-style semicolons
+ target = target.replace(";", "");
+
+ try {
+ engine.execute("import " + target);
+ terminal.writer().println("==> import " + target);
+ } catch (Exception e) {
+ terminal.writer().println("Failed to import '" + target + "': " + e.getMessage());
+ }
+
+ terminal.writer().flush();
+ return null;
+ }
+}
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
deleted file mode 100644
index 90169e0964..0000000000
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/KnoxLoginDialog.java
+++ /dev/null
@@ -1,111 +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.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 username;
- }
-
- @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/LoadCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/LoadCommand.java
new file mode 100644
index 0000000000..f621d32852
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/LoadCommand.java
@@ -0,0 +1,203 @@
+/*
+ * 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 org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.builtins.Completers;
+import org.jline.reader.Completer;
+import org.jline.reader.impl.completer.NullCompleter;
+import org.jline.terminal.Terminal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Loads a Groovy script file or URL into the shell and executes it.
+ * Matches the old Groovysh :load command behavior.
+ *
+ * Usage:
+ * :load /path/to/script.groovy
+ * :load ~/scripts/setup.groovy
+ * :load https://example.com/script.groovy
+ * . /path/to/script.groovy (alias)
+ */
+public class LoadCommand extends AbstractKnoxShellCommand {
+
+ private static final String NAME = ":load";
+ private static final String SHORTCUT = ".";
+ private static final String DESC = "Load a file or URL into the buffer";
+ private static final String USAGE = "Usage: :load \n"
+ + " :load /path/to/script.groovy\n"
+ + " :load ~/scripts/setup.groovy\n"
+ + " :load https://example.com/script.groovy\n"
+ + " . /path/to/script.groovy";
+ private static final String HELP = USAGE;
+
+ public LoadCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, NAME, SHORTCUT, DESC, USAGE, HELP);
+ }
+
+ @Override
+ public Object execute(List args) throws Exception {
+ if (args == null || args.isEmpty()) {
+ terminal.writer().println(USAGE);
+ terminal.writer().flush();
+ return null;
+ }
+
+ Object lastResult = null;
+
+ // Iterate over arguments to support multi-file loading
+ // (e.g., :load file1.groovy file2.groovy)
+ for (String location : args) {
+ String script;
+ try {
+ script = readScript(location);
+ } catch (Exception e) {
+ terminal.writer().println("Failed to load '" + location + "': " + e.getMessage());
+ terminal.writer().flush();
+ continue; // Skip to the next file instead of aborting the whole command
+ }
+
+ // Legacy feature: strip Unix shebangs (#!/usr/bin/env groovy)
+ if (script.startsWith("#!")) {
+ int newlineIndex = script.indexOf('\n');
+ if (newlineIndex != -1) {
+ script = script.substring(newlineIndex + 1);
+ } else {
+ script = "";
+ }
+ }
+
+ if (script.trim().isEmpty()) {
+ terminal.writer().println("Warning: '" + location + "' is empty, nothing to execute.");
+ terminal.writer().flush();
+ continue;
+ }
+
+ terminal.writer().println("Loading " + location + " ...");
+ terminal.writer().flush();
+
+ try {
+ lastResult = engine.execute(script);
+ if (lastResult != null) {
+ terminal.writer().println("==> " + lastResult);
+ terminal.writer().flush();
+ }
+ } catch (Exception e) {
+ terminal.writer().println("Error executing script '" + location + "': " + e.getMessage());
+ terminal.writer().flush();
+ }
+ }
+
+ return lastResult;
+ }
+
+ private String readScript(String location) throws IOException {
+ // Try as URL first (http://, https://, file://)
+ if (isUrl(location)) {
+ return readFromUrl(location);
+ }
+
+ // Expand ~ to user home
+ if (location.startsWith("~")) {
+ location = System.getProperty("user.home") + location.substring(1);
+ }
+
+ Path path = Paths.get(location);
+ if (!Files.exists(path)) {
+ throw new IOException("File not found: " + path.toAbsolutePath());
+ }
+ if (!Files.isReadable(path)) {
+ throw new IOException("File is not readable: " + path.toAbsolutePath());
+ }
+ if (Files.isDirectory(path)) {
+ throw new IOException("Path is a directory, not a file: " + path.toAbsolutePath());
+ }
+
+ try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ return readAndSkipShebang(reader);
+ }
+ }
+
+ private boolean isUrl(String location) {
+ return location!=null &&
+ (location.startsWith("http://")
+ || location.startsWith("https://")
+ || location.startsWith("file://"));
+ }
+
+ private String readFromUrl(String urlStr) throws IOException {
+ URL url;
+ try {
+ url = new URL(urlStr);
+ } catch (MalformedURLException e) {
+ throw new IOException("Invalid URL: " + urlStr, e);
+ }
+
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+ return readAndSkipShebang(reader);
+ }
+ }
+
+ private String readAndSkipShebang(BufferedReader reader) throws IOException {
+ String firstLine = reader.readLine();
+ if (firstLine == null) {
+ return "";
+ }
+
+ StringBuilder scriptBuilder = new StringBuilder();
+
+ // If it's not a shebang, preserve the first line
+ if (!firstLine.startsWith("#!")) {
+ scriptBuilder.append(firstLine).append(System.lineSeparator());
+ }
+
+ // Read the rest of the file
+ String line;
+ while ((line = reader.readLine()) != null) {
+ scriptBuilder.append(line).append(System.lineSeparator());
+ }
+
+ return scriptBuilder.toString();
+ }
+
+ @Override
+ public List getCompleters() {
+ Completers.FileNameCompleter fileNameCompleter = new Completers.FileNameCompleter();
+ Completer fileCompleter = (reader, parsedLine, candidates) -> {
+ String word = parsedLine.word();
+ if (isUrl(word)) {
+ return;
+ }
+ fileNameCompleter.complete(reader, parsedLine, candidates);
+ };
+ return Arrays.asList(fileCompleter, NullCompleter.INSTANCE);
+ }
+
+}
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
index e82e72d68c..e603092702 100644
--- 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
@@ -21,38 +21,89 @@
import java.util.ArrayList;
import java.util.List;
-import org.apache.knox.gateway.shell.CredentialCollectionException;
import org.apache.knox.gateway.shell.KnoxSession;
-import org.apache.groovy.groovysh.CommandSupport;
-import org.apache.groovy.groovysh.Groovysh;
-public class LoginCommand extends CommandSupport {
+import org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
- public LoginCommand(Groovysh shell) {
- super(shell, ":login", ":lgn");
+public class LoginCommand extends AbstractKnoxShellCommand {
+
+ public LoginCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, ":login", ":lgn",
+ "Establishes a Knox session",
+ "Usage: :login ",
+ "Establishes a Knox session using terminal credentials");
}
- @SuppressWarnings("unchecked")
@Override
public Object execute(List args) {
+ if (args == null || args.isEmpty()) {
+ terminal.writer().println("Error: Knox Gateway URL required.");
+ terminal.writer().println(getUsage());
+ terminal.writer().flush();
+ return null;
+ }
+
+ String url = args.get(0);
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);
+ LineReader reader = LineReaderBuilder.builder()
+ .terminal(terminal)
+ .build();
+
+ // 1. Prompt for Username (Clear text)
+ String username = reader.readLine("Username: ");
+ if (username == null || username.trim().isEmpty()) {
+ terminal.writer().println("Login cancelled: Username cannot be empty.");
+ terminal.writer().flush();
+ return null;
}
- } catch (CredentialCollectionException | URISyntaxException e) {
- e.printStackTrace();
+
+ // 2. Prompt for Password (Masked with '*')
+ // JLine 3 intercepts keystrokes and prints the mask char instead of the actual key
+ String password = reader.readLine("Password: ", '*');
+
+ if (password != null) {
+ // Create the session
+ session = KnoxSession.login(url, username, password);
+
+ // Inject the session into the Groovy 5 environment
+ engine.put("__knoxsession", session);
+
+ terminal.writer().println("Session established for: " + url);
+ terminal.writer().flush();
+ } else {
+ terminal.writer().println("Login cancelled.");
+ terminal.writer().flush();
+ }
+
+ } catch (URISyntaxException e) {
+ terminal.writer().println("Invalid URL syntax: " + e.getMessage());
+ terminal.writer().flush();
+ } catch (Exception e) {
+ terminal.writer().println("Failed to establish session: " + e.getMessage());
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
}
- return "Session established for: " + args.get(0);
+
+ return session;
}
public static void main(String[] args) {
- LoginCommand cmd = new LoginCommand(new Groovysh());
- List args2 = new ArrayList<>();
- args2.add("https://localhost:8443/gateway");
- cmd.execute(args2);
+ try {
+ Terminal terminal = TerminalBuilder.builder().system(true).build();
+ GroovyEngine engine = new GroovyEngine();
+ LoginCommand cmd = new LoginCommand(engine, terminal);
+
+ List args2 = new ArrayList<>();
+ args2.add("https://localhost:8443/gateway/sandbox");
+ cmd.execute(args2);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/PurgeCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/PurgeCommand.java
new file mode 100644
index 0000000000..1d2564df7c
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/PurgeCommand.java
@@ -0,0 +1,134 @@
+/*
+ * 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 org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.reader.Completer;
+import org.jline.reader.impl.completer.NullCompleter;
+import org.jline.reader.impl.completer.StringsCompleter;
+import org.jline.terminal.Terminal;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Clears variables, imports or both from the current shell session.
+ * Internal Knox variables (prefixed with __knox) are preserved by default.
+ *
+ * Usage:
+ *
+ * - :purge - clears user variables (preserves internal Knox state)
+ * - :purge variables - same as above
+ * - :purge imports - clears user-added imports (preserves built-in Knox imports)
+ * - :purge all - clears both variables and user imports
+ *
+ *
+ */
+public class PurgeCommand extends AbstractKnoxShellCommand {
+
+ private static final String NAME = ":purge";
+ private static final String SHORTCUT = ":p";
+ private static final String DESC = "Purge variables, classes, imports or preferences";
+ private static final String USAGE = "Usage: :purge [variables|imports|all]";
+ private static final String HELP = USAGE + "\n"
+ + " variables - purge user variables, keep internal Knox state\n"
+ + " imports - purge user-added imports, keep built-in Knox imports\n"
+ + " all - purge both variables and user imports";
+
+ /** Prefix used by Knox internal bindings (__knoxdatasource, __knoxsession, etc.) */
+ private static final String KNOX_INTERNAL_PREFIX = "__knox";
+
+ /**
+ * @param engine the GroovyEngine
+ * @param terminal the JLine terminal
+ */
+ public PurgeCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, NAME, SHORTCUT, DESC, USAGE, HELP);
+ }
+
+ @Override
+ public Object execute(List args) {
+ String what = (args == null || args.isEmpty()) ? "variables" : args.get(0).toLowerCase(Locale.ROOT);
+
+ switch (what) {
+ case "variables":
+ int varCount = clearVariables();
+ terminal.writer().println("Purged " + varCount + " variable(s). Internal Knox state preserved.");
+ break;
+ case "imports":
+ int importCount = clearImports();
+ terminal.writer().println("Purged " + importCount + " import(s).");
+ break;
+ case "all":
+ int vc = clearVariables();
+ int ic = clearImports();
+ terminal.writer().println("Purged " + vc + " variable(s) and " + ic + " import(s).");
+ break;
+ default:
+ terminal.writer().println(USAGE);
+ break;
+ }
+
+ terminal.writer().flush();
+ return null;
+ }
+
+ private int clearVariables() {
+ java.util.Map variables = engine.find();
+ if (variables == null || variables.isEmpty()) {
+ return 0;
+ }
+
+ int count = 0;
+ List keysToDelete = new java.util.ArrayList<>();
+ for (String variableName : variables.keySet()) {
+ // Preserve internal Knox bindings
+ if (variableName != null && !variableName.startsWith(KNOX_INTERNAL_PREFIX)) {
+ keysToDelete.add(variableName);
+ count++;
+ }
+ }
+ if (!keysToDelete.isEmpty()) {
+ engine.del(keysToDelete.toArray(new String[0]));
+ }
+ return count;
+ }
+
+ private int clearImports() {
+ Map imports = engine.getImports();
+
+ if (imports == null || imports.isEmpty()) {
+ return 0;
+ }
+
+ int count = 0;
+ for (String importName : imports.keySet()) {
+ engine.removeImport(importName);
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public List getCompleters() {
+ Completer subCommandCompleter = new StringsCompleter("variables", "imports", "all");
+ return Arrays.asList(subCommandCompleter, NullCompleter.INSTANCE);
+ }
+}
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
index 6f8ca169d8..67d8ecf61a 100644
--- 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
@@ -7,7 +7,7 @@
* "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
+ * 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,
@@ -26,17 +26,19 @@
import java.util.List;
import java.util.Map;
+import org.apache.knox.gateway.shell.CredentialCollector;
+import org.apache.knox.gateway.shell.KnoxDataSource;
+import org.apache.knox.gateway.shell.table.KnoxShellTable;
+
+import org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.terminal.Terminal;
+
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.CredentialCollector;
-import org.apache.knox.gateway.shell.KnoxDataSource;
-import org.apache.knox.gateway.shell.table.KnoxShellTable;
-import org.apache.groovy.groovysh.Groovysh;
-
public class SelectCommand extends AbstractSQLCommandSupport implements KeyListener {
private static final String USAGE = ":sql [assign resulting-variable-name]";
private static final String DESC = "Build table from SQL ResultSet";
@@ -46,8 +48,8 @@ public class SelectCommand extends AbstractSQLCommandSupport implements KeyListe
private List sqlHistory;
private int historyIndex = -1;
- public SelectCommand(Groovysh shell) {
- super(shell, ":SQL", ":sql", DESC, USAGE, DESC);
+ public SelectCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, ":SQL", ":sql", DESC, USAGE, DESC);
}
@Override
@@ -59,14 +61,14 @@ public void keyPressed(KeyEvent event) {
historyIndex = sqlHistory.size() + 1;
}
if (code == KeyEvent.VK_KP_UP ||
- code == KeyEvent.VK_UP) {
+ code == KeyEvent.VK_UP) {
if (historyIndex > 0) {
historyIndex -= 1;
}
setFromHistory = true;
}
else if (code == KeyEvent.VK_KP_DOWN ||
- code == KeyEvent.VK_DOWN) {
+ code == KeyEvent.VK_DOWN) {
if (historyIndex < sqlHistory.size() - 1) {
historyIndex += 1;
setFromHistory = true;
@@ -87,7 +89,7 @@ public void keyReleased(KeyEvent event) {
public void keyTyped(KeyEvent event) {
}
- @SuppressWarnings({"unchecked", "PMD.CloseResource"})
+ @SuppressWarnings({"PMD.CloseResource"})
@Override
public Object execute(List args) {
boolean ok = false;
@@ -95,29 +97,28 @@ public Object execute(List args) {
String bindVariableName = null;
KnoxShellTable table = null;
- if (!args.isEmpty()) {
+ if (args != null && !args.isEmpty()) {
bindVariableName = getBindingVariableNameForResultingTable(args);
}
- String dsName = (String) getVariables().get(KNOXDATASOURCE);
+ String dsName = (String) engine.get(KNOXDATASOURCE);
Map dataSources = getDataSources();
- KnoxDataSource ds = null;
+ KnoxDataSource ds;
+
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) {
+ 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}'.";
+ } else {
+ return "Multiple datasources configured. Please disambiguate with ':datasources select {name}'.";
}
}
+ ds = dataSources.get(dsName);
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);
@@ -132,7 +133,7 @@ else if (dataSources.size() == 1) {
SwingUtils.workAroundFocusIssue(sqlField);
int x = JOptionPane.showConfirmDialog(null, box,
- "SQL Query Input", JOptionPane.OK_CANCEL_OPTION);
+ "SQL Query Input", JOptionPane.OK_CANCEL_OPTION);
if (x == JOptionPane.OK_OPTION) {
ok = true;
@@ -141,6 +142,7 @@ else if (dataSources.size() == 1) {
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) {
@@ -155,7 +157,8 @@ else if (dataSources.size() == 1) {
username = dlg.name();
pass = dlg.chars();
}
- conn = getConnection(ds, username, new String(pass));
+ String passStr = (pass == null) ? null : new String(pass);
+ conn = getConnection(ds, username, passStr);
}
try (Statement statement = conn.createStatement()) {
if (statement.execute(sql)) {
@@ -164,22 +167,26 @@ else if (dataSources.size() == 1) {
}
}
}
- }
- catch (SQLException e) {
- System.out.println("SQL Exception encountered... " + e.getMessage());
+ } catch (SQLException e) {
+ terminal.writer().println("SQL Exception encountered: " + e.getMessage());
+ terminal.writer().flush();
}
}
+ } catch (Exception e) {
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
}
- catch (Exception e) {
- e.printStackTrace();
- }
- }
- else {
- return "please select a datasource via ':datasources select {name}'.";
+ } else {
+ return "Please select a datasource via ':datasources select {name}'.";
}
+
if (table != null && bindVariableName != null) {
- getVariables().put(bindVariableName, table);
+ engine.put(bindVariableName, table);
+ terminal.writer().println("Assigned resulting table to variable: " + bindVariableName);
+ terminal.writer().flush();
}
+
return table;
}
+
}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ShowCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ShowCommand.java
new file mode 100644
index 0000000000..7879868a58
--- /dev/null
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/ShowCommand.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 org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.reader.Completer;
+import org.jline.reader.impl.completer.NullCompleter;
+import org.jline.reader.impl.completer.StringsCompleter;
+import org.jline.terminal.Terminal;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Shows variables, classes or imports in the current shell session.
+ *
+ * Usage:
+ * :show - lists all variables (default)
+ * :show variables - lists all variables
+ * :show imports - lists active imports
+ * :show all - lists both variables and imports
+ */
+public class ShowCommand extends AbstractKnoxShellCommand {
+
+ private static final String NAME = ":show";
+ private static final String SHORTCUT = ":S";
+ private static final String DESC = "Show variables, imports or both";
+ private static final String USAGE = "Usage: :show [variables|imports|all]";
+ private static final String HELP = USAGE + "\n"
+ + " variables - list all bound variables (default)\n"
+ + " imports - list active import statements\n"
+ + " all - list both variables and imports";
+
+ public ShowCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, NAME, SHORTCUT, DESC, USAGE, HELP);
+ }
+
+ @Override
+ public Object execute(List args) {
+ String what = (args == null || args.isEmpty()) ? "variables" : args.get(0).toLowerCase(Locale.ROOT);
+
+ switch (what) {
+ case "variables":
+ showVariables();
+ break;
+ case "imports":
+ showImports();
+ break;
+ case "all":
+ showVariables();
+ terminal.writer().println();
+ showImports();
+ break;
+ default:
+ terminal.writer().println(USAGE);
+ break;
+ }
+
+ terminal.writer().flush();
+ return null;
+ }
+
+ private void showVariables() {
+ Map variables = engine.find();
+ if (variables == null || variables.isEmpty()) {
+ terminal.writer().println("No variables defined.");
+ return;
+ }
+
+ terminal.writer().println("Variables:");
+ variables.forEach((name, value) -> {
+ String type = (value != null) ? value.getClass().getSimpleName() : "null";
+ String display = (value != null) ? value.toString() : "null";
+ terminal.writer().printf(Locale.ROOT, " %-25s (%s) = %s%n", name, type, display);
+ });
+ }
+
+ private void showImports() {
+ java.util.Set imports = engine.getImports().keySet();
+ if (imports.isEmpty()) {
+ terminal.writer().println("No imports registered.");
+ } else {
+ terminal.writer().println("Imports:");
+ imports.forEach(i -> terminal.writer().println(" import " + i));
+ }
+ }
+
+ @Override
+ public List getCompleters() {
+ Completer subCommandCompleter = new StringsCompleter("variables", "imports", "all");
+ return Arrays.asList(subCommandCompleter, NullCompleter.INSTANCE);
+ }
+
+}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/WebHDFSCommand.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/WebHDFSCommand.java
index 4dc6d88881..b1ca9e8199 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/WebHDFSCommand.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/commands/WebHDFSCommand.java
@@ -17,7 +17,6 @@
*/
package org.apache.knox.gateway.shell.commands;
-import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -38,127 +37,157 @@
import org.apache.knox.gateway.shell.hdfs.Status.Response;
import org.apache.knox.gateway.shell.table.KnoxShellTable;
import org.apache.knox.gateway.util.JsonUtils;
-import org.apache.groovy.groovysh.Groovysh;
+
+import org.apache.groovy.groovysh.jline.GroovyEngine;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
public class WebHDFSCommand extends AbstractKnoxShellCommand {
private static final String DESC = "POSIX style commands for Hadoop Filesystems";
private static final String USAGE = "Usage: \n" +
- " :fs mounts \n" +
- " :fs mount target-topology-url mountpoint-name \n" +
- " :fs unmount mountpoint-name \n" +
- " :fs ls {target-path} \n" +
- " :fs cat {target-path} \n" +
- " :fs get {from-path} {to-path} \n" +
- " :fs put {from-path} {tp-path} \n" +
- " :fs rm {target-path} \n" +
- " :fs mkdir {dir-path} \n";
+ " :fs mounts \n" +
+ " :fs mount target-topology-url mountpoint-name \n" +
+ " :fs unmount mountpoint-name \n" +
+ " :fs ls {target-path} \n" +
+ " :fs cat {target-path} \n" +
+ " :fs get {from-path} {to-path} \n" +
+ " :fs put {from-path} {to-path} \n" +
+ " :fs rm {target-path} \n" +
+ " :fs mkdir {dir-path} \n";
+
private Map sessions = new HashMap<>();
- public WebHDFSCommand(Groovysh shell) {
- super(shell, ":filesystem", ":fs", DESC, USAGE, DESC);
+ public WebHDFSCommand(GroovyEngine engine, Terminal terminal) {
+ super(engine, terminal, ":filesystem", ":fs", DESC, USAGE, DESC);
}
@Override
public Object execute(List args) {
Map mounts = getMountPoints();
- if (args.isEmpty()) {
- args.add("ls");
+ if (mounts == null) {
+ mounts = new HashMap<>();
}
- if (args.get(0).equalsIgnoreCase("mount")) {
- String url = args.get(1);
- String mountPoint = args.get(2);
- return mount(mounts, url, mountPoint);
+
+ String action = (args == null || args.isEmpty()) ? "ls" : args.get(0);
+
+ if ("mount".equalsIgnoreCase(action)) {
+ if (args.size() < 3) {
+ return printError("Usage: :fs mount ");
+ }
+ return mount(mounts, args.get(1), args.get(2));
}
- else if (args.get(0).equalsIgnoreCase("unmount")) {
- String mountPoint = args.get(1);
- unmount(mounts, mountPoint);
+ else if ("unmount".equalsIgnoreCase(action)) {
+ if (args.size() < 2) {
+ return printError("Usage: :fs unmount ");
+ }
+ unmount(mounts, args.get(1));
+ return "Unmounted " + args.get(1);
}
- else if (args.get(0).equalsIgnoreCase("mounts")) {
+ else if ("mounts".equalsIgnoreCase(action)) {
return listMounts(mounts);
}
- else if (args.get(0).equalsIgnoreCase("ls")) {
- String path = args.get(1);
- return listStatus(mounts, path);
+ else if ("ls".equalsIgnoreCase(action)) {
+ if (args == null || args.size() < 2) {
+ return printError("Usage: :fs ls ");
+ } else {
+ return listStatus(mounts, args.get(1));
+ }
}
- else if (args.get(0).equalsIgnoreCase("put")) {
+ else if ("put".equalsIgnoreCase(action)) {
// Hdfs.put( session ).file( dataFile ).to( dataDir + "/" + dataFile ).now()
// :fs put from-path to-path
+ if (args.size() < 3) {
+ return printError("Usage: :fs put [permissions]");
+ }
String localFile = args.get(1);
String path = args.get(2);
int permission = 755;
if (args.size() >= 4) {
- permission = Integer.parseInt(args.get(3));
+ try {
+ permission = Integer.parseInt(args.get(3));
+ } catch (NumberFormatException e) {
+ return printError("Invalid permission format. Expected integer.");
+ }
}
-
return put(mounts, localFile, path, permission);
}
- else if (args.get(0).equalsIgnoreCase("rm")) {
+ else if ("rm".equalsIgnoreCase(action)) {
// Hdfs.rm( session ).file( dataFile ).now()
// :fs rm target-path
- String path = args.get(1);
- return remove(mounts, path);
+ if (args.size() < 2) {
+ return printError("Usage: :fs rm ");
+ }
+ return remove(mounts, args.get(1));
}
- else if (args.get(0).equalsIgnoreCase("cat")) {
+ else if ("cat".equalsIgnoreCase(action)) {
// println Hdfs.get( session ).from( dataDir + "/" + dataFile ).now().string
// :fs cat target-path
- String path = args.get(1);
- return cat(mounts, path);
+ if (args.size() < 2) {
+ return printError("Usage: :fs cat ");
+ }
+ return cat(mounts, args.get(1));
}
- else if (args.get(0).equalsIgnoreCase("mkdir")) {
+ else if ("mkdir".equalsIgnoreCase(action)) {
// println Hdfs.mkdir( session ).dir( directoryPath ).perm( "777" ).now().string
// :fs mkdir target-path [perms]
- String path = args.get(1);
- String perms = null;
- if (args.size() == 3) {
- perms = args.get(2);
+ if (args.size() < 2) {
+ return printError("Usage: :fs mkdir [perms]");
}
-
- return mkdir(mounts, path, perms);
+ String perms = (args.size() == 3) ? args.get(2) : null;
+ return mkdir(mounts, args.get(1), perms);
}
- else if (args.get(0).equalsIgnoreCase("get")) {
+ else if ("get".equalsIgnoreCase(action)) {
// println Hdfs.get( session ).from( dataDir + "/" + dataFile ).now().string
// :fs get from-path [to-path]
+ if (args.size() < 2) {
+ return printError("Usage: :fs get [to-path]");
+ }
String path = args.get(1);
-
String mountPoint = determineMountPoint(path);
KnoxSession session = getSessionForMountPoint(mounts, mountPoint);
+
if (session != null) {
String from = determineTargetPath(path, mountPoint);
- String to = null;
- if (args.size() > 2) {
- to = args.get(2);
- }
- else {
- to = System.getProperty("user.home") + File.separator +
- path.substring(path.lastIndexOf(File.separator));
- }
+ String to = (args.size() > 2) ? args.get(2) :
+ System.getProperty("user.home") + File.separator + getFileName(path);
return get(mountPoint, from, to);
+ } else {
+ return "No session established for mountPoint: " + mountPoint + ". Use :fs mount {topology-url} {mountpoint-name}";
}
- else {
- return "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
- }
- }
- else {
- System.out.println("Unknown filesystem command");
- System.out.println(getUsage());
+ } else {
+ terminal.writer().println("Unknown filesystem command: " + action);
+ terminal.writer().println(getUsage());
+ terminal.writer().flush();
}
return "";
}
+ private String printError(String msg) {
+ terminal.writer().println("Error: " + msg);
+ terminal.writer().flush();
+ return null;
+ }
+
+ // HELPER to safely extract filename
+ private String getFileName(String path) {
+ int index = path.lastIndexOf(File.separator);
+ return (index > -1) ? path.substring(index) : path;
+ }
+
private String get(String mountPoint, String from, String to) {
- String result = null;
try {
Hdfs.get(sessions.get(mountPoint)).from(from).file(to).now().getString();
- result = "Successfully copied: " + from + " to: " + to;
+ return "Successfully copied: " + from + " to: " + to;
} catch (KnoxShellException | IOException e) {
- e.printStackTrace();
- result = "Exception ocurred: " + e.getMessage();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return "Exception occurred: " + e.getMessage();
}
- return result;
}
private String mkdir(Map mounts, String path, String perms) {
- String result = null;
String mountPoint = determineMountPoint(path);
KnoxSession session = getSessionForMountPoint(mounts, mountPoint);
if (session != null) {
@@ -166,45 +195,37 @@ private String mkdir(Map mounts, String path, String perms) {
if (!exists(session, targetPath)) {
try {
if (perms != null) {
- Hdfs.mkdir(sessions.get(mountPoint)).dir(targetPath).now().getString();
+ Hdfs.mkdir(sessions.get(mountPoint)).dir(targetPath).perm(perms).now().getString();
+ } else {
+ Hdfs.mkdir(session).dir(targetPath).now().getString();
}
- else {
- Hdfs.mkdir(session).dir(targetPath).perm(perms).now().getString();
- }
- result = "Successfully created directory: " + targetPath;
+ return "Successfully created directory: " + targetPath;
} catch (KnoxShellException | IOException e) {
- e.printStackTrace();
- result = "Exception ocurred: " + e.getMessage();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return "Exception occurred: " + e.getMessage();
}
- }
- else {
- result = targetPath + " already exists";
+ } else {
+ return targetPath + " already exists";
}
}
- else {
- result = "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
- }
- return result;
+ return "No session established for mountPoint: " + mountPoint;
}
private String cat(Map mounts, String path) {
- String response = null;
String mountPoint = determineMountPoint(path);
KnoxSession session = getSessionForMountPoint(mounts, mountPoint);
if (session != null) {
String targetPath = determineTargetPath(path, mountPoint);
try {
- String contents = Hdfs.get(session).from(targetPath).now().getString();
- response = contents;
+ return Hdfs.get(session).from(targetPath).now().getString();
} catch (KnoxShellException | IOException e) {
- e.printStackTrace();
- response = "Exception ocurred: " + e.getMessage();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return "Exception occurred: " + e.getMessage();
}
}
- else {
- response = "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
- }
- return response;
+ return "No session established for mountPoint: " + mountPoint;
}
private String remove(Map mounts, String path) {
@@ -215,11 +236,11 @@ private String remove(Map mounts, String path) {
try {
Hdfs.rm(session).file(targetPath).now().getString();
} catch (KnoxShellException | IOException e) {
- e.printStackTrace();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
}
- }
- else {
- return "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
+ } else {
+ return "No session established for mountPoint: " + mountPoint;
}
return "Successfully removed: " + path;
}
@@ -232,70 +253,68 @@ private String put(Map mounts, String localFile, String path, in
try {
boolean overwrite = false;
if (exists(session, targetPath)) {
- if (collectClearInput(targetPath + " already exists would you like to overwrite (Y/n)").equalsIgnoreCase("y")) {
+ //Replaced System.console() with JLine 3 input
+ String answer = collectClearInput(targetPath + " already exists. Would you like to overwrite? (Y/n): ");
+ if (answer != null && answer.trim().equalsIgnoreCase("y")) {
overwrite = true;
+ } else {
+ return "Put operation cancelled.";
}
}
Hdfs.put(session).file(localFile).to(targetPath).overwrite(overwrite).permission(permission).now().getString();
} catch (IOException e) {
- e.printStackTrace();
- return "Exception ocurred: " + e.getMessage();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return "Exception occurred: " + e.getMessage();
}
- }
- else {
- return "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
+ } else {
+ return "No session established for mountPoint: " + mountPoint;
}
return "Successfully put: " + localFile + " to: " + path;
}
private boolean exists(KnoxSession session, String path) {
- boolean rc = false;
try {
Response response = Hdfs.status(session).file(path).now();
- rc = response.exists();
+ return response.exists();
} catch (KnoxShellException e) {
- // NOP
+ return false;
}
- return rc;
}
private Object listStatus(Map mounts, String path) {
- Object response = null;
try {
- String directory;
String mountPoint = determineMountPoint(path);
if (mountPoint != null) {
KnoxSession session = getSessionForMountPoint(mounts, mountPoint);
if (session != null) {
- directory = determineTargetPath(path, mountPoint);
+ String directory = determineTargetPath(path, mountPoint);
String json = Hdfs.ls(session).dir(directory).now().getString();
- Map>>> map =
- JsonUtils.getFileStatusesAsMap(json);
- if (map != null) {
+
+ Map>>> map = JsonUtils.getFileStatusesAsMap(json);
+ if (map != null && map.containsKey("FileStatuses")) {
ArrayList> list = map.get("FileStatuses").get("FileStatus");
- KnoxShellTable table = buildTableFromListStatus(directory, list);
- response = table;
+ return buildTableFromListStatus(directory, list);
}
+ } else {
+ return "No session established for mountPoint: " + mountPoint;
}
- else {
- response = "No session established for mountPoint: " + mountPoint + " Use :fs mount {topology-url} {mountpoint-name}";
- }
- }
- else {
- response = "No mountpoint found. Use ':fs mount {topologyURL} {mountpoint}'.";
+ } else {
+ return "No mountPoint found. Use ':fs mount {topologyURL} {mountPoint}'.";
}
} catch (KnoxShellException | IOException e) {
- response = "Exception ocurred: " + e.getMessage();
- e.printStackTrace();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return "Exception occurred: " + e.getMessage();
}
- return response;
+ return null;
}
private KnoxShellTable listMounts(Map mounts) {
KnoxShellTable table = new KnoxShellTable();
table.header("Mount Point").header("Topology URL");
- for (String mountPoint : mounts.keySet()) {
- table.row().value(mountPoint).value(mounts.get(mountPoint));
+ for (Map.Entry entry : mounts.entrySet()) {
+ table.row().value(entry.getKey()).value(entry.getValue());
}
return table;
}
@@ -332,31 +351,31 @@ private KnoxSession establishSession(String mountPoint, String url) {
try {
dlg = login();
} catch (CredentialCollectionException e) {
- e.printStackTrace();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
return null;
}
String username = dlg.name();
String password = new String(dlg.chars());
- KnoxSession session = null;
try {
- session = KnoxSession.login(url, username, password);
+ KnoxSession session = KnoxSession.login(url, username, password);
sessions.put(mountPoint, session);
+ return session;
} catch (URISyntaxException e) {
- e.printStackTrace();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
+ return null;
}
- return session;
}
+ // Safely prompt for input using JLine 3
private String collectClearInput(String prompt) {
- Console c = System.console();
- if (c == null) {
- System.err.println("No console.");
- System.exit(1);
+ try {
+ LineReader reader = LineReaderBuilder.builder().terminal(terminal).build();
+ return reader.readLine(prompt);
+ } catch (Exception e) {
+ return ""; // Fallback gracefully if interrupted
}
-
- String value = c.readLine(prompt);
-
- return value;
}
private String determineTargetPath(String path, String mountPoint) {
@@ -373,14 +392,15 @@ private String stripMountPoint(String path, String mountPoint) {
}
private String determineMountPoint(String path) {
- String mountPoint = null;
- if (path.startsWith("/")) {
+ if (path != null && path.startsWith("/")) {
// does the user supplied path starts at a root
// if so check for a mountPoint based on the first element of the path
String[] pathElements = path.split("/");
- mountPoint = pathElements[1];
+ if (pathElements.length > 1) {
+ return pathElements[1];
+ }
}
- return mountPoint;
+ return null;
}
private KnoxShellTable buildTableFromListStatus(String directory, List> list) {
@@ -394,32 +414,43 @@ private KnoxShellTable buildTableFromListStatus(String directory, List map : list) {
- cal.setTimeInMillis(Long.parseLong(map.get("modificationTime")));
- table.row()
+ if (list != null) {
+ for (Map map : list) {
+ cal.setTimeInMillis(Long.parseLong(map.get("modificationTime")));
+ table.row()
.value(map.get("permission"))
.value(map.get("owner"))
.value(map.get("group"))
.value(map.get("length"))
.value(cal.getTime())
.value(map.get("pathSuffix"));
+ }
}
-
return table;
}
protected Map getMountPoints() {
- Map mounts = null;
try {
- mounts = KnoxSession.loadMountPoints();
+ return KnoxSession.loadMountPoints();
} catch (IOException e) {
- e.printStackTrace();
+ e.printStackTrace(terminal.writer());
+ terminal.writer().flush();
}
- return mounts;
+ return null;
}
public static void main(String[] args) {
- WebHDFSCommand cmd = new WebHDFSCommand(new Groovysh());
- cmd.execute(new ArrayList<>(Arrays.asList(args)));
+ try {
+ Terminal terminal = TerminalBuilder.builder().system(true).build();
+ GroovyEngine engine = new GroovyEngine();
+ WebHDFSCommand cmd = new WebHDFSCommand(engine, terminal);
+ Object result = cmd.execute(new ArrayList<>(Arrays.asList(args)));
+ if (result != null) {
+ terminal.writer().println(result);
+ terminal.writer().flush();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/hdfs/Rename.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/hdfs/Rename.java
index cec30e540c..f7633af24d 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/hdfs/Rename.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/hdfs/Rename.java
@@ -67,4 +67,4 @@ public static class Response extends EmptyResponse {
super(response);
}
}
-}
\ 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 d83fc3a6d7..99d89a191f 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
@@ -22,7 +22,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SortOrder;
@@ -292,7 +291,7 @@ public KnoxShellTable apply(KnoxShellTableCell extends Comparable extends Ob
/**
* Trims the String value of whitespace for each of the values in a column
* given the column name.
- * @param colIndex
+ * @param colName column name
* @return table
*/
public KnoxShellTable trim(String colName) {
@@ -335,8 +334,15 @@ public static KnoxShellTableBuilder builder() {
return new KnoxShellTableBuilder();
}
+ private static final java.util.concurrent.atomic.AtomicLong LAST_TIME_MS =
+ new java.util.concurrent.atomic.AtomicLong(System.currentTimeMillis());
+
static long getUniqueTableId() {
- return System.currentTimeMillis() + ThreadLocalRandom.current().nextLong(1000);
+ return LAST_TIME_MS.updateAndGet(lastTime -> {
+ long now = System.currentTimeMillis();
+ // If we are moving too fast, artificially step forward by 1ms to avoid collision
+ return (now > lastTime) ? now : lastTime + 1;
+ });
}
public List getCallHistoryList() {
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistory.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistory.java
index 77451de541..5cfa125d65 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistory.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistory.java
@@ -66,6 +66,14 @@ void removeCallsById(long id) {
callHistory.remove(id);
}
+ /**
+ * Clears the entire call history.
+ * Useful for ensuring clean state between unit tests.
+ */
+ void clear() {
+ callHistory.clear();
+ }
+
public List getCallHistory(long id) {
return callHistory.containsKey(id) ? Collections.unmodifiableList(callHistory.get(id)) : Collections.emptyList();
}
diff --git a/gateway-shell/src/main/resources/META-INF/aop.xml b/gateway-shell/src/main/resources/META-INF/aop.xml
index 0070403377..7f4fa2514a 100644
--- a/gateway-shell/src/main/resources/META-INF/aop.xml
+++ b/gateway-shell/src/main/resources/META-INF/aop.xml
@@ -20,7 +20,7 @@
-
+
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java
index 8778b75a14..87dd685392 100644
--- a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/GetTest.java
@@ -76,4 +76,4 @@ private void testGetRequest(boolean setDoAsUser, String doAsUser) {
verify(knoxSession);
}
-}
\ No newline at end of file
+}
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java
index d93357c436..a1849b54bc 100644
--- a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/knox/token/TokenTest.java
@@ -221,4 +221,4 @@ private void testTokenLifecyle(AbstractTokenLifecycleRequest request, final Stri
assertEquals(testToken, postData);
}
-}
\ No newline at end of file
+}
diff --git a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistoryTest.java b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistoryTest.java
index edb9f2f518..1b99962f5c 100644
--- a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistoryTest.java
+++ b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableCallHistoryTest.java
@@ -26,6 +26,8 @@
import java.util.LinkedList;
import java.util.List;
+import org.junit.After;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -49,6 +51,17 @@ public static void init() {
CALL_LIST.add(new KnoxShellTableCall("org.apache.knox.gateway.shell.table.KnoxShellTableFilter", "greaterThan", true, Collections.singletonMap("5", String.class)));
}
+ @Before
+ public void setUp() {
+ KnoxShellTableCallHistory.getInstance().clear();
+ }
+
+ @After
+ public void tearDown() {
+ KnoxShellTableCallHistory.getInstance().clear();
+ }
+
+
@Test
public void shouldReturnEmptyListInCaseThereWasNoCall() throws Exception {
final long id = KnoxShellTable.getUniqueTableId();
@@ -122,7 +135,7 @@ public void shouldRollbackToValidPreviousStep() throws Exception {
table.rollback();
assertNotNull(table);
assertEquals(14, table.rows.size());
- assertEquals(table.values(0).get(13), "14"); // selected the first column (ZIP) where the last element - index 13 - is 14
+ assertEquals("14", table.values(0).get(13)); // selected the first column (ZIP) where the last element - index 13 - is 14
}
private void recordCallHistory(long id, int steps) {
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 5835dbe45c..b73e20e487 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
@@ -55,6 +55,8 @@
import org.apache.knox.gateway.shell.jdbc.Database;
import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
import org.easymock.IAnswer;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -67,6 +69,16 @@ public class KnoxShellTableTest {
private static final String SYSTEM_PROPERTY_DERBY_STREAM_ERROR_FILE = "derby.stream.error.file";
private static final String SAMPLE_DERBY_DATABASE_NAME = "sampleDerbyDatabase";
+ @Before
+ public void setUp() {
+ KnoxShellTableCallHistory.getInstance().clear();
+ }
+
+ @After
+ public void tearDown() {
+ KnoxShellTableCallHistory.getInstance().clear();
+ }
+
@Test
public void testSimpleTableRendering() {
String expectedResult = "+------------+------------+------------+\n"
diff --git a/pom.xml b/pom.xml
index a9c4cc46eb..7512f56aa9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -167,7 +167,7 @@
0.13
1.8.1
9.0
- 1.9.6
+ 1.9.25.1
4.1.5
1.79
1.79
@@ -212,7 +212,7 @@
4.0.5
2.10.1
1.9.0
- 4.0.29
+ 5.0.4
32.1.3-jre
3.4.1
2.2
@@ -223,7 +223,6 @@
5.3.6
2.18.2
0.8.13
- 1.18
1.2.1
1.2.2
1.3.2
@@ -240,8 +239,8 @@
3.4
2.47
9.4.57.v20241219
- 3.21.0
- 5.9.0
+ 3.30.6
+ 5.18.1
2.10.8
2.9.0
2.5.2
@@ -272,7 +271,7 @@
2.0.9
0.0.11.1
0.12.4
- 5.5.6
+ 6.0.0
1.13.0
1.13.0
1.2.6
@@ -1653,14 +1652,28 @@