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

clebertsuconic pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git

commit 6b1ada8aca2f2d8a78d8230dfc55147ff0027cb5
Author: Clebert Suconic <[email protected]>
AuthorDate: Sat Mar 14 14:12:24 2026 -0400

    ARTEMIS-5954 Artemis Shell History
---
 .../org/apache/activemq/artemis/cli/Shell.java     | 129 ++++++++++++++++++++-
 .../artemis/cli/commands/InputAbstract.java        |  80 +------------
 .../cli/commands/util/input/InputReader.java       |  52 +++++++++
 .../cli/commands/util/input/JLineInputReader.java  |  44 +++++++
 .../cli/commands/util/input/SystemInputReader.java |  59 ++++++++++
 5 files changed, 285 insertions(+), 79 deletions(-)

diff --git 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Shell.java 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Shell.java
index 3c627e49d0..b98fce02fb 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Shell.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Shell.java
@@ -17,14 +17,26 @@
 
 package org.apache.activemq.artemis.cli;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
 
 import org.apache.activemq.artemis.cli.commands.ActionContext;
 import org.apache.activemq.artemis.cli.commands.Connect;
 import org.apache.activemq.artemis.cli.commands.messages.ConnectionAbstract;
+import org.apache.activemq.artemis.cli.commands.util.input.SystemInputReader;
 import org.jline.console.SystemRegistry;
 import org.jline.console.impl.SystemRegistryImpl;
 import org.jline.reader.EndOfFileException;
@@ -36,6 +48,7 @@ import org.jline.reader.UserInterruptException;
 import org.jline.reader.impl.DefaultParser;
 import org.jline.terminal.Terminal;
 import org.jline.terminal.TerminalBuilder;
+import org.jspecify.annotations.Nullable;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 import picocli.shell.jline3.PicocliCommands;
@@ -52,6 +65,11 @@ public class Shell implements Runnable {
    @CommandLine.Option(names = "--password", description = "It will be used 
for an initial connection if set.")
    protected String password;
 
+   @CommandLine.Option(names = "--history", description = "File where shell 
history is being stored.")
+   protected File historyFile;
+
+   private static final String DEFAULT_HISTORY_FILE = "history-file";
+
    public Shell(CommandLine commandLine) {
    }
 
@@ -64,7 +82,7 @@ public class Shell implements Runnable {
          connect.setUser(user).setPassword(password).setBrokerURL(brokerURL);
          connect.run();
       }
-      runShell(false);
+      runShell(false, historyFile);
    }
 
    private static ThreadLocal<AtomicBoolean> IN_SHELL = 
ThreadLocal.withInitial(() -> new AtomicBoolean(false));
@@ -88,10 +106,20 @@ public class Shell implements Runnable {
    }
 
    public static void runShell(boolean printBanner) {
+      runShell(printBanner, null);
+   }
+
+   public static void runShell(boolean printBanner, File historyFile) {
       try {
          setInShell();
 
-         boolean isInstance = System.getProperty("artemis.instance") != null;
+         String artemisInstance = System.getProperty("artemis.instance");
+
+         boolean isInstance = artemisInstance != null;
+
+         if (isInstance && historyFile == null) {
+            historyFile = inquiryDefaultHistory(historyFile, artemisInstance);
+         }
 
          Supplier<Path> workDir = () -> 
Paths.get(System.getProperty("user.dir"));
 
@@ -117,12 +145,18 @@ public class Shell implements Runnable {
             systemRegistry.setCommandRegistries(picocliCommands);
             systemRegistry.register("help", picocliCommands);
 
-            LineReader reader = LineReaderBuilder.builder()
+            LineReaderBuilder readerBuilder = LineReaderBuilder.builder()
                .terminal(terminal)
                .completer(systemRegistry.completer())
                .parser(parser)
-               .variable(LineReader.LIST_MAX, 50)   // max tab completion 
candidates
-               .build();
+               .variable(LineReader.LIST_MAX, 50);   // max tab completion 
candidates
+
+            if (historyFile != null) {
+               readerBuilder.variable(LineReader.HISTORY_FILE, 
historyFile.toPath());
+            }
+
+            LineReader reader = readerBuilder.build();
+
             factory.setTerminal(terminal);
             if (ActionContext.system() != null) {
                ActionContext.system().lineReader = reader;
@@ -134,6 +168,11 @@ public class Shell implements Runnable {
                printBanner();
             }
 
+            if (historyFile != null) {
+               
System.out.println(org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE
 + "Shell history being saved at " + historyFile.getAbsolutePath() + 
org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
+               System.out.println();
+            }
+
             System.out.println("For a list of commands, type " + 
org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "help" + 
org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " or press " + 
org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "<TAB>" + 
org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + ":");
             System.out.println("Type " + 
org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "exit" + 
org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " or press " + 
org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "<CTRL-D>" + 
org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE + " to leave the 
session:");
 
@@ -141,11 +180,14 @@ public class Shell implements Runnable {
             String line;
             while (true) {
                try {
+                  // load the history on each loop because other instances may 
be changing it as well
+                  loadHistory(historyFile, reader);
                   // We build a new command every time, as they could have 
state from previous executions
                   systemRegistry.setCommandRegistries(new 
PicocliCommands(Artemis.buildCommand(isInstance, !isInstance, false)));
                   systemRegistry.cleanUp();
                   line = reader.readLine(getPrompt(), rightPrompt, 
(MaskingCallback) null, null);
                   systemRegistry.execute(line);
+                  saveHistory(reader, historyFile);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                   // Ignore
@@ -171,6 +213,67 @@ public class Shell implements Runnable {
 
    }
 
+   private static void saveHistory(LineReader reader, File historyFile) {
+      try {
+         setHistoryFilePermissions(historyFile);
+         reader.getHistory().save();
+      } catch (Throwable e) {
+         System.err.println("Error saving history of shell : " + 
e.getMessage());
+      }
+   }
+
+   private static void loadHistory(File historyFile, LineReader reader) {
+      if (historyFile != null) {
+         try {
+            if (historyFile.length() > 0) {
+               reader.getHistory().load();
+            }
+         } catch (IOException e) {
+            System.err.println("Could not load history from " + historyFile + 
": " + e.getMessage());
+         }
+      }
+   }
+
+   private static @Nullable File inquiryDefaultHistory(File historyFile, 
String artemisInstance) {
+      final String NO_HISTORY = "NO_HISTORY";
+      SystemInputReader inputReader = new SystemInputReader();
+      File defaultHistoryFile = new File(artemisInstance + "/etc/" + 
DEFAULT_HISTORY_FILE);
+      try {
+         if (!defaultHistoryFile.exists()) {
+            defaultHistoryFile.createNewFile();
+         }
+
+         if (defaultHistoryFile.exists()) {
+            if (defaultHistoryFile.length() == 0) {
+               String input = inputReader.inputLoop(null, "Allow shell 
history? (Y/N)", s -> s != null && (s.toUpperCase().equals("Y") || 
s.toUpperCase().equals("N"))).toUpperCase();
+               if (input.equals("N")) {
+                  historyFile = null;
+                  try (PrintStream fileOutputStream = new PrintStream(new 
FileOutputStream(defaultHistoryFile))) {
+                     fileOutputStream.println(NO_HISTORY);
+                  }
+               } else {
+                  defaultHistoryFile.createNewFile();
+               }
+            }
+
+            if (historyFile == null) {
+               try (BufferedReader fileReader = new BufferedReader(new 
InputStreamReader(new FileInputStream(defaultHistoryFile)))) {
+                  String line = fileReader.readLine();
+                  if (line != null && line.equals(NO_HISTORY)) {
+                     // the user selected no history in the past
+                     historyFile = null;
+                  } else {
+                     historyFile = defaultHistoryFile;
+                  }
+               }
+            }
+         }
+      } catch (IOException e) {
+         // no history on this case then
+      }
+      return historyFile;
+   }
+
    private static void printBanner() {
       
System.out.print(org.apache.activemq.artemis.cli.Terminal.INFO_COLOR_UNICODE);
       try {
@@ -205,4 +308,20 @@ public class Shell implements Runnable {
       PROMPT.set(org.apache.activemq.artemis.cli.Terminal.INPUT_COLOR_UNICODE 
+ prompt + " > " + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
    }
 
+   private static void setHistoryFilePermissions(File historyFile) {
+      try {
+         Path path = historyFile.toPath();
+         if (!Files.exists(path)) {
+            historyFile.createNewFile();
+         }
+         if (Files.exists(path)) {
+            Set<PosixFilePermission> perms = new HashSet<>();
+            perms.add(PosixFilePermission.OWNER_READ);
+            perms.add(PosixFilePermission.OWNER_WRITE);
+            Files.setPosixFilePermissions(path, perms);
+         }
+      } catch (Exception e) {
+      }
+   }
+
 }
diff --git 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java
 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java
index a49452125d..602d81cdf3 100644
--- 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java
+++ 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/InputAbstract.java
@@ -17,68 +17,13 @@
 
 package org.apache.activemq.artemis.cli.commands;
 
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.util.Scanner;
-
-import org.apache.activemq.artemis.cli.Terminal;
-import org.jline.reader.LineReader;
+import org.apache.activemq.artemis.cli.commands.util.input.InputReader;
+import org.apache.activemq.artemis.cli.commands.util.input.JLineInputReader;
+import org.apache.activemq.artemis.cli.commands.util.input.SystemInputReader;
 import picocli.CommandLine.Option;
 
 public class InputAbstract extends ActionAbstract {
 
-   public interface InputReader {
-      String readLine(String prompt);
-      String readPassword(String prompt);
-   }
-
-   private class ScanReader implements InputReader {
-      ScanReader(InputStream inputStream, PrintStream promptStream) {
-         this.scanner = new Scanner(inputStream);
-         this.promptStream = promptStream;
-      }
-
-      Scanner scanner;
-      PrintStream promptStream;
-
-
-      @Override
-      public String readLine(String prompt) {
-         promptStream.println(prompt);
-         return scanner.nextLine();
-      }
-
-      @Override
-      public String readPassword(String prompt) {
-         promptStream.println(prompt);
-         char[] typedPassword = System.console().readPassword();
-         if (typedPassword == null) {
-            return null;
-         } else {
-            return new String(typedPassword);
-         }
-      }
-   }
-
-
-   private class JLineReader implements InputReader {
-      JLineReader(LineReader reader) {
-         this.reader = reader;
-      }
-
-      public LineReader reader;
-
-      @Override
-      public String readLine(String prompt) {
-         return reader.readLine(Terminal.INPUT_COLOR_UNICODE + prompt + ":" + 
Terminal.CLEAR_UNICODE);
-      }
-
-      @Override
-      public String readPassword(String prompt) {
-         return reader.readLine(Terminal.INPUT_COLOR_UNICODE + prompt + ":" + 
Terminal.CLEAR_UNICODE, '*');
-      }
-   }
-
 
    InputReader lineReader;
 
@@ -154,20 +99,7 @@ public class InputAbstract extends ActionAbstract {
          return silentDefault;
       }
 
-      String inputStr;
-      boolean valid = false;
-      getActionContext().out.println();
-      do {
-         getActionContext().out.println(propertyName + ":");
-         inputStr = lineReader.readLine(prompt);
-
-         if (!acceptNull && inputStr.trim().isEmpty()) {
-            getActionContext().out.println("Invalid Entry!");
-         } else {
-            valid = true;
-         }
-      }
-      while (!valid);
+      String inputStr = lineReader.inputLoop(propertyName, prompt, s -> s != 
null && !s.isEmpty());
 
       return inputStr.trim();
    }
@@ -206,9 +138,9 @@ public class InputAbstract extends ActionAbstract {
       super.execute(context);
 
       if (context.lineReader != null) {
-         this.lineReader = new JLineReader(context.lineReader);
+         this.lineReader = new JLineInputReader(context.lineReader);
       } else {
-         this.lineReader = new ScanReader(context.in, context.out);
+         this.lineReader = new SystemInputReader(context.in, context.out);
       }
 
       return null;
diff --git 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/InputReader.java
 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/InputReader.java
new file mode 100644
index 0000000000..1a272ce44f
--- /dev/null
+++ 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/InputReader.java
@@ -0,0 +1,52 @@
+/*
+ * 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.activemq.artemis.cli.commands.util.input;
+
+import java.util.function.Function;
+
+public abstract class InputReader {
+   public abstract String readLine(String prompt);
+   public abstract String readPassword(String prompt);
+
+   protected abstract void printLine(String line);
+
+   public String inputLoop(String propertyName, String prompt, 
Function<String, Boolean> inputValidator) {
+      String inputStr;
+      boolean valid = false;
+
+      printLine("");
+
+      do {
+         if (propertyName != null) {
+            printLine(propertyName + ":");
+         }
+
+         inputStr = readLine(prompt);
+
+         if (inputValidator == null || inputValidator.apply(inputStr)) {
+            valid = true;
+         } else {
+            valid = false;
+         }
+      }
+      while (!valid);
+
+      return inputStr;
+   }
+
+}
diff --git 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/JLineInputReader.java
 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/JLineInputReader.java
new file mode 100644
index 0000000000..ae150e8e61
--- /dev/null
+++ 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/JLineInputReader.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.activemq.artemis.cli.commands.util.input;
+
+import org.apache.activemq.artemis.cli.Terminal;
+import org.jline.reader.LineReader;
+
+public class JLineInputReader extends InputReader {
+   private final LineReader reader;
+
+   public JLineInputReader(LineReader reader) {
+      this.reader = reader;
+   }
+
+   @Override
+   public String readLine(String prompt) {
+      return reader.readLine(Terminal.INPUT_COLOR_UNICODE + prompt + ":" + 
Terminal.CLEAR_UNICODE);
+   }
+
+   @Override
+   public String readPassword(String prompt) {
+      return reader.readLine(Terminal.INPUT_COLOR_UNICODE + prompt + ":" + 
Terminal.CLEAR_UNICODE, '*');
+   }
+
+   @Override
+   protected void printLine(String line) {
+      System.out.println(line);
+   }
+}
diff --git 
a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/SystemInputReader.java
 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/SystemInputReader.java
new file mode 100644
index 0000000000..978c059716
--- /dev/null
+++ 
b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/input/SystemInputReader.java
@@ -0,0 +1,59 @@
+/*
+ * 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.activemq.artemis.cli.commands.util.input;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+public class SystemInputReader extends InputReader {
+   public SystemInputReader() {
+      this(System.in, System.out);
+   }
+
+   public SystemInputReader(InputStream inputStream, PrintStream promptStream) 
{
+      this.scanner = new Scanner(inputStream);
+      this.promptStream = promptStream;
+   }
+
+   private final Scanner scanner;
+   private final PrintStream promptStream;
+
+
+   @Override
+   public String readLine(String prompt) {
+      promptStream.println(prompt);
+      return scanner.nextLine();
+   }
+
+   @Override
+   public String readPassword(String prompt) {
+      promptStream.println(prompt);
+      char[] typedPassword = System.console().readPassword();
+      if (typedPassword == null) {
+         return null;
+      } else {
+         return new String(typedPassword);
+      }
+   }
+
+   @Override
+   protected void printLine(String line) {
+      promptStream.println(line);
+   }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to