FELIX-2084: Make the display of exception stack traces available through a variable in the shell

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@910845 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/Console.java b/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/Console.java
index c8a7752..4f1ca87 100644
--- a/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/Console.java
+++ b/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/Console.java
@@ -32,7 +32,6 @@
 import java.util.Properties;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -44,6 +43,7 @@
 import org.apache.felix.karaf.shell.console.Completer;
 import org.apache.felix.karaf.shell.console.completer.AggregateCompleter;
 import org.apache.felix.karaf.shell.console.completer.SessionScopeCompleter;
+import org.fusesource.jansi.Ansi;
 import org.osgi.service.command.CommandProcessor;
 import org.osgi.service.command.CommandSession;
 import org.osgi.service.command.Converter;
@@ -57,6 +57,7 @@
     public static final String PROMPT = "PROMPT";
     public static final String DEFAULT_PROMPT = "\u001B[1m${USER}\u001B[0m@${APPLICATION}> ";
     public static final String PRINT_STACK_TRACES = "karaf.printStackTraces";
+    public static final String LAST_EXCEPTION = "karaf.lastException";
 
     private static final Logger LOGGER = LoggerFactory.getLogger(Console.class);
 
@@ -72,7 +73,6 @@
     private InputStream in;
     private PrintStream out;
     private PrintStream err;
-    private Callable<Boolean> printStackTraces;
 
     public Console(CommandProcessor processor,
                    InputStream in,
@@ -80,8 +80,7 @@
                    PrintStream err,
                    Terminal term,
                    Completer completer,
-                   Runnable closeCallback,
-                   Callable<Boolean> printStackTraces) throws Exception
+                   Runnable closeCallback) throws Exception
     {
         this.in = in;
         this.out = out;
@@ -92,7 +91,6 @@
         this.session = processor.createSession(this.consoleInput, this.out, this.err);
         this.session.put("SCOPE", "shell:osgi:*");
         this.closeCallback = closeCallback;
-        this.printStackTraces = printStackTraces;
 
         reader = new ConsoleReader(this.consoleInput,
                                    new PrintWriter(this.out),
@@ -191,12 +189,17 @@
             catch (Throwable t)
             {
                 try {
-                    if ( printStackTraces.call()) {
+                    LOGGER.info("Exception caught while executing command", t);
+                    session.put(LAST_EXCEPTION, t);
+                    session.getConsole().print(Ansi.ansi().fg(Ansi.Color.RED).toString());
+                    if ( isPrintStackTraces()) {
                         t.printStackTrace(session.getConsole());
                     }
                     else {
-                        session.getConsole().println(t.getMessage());
+                        session.getConsole().println("Error executing command: "
+                                + (t.getMessage() != null ? t.getMessage() : t.getClass().getName()));
                     }
+                    session.getConsole().print(Ansi.ansi().fg(Ansi.Color.DEFAULT).toString());
                 } catch (Exception ignore) {
                         // ignore
                 }
@@ -209,6 +212,20 @@
         }
     }
 
+    protected boolean isPrintStackTraces() {
+        Object s = session.get(PRINT_STACK_TRACES);
+        if (s == null) {
+            s = System.getProperty(PRINT_STACK_TRACES);
+        }
+        if (s == null) {
+            return false;
+        }
+        if (s instanceof Boolean) {
+            return (Boolean) s;
+        }
+        return Boolean.parseBoolean(s.toString());
+    }
+
     protected void welcome() {
         Properties props = new Properties();
         loadProps(props, "org/apache/felix/karaf/shell/console/branding.properties");
diff --git a/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/ConsoleFactory.java b/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/ConsoleFactory.java
index 756fb12..1a9d4ae 100644
--- a/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/ConsoleFactory.java
+++ b/karaf/shell/console/src/main/java/org/apache/felix/karaf/shell/console/jline/ConsoleFactory.java
@@ -82,19 +82,13 @@
                     }
                 }
             };
-            final Callable<Boolean> printStackTraces = new Callable<Boolean>() {
-                public Boolean call() {
-                    return Boolean.valueOf(bundleContext.getProperty(Console.PRINT_STACK_TRACES));
-                }
-            };
             this.console = new Console(commandProcessor,
                                        in,
                                        wrap(out),
                                        wrap(err),
                                        terminal,
                                        new AggregateCompleter(completers),
-                                       callback,
-                                       printStackTraces);
+                                       callback);
             CommandSession session = console.getSession();
             session.put("USER", "karaf");
             session.put("APPLICATION", System.getProperty("karaf.name", "root"));
diff --git a/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/PrintStackTraces.java b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/PrintStackTraces.java
new file mode 100644
index 0000000..a34c03d
--- /dev/null
+++ b/karaf/shell/dev/src/main/java/org/apache/felix/karaf/shell/dev/PrintStackTraces.java
@@ -0,0 +1,47 @@
+/*
+ * 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.felix.karaf.shell.dev;
+
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.karaf.shell.console.OsgiCommandSupport;
+import org.apache.felix.karaf.shell.console.jline.Console;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.lang.String.format;
+
+/**
+ * Command for showing the full tree of bundles that have been used to resolve
+ * a given bundle.
+ */
+@Command(scope = "dev", name = "print-stack-traces",
+         description = "Print the full stack trace in the console when the execution of a command throws an exception")
+public class PrintStackTraces extends OsgiCommandSupport {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PrintStackTraces.class);
+
+    @Argument(name = "print", description="Print stack traces or not", required = false, multiValued = false)
+    boolean print = true;
+
+    protected Object doExecute() throws Exception {
+        session.put(Console.PRINT_STACK_TRACES, Boolean.valueOf(print));
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml b/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
index d3fab7d..d19064f 100644
--- a/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
+++ b/karaf/shell/dev/src/main/resources/OSGI-INF/blueprint/shell-dev.xml
@@ -29,6 +29,9 @@
         <command name="dev/dynamic-import">
             <action class="org.apache.felix.karaf.shell.dev.DynamicImport" />
         </command>
+        <command name="dev/print-stack-traces">
+            <action class="org.apache.felix.karaf.shell.dev.PrintStackTraces" />
+        </command>
     </command-bundle>
 
 </blueprint>
diff --git a/karaf/shell/ssh/src/main/java/org/apache/felix/karaf/shell/ssh/ShellFactoryImpl.java b/karaf/shell/ssh/src/main/java/org/apache/felix/karaf/shell/ssh/ShellFactoryImpl.java
index 6c61af7..996a2f4 100644
--- a/karaf/shell/ssh/src/main/java/org/apache/felix/karaf/shell/ssh/ShellFactoryImpl.java
+++ b/karaf/shell/ssh/src/main/java/org/apache/felix/karaf/shell/ssh/ShellFactoryImpl.java
@@ -93,11 +93,6 @@
 
         public void start(final Environment env) throws IOException {
             try {
-                final Callable<Boolean> printStackTraces = new Callable<Boolean>() {
-                    public Boolean call() {
-                        return Boolean.valueOf(System.getProperty(Console.PRINT_STACK_TRACES));
-                    }
-                };
                 Console console = new Console(commandProcessor,
                                               in,
                                               new PrintStream(new LfToCrLfFilterOutputStream(out), true),
@@ -108,8 +103,7 @@
                                                   public void run() {
                                                       destroy();
                                                   }
-                                              },
-                                              printStackTraces);
+                                              });
                 CommandSession session = console.getSession();
                 session.put("APPLICATION", System.getProperty("karaf.name", "root"));
                 for (Map.Entry<String,String> e : env.getEnv().entrySet()) {
diff --git a/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java
index 1d72c71..bed29dc 100644
--- a/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java
+++ b/karaf/webconsole/gogo/src/main/java/org/apache/felix/karaf/webconsole/gogo/GogoPlugin.java
@@ -208,20 +208,13 @@
                 out = new PipedInputStream();
                 PrintStream pipedOut = new PrintStream(new PipedOutputStream(out), true);
 
-                final Callable<Boolean> printStackTraces = new Callable<Boolean>() {
-                    public Boolean call() {
-                        return Boolean.valueOf(bundleContext.getProperty(Console.PRINT_STACK_TRACES));
-                    }
-                };
-
                 console = new Console(commandProcessor,
                                       new PipedInputStream(in),
                                       pipedOut,
                                       pipedOut,
                                       new WebTerminal(TERM_WIDTH, TERM_HEIGHT),
                                       new AggregateCompleter(completers),
-                                      null,
-                                      printStackTraces);
+                                      null);
                 CommandSession session = console.getSession();
                 session.put("APPLICATION", System.getProperty("karaf.name", "root"));
                 session.put("USER", "karaf");