diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethod.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethod.java new file mode 100644 index 00000000000..b47f90ba341 --- /dev/null +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethod.java @@ -0,0 +1,106 @@ +package org.eclipse.jetty.websocket.annotations; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +public class EventMethod +{ + public static final EventMethod NOOP = new EventMethod(); + private static final Logger LOG = Log.getLogger(EventMethod.class); + + private static Object[] dropFirstArg(Object[] args) + { + if (args.length == 1) + { + return null; + } + Object ret[] = new Object[args.length - 1]; + System.arraycopy(args,1,ret,0,ret.length); + return ret; + } + + public static EventMethod findAnnotatedMethod(Object pojo, Class extends Annotation> annoClass, Class>... paramTypes) + { + Class>[] possibleParams = new Class>[paramTypes.length]; + System.arraycopy(paramTypes,0,possibleParams,0,possibleParams.length); + + for (Method method : pojo.getClass().getDeclaredMethods()) + { + if (method.getAnnotation(annoClass) == null) + { + // skip, not interested + continue; + } + + } + return NOOP; + } + + private Class> pojo; + + private Method method; + + private Class>[] paramTypes; + + private EventMethod() + { + this.method = null; + } + + public EventMethod(Class> pojo, Method method) + { + this.pojo = pojo; + this.paramTypes = method.getParameterTypes(); + this.method = method; + } + + public EventMethod(Class> pojo, String methodName, Class>... paramTypes) + { + try + { + this.pojo = pojo; + this.paramTypes = paramTypes; + this.method = pojo.getClass().getMethod(methodName,paramTypes); + } + catch (NoSuchMethodException | SecurityException e) + { + LOG.debug("Cannot use method {}({}): {}",methodName,paramTypes,e.getMessage(),e); + this.method = null; + } + } + + public void call(Object obj, Object... args) + { + if ((this.pojo == null) || (this.method == null)) + { + return; // no call event method determined + } + if (obj == null) + { + LOG.warn("Cannot call {} on null object",this.method); + return; + } + if (args.length > paramTypes.length) + { + Object trimArgs[] = dropFirstArg(args); + call(trimArgs); + return; + } + if (args.length < paramTypes.length) + { + throw new IllegalArgumentException("Call arguments length must always be greater than or equal to captured args length"); + } + try + { + this.method.invoke(obj,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + LOG.warn("Cannot call method {} on {} with {}",method,pojo,args,e); + } + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethods.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethods.java new file mode 100644 index 00000000000..e1e5a11ddeb --- /dev/null +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethods.java @@ -0,0 +1,74 @@ +package org.eclipse.jetty.websocket.annotations; + +/** + * A representation of the methods available to call for a particular class. + *
+ * This class used to cache the method lookups via the {@link EventMethodsCache}
+ */
+public class EventMethods
+{
+ private Class> pojoClass;
+ private boolean isAnnotated = false;
+ public EventMethod onConnect = EventMethod.NOOP;
+ public EventMethod onClose = EventMethod.NOOP;
+ public EventMethod onBinary = EventMethod.NOOP;
+ public EventMethod onText = EventMethod.NOOP;
+ public EventMethod onFrame = EventMethod.NOOP;
+ public EventMethod onException = EventMethod.NOOP;
+
+ public EventMethods(Class> pojoClass, boolean annotated)
+ {
+ this.pojoClass = pojoClass;
+ this.isAnnotated = annotated;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ EventMethods other = (EventMethods)obj;
+ if (pojoClass == null)
+ {
+ if (other.pojoClass != null)
+ {
+ return false;
+ }
+ }
+ else if (!pojoClass.getName().equals(other.pojoClass.getName()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public Class> getPojoClass()
+ {
+ return pojoClass;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pojoClass == null)?0:pojoClass.getName().hashCode());
+ return result;
+ }
+
+ public boolean isAnnotated()
+ {
+ return isAnnotated;
+ }
+
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethodsCache.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethodsCache.java
new file mode 100644
index 00000000000..273aa7be9eb
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethodsCache.java
@@ -0,0 +1,282 @@
+package org.eclipse.jetty.websocket.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketConnection;
+import org.eclipse.jetty.websocket.api.WebSocketException;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.eclipse.jetty.websocket.frames.BaseFrame;
+import org.eclipse.jetty.websocket.frames.BinaryFrame;
+import org.eclipse.jetty.websocket.frames.CloseFrame;
+import org.eclipse.jetty.websocket.frames.ControlFrame;
+import org.eclipse.jetty.websocket.frames.DataFrame;
+import org.eclipse.jetty.websocket.frames.PingFrame;
+import org.eclipse.jetty.websocket.frames.PongFrame;
+import org.eclipse.jetty.websocket.frames.TextFrame;
+
+public class EventMethodsCache
+{
+ @SuppressWarnings("serial")
+ private static class ParamList extends ArrayList
+ * A valid WebSocket should do one of the following:
+ *
* There will be an instance of the WebSocketEventDriver per connection.
*/
-public class WebSocketEventDriver
+public class WebSocketEventDriver implements Parser.Listener
{
private Object websocket;
+ private WebSocketPolicy policy;
private WebSocketConnection connection;
+ private EventMethods events;
/**
* Establish the driver for the Websocket POJO
*
* @param websocket
*/
- public WebSocketEventDriver(Object websocket)
+ public WebSocketEventDriver(EventMethodsCache methodsCache, WebSocketPolicy policy, Object websocket)
{
+ this.policy = policy;
this.websocket = websocket;
- // TODO Discover and bind what routing is available in the POJO
+ this.events = methodsCache.getMethods(websocket.getClass());
+
+ if (events.isAnnotated())
+ {
+ WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
+ // Setup the policy
+ policy.setBufferSize(anno.maxBufferSize());
+ policy.setMaxBinaryMessageSize(anno.maxBinarySize());
+ policy.setMaxTextMessageSize(anno.maxTextSize());
+ policy.setMaxIdleTime(anno.maxIdleTime());
+ }
+ }
+
+ public WebSocketPolicy getPolicy()
+ {
+ return policy;
}
/**
@@ -42,15 +63,7 @@ public class WebSocketEventDriver
*/
public void onConnect()
{
- // TODO Auto-generated method stub
- }
-
- /**
- * Internal entry point for connection disconnected
- */
- public void onDisconnect()
- {
- // TODO Auto-generated method stub
+ events.onConnect.call(websocket,connection);
}
/**
@@ -59,9 +72,18 @@ public class WebSocketEventDriver
* @param frame
* the frame that appeared
*/
+ @Override
public void onFrame(BaseFrame frame)
{
// TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onWebSocketException(WebSocketException e)
+ {
+ // TODO Auto-generated method stub
+
}
/**
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
index 3aee99fc01a..d6d6a4bd342 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java
@@ -84,6 +84,17 @@ public class WebSocketPolicy
}
}
+ public WebSocketPolicy clonePolicy()
+ {
+ WebSocketPolicy clone = new WebSocketPolicy(this.behavior);
+ clone.bufferSize = this.bufferSize;
+ clone.masker = this.masker;
+ clone.maxBinaryMessageSize = this.maxBinaryMessageSize;
+ clone.maxIdleTime = this.maxIdleTime;
+ clone.maxTextMessageSize = this.maxTextMessageSize;
+ return clone;
+ }
+
public WebSocketBehavior getBehavior()
{
return behavior;
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/EventMethodsCacheTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/EventMethodsCacheTest.java
new file mode 100644
index 00000000000..fc7378208c8
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/EventMethodsCacheTest.java
@@ -0,0 +1,95 @@
+package org.eclipse.jetty.websocket.annotations;
+
+import static org.hamcrest.Matchers.*;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EventMethodsCacheTest
+{
+ private void assertHasEventMethod(String message, EventMethod actual)
+ {
+ Assert.assertNotSame(message + "Event method should have been discovered",actual,EventMethod.NOOP);
+ }
+
+ private void assertNoEventMethod(String message, EventMethod actual)
+ {
+ Assert.assertEquals(message + "Event method should have been NOOP",actual,EventMethod.NOOP);
+ }
+
+ /**
+ * Test Case for no exceptions and 3 methods
+ */
+ @Test
+ public void testDiscoverMyEchoSocket()
+ {
+ EventMethodsCache cache = new EventMethodsCache();
+ EventMethods methods = cache.getMethods(MyEchoSocket.class);
+
+ Assert.assertThat("EventMethods for MyEchoSocket",methods,notNullValue());
+
+ assertNoEventMethod("MyEchoSocket.onBinary",methods.onBinary);
+ assertHasEventMethod("MyEchoSocket.onClose",methods.onClose);
+ assertHasEventMethod("MyEchoSocket.onConnect",methods.onConnect);
+ assertNoEventMethod("MyEchoSocket.onException",methods.onException);
+ assertNoEventMethod("MyEchoSocket.onFrame",methods.onFrame);
+ assertHasEventMethod("MyEchoSocket.onText",methods.onText);
+ }
+
+ /**
+ * Test Case for no exceptions and 1 method
+ */
+ @Test
+ public void testDiscoverMyStatelessEchoSocket()
+ {
+ EventMethodsCache cache = new EventMethodsCache();
+ EventMethods methods = cache.getMethods(MyStatelessEchoSocket.class);
+
+ Assert.assertThat("EventMethods for MyStatelessEchoSocket",methods,notNullValue());
+
+ assertNoEventMethod("MyStatelessEchoSocket.onBinary",methods.onBinary);
+ assertNoEventMethod("MyStatelessEchoSocket.onClose",methods.onClose);
+ assertNoEventMethod("MyStatelessEchoSocket.onConnect",methods.onConnect);
+ assertNoEventMethod("MyStatelessEchoSocket.onException",methods.onException);
+ assertNoEventMethod("MyStatelessEchoSocket.onFrame",methods.onFrame);
+ assertHasEventMethod("MyStatelessEchoSocket.onText",methods.onText);
+ }
+
+ /**
+ * Test Case for no exceptions and no methods
+ */
+ @Test
+ public void testDiscoverNoop()
+ {
+ EventMethodsCache cache = new EventMethodsCache();
+ EventMethods methods = cache.getMethods(NoopSocket.class);
+
+ Assert.assertThat("Methods for NoopSocket",methods,notNullValue());
+
+ assertNoEventMethod("NoopSocket.onBinary",methods.onBinary);
+ assertNoEventMethod("NoopSocket.onClose",methods.onClose);
+ assertNoEventMethod("NoopSocket.onConnect", methods.onConnect);
+ assertNoEventMethod("NoopSocket.onException",methods.onException);
+ assertNoEventMethod("NoopSocket.onFrame",methods.onFrame);
+ assertNoEventMethod("NoopSocket.onText",methods.onText);
+ }
+
+ /**
+ * Test Case for no exceptions and 3 methods
+ */
+ @Test
+ public void testDiscoverOnFrame()
+ {
+ EventMethodsCache cache = new EventMethodsCache();
+ EventMethods methods = cache.getMethods(FrameSocket.class);
+
+ Assert.assertThat("EventMethods for MyEchoSocket",methods,notNullValue());
+
+ assertNoEventMethod("MyEchoSocket.onBinary",methods.onBinary);
+ assertNoEventMethod("MyEchoSocket.onClose",methods.onClose);
+ assertNoEventMethod("MyEchoSocket.onConnect",methods.onConnect);
+ assertNoEventMethod("MyEchoSocket.onException",methods.onException);
+ assertHasEventMethod("MyEchoSocket.onFrame",methods.onFrame);
+ assertNoEventMethod("MyEchoSocket.onText",methods.onText);
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/FrameSocket.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/FrameSocket.java
new file mode 100644
index 00000000000..242eb5a4207
--- /dev/null
+++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/FrameSocket.java
@@ -0,0 +1,13 @@
+package org.eclipse.jetty.websocket.annotations;
+
+import org.eclipse.jetty.websocket.frames.BaseFrame;
+
+@WebSocket
+public class FrameSocket
+{
+ @OnWebSocketFrame
+ public void frameMe(BaseFrame frame)
+ {
+ /* ignore */
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/WebSocketAnnotationTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/WebSocketAnnotationTest.java
deleted file mode 100644
index c93d4e5c1f0..00000000000
--- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/WebSocketAnnotationTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.eclipse.jetty.websocket.annotations;
-
-import org.eclipse.jetty.websocket.api.LocalWebSocketConnection;
-import org.eclipse.jetty.websocket.api.StatusCode;
-import org.eclipse.jetty.websocket.api.WebSocketConnection;
-import org.eclipse.jetty.websocket.api.WebSocketEventDriver;
-import org.eclipse.jetty.websocket.frames.CloseFrame;
-import org.eclipse.jetty.websocket.frames.TextFrame;
-import org.junit.Test;
-
-public class WebSocketAnnotationTest
-{
- /**
- * Test Case for no exceptions
- */
- @Test
- public void testCapture()
- {
- WebSocketEventDriver driver = new WebSocketEventDriver(new NoopSocket());
- WebSocketConnection conn = new LocalWebSocketConnection();
-
- driver.setConnection(conn);
- driver.onConnect();
- driver.onFrame(new TextFrame("Hello World"));
- driver.onFrame(new CloseFrame(StatusCode.NORMAL));
- driver.onDisconnect();
- }
-
- /**
- * Test Case for no exceptions
- */
- @Test
- public void testNoop()
- {
- WebSocketEventDriver driver = new WebSocketEventDriver(new NoopSocket());
- WebSocketConnection conn = new LocalWebSocketConnection();
-
- driver.setConnection(conn);
- driver.onConnect();
- driver.onFrame(new TextFrame("Hello World"));
- driver.onFrame(new CloseFrame(StatusCode.NORMAL));
- driver.onDisconnect();
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
index c435a1b3069..d69c6fcc4c7 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java
@@ -32,6 +32,8 @@ import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.annotations.EventMethodsCache;
+import org.eclipse.jetty.websocket.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.ExtensionConfig;
import org.eclipse.jetty.websocket.api.WebSocketEventDriver;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
@@ -65,11 +67,14 @@ public class WebSocketServerFactory extends AbstractLifeCycle
}
private final String supportedVersions;
- private WebSocketPolicy policy;
+ private WebSocketPolicy basePolicy;
+ private WebSocketCreator creator;
+ private EventMethodsCache methodsCache;
public WebSocketServerFactory(WebSocketPolicy policy)
{
- this.policy = policy;
+ this.basePolicy = policy;
+ this.methodsCache = new EventMethodsCache();
// Create supportedVersions
List
+ * Note: individual WebSocket implementations can override some of the values in here by using the {@link WebSocket @WebSocket} annotation.
*
- * @return
+ * @return the base policy
*/
public WebSocketPolicy getPolicy()
{
- return policy;
+ return basePolicy;
}
public List
+ *
+ */
+@SuppressWarnings("serial")
+public class InvalidWebSocketException extends WebSocketException
+{
+ public InvalidWebSocketException(String message)
+ {
+ super(message);
+ }
+
+ public InvalidWebSocketException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketEventDriver.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketEventDriver.java
index 4ec6298d4de..915fa611234 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketEventDriver.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocketEventDriver.java
@@ -1,7 +1,10 @@
package org.eclipse.jetty.websocket.api;
+import org.eclipse.jetty.websocket.annotations.EventMethods;
+import org.eclipse.jetty.websocket.annotations.EventMethodsCache;
import org.eclipse.jetty.websocket.annotations.WebSocket;
import org.eclipse.jetty.websocket.frames.BaseFrame;
+import org.eclipse.jetty.websocket.parser.Parser;
/**
* Responsible for routing the internally generated events destined for a specific WebSocket instance to whatever choice of development style the developer has
@@ -11,20 +14,38 @@ import org.eclipse.jetty.websocket.frames.BaseFrame;
*