From 08796a7b374f7e6a2b90ee9e8a65798327c0e3ff Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Jun 2012 17:04:37 -0700 Subject: [PATCH] Working EventMethod/EventMethods/EventMethodsCache with tests --- .../websocket/annotations/EventMethod.java | 106 +++++++ .../websocket/annotations/EventMethods.java | 74 +++++ .../annotations/EventMethodsCache.java | 282 ++++++++++++++++++ .../websocket/annotations/WebSocket.java | 2 + .../api/InvalidWebSocketException.java | 27 ++ .../websocket/api/WebSocketEventDriver.java | 46 ++- .../jetty/websocket/api/WebSocketPolicy.java | 11 + .../annotations/EventMethodsCacheTest.java | 95 ++++++ .../websocket/annotations/FrameSocket.java | 13 + .../annotations/WebSocketAnnotationTest.java | 44 --- .../server/WebSocketServerFactory.java | 49 ++- 11 files changed, 679 insertions(+), 70 deletions(-) create mode 100644 jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethod.java create mode 100644 jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethods.java create mode 100644 jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/EventMethodsCache.java create mode 100644 jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/EventMethodsCacheTest.java create mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/FrameSocket.java delete mode 100644 jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/annotations/WebSocketAnnotationTest.java 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 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[]> + { + public void addParams(Class... paramTypes) + { + this.add(paramTypes); + } + } + + /** + * Parameter list for @OnWebSocketBinary + */ + private static final ParamList validBinaryParams; + /** + * Parameter list for @OnWebSocketConnect + */ + private static final ParamList validConnectParams; + private static final ParamList validCloseParams; + private static final ParamList validFrameParams; + private static final ParamList validTextParams; + + static + { + validBinaryParams = new ParamList(); + validBinaryParams.addParams(ByteBuffer.class); + validBinaryParams.addParams(byte[].class,int.class,int.class); + validBinaryParams.addParams(WebSocketConnection.class,ByteBuffer.class); + validBinaryParams.addParams(WebSocketConnection.class,byte[].class,int.class,int.class); + + validConnectParams = new ParamList(); + validConnectParams.addParams(WebSocketConnection.class); + + validCloseParams = new ParamList(); + validCloseParams.addParams(int.class,String.class); + validCloseParams.addParams(WebSocketConnection.class,int.class,String.class); + + validTextParams = new ParamList(); + validTextParams.addParams(String.class); + validTextParams.addParams(WebSocketConnection.class,String.class); + + validFrameParams = new ParamList(); + validFrameParams.addParams(BaseFrame.class); + validFrameParams.addParams(BinaryFrame.class); + validFrameParams.addParams(CloseFrame.class); + validFrameParams.addParams(ControlFrame.class); + validFrameParams.addParams(DataFrame.class); + validFrameParams.addParams(PingFrame.class); + validFrameParams.addParams(PongFrame.class); + validFrameParams.addParams(TextFrame.class); + + validFrameParams.addParams(WebSocketConnection.class,BaseFrame.class); + validFrameParams.addParams(WebSocketConnection.class,BinaryFrame.class); + validFrameParams.addParams(WebSocketConnection.class,CloseFrame.class); + validFrameParams.addParams(WebSocketConnection.class,ControlFrame.class); + validFrameParams.addParams(WebSocketConnection.class,DataFrame.class); + validFrameParams.addParams(WebSocketConnection.class,PingFrame.class); + validFrameParams.addParams(WebSocketConnection.class,PongFrame.class); + validFrameParams.addParams(WebSocketConnection.class,TextFrame.class); + } + + private ConcurrentHashMap, EventMethods> cache; + + public EventMethodsCache() + { + cache = new ConcurrentHashMap<>(); + } + + private void assertUnset(EventMethod event, Class annoClass, Class pojo, Method method) + { + if (event == EventMethod.NOOP) + { + return; + } + } + + private void assertValidParams(Class pojo, Method method, Class annoClass, ParamList validParams) + { + boolean valid = false; + + Class actual[] = method.getParameterTypes(); + for (Class params[] : validParams) + { + if (isSameParameters(actual,params)) + { + valid = true; + break; + } + } + + if (!valid) + { + // Build big detailed exception to help the developer + StringBuilder err = new StringBuilder(); + err.append("Invalid declaration of "); + err.append(method); + err.append(StringUtil.__LINE_SEPARATOR); + + err.append("Acceptable method declarations for @"); + err.append(annoClass.getSimpleName()); + err.append(" are:"); + for (Class params[] : validParams) + { + err.append(StringUtil.__LINE_SEPARATOR); + err.append("public void ").append(method.getName()); + err.append('('); + boolean delim = false; + for (Class type : params) + { + if (delim) + { + err.append(','); + } + err.append(' '); + err.append(type.getName()); + if (type.isArray()) + { + err.append("[]"); + } + delim = true; + } + err.append(')'); + } + + throw new InvalidWebSocketException(err.toString()); + } + } + + /** + * Perform the basic discovery mechanism for WebSocket events from the provided pojo. + * + * @param pojo + * the pojo to scan + * @return the discovered event methods + * @throws InvalidWebSocketException + */ + private EventMethods discoverMethods(Class pojo) throws InvalidWebSocketException + { + if (WebSocketListener.class.isInstance(pojo)) + { + return scanListenerMethods(pojo); + } + + WebSocket anno = pojo.getAnnotation(WebSocket.class); + if (anno == null) + { + throw new InvalidWebSocketException(pojo.getName() + " does not implement " + WebSocketListener.class.getName() + " or use the @" + + WebSocket.class.getName() + " annotation"); + } + + return scanAnnotatedMethods(pojo); + } + + public EventMethods getMethods(Class pojo) throws InvalidWebSocketException + { + EventMethods methods = cache.get(pojo); + if (methods == null) + { + methods = discoverMethods(pojo); + cache.put(pojo,methods); + } + return methods; + } + + private boolean isSameParameters(Class[] actual, Class[] params) + { + if(actual.length != params.length) { + // skip + return false; + } + + int len = params.length; + for(int i=0; i pojo) throws InvalidWebSocketException + { + discoverMethods(pojo); + } + + private EventMethods scanAnnotatedMethods(Class pojo) + { + EventMethods events = new EventMethods(pojo,true); + + for (Method method : pojo.getDeclaredMethods()) + { + if (method.getAnnotation(OnWebSocketConnect.class) != null) + { + assertUnset(events.onConnect,OnWebSocketConnect.class,pojo,method); + assertValidParams(pojo,method,OnWebSocketConnect.class,validConnectParams); + events.onConnect = new EventMethod(pojo,method); + continue; + } + + if (method.getAnnotation(OnWebSocketBinary.class) != null) + { + assertUnset(events.onBinary,OnWebSocketBinary.class,pojo,method); + assertValidParams(pojo,method,OnWebSocketBinary.class,validBinaryParams); + events.onBinary = new EventMethod(pojo,method); + continue; + } + + if (method.getAnnotation(OnWebSocketClose.class) != null) + { + assertUnset(events.onClose,OnWebSocketClose.class,pojo,method); + assertValidParams(pojo,method,OnWebSocketClose.class,validCloseParams); + events.onClose = new EventMethod(pojo,method); + continue; + } + + if (method.getAnnotation(OnWebSocketText.class) != null) + { + assertUnset(events.onText,OnWebSocketText.class,pojo,method); + assertValidParams(pojo,method,OnWebSocketText.class,validTextParams); + events.onText = new EventMethod(pojo,method); + continue; + } + + if (method.getAnnotation(OnWebSocketFrame.class) != null) + { + assertUnset(events.onFrame,OnWebSocketFrame.class,pojo,method); + assertValidParams(pojo,method,OnWebSocketFrame.class,validFrameParams); + events.onFrame = new EventMethod(pojo,method); + continue; + } + + // Not a tagged method we are interested in, ignore + } + + return events; + } + + private EventMethods scanListenerMethods(Class pojo) + { + EventMethods events = new EventMethods(pojo,false); + // This is a WebSocketListener object + events.onConnect = new EventMethod(pojo,"onWebSocketConnect",WebSocketConnection.class); + events.onClose = new EventMethod(pojo,"onWebSocketClose",Integer.TYPE,String.class); + events.onBinary = new EventMethod(pojo,"onWebSocketBinary",byte[].class,Integer.TYPE,Integer.TYPE); + events.onText = new EventMethod(pojo,"onWebSocketText",WebSocketConnection.class); + events.onException = new EventMethod(pojo,"onWebSocketException",WebSocketException.class); + + return events; + } +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java index 9bdf852f335..8bab3d211bf 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/annotations/WebSocket.java @@ -19,5 +19,7 @@ public @interface WebSocket int maxBufferSize() default 8192; + int maxIdleTime() default 300000; + int maxTextSize() default 8192; } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java new file mode 100644 index 00000000000..62dd0308c8c --- /dev/null +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/InvalidWebSocketException.java @@ -0,0 +1,27 @@ +package org.eclipse.jetty.websocket.api; + +import org.eclipse.jetty.websocket.annotations.WebSocket; + +/** + * Indicating that the provided Class is not a valid WebSocket as defined by the API. + *

+ * A valid WebSocket should do one of the following: + *

+ */ +@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; *

* 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 versions = new ArrayList<>(); @@ -109,7 +114,8 @@ public class WebSocketServerFactory extends AbstractLifeCycle // TODO: discover type, create proxy // Send the upgrade - WebSocketEventDriver websocket = new WebSocketEventDriver(websocketPojo); + WebSocketPolicy objPolicy = this.basePolicy.clonePolicy(); + WebSocketEventDriver websocket = new WebSocketEventDriver(methodsCache,objPolicy,websocketPojo); return upgrade(sockreq,sockresp,websocket); } @@ -134,8 +140,7 @@ public class WebSocketServerFactory extends AbstractLifeCycle public WebSocketCreator getCreator() { - // TODO: implement - return null; + return this.creator; } /** @@ -147,13 +152,15 @@ public class WebSocketServerFactory extends AbstractLifeCycle } /** - * Get the policy in use for WebSockets. + * Get the base policy in use for WebSockets. + *

+ * 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 initExtensions(List requested) @@ -179,12 +186,26 @@ public class WebSocketServerFactory extends AbstractLifeCycle public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response) { - // TODO: other checks against the spec? + String upgrade = request.getHeader("Upgrade"); + if (upgrade == null) + { + // Quietly fail + return false; + } + + if (!"websocket".equalsIgnoreCase(upgrade)) + { + LOG.warn("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])"); + return false; + } + if (!"HTTP/1.1".equals(request.getProtocol())) { - throw new IllegalStateException("Not a 'HTTP/1.1' request"); + LOG.warn("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])"); + return false; } - return ("websocket".equalsIgnoreCase(request.getHeader("Upgrade"))); + + return true; } private Extension newExtension(String name) @@ -226,7 +247,7 @@ public class WebSocketServerFactory extends AbstractLifeCycle public void register(Class websocketClass) { - // TODO: implement + methodsCache.register(websocketClass); } protected boolean removeConnection(AsyncWebSocketConnection connection) @@ -236,7 +257,7 @@ public class WebSocketServerFactory extends AbstractLifeCycle public void setCreator(WebSocketCreator creator) { - // TODO: implement + this.creator = creator; } /** @@ -287,7 +308,7 @@ public class WebSocketServerFactory extends AbstractLifeCycle HttpConnection http = HttpConnection.getCurrentConnection(); AsyncEndPoint endp = http.getEndPoint(); Executor executor = http.getConnector().findExecutor(); - final AsyncWebSocketConnection connection = new AsyncWebSocketConnection(endp,executor,policy); + final AsyncWebSocketConnection connection = new AsyncWebSocketConnection(endp,executor,websocket.getPolicy()); endp.setAsyncConnection(connection); // Notify POJO of connection