Repository: mina-sshd Updated Branches: refs/heads/master 6af9457d3 -> 7f88d3a18
[SSHD-403] Lay down the groundwork for more event listeners Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/7f88d3a1 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/7f88d3a1 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/7f88d3a1 Branch: refs/heads/master Commit: 7f88d3a18f9a4df77ae429ae724f4fd80b8685f5 Parents: 6af9457 Author: Guillaume Nodet <[email protected]> Authored: Tue Feb 10 11:32:10 2015 +0100 Committer: Guillaume Nodet <[email protected]> Committed: Tue Feb 10 11:32:18 2015 +0100 ---------------------------------------------------------------------- .../org/apache/sshd/common/SessionListener.java | 4 +- .../sshd/common/session/AbstractSession.java | 17 ++-- .../common/session/AbstractSessionFactory.java | 4 + .../sshd/common/util/EventListenerUtils.java | 102 +++++++++++++++++++ .../common/util/EventListenerUtilsTest.java | 84 +++++++++++++++ .../java/org/apache/sshd/util/BaseTest.java | 13 ++- 6 files changed, 210 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java b/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java index 32b69b3..6bbfecc 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java @@ -18,12 +18,14 @@ */ package org.apache.sshd.common; +import java.util.EventListener; + /** * Represents an interface receiving Session events. * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface SessionListener { +public interface SessionListener extends EventListener { enum Event { KeyEstablished, Authenticated, KexCompleted http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java index 13ce253..a0b16c1 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java @@ -54,6 +54,7 @@ import org.apache.sshd.common.io.IoWriteFuture; import org.apache.sshd.common.util.Buffer; import org.apache.sshd.common.util.BufferUtils; import org.apache.sshd.common.util.CloseableUtils; +import org.apache.sshd.common.util.EventListenerUtils; import org.apache.sshd.common.util.Readable; import static org.apache.sshd.common.SshConstants.SSH_MSG_DEBUG; @@ -102,12 +103,12 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea protected final Random random; /** Boolean indicating if this session has been authenticated or not */ protected boolean authed; - /** The name of the authenticated useer */ + /** The name of the authenticated user */ protected String username; - /** Session listener */ + /** Session listeners container */ protected final List<SessionListener> listeners = new CopyOnWriteArrayList<SessionListener>(); - + protected final SessionListener sessionListenerProxy; // // Key exchange support // @@ -177,6 +178,7 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea this.isServer = isServer; this.factoryManager = factoryManager; this.ioSession = ioSession; + sessionListenerProxy = EventListenerUtils.proxyWrapper(SessionListener.class, getClass().getClassLoader(), listeners); random = factoryManager.getRandomFactory().create(); authTimeoutMs = getLongProperty(FactoryManager.AUTH_TIMEOUT, authTimeoutMs); authTimeoutTimestamp = System.currentTimeMillis() + authTimeoutMs; @@ -478,9 +480,7 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea protected void doCloseImmediately() { super.doCloseImmediately(); // Fire 'close' event - for (SessionListener sl : listeners) { - sl.sessionClosed(this); - } + sessionListenerProxy.sessionClosed(this); } protected Service[] getServices() { @@ -1280,12 +1280,9 @@ public abstract class AbstractSession extends CloseableUtils.AbstractInnerClosea } protected void sendEvent(SessionListener.Event event) throws IOException { - for (SessionListener sl : listeners) { - sl.sessionEvent(this, event); - } + sessionListenerProxy.sessionEvent(this, event); } - /** * {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSessionFactory.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSessionFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSessionFactory.java index 8c8429c..5127538 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSessionFactory.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSessionFactory.java @@ -34,6 +34,10 @@ public abstract class AbstractSessionFactory extends AbstractSessionIoHandler { protected final List<SessionListener> listeners = new CopyOnWriteArrayList<SessionListener>(); + protected AbstractSessionFactory() { + super(); + } + protected AbstractSession createSession(IoSession ioSession) throws Exception { AbstractSession session = doCreateSession(ioSession); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java new file mode 100644 index 0000000..f951a91 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java @@ -0,0 +1,102 @@ +/* + * 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.sshd.common.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.EventListener; + + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class EventListenerUtils { + /** + * Provides proxy wrapper around an {@link Iterable} container of listener + * interface implementation. <b>Note:</b> a listener interface is one whose + * invoked methods return <u>only</u> {@code void}. + * + * @param listenerType The expected listener <u>interface</u> + * @param listeners An {@link Iterable} container of listeners to be invoked.</P> + * <b>Note(s):</b> + * <ul> + * <li>The invocation order is same as the {@link Iterable} container</li> + * <p/> + * <li>If any of the invoked listener methods throws an exception, the + * rest of the listener are <u>not</u> invoked and the exception is + * propagated to the caller</li> + * <p/> + * <li>It is up to the <u>caller</u> to ensure that the container does + * not change while the proxy is invoked</li> + * </ul> + * @return A proxy wrapper implementing the same interface, but delegating + * the calls to the container + * @see #proxyWrapper(Class, ClassLoader, Iterable) + */ + public static <T extends EventListener> T proxyWrapper(Class<T> listenerType, Iterable<? extends T> listeners) { + return proxyWrapper(listenerType, listenerType.getClassLoader(), listeners); + } + + /** + * Provides proxy wrapper around an {@link Iterable} container of listener + * interface implementation. <b>Note:</b> a listener interface is one whose + * invoked methods return <u>only</u> {@code void}. + * + * @param listenerType The expected listener <u>interface</u> + * @param loader The {@link ClassLoader} to use for the proxy + * @param listeners An {@link Iterable} container of listeners to be invoked.</P> + * <b>Note(s):</b> + * <ul> + * <li>The invocation order is same as the {@link Iterable} container</li> + * <p/> + * <li>If any of the invoked listener methods throws an exception, the + * rest of the listener are <u>not</u> invoked and the exception is + * propagated to the caller</li> + * <p/> + * <li>It is up to the <u>caller</u> to ensure that the container does + * not change while the proxy is invoked</li> + * </ul> + * @return A proxy wrapper implementing the same interface, but delegating + * the calls to the container + * @throws IllegalArgumentException if <tt>listenerType</tt> is not an interface + * or a {@code null} container has been provided + * @see #proxyWrapper(Class, ClassLoader, Iterable) + */ + public static <T extends EventListener> T proxyWrapper(Class<T> listenerType, ClassLoader loader, final Iterable<? extends T> listeners) { + if ((listenerType == null) || (!listenerType.isInterface())) { + throw new IllegalArgumentException("Target proxy is not an interface"); + } + + if (listeners == null) { + throw new IllegalArgumentException("No listeners container provided"); + } + + Object wrapper = Proxy.newProxyInstance(loader, new Class<?>[]{listenerType}, new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + for (T l : listeners) { + method.invoke(l, args); + } + return null; // we assume always void return value... + } + }); + return listenerType.cast(wrapper); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/test/java/org/apache/sshd/common/util/EventListenerUtilsTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/util/EventListenerUtilsTest.java b/sshd-core/src/test/java/org/apache/sshd/common/util/EventListenerUtilsTest.java new file mode 100644 index 0000000..31dffaa --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/common/util/EventListenerUtilsTest.java @@ -0,0 +1,84 @@ +/* + * 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.sshd.common.util; + +import java.util.ArrayList; +import java.util.EventListener; +import java.util.List; + +import org.apache.sshd.util.BaseTest; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class EventListenerUtilsTest extends BaseTest { + public EventListenerUtilsTest() { + super(); + } + + @Test + public void testProxyWrapper() { + List<ProxyListenerImpl> impls = new ArrayList<ProxyListenerImpl>(); + for (int index = 0; index < Byte.SIZE; index++) { + impls.add(new ProxyListenerImpl()); + } + + ProxyListener listener = EventListenerUtils.proxyWrapper(ProxyListener.class, impls); + String expStr = getCurrentTestName(); + Number expNum = System.currentTimeMillis(); + listener.callMeWithString(expStr); + listener.callMeWithNumber(expNum); + + for (int index = 0; index < impls.size(); index++) { + ProxyListenerImpl l = impls.get(index); + Assert.assertSame("Mismatched string at listener #" + index, expStr, l.getStringValue()); + Assert.assertSame("Mismatched number at listener #" + index, expNum, l.getNumberValue()); + } + } + + private static interface ProxyListener extends EventListener { + void callMeWithString(String s); + + void callMeWithNumber(Number n); + } + + private static class ProxyListenerImpl implements ProxyListener { + private String strValue; + private Number numValue; + + public String getStringValue() { + return strValue; + } + + public void callMeWithString(String s) { + strValue = s; + } + + public Number getNumberValue() { + return numValue; + } + + public void callMeWithNumber(Number n) { + numValue = n; + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/7f88d3a1/sshd-core/src/test/java/org/apache/sshd/util/BaseTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/util/BaseTest.java b/sshd-core/src/test/java/org/apache/sshd/util/BaseTest.java index d7b38c4..e81de2c 100644 --- a/sshd-core/src/test/java/org/apache/sshd/util/BaseTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/util/BaseTest.java @@ -19,6 +19,7 @@ package org.apache.sshd.util; import org.junit.Rule; +import org.junit.rules.TestName; import org.junit.rules.TestWatcher; import org.junit.runner.Description; @@ -28,11 +29,17 @@ import org.junit.runner.Description; * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public abstract class BaseTest extends TestWatcher { + @Rule public TestWatcher rule = this; + @Rule public final TestName TEST_NAME_HOLDER = new TestName(); + private long startTime; - @Rule - public TestWatcher rule = this; + protected BaseTest() { + super(); + } - private long startTime; + public final String getCurrentTestName() { + return TEST_NAME_HOLDER.getMethodName(); + } @Override protected void starting(Description description) {
