Repository: karaf Updated Branches: refs/heads/master e5e50310a -> 57ec802bc
[KARAF-2983] Support window size change signals in Terminal Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/a5450aa2 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/a5450aa2 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/a5450aa2 Branch: refs/heads/master Commit: a5450aa241cb6e15d430ad4792d924f2636bd3f1 Parents: e5e5031 Author: Guillaume Nodet <[email protected]> Authored: Thu May 15 19:09:32 2014 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Thu May 15 19:09:32 2014 +0200 ---------------------------------------------------------------------- .../apache/karaf/shell/api/console/Signal.java | 81 +++++++++++++++++ .../karaf/shell/api/console/SignalListener.java | 31 +++++++ .../karaf/shell/api/console/Terminal.java | 27 ++++++ .../shell/impl/console/ConsoleSessionImpl.java | 8 ++ .../karaf/shell/impl/console/JLineTerminal.java | 57 +++++++++++- .../impl/console/osgi/LocalConsoleManager.java | 5 +- .../shell/support/terminal/SignalSupport.java | 96 ++++++++++++++++++++ .../org/apache/karaf/shell/ssh/SshTerminal.java | 12 ++- .../karaf/webconsole/gogo/WebTerminal.java | 3 +- 9 files changed, 311 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/api/console/Signal.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Signal.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Signal.java new file mode 100644 index 0000000..1e4a563 --- /dev/null +++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Signal.java @@ -0,0 +1,81 @@ +/* + * 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.karaf.shell.api.console; + +import java.util.HashMap; +import java.util.Map; + +public enum Signal { + + HUP(1), + INT(2), + QUIT(3), + ILL(4), + TRAP(5), + IOT(6), + BUS(7), + FPE(8), + KILL(9), + USR1(10), + SEGV(11), + USR2(12), + PIPE(13), + ALRM(14), + TERM(15), + STKFLT(16), + CHLD(17), + CONT(18), + STOP(19), + TSTP(20), + TTIN(21), + TTOU(22), + URG(23), + XCPU(24), + XFSZ(25), + VTALRM(26), + PROF(27), + WINCH(28), + IO(29), + PWR(30); + + private static final Map<String, Signal> lookupTable = new HashMap<String, Signal>(40); + + static { + // registering the signals in the lookup table to allow faster + // string based signal lookups + for (Signal s : Signal.values()) { + lookupTable.put(s.name(), s); + } + } + + public static Signal get(String name) { + return lookupTable.get(name); + } + + private final int numeric; + + private Signal(int numeric) { + this.numeric = numeric; + } + + public int getNumeric() { + return numeric; + } + +} http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/api/console/SignalListener.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/SignalListener.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/SignalListener.java new file mode 100644 index 0000000..3f6927a --- /dev/null +++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/SignalListener.java @@ -0,0 +1,31 @@ +/* + * 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.karaf.shell.api.console; + +/** + * Define a listener to receive signals + */ +public interface SignalListener { + + /** + * + * @param signal + */ + void signal(Signal signal); +} http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java index 3549ec7..c0ee911 100644 --- a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java +++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Terminal.java @@ -18,6 +18,8 @@ */ package org.apache.karaf.shell.api.console; +import java.util.EnumSet; + /** * Session terminal. */ @@ -48,4 +50,29 @@ public interface Terminal { */ void setEchoEnabled(boolean enabled); + /** + * Add a qualified listener for the specific signal + * @param listener the listener to register + * @param signal the signal the listener is interested in + */ + void addSignalListener(SignalListener listener, Signal... signal); + + /** + * Add a qualified listener for the specific set of signal + * @param listener the listener to register + * @param signals the signals the listener is interested in + */ + void addSignalListener(SignalListener listener, EnumSet<Signal> signals); + + /** + * Add a global listener for all signals + * @param listener the listener to register + */ + void addSignalListener(SignalListener listener); + + /** + * Remove a previously registered listener for all the signals it was registered + * @param listener the listener to remove + */ + void removeSignalListener(SignalListener listener); } http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java index 1673f37..dbfc59a 100644 --- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java +++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java @@ -19,6 +19,7 @@ package org.apache.karaf.shell.impl.console; import java.io.CharArrayWriter; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -233,6 +234,13 @@ public class ConsoleSessionImpl implements Session { if (closeCallback != null) { closeCallback.run(); } + if (terminal instanceof Closeable) { + try { + ((Closeable) terminal).close(); + } catch (IOException e) { + // Ignore + } + } } public void run() { http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java index d000885..aaf4eb6 100644 --- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java +++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/JLineTerminal.java @@ -18,17 +18,26 @@ */ package org.apache.karaf.shell.impl.console; +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.karaf.shell.api.console.Signal; import org.apache.karaf.shell.api.console.Terminal; +import org.apache.karaf.shell.support.terminal.SignalSupport; /** -* Created by gnodet on 27/02/14. -*/ -public class JLineTerminal implements Terminal { + * Created by gnodet on 27/02/14. + */ +public class JLineTerminal extends SignalSupport implements Terminal, Closeable { private final jline.Terminal terminal; public JLineTerminal(jline.Terminal terminal) { this.terminal = terminal; + registerSignalHandler(); } public jline.Terminal getTerminal() { @@ -59,4 +68,46 @@ public class JLineTerminal implements Terminal { public void setEchoEnabled(boolean enabled) { terminal.setEchoEnabled(enabled); } + + @Override + public void close() throws IOException { + unregisterSignalHandler(); + } + + private void registerSignalHandler() { + try { + Class<?> signalClass = Class.forName("sun.misc.Signal"); + Class<?> signalHandlerClass = Class.forName("sun.misc.SignalHandler"); + // Implement signal handler + Object signalHandler = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class<?>[]{signalHandlerClass}, new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + JLineTerminal.this.signal(Signal.WINCH); + return null; + } + } + ); + // Register the signal handler, this code is equivalent to: + // Signal.handle(new Signal("CONT"), signalHandler); + signalClass.getMethod("handle", signalClass, signalHandlerClass).invoke(null, signalClass.getConstructor(String.class).newInstance("WINCH"), signalHandler); + } catch (Exception e) { + // Ignore this exception, if the above failed, the signal API is incompatible with what we're expecting + + } + } + + private void unregisterSignalHandler() { + try { + Class<?> signalClass = Class.forName("sun.misc.Signal"); + Class<?> signalHandlerClass = Class.forName("sun.misc.SignalHandler"); + + Object signalHandler = signalHandlerClass.getField("SIG_DFL").get(null); + // Register the signal handler, this code is equivalent to: + // Signal.handle(new Signal("CONT"), signalHandler); + signalClass.getMethod("handle", signalClass, signalHandlerClass).invoke(null, signalClass.getConstructor(String.class).newInstance("WINCH"), signalHandler); + } catch (Exception e) { + // Ignore this exception, if the above failed, the signal API is incompatible with what we're expecting + + } + } } http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java index 1c30dd0..e93d93e 100644 --- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java +++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/osgi/LocalConsoleManager.java @@ -67,7 +67,7 @@ public class LocalConsoleManager { final Subject subject = createLocalKarafSubject(); - this.session = JaasHelper.<Session>doAs(subject, new PrivilegedAction<Session>() { + this.session = JaasHelper.doAs(subject, new PrivilegedAction<Session>() { public Session run() { String encoding = getEncoding(); session = sessionFactory.create( @@ -95,8 +95,7 @@ public class LocalConsoleManager { } private String getEncoding() { - String ctype = System.getenv("LC_CTYPE"); - String encoding = ctype; + String encoding = System.getenv("LC_CTYPE"); if (encoding != null && encoding.indexOf('.') > 0) { encoding = encoding.substring(encoding.indexOf('.') + 1); } else { http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/core/src/main/java/org/apache/karaf/shell/support/terminal/SignalSupport.java ---------------------------------------------------------------------- diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/terminal/SignalSupport.java b/shell/core/src/main/java/org/apache/karaf/shell/support/terminal/SignalSupport.java new file mode 100644 index 0000000..39ff0f7 --- /dev/null +++ b/shell/core/src/main/java/org/apache/karaf/shell/support/terminal/SignalSupport.java @@ -0,0 +1,96 @@ +/* + * 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.karaf.shell.support.terminal; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.karaf.shell.api.console.Signal; +import org.apache.karaf.shell.api.console.SignalListener; + +public class SignalSupport { + + private final Map<Signal, Set<SignalListener>> listeners; + + public SignalSupport() { + listeners = new ConcurrentHashMap<>(3); + } + + public void addSignalListener(SignalListener listener, Signal... signals) { + if (signals == null) { + throw new IllegalArgumentException("signals may not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener may not be null"); + } + for (Signal s : signals) { + getSignalListeners(s, true).add(listener); + } + } + + public void addSignalListener(SignalListener listener) { + addSignalListener(listener, EnumSet.allOf(Signal.class)); + } + + public void addSignalListener(SignalListener listener, EnumSet<Signal> signals) { + if (signals == null) { + throw new IllegalArgumentException("signals may not be null"); + } + addSignalListener(listener, signals.toArray(new Signal[signals.size()])); + } + + public void removeSignalListener(SignalListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener may not be null"); + } + for (Signal s : EnumSet.allOf(Signal.class)) { + final Set<SignalListener> ls = getSignalListeners(s, false); + if (ls != null) { + ls.remove(listener); + } + } + } + + public void signal(Signal signal) { + final Set<SignalListener> ls = getSignalListeners(signal, false); + if (ls != null) { + for (SignalListener l : ls) { + l.signal(signal); + } + } + } + + protected Set<SignalListener> getSignalListeners(Signal signal, boolean create) { + Set<SignalListener> ls = listeners.get(signal); + if (ls == null && create) { + synchronized (listeners) { + ls = listeners.get(signal); + if (ls == null) { + ls = new CopyOnWriteArraySet<>(); + listeners.put(signal, ls); + } + } + } + // may be null in case create=false + return ls; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java ---------------------------------------------------------------------- diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java index fa24169..17291d0 100644 --- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java +++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java @@ -18,16 +18,23 @@ */ package org.apache.karaf.shell.ssh; +import org.apache.karaf.shell.api.console.Signal; import org.apache.karaf.shell.api.console.Terminal; +import org.apache.karaf.shell.support.terminal.SignalSupport; import org.apache.sshd.server.Environment; -public class SshTerminal implements Terminal { +public class SshTerminal extends SignalSupport implements Terminal { private Environment environment; - public SshTerminal(Environment environment) { this.environment = environment; + this.environment.addSignalListener(new org.apache.sshd.server.SignalListener() { + @Override + public void signal(org.apache.sshd.server.Signal signal) { + SshTerminal.this.signal(Signal.WINCH); + } + }, org.apache.sshd.server.Signal.WINCH); } @Override @@ -66,4 +73,5 @@ public class SshTerminal implements Terminal { public void setEchoEnabled(boolean enabled) { // TODO: how to disable echo over ssh ? } + } http://git-wip-us.apache.org/repos/asf/karaf/blob/a5450aa2/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/WebTerminal.java ---------------------------------------------------------------------- diff --git a/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/WebTerminal.java b/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/WebTerminal.java index 865a528..f0dd828 100644 --- a/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/WebTerminal.java +++ b/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/WebTerminal.java @@ -17,8 +17,9 @@ package org.apache.karaf.webconsole.gogo; import org.apache.karaf.shell.api.console.Terminal; + import org.apache.karaf.shell.support.terminal.SignalSupport; -public class WebTerminal implements Terminal { +public class WebTerminal extends SignalSupport implements Terminal { private int width; private int height;
