FELIX-3530 Replace JLine with adapted AjaxTerm

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1345205 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/gogo/pom.xml b/webconsole-plugins/gogo/pom.xml
index e155deb..c1f2614 100644
--- a/webconsole-plugins/gogo/pom.xml
+++ b/webconsole-plugins/gogo/pom.xml
@@ -78,19 +78,6 @@
 						<Bundle-Activator>

 							org.apache.felix.webconsole.plugins.gogo.impl.Activator

                         </Bundle-Activator>

-                        <Embed-Dependency>

-                            jline;inline=true

-                        </Embed-Dependency>

-                        <Bundle-NativeCode>

-                            META-INF/native/windows32/jansi.dll;osname=Win32;processor=x86,

-                            META-INF/native/windows64/jansi.dll;osname=Win32;processor=x86-64,

-                            <!--

-                            META-INF/native/linux32/libjansi.so;osname=Linux;processor=x86,

-                            META-INF/native/linux64/libjansi.so;osname=Linux;processor=x86-64,

-                            META-INF/native/osx/libjansi.jnilib;osname=MacOSX,

-                            -->

-                            *

-                        </Bundle-NativeCode>

 					</instructions>

 				</configuration>

 			</plugin>

@@ -128,11 +115,5 @@
             <version>2.3</version>

             <scope>provided</scope>

         </dependency>

-        <dependency>

-            <groupId>org.sonatype.jline</groupId>

-            <artifactId>jline</artifactId>

-            <version>2.5</version>

-            <scope>provided</scope>

-        </dependency>

  	</dependencies>

 </project>

diff --git a/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES b/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES
index bbd3414..81ae103 100644
--- a/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES
+++ b/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES
@@ -5,10 +5,9 @@
 The Apache Software Foundation (http://www.apache.org/).
 Licensed under the Apache License 2.0.
 
-This product includes software from https://github.com/jline/jline2
-Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
-All rights reserved.
-Licensed under the BSD License
+This product includes files derived from Ajaxterm
+http://antony.lesuisse.org/software/ajaxterm/
+Ajaxterm files are released in the Public Domain
 
 This plugin draws from Apache Karaf Console and Web Console bundles.
 Kudos!
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java
index 59703ee..5f60721 100644
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java
@@ -18,18 +18,18 @@
  */
 package org.apache.felix.webconsole.plugins.gogo.impl;
 
-import org.fusesource.jansi.AnsiConsole;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
 public class Activator implements BundleActivator {
 
+    private SessionTerminalManager terminalManager;
+
     private GogoPlugin plugin;
 
     public void start(BundleContext context) throws Exception {
-        AnsiConsole.systemInstall();
-
-        this.plugin = new GogoPlugin();
+        this.terminalManager = new SessionTerminalManager(context);
+        this.plugin = new GogoPlugin(this.terminalManager);
         this.plugin.register(context);
     }
 
@@ -38,8 +38,10 @@
             this.plugin.unregister();
             this.plugin = null;
         }
-
-        AnsiConsole.systemUninstall();
+        if (this.terminalManager != null) {
+            this.terminalManager.shutdown();
+            this.terminalManager = null;
+        }
     }
 
 }
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java
deleted file mode 100644
index b8ee42e..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java
+++ /dev/null
@@ -1,89 +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.felix.webconsole.plugins.gogo.impl;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Collection;
-
-import jline.console.completer.Completer;
-
-/**
- * Completer which contains multipule completers and aggregates them together.
- */
-public class AggregateCompleter implements Completer
-{
-    private final Collection<Completer> completers;
-
-    public AggregateCompleter(final Collection<Completer> completers) {
-        assert completers != null;
-        this.completers = completers;
-    }
-
-    public int complete(final String buffer, final int cursor, final List candidates) {
-        // buffer could be null
-        assert candidates != null;
-
-        List<Completion> completions = new ArrayList<Completion>(completers.size());
-
-        // Run each completer, saving its completion results
-        int max = -1;
-        for (Completer completer : completers) {
-            Completion completion = new Completion(candidates);
-            completion.complete(completer, buffer, cursor);
-
-            // Compute the max cursor position
-            max = Math.max(max, completion.cursor);
-
-            completions.add(completion);
-        }
-
-        // Append candiates from completions which have the same cursor position as max
-        for (Completion completion : completions) {
-            if (completion.cursor == max) {
-                // noinspection unchecked
-                candidates.addAll(completion.candidates);
-            }
-        }
-
-        return max;
-    }
-
-    private class Completion
-    {
-        public final List<CharSequence> candidates;
-
-        public int cursor;
-
-        public Completion(final List candidates) {
-            assert candidates != null;
-
-            // noinspection unchecked
-            this.candidates = new LinkedList<CharSequence>(candidates);
-        }
-
-        public void complete(final Completer completer, final String buffer, final int cursor) {
-            assert completer != null;
-
-            this.cursor = completer.complete(buffer, cursor, candidates);
-        }
-    }
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java
deleted file mode 100644
index f3540a2..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java
+++ /dev/null
@@ -1,25 +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.felix.webconsole.plugins.gogo.impl;
-
-
-@SuppressWarnings("serial")
-public class CloseShellException extends Exception {
-
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java
deleted file mode 100644
index 0c04196..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java
+++ /dev/null
@@ -1,38 +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.felix.webconsole.plugins.gogo.impl;
-
-import org.apache.felix.service.command.CommandSession;
-
-public class CommandSessionHolder {
-
-    private static final ThreadLocal<CommandSession> session = new ThreadLocal<CommandSession>();
-
-    public static CommandSession getSession() {
-        return session.get();
-    }
-
-    public static void setSession(CommandSession commandSession) {
-        session.set(commandSession);
-    }
-
-    public static void unset() {
-        session.remove();
-    }
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java
deleted file mode 100644
index 0a55a35..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java
+++ /dev/null
@@ -1,85 +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.felix.webconsole.plugins.gogo.impl;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import jline.console.completer.Completer;
-
-import org.apache.felix.service.command.CommandSession;
-
-public class CommandsCompleter implements Completer {
-
-    private CommandSession session;
-    private final List<Completer> completers = new ArrayList<Completer>();
-    private final Set<String> commands = new HashSet<String>();
-
-    public CommandsCompleter(CommandSession session) {
-        this.session = session;
-    }
-
-
-    public int complete(String buffer, int cursor, List<CharSequence> candidates) {
-        checkData();
-        int res = new AggregateCompleter(completers).complete(buffer, cursor, candidates);
-//        TODO: Collections.sort(candidates);
-        return res;
-    }
-
-    protected synchronized void checkData() {
-        // Copy the set to avoid concurrent modification exceptions
-        // TODO: fix that in gogo instead
-        Set<String> names = new HashSet<String>((Set<String>) session.get(".commands" /* CommandSessionImpl.COMMANDS */));
-        if (!names.equals(commands)) {
-            commands.clear();
-            completers.clear();
-
-            // get command aliases
-            Set<String> aliases = this.getAliases();
-            completers.add(new StringsCompleter(aliases));
-
-            // add argument completers for each command
-            for (String command : names) {
-                commands.add(command);
-            }
-        }
-    }
-
-    /**
-     * Get the aliases defined in the console session.
-     *
-     * @return the aliases set
-     */
-    private Set<String> getAliases() {
-        Set<String> vars = (Set<String>) session.get(null);
-        Set<String> aliases = new HashSet<String>();
-        for (String var : vars) {
-            Object content = session.get(var);
-            if (content != null && content.getClass().getName().equals("org.apache.felix.gogo.runtime.Closure"))  {
-                aliases.add(var);
-            }
-        }
-        return aliases;
-    }
-
-}
-
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java
index 42b3585..44c7ae1 100644
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java
@@ -18,52 +18,24 @@
  */
 package org.apache.felix.webconsole.plugins.gogo.impl;
 
-import java.io.CharArrayWriter;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.InterruptedIOException;
 import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.io.Reader;
 import java.util.Map;
-import java.util.Properties;
+import java.util.Map.Entry;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import jline.Terminal;
-import jline.UnsupportedTerminal;
-import jline.console.ConsoleReader;
-import jline.console.completer.Completer;
-import jline.console.history.PersistentHistory;
 
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
-import org.apache.felix.service.command.Converter;
-import org.fusesource.jansi.Ansi;
 
 public class Console implements Runnable {
 
-    public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
-
-    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";
-
     public static final String IGNORE_INTERRUPTS = "karaf.ignoreInterrupts";
 
     protected CommandSession session;
 
-    private ConsoleReader reader;
-
     private BlockingQueue<Integer> queue;
 
     private boolean interrupt;
@@ -76,8 +48,6 @@
 
     private Runnable closeCallback;
 
-    private Terminal terminal;
-
     private InputStream consoleInput;
 
     private InputStream in;
@@ -88,28 +58,20 @@
 
     private Thread thread;
 
-    public Console(CommandProcessor processor, InputStream in, PrintStream out, PrintStream err, Terminal term,
-            Runnable closeCallback) throws Exception {
+    public Console(CommandProcessor processor, InputStream in, PrintStream out, PrintStream err,
+            Runnable closeCallback, Map<String, String> sessionProps) throws Exception {
         this.in = in;
         this.out = out;
         this.err = err;
         this.queue = new ArrayBlockingQueue<Integer>(1024);
-        this.terminal = term == null ? new UnsupportedTerminal() : term;
         this.consoleInput = new ConsoleInputStream();
         this.session = processor.createSession(this.consoleInput, this.out, this.err);
-        this.session.put("SCOPE", "shell:osgi:*");
         this.closeCallback = closeCallback;
 
-        reader = new ConsoleReader(this.consoleInput, new PrintWriter(this.out), getClass().getResourceAsStream(
-            "keybinding.properties"), this.terminal);
-
-        session.put(".jline.history", reader.getHistory());
-        Completer completer = createCompleter();
-        if (completer != null) {
-            reader.addCompleter(completer);
-        }
-        if (Boolean.getBoolean("jline.nobell")) {
-            reader.setBellEnabled(false);
+        if (sessionProps != null) {
+            for (Entry<String, String> entry: sessionProps.entrySet()) {
+                this.session.put(entry.getKey(), entry.getValue());
+            }
         }
         pipe = new Thread(new Pipe());
         pipe.setName("gogo shell pipe thread");
@@ -121,109 +83,30 @@
     }
 
     public void close() {
-        // System.err.println("Closing");
-        if (reader.getHistory() instanceof PersistentHistory) {
-            try {
-                ((PersistentHistory) reader.getHistory()).flush();
-            } catch (IOException e) {
-                // ignore
-            }
-        }
         running = false;
-        CommandSessionHolder.unset();
         pipe.interrupt();
     }
 
     public void run() {
-        ThreadLocal<CommandSessionHolder> consoleState = new ThreadLocal<CommandSessionHolder>();
         thread = Thread.currentThread();
-        CommandSessionHolder.setSession(session);
         running = true;
         pipe.start();
-        welcome();
-        setSessionProperties();
-        String scriptFileName = System.getProperty(SHELL_INIT_SCRIPT);
-        if (scriptFileName != null) {
-            Reader r = null;
-            try {
-                File scriptFile = new File(scriptFileName);
-                r = new InputStreamReader(new FileInputStream(scriptFile));
-                CharArrayWriter w = new CharArrayWriter();
-                int n;
-                char[] buf = new char[8192];
-                while ((n = r.read(buf)) > 0) {
-                    w.write(buf, 0, n);
-                }
-                session.execute(new String(w.toCharArray()));
-            } catch (Exception e) {
-                System.err.println("Error in initialization script: " + e.getMessage());
-            } finally {
-                if (r != null) {
-                    try {
-                        r.close();
-                    } catch (IOException e) {
-                        // Ignore
-                    }
-                }
-            }
+        try
+        {
+            session.execute("gosh --login --noshutdown");
         }
-        while (running) {
-            try {
-                String command = null;
-                boolean loop = true;
-                boolean first = true;
-                while (loop) {
-                    checkInterrupt();
-                    String line = reader.readLine(first ? getPrompt() : "> ");
-                    if (line == null) {
-                        break;
-                    }
-                    if (command == null) {
-                        command = line;
-                    } else {
-                        command += " " + line;
-                    }
-                    if (reader.getHistory().size() == 0) {
-                        reader.getHistory().add(command);
-                    } else {
-                        reader.getHistory().replace(command);
-                    }
-                    try {
-                        new Parser(command, 0).program();
-                        loop = false;
-                    } catch (Exception e) {
-                        loop = true;
-                        first = false;
-                    }
-                }
-                if (command == null) {
-                    break;
-                }
-                // session.getConsole().println("Executing: " + line);
-                Object result = session.execute(command);
-                if (result != null) {
-                    session.getConsole().println(session.format(result, Converter.INSPECT));
-                }
-            } catch (InterruptedIOException e) {
-                // System.err.println("^C");
-                // TODO: interrupt current thread
-            } catch (CloseShellException e) {
-                break;
-            } catch (Exception t) {
-                try {
-                    session.put(LAST_EXCEPTION, t);
-                    session.getConsole().print(Ansi.ansi().fg(Ansi.Color.RED).toString());
-                    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
-                }
-            }
+        catch (Exception e)
+        {
+            e.printStackTrace(this.err);
+        }
+        finally
+        {
+            session.close();
+
+            this.out.println("Good Bye!");
         }
         close();
-        // System.err.println("Exiting console...");
+
         if (closeCallback != null) {
             closeCallback.run();
         }
@@ -243,90 +126,6 @@
         return Boolean.parseBoolean(s.toString());
     }
 
-    protected void welcome() {
-        Properties props = loadBrandingProperties();
-        String welcome = props.getProperty("welcome");
-        if (welcome != null && welcome.length() > 0) {
-            session.getConsole().println(welcome);
-        }
-    }
-
-    protected void setSessionProperties() {
-        Properties props = loadBrandingProperties();
-        for (Map.Entry<Object, Object> entry : props.entrySet()) {
-            String key = (String) entry.getKey();
-            if (key.startsWith("session.")) {
-                session.put(key.substring("session.".length()), entry.getValue());
-            }
-        }
-    }
-
-    protected Completer createCompleter() {
-        return new CommandsCompleter(session);
-    }
-
-    protected Properties loadBrandingProperties() {
-        Properties props = new Properties();
-        loadProps(props, "org/apache/karaf/shell/console/branding.properties");
-        loadProps(props, "org/apache/karaf/branding/branding.properties");
-        return props;
-    }
-
-    protected void loadProps(Properties props, String resource) {
-        InputStream is = null;
-        try {
-            is = getClass().getClassLoader().getResourceAsStream(resource);
-            if (is != null) {
-                props.load(is);
-            }
-        } catch (IOException e) {
-            // ignore
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    // Ignore
-                }
-            }
-        }
-    }
-
-    protected String getPrompt() {
-        try {
-            String prompt;
-            try {
-                Object p = session.get(PROMPT);
-                if (p != null) {
-                    prompt = p.toString();
-                } else {
-                    Properties properties = loadBrandingProperties();
-                    if (properties.getProperty("prompt") != null) {
-                        prompt = properties.getProperty("prompt");
-                        // we put the PROMPT in ConsoleSession to avoid to read
-                        // the properties file each time.
-                        session.put(PROMPT, prompt);
-                    } else {
-                        prompt = DEFAULT_PROMPT;
-                    }
-                }
-            } catch (Throwable t) {
-                prompt = DEFAULT_PROMPT;
-            }
-            Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(prompt);
-            while (matcher.find()) {
-                Object rep = session.get(matcher.group(1));
-                if (rep != null) {
-                    prompt = prompt.replace(matcher.group(0), rep.toString());
-                    matcher.reset(prompt);
-                }
-            }
-            return prompt;
-        } catch (Throwable t) {
-            return "$ ";
-        }
-    }
-
     private void checkInterrupt() throws IOException {
         if (Thread.interrupted() || interrupt) {
             interrupt = false;
@@ -408,14 +207,13 @@
             try {
                 while (running) {
                     try {
-                        int c = terminal.readCharacter(in);
+                        int c = in.read();
                         if (c == -1) {
                             return;
                         } else if (c == 4 && !getBoolean(IGNORE_INTERRUPTS)) {
                             err.println("^D");
                         } else if (c == 3 && !getBoolean(IGNORE_INTERRUPTS)) {
                             err.println("^C");
-                            reader.getCursorBuffer().clear();
                             interrupt();
                         }
                         queue.put(c);
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java
index 70b57b2..c4f12cd 100644
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java
@@ -22,22 +22,12 @@
 
 package org.apache.felix.webconsole.plugins.gogo.impl;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.InterruptedIOException;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.zip.GZIPOutputStream;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.osgi.framework.BundleContext;
 
@@ -54,175 +44,71 @@
     public static final String TITLE = "Gogo";
 
     public static final int TERM_WIDTH = 120;
+
     public static final int TERM_HEIGHT = 39;
 
-    private CommandProcessor commandProcessor;
+    private final SessionTerminalManager terminalManager;
 
-    public GogoPlugin() {
+    public GogoPlugin(final SessionTerminalManager terminalManager) {
         super(LABEL, TITLE, null);
+        this.terminalManager = terminalManager;
     }
 
     @Override
     public void activate(BundleContext bundleContext) {
         super.activate(bundleContext);
-        this.commandProcessor = new CommandProcessor() {
-            public CommandSession createSession(InputStream in, PrintStream out, PrintStream err) {
-                return ((CommandProcessor) getService(CommandProcessor.class.getName())).createSession(in, out, err);
-            }
-        };
     }
 
     @Override
     public void deactivate() {
-        this.commandProcessor = null;
         super.deactivate();
     }
 
-    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
-    {
+    protected void renderContent(HttpServletRequest request, HttpServletResponse response) throws IOException {
         PrintWriter pw = response.getWriter();
 
         String appRoot = request.getContextPath() + request.getServletPath();
-        pw.println( "<link href=\"" + appRoot + "/gogo/res/ui/gogo.css\" rel=\"stylesheet\" type=\"text/css\" />" );
-        pw.println( "<script src=\"" + appRoot + "/gogo/res/ui/gogo.js\" type=\"text/javascript\"></script>" );
-        pw.println( "<div id='console'><div id='term'></div></div>" );
-        pw.println( "<script type=\"text/javascript\"><!--" );
-        pw.println( "window.onload = function() { gogo.Terminal(document.getElementById(\"term\"), " + TERM_WIDTH + ", " + TERM_HEIGHT + "); }" );
-        pw.println( "--></script>" );
+        pw.println("<link href=\"" + appRoot + "/gogo/res/ui/gogo.css\" rel=\"stylesheet\" type=\"text/css\" />");
+        pw.println("<script src=\"" + appRoot + "/gogo/res/ui/gogo.js\" type=\"text/javascript\"></script>");
+        pw.println("<p id=\"statline\" class=\"statline\">&nbsp;</p>");
+        pw.println("<div id='console'><div id='term'></div></div>");
+        pw.println("<script type=\"text/javascript\"><!--");
+        pw.println("window.onload = function() { gogo.Terminal(document.getElementById(\"term\"), " + TERM_WIDTH + ", "
+            + TERM_HEIGHT + "); }");
+        pw.println("--></script>");
     }
 
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
         String encoding = request.getHeader("Accept-Encoding");
         boolean supportsGzip = (encoding != null && encoding.toLowerCase().indexOf("gzip") > -1);
-        SessionTerminal st = getSessionTerminal(request);
-        String str = request.getParameter("k");
-        String f = request.getParameter("f");
-        String dump = st.handle(str, f != null && f.length() > 0);
-        if (dump != null) {
-            if (supportsGzip) {
-                response.setHeader("Content-Encoding", "gzip");
-                response.setHeader("Content-Type", "text/html");
-                try {
-                    GZIPOutputStream gzos =  new GZIPOutputStream(response.getOutputStream());
-                    gzos.write(dump.getBytes());
-                    gzos.close();
-                } catch (IOException ie) {
-                    // handle the error here
-                    ie.printStackTrace();
+        SessionTerminal st = terminalManager.getSessionTerminal(request);
+        if (st != null) {
+            String str = request.getParameter("k");
+            String f = request.getParameter("f");
+            String dump = st.handle(str, f != null && f.length() > 0);
+            if (dump != null) {
+                if (supportsGzip) {
+                    response.setHeader("Content-Encoding", "gzip");
+                    response.setHeader("Content-Type", "text/html; charset=UTF-8");
+                    try {
+                        GZIPOutputStream gzos = new GZIPOutputStream(response.getOutputStream());
+                        gzos.write(dump.getBytes("UTF-8"));
+                        gzos.close();
+                    } catch (IOException ie) {
+                        // handle the error here
+                        ie.printStackTrace();
+                    }
+                } else {
+                    response.setContentType("text/html; charset=UTF-8");
+                    response.getOutputStream().write(dump.getBytes("UTF-8"));
                 }
-            } else {
-                response.getOutputStream().write(dump.getBytes());
             }
+        } else {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
         }
+
+        response.flushBuffer();
     }
 
-    private SessionTerminal getSessionTerminal(final HttpServletRequest request) throws IOException{
-        final Object terminal = request.getSession(true).getAttribute("terminal");
-        if (terminal instanceof SessionTerminal) {
-            final SessionTerminal st = (SessionTerminal) terminal;
-            if (!st.isClosed()) {
-                return st;
-            }
-        }
-
-        final SessionTerminal st = new SessionTerminal(request.getRemoteUser());
-        request.getSession().setAttribute("terminal", st);
-        return st;
-    }
-
-    public class SessionTerminal implements Runnable {
-
-        private Terminal terminal;
-        private Console console;
-        private PipedOutputStream in;
-        private PipedInputStream out;
-        private boolean closed;
-
-        public SessionTerminal(final String user) throws IOException {
-            try {
-                this.terminal = new Terminal(TERM_WIDTH, TERM_HEIGHT);
-                terminal.write("\u001b\u005B20\u0068"); // set newline mode on
-
-                in = new PipedOutputStream();
-                out = new PipedInputStream();
-                PrintStream pipedOut = new PrintStream(new PipedOutputStream(out), true);
-
-                console = new Console(commandProcessor,
-                                      new PipedInputStream(in),
-                                      pipedOut,
-                                      pipedOut,
-                                      new WebTerminal(TERM_WIDTH, TERM_HEIGHT),
-                                      null);
-                CommandSession session = console.getSession();
-                session.put("APPLICATION", System.getProperty("karaf.name", "root"));
-                session.put("USER", user);
-                session.put("COLUMNS", Integer.toString(TERM_WIDTH));
-                session.put("LINES", Integer.toString(TERM_HEIGHT));
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw e;
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw (IOException) new IOException().initCause(e);
-            }
-            new Thread(console).start();
-            new Thread(this).start();
-        }
-
-        public boolean isClosed() {
-            return closed;
-        }
-
-        public String handle(String str, boolean forceDump) throws IOException {
-            try {
-                if (str != null && str.length() > 0) {
-                    String d = terminal.pipe(str);
-                    for (byte b : d.getBytes()) {
-                        in.write(b);
-                    }
-                    in.flush();
-                }
-            } catch (IOException e) {
-                closed = true;
-                throw e;
-            }
-            try {
-                return terminal.dump(10, forceDump);
-            } catch (InterruptedException e) {
-                throw new InterruptedIOException(e.toString());
-            }
-        }
-
-        public void run() {
-            try {
-                for (;;) {
-                    byte[] buf = new byte[8192];
-                    int l = out.read(buf);
-                    InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(buf, 0, l));
-                    StringBuilder sb = new StringBuilder();
-                    for (;;) {
-                        int c = r.read();
-                        if (c == -1) {
-                            break;
-                        }
-                        sb.append((char) c);
-                    }
-                    if (sb.length() > 0) {
-                        terminal.write(sb.toString());
-                    }
-                    String s = terminal.read();
-                    if (s != null && s.length() > 0) {
-                        for (byte b : s.getBytes()) {
-                            in.write(b);
-                        }
-                    }
-                }
-            } catch (IOException e) {
-                closed = true;
-                e.printStackTrace();
-            }
-        }
-
-    }
 }
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java
deleted file mode 100644
index 3d4b025..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java
+++ /dev/null
@@ -1,73 +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.felix.webconsole.plugins.gogo.impl;
-
-import org.apache.felix.service.command.CommandSession;
-
-
-/**
- * A helper class for name scoping
- */
-public class NameScoping {
-
-    public static final String MULTI_SCOPE_MODE_KEY = "MULTI_SCOPE_MODE";
-
-    /**
-     * Returns the name of the command which can omit the global scope prefix if the command starts with the
-     * same prefix as the current application
-     */
-    public static String getCommandNameWithoutGlobalPrefix(CommandSession session, String key) {
-        if (!isMultiScopeMode(session)) {
-            String globalScope = (String) session.get("APPLICATION");
-            if (globalScope != null) {
-                String prefix = globalScope + ":";
-                if (key.startsWith(prefix)) {
-                    // TODO we may only want to do this for single-scope mode when outside of OSGi?
-                    // so we may want to also check for a isMultiScope mode == false
-                    return key.substring(prefix.length());
-                }
-            }
-        }
-        return key;
-    }
-
-    /**
-     * Returns true if the given scope is the global scope so that it can be hidden from help messages
-     */
-    public static boolean isGlobalScope(CommandSession session, String scope) {
-        if (!isMultiScopeMode(session)) {
-            String globalScope = (String) session.get("APPLICATION");
-            if (globalScope != null) {
-                return scope.equals(globalScope);
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if we are in multi-scope mode (the default) or if we are in single scope mode which means we
-     * avoid prefixing commands with their scope
-     */
-    public static boolean isMultiScopeMode(CommandSession session) {
-        Object value = session.get(MULTI_SCOPE_MODE_KEY);
-        if (value != null && value.equals("false")) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java
deleted file mode 100644
index d2df531..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java
+++ /dev/null
@@ -1,473 +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.
- */
-// DWB14: parser loops if // comment at start of program
-// DWB15: allow program to have trailing ';'
-package org.apache.felix.webconsole.plugins.gogo.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class Parser
-{
-    int current = 0;
-    String text;
-    boolean escaped;
-    static final String SPECIAL = "<;|{[\"'$`(=";
-
-    List<List<List<String>>> program;
-    List<List<String>> statements;
-    List<String> statement;
-    int cursor;
-    int start = -1;
-    int c0;
-    int c1;
-    int c2;
-    int c3;
-
-    public Parser(String text, int cursor)
-    {
-        this.text = text;
-        this.cursor = cursor;
-    }
-
-    void ws()
-    {
-        // derek: BUGFIX: loop if comment  at beginning of input
-        //while (!eof() && Character.isWhitespace(peek())) {
-        while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0))
-        {
-            if (current != 0 || !escaped && Character.isWhitespace(peek()))
-            {
-                current++;
-            }
-            if (peek() == '/' && current < text.length() - 2
-                && text.charAt(current + 1) == '/')
-            {
-                comment();
-            }
-            if (current == 0)
-            {
-                break;
-            }
-        }
-    }
-
-    private void comment()
-    {
-        while (!eof() && peek() != '\n' && peek() != '\r')
-        {
-            next();
-        }
-    }
-
-    boolean eof()
-    {
-        return current >= text.length();
-    }
-
-    char peek()
-    {
-        return peek(false);
-    }
-
-    char peek(boolean increment)
-    {
-        escaped = false;
-        if (eof())
-        {
-            return 0;
-        }
-
-        int last = current;
-        char c = text.charAt(current++);
-
-        if (c == '\\')
-        {
-            escaped = true;
-            if (eof())
-            {
-                throw new RuntimeException("Eof found after \\"); // derek
-            }
-
-            c = text.charAt(current++);
-
-            switch (c)
-            {
-                case 't':
-                    c = '\t';
-                    break;
-                case '\r':
-                case '\n':
-                    c = ' ';
-                    break;
-                case 'b':
-                    c = '\b';
-                    break;
-                case 'f':
-                    c = '\f';
-                    break;
-                case 'n':
-                    c = '\n';
-                    break;
-                case 'r':
-                    c = '\r';
-                    break;
-                case 'u':
-                    c = unicode();
-                    current += 4;
-                    break;
-                default:
-                    // We just take the next character literally
-                    // but have the escaped flag set, important for {},[] etc
-            }
-        }
-        if (cursor > last && cursor <= current)
-        {
-            c0 = program != null ? program.size() : 0;
-            c1 = statements != null ? statements.size() : 0;
-            c2 = statement != null ? statement.size() : 0;
-            c3 = (start >= 0) ? current - start : 0;
-        }
-        if (!increment)
-        {
-            current = last;
-        }
-        return c;
-    }
-
-    public List<List<List<String>>> program()
-    {
-        program = new ArrayList<List<List<String>>>();
-        ws();
-        if (!eof())
-        {
-            program.add(pipeline());
-            while (peek() == ';')
-            {
-                current++;
-                List<List<String>> pipeline = pipeline();
-                program.add(pipeline);
-            }
-        }
-        if (!eof())
-        {
-            throw new RuntimeException("Program has trailing text: " + context(current));
-        }
-
-        List<List<List<String>>> p = program;
-        program = null;
-        return p;
-    }
-
-    CharSequence context(int around)
-    {
-        return text.subSequence(Math.max(0, current - 20), Math.min(text.length(),
-            current + 4));
-    }
-
-    public List<List<String>> pipeline()
-    {
-        statements = new ArrayList<List<String>>();
-        statements.add(statement());
-        while (peek() == '|')
-        {
-            current++;
-            ws();
-            if (!eof())
-            {
-                statements.add(statement());
-            }
-            else
-            {
-                statements.add(new ArrayList<String>());
-                break;
-            }
-        }
-        List<List<String>> s = statements;
-        statements = null;
-        return s;
-    }
-
-    public List<String> statement()
-    {
-        statement = new ArrayList<String>();
-        statement.add(value());
-        while (!eof())
-        {
-            ws();
-            if (peek() == '|' || peek() == ';')
-            {
-                break;
-            }
-
-            if (!eof())
-            {
-                statement.add(messy());
-            }
-        }
-        List<String> s = statement;
-        statement = null;
-        return s;
-    }
-
-    public String messy()
-    {
-        start = current;
-        char c = peek();
-        if (c > 0 && SPECIAL.indexOf(c) < 0)
-        {
-            current++;
-            try {
-                while (!eof())
-                {
-                    c = peek();
-                    if (!escaped && (c == ';' || c == '|' || Character.isWhitespace(c)))
-                    {
-                        break;
-                    }
-                    next();
-                }
-                return text.substring(start, current);
-            } finally {
-                start = -1;
-            }
-        }
-        else
-        {
-            return value();
-        }
-    }
-
-    String value()
-    {
-        ws();
-
-        start = current;
-        try {
-            char c = next();
-            if (!escaped)
-            {
-                switch (c)
-                {
-                    case '{':
-                        return text.substring(start, find('}', '{'));
-                    case '(':
-                        return text.substring(start, find(')', '('));
-                    case '[':
-                        return text.substring(start, find(']', '['));
-                    case '<':
-                        return text.substring(start, find('>', '<'));
-                    case '=':
-                        return text.substring(start, current);
-                    case '"':
-                    case '\'':
-                        quote(c);
-                        break;
-                }
-            }
-
-            // Some identifier or number
-            while (!eof())
-            {
-                c = peek();
-                if (!escaped)
-                {
-                    if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=')
-                    {
-                        break;
-                    }
-                    else if (c == '{')
-                    {
-                        next();
-                        find('}', '{');
-                    }
-                    else if (c == '(')
-                    {
-                        next();
-                        find(')', '(');
-                    }
-                    else if (c == '<')
-                    {
-                        next();
-                        find('>', '<');
-                    }
-                    else if (c == '[')
-                    {
-                        next();
-                        find(']', '[');
-                    }
-                    else if (c == '\'' || c == '"')
-                    {
-                        next();
-                        quote(c);
-                        next();
-                    }
-                    else
-                    {
-                        next();
-                    }
-                }
-                else
-                {
-                    next();
-                }
-            }
-            return text.substring(start, current);
-        } finally {
-            start = -1;
-        }
-    }
-
-    boolean escaped()
-    {
-        return escaped;
-    }
-
-    char next()
-    {
-        return peek(true);
-    }
-
-    char unicode()
-    {
-        if (current + 4 > text.length())
-        {
-            throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..."
-                + context(current) + "...");
-        }
-
-        String s = text.subSequence(current, current + 4).toString();
-        int n = Integer.parseInt(s, 16);
-        return (char) n;
-    }
-
-    int find(char target, char deeper)
-    {
-        int start = current;
-        int level = 1;
-
-        while (level != 0)
-        {
-            if (eof())
-            {
-                throw new RuntimeException("Eof found in the middle of a compound for '"
-                    + target + deeper + "', begins at " + context(start));
-            }
-
-            char c = next();
-            if (!escaped)
-            {
-                if (c == target)
-                {
-                    level--;
-                }
-                else
-                {
-                    if (c == deeper)
-                    {
-                        level++;
-                    }
-                    else
-                    {
-                        if (c == '"')
-                        {
-                            quote('"');
-                        }
-                        else
-                        {
-                            if (c == '\'')
-                            {
-                                quote('\'');
-                            }
-                            else
-                            {
-                                if (c == '`')
-                                {
-                                    quote('`');
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return current;
-    }
-
-    int quote(char which)
-    {
-        while (!eof() && (peek() != which || escaped))
-        {
-            next();
-        }
-
-        return current++;
-    }
-
-    CharSequence findVar()
-    {
-        int start = current;
-        char c = peek();
-
-        if (c == '{')
-        {
-            next();
-            int end = find('}', '{');
-            return text.subSequence(start, end);
-        }
-        if (c == '(')
-        {
-            next();
-            int end = find(')', '(');
-            return text.subSequence(start, end);
-        }
-
-        if (Character.isJavaIdentifierPart(c))
-        {
-            while (c == '$')
-            {
-                c = next();
-            }
-            while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$')
-            {
-                next();
-                c = peek();
-            }
-            return text.subSequence(start, current);
-        }
-        throw new IllegalArgumentException(
-            "Reference to variable does not match syntax of a variable: "
-                + context(start));
-    }
-
-    public String toString()
-    {
-        return "..." + context(current) + "...";
-    }
-
-    public String unescape()
-    {
-        StringBuilder sb = new StringBuilder();
-        while (!eof())
-        {
-            sb.append(next());
-        }
-        return sb.toString();
-    }
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminal.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminal.java
new file mode 100644
index 0000000..6a9baff
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminal.java
@@ -0,0 +1,143 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.util.HashMap;
+
+import org.apache.felix.service.command.CommandProcessor;
+
+public class SessionTerminal implements Runnable {
+
+    private Terminal terminal;
+    private Console console;
+    private PipedOutputStream in;
+    private PipedInputStream out;
+    private boolean closed;
+
+    @SuppressWarnings("serial")
+    public SessionTerminal(final CommandProcessor commandProcessor, final String user) throws IOException {
+        try {
+            this.terminal = new Terminal(GogoPlugin.TERM_WIDTH, GogoPlugin.TERM_HEIGHT);
+            terminal.write("\u001b\u005B20\u0068"); // set newline mode on
+
+            in = new PipedOutputStream();
+            out = new PipedInputStream();
+            PrintStream pipedOut = new PrintStream(new PipedOutputStream(out), true);
+
+            console = new Console(commandProcessor, new PipedInputStream(in), pipedOut, pipedOut, new Runnable() {
+                public void run() {
+                    SessionTerminal.this.terminal.write("done...");
+                    close();
+                }
+            }, new HashMap<String, String>() {
+                {
+                    put("USER", user);
+                    put("COLUMNS", Integer.toString(GogoPlugin.TERM_WIDTH));
+                    put("LINES", Integer.toString(GogoPlugin.TERM_HEIGHT));
+                }
+            });
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw (IOException) new IOException().initCause(e);
+        }
+        new Thread(console).start();
+        new Thread(this).start();
+    }
+
+    public boolean isClosed() {
+        return closed;
+    }
+
+    public void close() {
+        if (!closed) {
+            this.closed = true;
+
+            this.console.close();
+
+            try {
+                this.in.close();
+            } catch (IOException e) {
+            }
+            try {
+                this.out.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    public String handle(String str, boolean forceDump) throws IOException {
+        try {
+            if (str != null && str.length() > 0) {
+                String d = terminal.pipe(str);
+                // echo unless backspace
+                if (d.charAt(0) != '\b') {
+                    terminal.write(d);
+                }
+                in.write(d.getBytes());
+                in.flush();
+            }
+        } catch (IOException e) {
+            // ignore
+        }
+        try {
+            return terminal.dump(10, forceDump);
+        } catch (InterruptedException e) {
+            throw new InterruptedIOException(e.toString());
+        }
+    }
+
+    public void run() {
+        try {
+            for (;;) {
+                byte[] buf = new byte[8192];
+                int l = out.read(buf);
+                InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(buf, 0, l));
+                StringBuilder sb = new StringBuilder();
+                for (;;) {
+                    int c = r.read();
+                    if (c == -1) {
+                        break;
+                    }
+                    sb.append((char) c);
+                }
+                if (sb.length() > 0) {
+                    terminal.write(sb.toString());
+                }
+                String s = terminal.read();
+                if (s != null && s.length() > 0) {
+                    for (byte b : s.getBytes()) {
+                        in.write(b);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            closed = true;
+            e.printStackTrace();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminalManager.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminalManager.java
new file mode 100644
index 0000000..a539ed5
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/SessionTerminalManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The <code>SessionTerminalManager</code> manages {@link SessionTerminal}
+ * instances on behalf of the {@link GogoPlugin}. The instances are stored in
+ * HttpSessions and cleared when (a) the owning session is destroyed and (b)
+ * when this manager is {@link #shutdown() shut down}.
+ */
+public class SessionTerminalManager implements HttpSessionListener {
+
+    private final ServiceRegistration service;
+
+    private final ServiceTracker commandProcessor;
+
+    private final Set<SessionTerminal> sessions = new HashSet<SessionTerminal>();
+
+    @SuppressWarnings("serial")
+    public SessionTerminalManager(final BundleContext context) {
+        this.commandProcessor = new ServiceTracker(context, CommandProcessor.class.getName(), null) {
+            @Override
+            public void removedService(ServiceReference reference, Object service) {
+                cleanupSessions();
+                super.removedService(reference, service);
+            }
+        };
+        this.commandProcessor.open();
+
+        Hashtable<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_DESCRIPTION, "Gogo Shell Terminal Session Reaper");
+        service = context.registerService(HttpSessionListener.class.getName(), this, props);
+    }
+
+    void shutdown() {
+        this.service.unregister();
+        this.commandProcessor.close();
+        this.cleanupSessions();
+    }
+
+    SessionTerminal getSessionTerminal(final HttpServletRequest request) throws IOException {
+        final HttpSession session = request.getSession(true);
+        final Object terminal = session.getAttribute("terminal");
+        if (terminal instanceof SessionTerminal) {
+            final SessionTerminal st = (SessionTerminal) terminal;
+            if (!st.isClosed()) {
+                return st;
+            }
+        }
+
+        final CommandProcessor cp = (CommandProcessor) this.commandProcessor.getService();
+        if (cp != null) {
+            final SessionTerminal st = new SessionTerminal(cp, request.getRemoteUser());
+            this.sessions.add(st);
+            session.setAttribute("terminal", st);
+            return st;
+        }
+
+        // no session because there is no command processor !
+        return null;
+    }
+
+    public void sessionCreated(HttpSessionEvent event) {
+        // don't care
+    }
+
+    public void sessionDestroyed(HttpSessionEvent event) {
+        final Object terminal = event.getSession().getAttribute("terminal");
+        if (terminal instanceof SessionTerminal) {
+            sessions.remove(terminal);
+            ((SessionTerminal) terminal).close();
+        }
+    }
+
+    void cleanupSessions() {
+        for (SessionTerminal session : sessions) {
+            session.close();
+        }
+        this.sessions.clear();
+    }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java
deleted file mode 100644
index c551db2..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java
+++ /dev/null
@@ -1,101 +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.felix.webconsole.plugins.gogo.impl;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import jline.console.completer.Completer;
-
-/**
- * Completer for a set of strings.
- */
-public class StringsCompleter implements Completer {
-
-    private final SortedSet<String> strings = new TreeSet<String>();
-
-    private final boolean caseSensitive;
-
-    public StringsCompleter() {
-        this(true);
-    }
-
-    public StringsCompleter(final boolean caseSensitive) {
-        this.caseSensitive = caseSensitive;
-    }
-
-    public StringsCompleter(final Collection<String> strings) {
-        this();
-        assert strings != null;
-        getStrings().addAll(strings);
-    }
-
-    public StringsCompleter(final String[] strings, boolean caseSensitive) {
-        this(Arrays.asList(strings), caseSensitive);
-    }
-
-    public StringsCompleter(final Collection<String> strings, boolean caseSensitive) {
-        this(caseSensitive);
-        assert strings != null;
-        getStrings().addAll(strings);
-    }
-
-    public StringsCompleter(final String[] strings) {
-        this(Arrays.asList(strings));
-    }
-
-    public SortedSet<String> getStrings() {
-        return strings;
-    }
-
-    public int complete(String buffer, final int cursor, final List candidates) {
-        // buffer could be null
-        assert candidates != null;
-
-        if (buffer == null) {
-            buffer = "";
-        }
-        if (!caseSensitive) {
-            buffer = buffer.toLowerCase();
-        }
-
-        // KARAF-421, use getStrings() instead strings field.
-        SortedSet<String> matches = getStrings().tailSet(buffer);
-
-        for (String match : matches) {
-            String s = caseSensitive ? match : match.toLowerCase();
-            if (!s.startsWith(buffer)) {
-                break;
-            }
-
-            // noinspection unchecked
-            candidates.add(match);
-        }
-
-        if (candidates.size() == 1) {
-            // noinspection unchecked
-            candidates.set(0, candidates.get(0) + " ");
-        }
-
-        return candidates.isEmpty() ? -1 : 0;
-    }
-}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java
deleted file mode 100644
index b47553e..0000000
--- a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java
+++ /dev/null
@@ -1,46 +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.felix.webconsole.plugins.gogo.impl;
-
-import jline.TerminalSupport;
-
-public class WebTerminal extends TerminalSupport {
-
-    private int width;
-    private int height;
-
-    public WebTerminal(int width, int height) {
-        super(true);
-        this.width = width;
-        this.height = height;
-    }
-
-    public void init() throws Exception {
-    }
-
-    public void restore() throws Exception {
-    }
-
-    public int getWidth() {
-        return width;
-    }
-
-    public int getHeight() {
-        return height;
-    }
-
-}
diff --git a/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js
index 77044a7..a204d48 100644
--- a/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js
+++ b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js
@@ -36,7 +36,7 @@
 
    var dstat = document.createElement('pre');
    var sled = document.createElement('span');
-   var sdebug = document.createElement('span');
+   var sdebug = document.getElementById('statline');
    var dterm = document.createElement('div');
 
    function debug(s) {
@@ -45,11 +45,11 @@
 
    function error() {
        sled.className = 'off';
-       debug("Connection lost timeout ts:" + ((new Date).getTime()));
+       debug("Gogo Shell Processor not available");
    }
 
    function update() {
-       if (sending == 0) {
+       if (sending == 0 /* && keybuf.length > 0 */ ) {
            sending = 1;
            sled.className = 'on';
            var r = new XMLHttpRequest();
@@ -79,8 +79,10 @@
                        sending=0;
                        sled.className = 'off';
                        timeout = window.setTimeout(update, rmax);
+                   } else if (r.status == 500) {
+                       debug("Gogo Shell Processor not available")
                    } else {
-                       debug("Connection error status:" + r.status);
+                       debug("General failure with Gogo Shell: " + r.status);
                    }
                }
            }
@@ -183,11 +185,13 @@
             default:    k = String.fromCharCode(kc); break;
         }
 
+        var s = encodeURIComponent(k);
+        
 //        debug("fromkeydown=" + fromkeydown + ", ev.keyCode=" + ev.keyCode + ", " +
 //              "ev.which=" + ev.which + ", ev.ctrlKey=" + ev.ctrlKey + ", " +
-//              "kc=" + kc + ", k=" + k);
+//              "kc=" + kc + ", k=" + k + ", s=" + s);
 
-        queue(encodeURIComponent(k));
+        queue(s);
 
         ev.cancelBubble = true;
         if (ev.stopPropagation) ev.stopPropagation();
@@ -203,8 +207,22 @@
                  113:1, 114:1, 115:1, 116:1, 117:1, 118:1, 119:1, 120:1, 121:1, 122:1, 123:1 };
            if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
                keypress(ev, true);
+           } else {
+               ev.cancelBubble = true;
+               if (ev.stopPropagation) ev.stopPropagation();
+               if (ev.preventDefault) ev.preventDefault();
            }
    }
+   
+   function ignoreKey(ev) {
+       if (!ev) {
+           ev = window.event;
+       }
+       
+       ev.cancelBubble = true;
+       if (ev.stopPropagation) ev.stopPropagation();
+       if (ev.preventDefault) ev.preventDefault();
+   }
 
    function init() {
        if (typeof(XMLHttpRequest) == "undefined") {
@@ -220,18 +238,22 @@
            throw new Error("This browser does not support XMLHttpRequest.");
          };
        }
+       
        sled.appendChild(document.createTextNode('\xb7'));
        sled.className = 'off';
        dstat.appendChild(sled);
        dstat.appendChild(document.createTextNode(' '));
-       dstat.appendChild(sdebug);
+//       dstat.appendChild(sdebug);
        dstat.className = 'stat';
+       
        div.appendChild(dstat);
        var d = document.createElement('div');
        d.appendChild(dterm);
        div.appendChild(d);
+       
        document.onkeypress = keypress;
-       document.onkeydown = keydown;
+       document.onkeydown = ignoreKey;
+       document.onkeyup = ignoreKey;
        timeout = window.setTimeout(update, 100);
    }