diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java new file mode 100644 index 00000000000..b1c63cd943a --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryEncoder.java @@ -0,0 +1,33 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.encoders; + +import java.nio.ByteBuffer; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; + +public class DefaultBinaryEncoder implements Encoder.Binary +{ + @Override + public ByteBuffer encode(ByteBuffer message) throws EncodeException + { + return message; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java new file mode 100644 index 00000000000..ecd77da63be --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultBinaryStreamEncoder.java @@ -0,0 +1,37 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.encoders; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; + +import org.eclipse.jetty.util.BufferUtil; + +public class DefaultBinaryStreamEncoder implements Encoder.BinaryStream +{ + @Override + public void encode(ByteBuffer message, OutputStream out) throws EncodeException, IOException + { + BufferUtil.writeTo(message,out); + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java new file mode 100644 index 00000000000..01cf167dc83 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextEncoder.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.encoders; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; + +public class DefaultTextEncoder implements Encoder.Text +{ + @Override + public String encode(String message) throws EncodeException + { + return message; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java new file mode 100644 index 00000000000..04dfbf8826b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/encoders/DefaultTextStreamEncoder.java @@ -0,0 +1,35 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.encoders; + +import java.io.IOException; +import java.io.Writer; + +import javax.websocket.EncodeException; +import javax.websocket.Encoder; + +public class DefaultTextStreamEncoder implements Encoder.TextStream +{ + @Override + public void encode(String message, Writer writer) throws EncodeException, IOException + { + writer.append(message); + writer.flush(); + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoAnnotationCache.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoAnnotationCache.java new file mode 100644 index 00000000000..d8524faae6b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoAnnotationCache.java @@ -0,0 +1,75 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.endpoints; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner; + +/** + * Cache for discovered javax.websocket {@link WebSocketEndpoint @WebSocketEndpoint} annotated websockets + */ +public class JavaxPojoAnnotationCache extends AbstractMethodAnnotationScanner +{ + private static final Logger LOG = Log.getLogger(JavaxPojoAnnotationCache.class); + public static final JavaxPojoAnnotationCache INSTANCE = new JavaxPojoAnnotationCache(); + + public synchronized static JavaxPojoMetadata discover(Class websocket) + { + // TODO: move to server side deployer + // WebSocketEndpoint anno = websocket.getAnnotation(WebSocketEndpoint.class); + // if (anno == null) + // { + // return null; + // } + + JavaxPojoMetadata metadata = INSTANCE.cache.get(websocket); + if (metadata == null) + { + metadata = new JavaxPojoMetadata(); + INSTANCE.scanMethodAnnotations(metadata,websocket); + INSTANCE.cache.put(websocket,metadata); + } + + return metadata; + } + + public static JavaxPojoMetadata discover(Object websocket) + { + return discover(websocket.getClass()); + } + + private ConcurrentHashMap, JavaxPojoMetadata> cache; + + public JavaxPojoAnnotationCache() + { + cache = new ConcurrentHashMap<>(); + } + + @Override + public void onMethodAnnotation(JavaxPojoMetadata metadata, Class pojo, Method method, Annotation annotation) + { + LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation); + // TODO Auto-generated method stub + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoMetadata.java new file mode 100644 index 00000000000..4504177d2b4 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JavaxPojoMetadata.java @@ -0,0 +1,27 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.endpoints; + +/** + * Represents the metadata associated with Annotation discovery of a specific class. + */ +public class JavaxPojoMetadata +{ + +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java new file mode 100644 index 00000000000..52c72a165cc --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java @@ -0,0 +1,125 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.endpoints; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.events.EventDriver; + +public class JsrAnnotatedEventDriver implements EventDriver +{ + + @Override + public void incomingError(WebSocketException e) + { + // TODO Auto-generated method stub + + } + + @Override + public void incomingFrame(Frame frame) + { + // TODO Auto-generated method stub + + } + + @Override + public WebSocketPolicy getPolicy() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public WebSocketSession getSession() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void onBinaryMessage(byte[] data) + { + // TODO Auto-generated method stub + + } + + @Override + public void onClose(CloseInfo close) + { + // TODO Auto-generated method stub + + } + + @Override + public void onConnect() + { + // TODO Auto-generated method stub + + } + + @Override + public void onError(Throwable t) + { + // TODO Auto-generated method stub + + } + + @Override + public void onFrame(Frame frame) + { + // TODO Auto-generated method stub + + } + + @Override + public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void onTextMessage(String message) + { + // TODO Auto-generated method stub + + } + + @Override + public void openSession(WebSocketSession session) + { + // TODO Auto-generated method stub + + } + +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedImpl.java new file mode 100644 index 00000000000..9653b5f7caf --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedImpl.java @@ -0,0 +1,31 @@ +package org.eclipse.jetty.websocket.jsr356.endpoints; + +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.events.EventDriverImpl; + +public class JsrAnnotatedImpl implements EventDriverImpl +{ + + @Override + public EventDriver create(Object websocket, WebSocketPolicy policy) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String describeRule() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean supports(Object websocket) + { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java new file mode 100644 index 00000000000..69a9cbc23af --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java @@ -0,0 +1,125 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.endpoints; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.events.EventDriver; + +public class JsrEndpointEventDriver implements EventDriver +{ + + @Override + public void incomingError(WebSocketException e) + { + // TODO Auto-generated method stub + + } + + @Override + public void incomingFrame(Frame frame) + { + // TODO Auto-generated method stub + + } + + @Override + public WebSocketPolicy getPolicy() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public WebSocketSession getSession() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void onBinaryMessage(byte[] data) + { + // TODO Auto-generated method stub + + } + + @Override + public void onClose(CloseInfo close) + { + // TODO Auto-generated method stub + + } + + @Override + public void onConnect() + { + // TODO Auto-generated method stub + + } + + @Override + public void onError(Throwable t) + { + // TODO Auto-generated method stub + + } + + @Override + public void onFrame(Frame frame) + { + // TODO Auto-generated method stub + + } + + @Override + public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void onTextMessage(String message) + { + // TODO Auto-generated method stub + + } + + @Override + public void openSession(WebSocketSession session) + { + // TODO Auto-generated method stub + + } + +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java new file mode 100644 index 00000000000..2a440879b37 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointImpl.java @@ -0,0 +1,31 @@ +package org.eclipse.jetty.websocket.jsr356.endpoints; + +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.events.EventDriverImpl; + +public class JsrEndpointImpl implements EventDriverImpl +{ + + @Override + public EventDriver create(Object websocket, WebSocketPolicy policy) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String describeRule() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean supports(Object websocket) + { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 6fadd7d9be5..092d1cc0935 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -236,6 +236,11 @@ public class WebSocketClient extends ContainerLifeCycle return cookieStore; } + public EventDriverFactory getEventDriverFactory() + { + return eventDriverFactory; + } + public Executor getExecutor() { return executor; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java new file mode 100644 index 00000000000..c5c75c703d2 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java @@ -0,0 +1,187 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.CloseException; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.WebSocketSession; + +/** + * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket. + */ +public abstract class AbstractEventDriver implements IncomingFrames, EventDriver +{ + private static final Logger LOG = Log.getLogger(EventDriver.class); + protected final WebSocketPolicy policy; + protected final Object websocket; + protected WebSocketSession session; + + public AbstractEventDriver(WebSocketPolicy policy, Object websocket) + { + this.policy = policy; + this.websocket = websocket; + } + + @Override + public WebSocketPolicy getPolicy() + { + return policy; + } + + @Override + public WebSocketSession getSession() + { + return session; + } + + @Override + public final void incomingError(WebSocketException e) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("incoming(WebSocketException)",e); + } + + if (e instanceof CloseException) + { + CloseException close = (CloseException)e; + terminateConnection(close.getStatusCode(),close.getMessage()); + } + + onError(e); + } + + @Override + public void incomingFrame(Frame frame) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame); + } + + onFrame(frame); + + try + { + switch (frame.getType().getOpCode()) + { + case OpCode.CLOSE: + { + boolean validate = true; + CloseInfo close = new CloseInfo(frame,validate); + + // notify user websocket pojo + onClose(close); + + // process handshake + if (session.getConnection().getIOState().onCloseHandshake(true)) + { + // handshake resolved, disconnect. + session.getConnection().disconnect(); + } + else + { + // respond + session.close(close.getStatusCode(),close.getReason()); + } + + return; + } + case OpCode.PING: + { + byte pongBuf[] = new byte[0]; + if (frame.hasPayload()) + { + pongBuf = BufferUtil.toArray(frame.getPayload()); + } + session.getRemote().sendPong(ByteBuffer.wrap(pongBuf)); + break; + } + case OpCode.BINARY: + { + onBinaryFrame(frame.getPayload(),frame.isFin()); + return; + } + case OpCode.TEXT: + { + onTextFrame(frame.getPayload(),frame.isFin()); + return; + } + } + } + catch (NotUtf8Exception e) + { + terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage()); + } + catch (CloseException e) + { + terminateConnection(e.getStatusCode(),e.getMessage()); + } + catch (Throwable t) + { + unhandled(t); + } + } + + @Override + public void openSession(WebSocketSession session) + { + LOG.debug("openSession({})",session); + this.session = session; + this.onConnect(); + } + + protected void terminateConnection(int statusCode, String rawreason) + { + String reason = rawreason; + reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2)); + LOG.debug("terminateConnection({},{})",statusCode,rawreason); + session.close(statusCode,reason); + } + + private void unhandled(Throwable t) + { + LOG.warn("Unhandled Error (closing connection)",t); + + // Unhandled Error, close the connection. + switch (policy.getBehavior()) + { + case SERVER: + terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName()); + break; + case CLIENT: + terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName()); + break; + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java index bf74188947e..1a0d74f7f26 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java @@ -1,201 +1,35 @@ -// -// ======================================================================== -// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - package org.eclipse.jetty.websocket.common.events; import java.io.IOException; import java.nio.ByteBuffer; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.api.CloseException; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.common.CloseInfo; -import org.eclipse.jetty.websocket.common.OpCode; -import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketSession; -/** - * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket. - */ -public abstract class EventDriver implements IncomingFrames +public interface EventDriver extends IncomingFrames { - private static final Logger LOG = Log.getLogger(EventDriver.class); - protected final WebSocketPolicy policy; - protected final Object websocket; - protected WebSocketSession session; + public WebSocketPolicy getPolicy(); - public EventDriver(WebSocketPolicy policy, Object websocket) - { - this.policy = policy; - this.websocket = websocket; - } + public WebSocketSession getSession(); - public WebSocketPolicy getPolicy() - { - return policy; - } + public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException; - public WebSocketSession getSession() - { - return session; - } + public void onBinaryMessage(byte[] data); - @Override - public final void incomingError(WebSocketException e) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("incoming(WebSocketException)",e); - } + public void onClose(CloseInfo close); - if (e instanceof CloseException) - { - CloseException close = (CloseException)e; - terminateConnection(close.getStatusCode(),close.getMessage()); - } + public void onConnect(); - onError(e); - } + public void onError(Throwable t); - @Override - public void incomingFrame(Frame frame) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame); - } + public void onFrame(Frame frame); - onFrame(frame); + public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException; - try - { - switch (frame.getType().getOpCode()) - { - case OpCode.CLOSE: - { - boolean validate = true; - CloseInfo close = new CloseInfo(frame,validate); + public void onTextMessage(String message); - // notify user websocket pojo - onClose(close); - - // process handshake - if (session.getConnection().getIOState().onCloseHandshake(true)) - { - // handshake resolved, disconnect. - session.getConnection().disconnect(); - } - else - { - // respond - session.close(close.getStatusCode(),close.getReason()); - } - - return; - } - case OpCode.PING: - { - byte pongBuf[] = new byte[0]; - if (frame.hasPayload()) - { - pongBuf = BufferUtil.toArray(frame.getPayload()); - } - session.getRemote().sendPong(ByteBuffer.wrap(pongBuf)); - break; - } - case OpCode.BINARY: - { - onBinaryFrame(frame.getPayload(),frame.isFin()); - return; - } - case OpCode.TEXT: - { - onTextFrame(frame.getPayload(),frame.isFin()); - return; - } - } - } - catch (NotUtf8Exception e) - { - terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage()); - } - catch (CloseException e) - { - terminateConnection(e.getStatusCode(),e.getMessage()); - } - catch (Throwable t) - { - unhandled(t); - } - } - - public abstract void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException; - - public abstract void onBinaryMessage(byte[] data); - - public abstract void onClose(CloseInfo close); - - public abstract void onConnect(); - - public abstract void onError(Throwable t); - - public abstract void onFrame(Frame frame); - - public abstract void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException; - - public abstract void onTextMessage(String message); - - public void openSession(WebSocketSession session) - { - LOG.debug("openSession({})",session); - this.session = session; - this.onConnect(); - } - - protected void terminateConnection(int statusCode, String rawreason) - { - String reason = rawreason; - reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2)); - LOG.debug("terminateConnection({},{})",statusCode,rawreason); - session.close(statusCode,reason); - } - - private void unhandled(Throwable t) - { - LOG.warn("Unhandled Error (closing connection)",t); - - // Unhandled Error, close the connection. - switch (policy.getBehavior()) - { - case SERVER: - terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName()); - break; - case CLIENT: - terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName()); - break; - } - } -} + public void openSession(WebSocketSession session); +} \ No newline at end of file diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java index e8e9ea6d02c..ff85269ac0c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java @@ -18,340 +18,75 @@ package org.eclipse.jetty.websocket.common.events; -import java.io.InputStream; -import java.io.Reader; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.List; -import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.api.extensions.Frame; /** * Create EventDriver implementations. */ public class EventDriverFactory { - /** - * Parameter list for @OnWebSocketMessage (Binary mode) - */ - private static final ParamList validBinaryParams; - - /** - * Parameter list for @OnWebSocketConnect - */ - private static final ParamList validConnectParams; - - /** - * Parameter list for @OnWebSocketClose - */ - private static final ParamList validCloseParams; - - /** - * Parameter list for @OnWebSocketError - */ - private static final ParamList validErrorParams; - - /** - * Parameter list for @OnWebSocketFrame - */ - private static final ParamList validFrameParams; - - /** - * Parameter list for @OnWebSocketMessage (Text mode) - */ - private static final ParamList validTextParams; - - static - { - validConnectParams = new ParamList(); - validConnectParams.addParams(Session.class); - - validCloseParams = new ParamList(); - validCloseParams.addParams(int.class,String.class); - validCloseParams.addParams(Session.class,int.class,String.class); - - validErrorParams = new ParamList(); - validErrorParams.addParams(Throwable.class); - validErrorParams.addParams(Session.class,Throwable.class); - - validTextParams = new ParamList(); - validTextParams.addParams(String.class); - validTextParams.addParams(Session.class,String.class); - validTextParams.addParams(Reader.class); - validTextParams.addParams(Session.class,Reader.class); - - validBinaryParams = new ParamList(); - validBinaryParams.addParams(byte[].class,int.class,int.class); - validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class); - validBinaryParams.addParams(InputStream.class); - validBinaryParams.addParams(Session.class,InputStream.class); - - validFrameParams = new ParamList(); - validFrameParams.addParams(Frame.class); - validFrameParams.addParams(Session.class,Frame.class); - } - - private ConcurrentHashMap, EventMethods> cache; + private static final Logger LOG = Log.getLogger(EventDriverFactory.class); private final WebSocketPolicy policy; + private final List implementations; public EventDriverFactory(WebSocketPolicy policy) { this.policy = policy; - this.cache = new ConcurrentHashMap<>(); + this.implementations = new ArrayList<>(); + + addImplementation(new JettyListenerImpl()); + addImplementation(new JettyAnnotatedImpl()); } - private void assertIsPublicNonStatic(Method method) + public void addImplementation(EventDriverImpl impl) { - int mods = method.getModifiers(); - if (!Modifier.isPublic(mods)) + if (implementations.contains(impl)) { - StringBuilder err = new StringBuilder(); - err.append("Invalid declaration of "); - err.append(method); - err.append(StringUtil.__LINE_SEPARATOR); - - err.append("Method modifier must be public"); - - throw new InvalidWebSocketException(err.toString()); + LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl); + return; } - if (Modifier.isStatic(mods)) - { - StringBuilder err = new StringBuilder(); - err.append("Invalid declaration of "); - err.append(method); - err.append(StringUtil.__LINE_SEPARATOR); - - err.append("Method modifier may not be static"); - - throw new InvalidWebSocketException(err.toString()); - } + implementations.add(impl); } - private void assertIsReturn(Method method, Class type) + public List getImplementations() { - if (!type.equals(method.getReturnType())) - { - StringBuilder err = new StringBuilder(); - err.append("Invalid declaration of "); - err.append(method); - err.append(StringUtil.__LINE_SEPARATOR); - - err.append("Return type must be ").append(type); - - throw new InvalidWebSocketException(err.toString()); - } + return implementations; } - private void assertUnset(EventMethod event, Class annoClass, Method method) + public boolean removeImplementation(EventDriverImpl impl) { - if (event != null) - { - // Attempt to add duplicate frame type (a no-no) - StringBuilder err = new StringBuilder(); - err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on "); - err.append(method); - err.append(StringUtil.__LINE_SEPARATOR); - - err.append("@").append(annoClass.getSimpleName()).append(" previously declared at "); - err.append(event.getMethod()); - - throw new InvalidWebSocketException(err.toString()); - } + return this.implementations.remove(impl); } - private void assertValidSignature(Method method, Class annoClass, ParamList validParams) - { - assertIsPublicNonStatic(method); - assertIsReturn(method,Void.TYPE); - boolean valid = false; - - // validate parameters - Class actual[] = method.getParameterTypes(); - for (Class[] params : validParams) - { - if (isSameParameters(actual,params)) - { - valid = true; - break; - } - } - - if (!valid) - { - throw InvalidSignatureException.build(method,annoClass,validParams); - } - } - - /** - * 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 - { - WebSocket anno = pojo.getAnnotation(WebSocket.class); - if (anno == null) - { - return null; - } - - return scanAnnotatedMethods(pojo); - } - - public EventMethods getMethods(Class pojo) throws InvalidWebSocketException - { - if (pojo == null) - { - throw new InvalidWebSocketException("Cannot get methods for null class"); - } - if (cache.containsKey(pojo)) - { - return cache.get(pojo); - } - EventMethods methods = discoverMethods(pojo); - if (methods == null) - { - return null; - } - 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 < len; i++) - { - if (!actual[i].equals(params[i])) - { - return false; // not valid - } - } - - return true; - } - - private boolean isSignatureMatch(Method method, ParamList validParams) - { - assertIsPublicNonStatic(method); - assertIsReturn(method,Void.TYPE); - - // validate parameters - Class actual[] = method.getParameterTypes(); - for (Class[] params : validParams) - { - if (isSameParameters(actual,params)) - { - return true; - } - } - - return false; - } - - private EventMethods scanAnnotatedMethods(Class pojo) - { - Class clazz = pojo; - EventMethods events = new EventMethods(pojo); - - clazz = pojo; - while (clazz.getAnnotation(WebSocket.class) != null) - { - for (Method method : clazz.getDeclaredMethods()) - { - if (method.getAnnotation(OnWebSocketConnect.class) != null) - { - assertValidSignature(method,OnWebSocketConnect.class,validConnectParams); - assertUnset(events.onConnect,OnWebSocketConnect.class,method); - events.onConnect = new EventMethod(pojo,method); - continue; - } - - if (method.getAnnotation(OnWebSocketMessage.class) != null) - { - if (isSignatureMatch(method,validTextParams)) - { - // Text mode - // TODO - - assertUnset(events.onText,OnWebSocketMessage.class,method); - events.onText = new EventMethod(pojo,method); - continue; - } - - if (isSignatureMatch(method,validBinaryParams)) - { - // Binary Mode - // TODO - assertUnset(events.onBinary,OnWebSocketMessage.class,method); - events.onBinary = new EventMethod(pojo,method); - continue; - } - - throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams); - } - - if (method.getAnnotation(OnWebSocketClose.class) != null) - { - assertValidSignature(method,OnWebSocketClose.class,validCloseParams); - assertUnset(events.onClose,OnWebSocketClose.class,method); - events.onClose = new EventMethod(pojo,method); - continue; - } - - if (method.getAnnotation(OnWebSocketError.class) != null) - { - assertValidSignature(method,OnWebSocketError.class,validErrorParams); - assertUnset(events.onError,OnWebSocketError.class,method); - events.onError = new EventMethod(pojo,method); - continue; - } - - if (method.getAnnotation(OnWebSocketFrame.class) != null) - { - assertValidSignature(method,OnWebSocketFrame.class,validFrameParams); - assertUnset(events.onFrame,OnWebSocketFrame.class,method); - events.onFrame = new EventMethod(pojo,method); - continue; - } - - // Not a tagged method we are interested in, ignore - } - - // try superclass now - clazz = clazz.getSuperclass(); - } - - return events; - } @Override public String toString() { - return String.format("EventMethodsCache [cache.count=%d]",cache.size()); + StringBuilder msg = new StringBuilder(); + msg.append(this.getClass().getSimpleName()); + msg.append("[implementations=["); + boolean delim = false; + for (EventDriverImpl impl : implementations) + { + if (delim) + { + msg.append(','); + } + msg.append(impl.toString()); + delim = true; + } + msg.append("]"); + return msg.toString(); } /** @@ -368,21 +103,32 @@ public class EventDriverFactory throw new InvalidWebSocketException("null websocket object"); } - if (websocket instanceof WebSocketListener) + for (EventDriverImpl impl : implementations) { - WebSocketPolicy pojoPolicy = policy.clonePolicy(); - WebSocketListener listener = (WebSocketListener)websocket; - return new ListenerEventDriver(pojoPolicy,listener); + if (impl.supports(websocket)) + { + return impl.create(websocket,policy); + } } - EventMethods methods = getMethods(websocket.getClass()); - if (methods != null) + // Create a clear error message for the developer + StringBuilder err = new StringBuilder(); + err.append(websocket.getClass().getName()); + err.append(" is not a valid WebSocket object."); + err.append(" Object must obey one of the following rules: "); + + int len = implementations.size(); + for (int i = 0; i < len; i++) { - WebSocketPolicy pojoPolicy = policy.clonePolicy(); - return new AnnotatedEventDriver(pojoPolicy,websocket,methods); + EventDriverImpl impl = implementations.get(i); + if (i > 0) + { + err.append("or "); + } + err.append('(').append(i + 1).append(") "); + err.append(impl.describeRule()); } - throw new InvalidWebSocketException(websocket.getClass().getName() + " does not implement " + WebSocketListener.class.getName() - + " or declare @WebSocket"); + throw new InvalidWebSocketException(err.toString()); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java new file mode 100644 index 00000000000..930f06f258d --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java @@ -0,0 +1,56 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +/** + * A specific implementation of a EventDriver. + */ +public interface EventDriverImpl +{ + /** + * Create the EventDriver based on this implementation. + * + * @param websocket + * the websocket to wrap + * @param policy + * the policy to use + * @return the created EventDriver + */ + EventDriver create(Object websocket, WebSocketPolicy policy); + + /** + * human readable string describing the rule that would support this EventDriver. + *

+ * Used to help developer with possible object annotations, listeners, or base classes. + * + * @return the human readable description of this event driver rule(s). + */ + String describeRule(); + + /** + * Test for if this implementation can support the provided websocket. + * + * @param websocket + * the possible websocket to test + * @return true if implementation can support it, false if otherwise. + */ + boolean supports(Object websocket); +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java similarity index 96% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java index 4cbd37a1854..26360939e5e 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AnnotatedEventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java @@ -36,13 +36,13 @@ import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; /** * Handler for Annotated User WebSocket objects. */ -public class AnnotatedEventDriver extends EventDriver +public class JettyAnnotatedEventDriver extends AbstractEventDriver { - private final EventMethods events; + private final JettyAnnotatedMetadata events; private MessageAppender activeMessage; private boolean hasCloseBeenCalled = false; - public AnnotatedEventDriver(WebSocketPolicy policy, Object websocket, EventMethods events) + public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events) { super(policy,websocket); this.events = events; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java new file mode 100644 index 00000000000..b4e75768402 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java @@ -0,0 +1,63 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +public class JettyAnnotatedImpl implements EventDriverImpl +{ + private ConcurrentHashMap, JettyAnnotatedMetadata> cache = new ConcurrentHashMap<>(); + + @Override + public EventDriver create(Object websocket, WebSocketPolicy policy) + { + Class websocketClass = websocket.getClass(); + JettyAnnotatedMetadata metadata = cache.get(websocketClass); + if (metadata == null) + { + JettyAnnotatedScanner scanner = new JettyAnnotatedScanner(); + metadata = scanner.scan(websocketClass); + cache.put(websocketClass,metadata); + } + + return new JettyAnnotatedEventDriver(policy,websocket,metadata); + } + + @Override + public String describeRule() + { + return "class is annotated with @" + WebSocket.class.getName(); + } + + @Override + public boolean supports(Object websocket) + { + WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class); + return (anno != null); + } + + @Override + public String toString() + { + return String.format("{} [cache.count=%d]",this.getClass().getSimpleName(),cache.size()); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java new file mode 100644 index 00000000000..4a5265f9b47 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java @@ -0,0 +1,53 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod; +import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod; + +public class JettyAnnotatedMetadata +{ + /** @OnWebSocketConnect () */ + public CallableMethod onConnect; + /** @OnWebSocketMessage (byte[], or ByteBuffer, or InputStream) */ + public OptionalSessionCallableMethod onBinary; + /** @OnWebSocketMessage (String, or Reader) */ + public OptionalSessionCallableMethod onText; + /** @OnWebSocketFrame (Frame) */ + public OptionalSessionCallableMethod onFrame; + /** @OnWebSocketError (Throwable) */ + public OptionalSessionCallableMethod onError; + /** @OnWebSocketClose (Frame) */ + public OptionalSessionCallableMethod onClose; + + @Override + public String toString() + { + StringBuilder s = new StringBuilder(); + s.append("JettyPojoMetadata["); + s.append("onConnect=").append(onConnect); + s.append(",onBinary=").append(onBinary); + s.append(",onText=").append(onText); + s.append(",onFrame=").append(onFrame); + s.append(",onError=").append(onError); + s.append(",onClose=").append(onClose); + s.append("]"); + return s.toString(); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java new file mode 100644 index 00000000000..eb8c2af190a --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java @@ -0,0 +1,170 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import java.io.InputStream; +import java.io.Reader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner; +import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod; +import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod; + +public class JettyAnnotatedScanner extends AbstractMethodAnnotationScanner +{ + private static final Logger LOG = Log.getLogger(JettyAnnotatedScanner.class); + + /** + * Parameter list for @OnWebSocketMessage (Binary mode) + */ + private static final ParamList validBinaryParams; + + /** + * Parameter list for @OnWebSocketConnect + */ + private static final ParamList validConnectParams; + + /** + * Parameter list for @OnWebSocketClose + */ + private static final ParamList validCloseParams; + + /** + * Parameter list for @OnWebSocketError + */ + private static final ParamList validErrorParams; + + /** + * Parameter list for @OnWebSocketFrame + */ + private static final ParamList validFrameParams; + + /** + * Parameter list for @OnWebSocketMessage (Text mode) + */ + private static final ParamList validTextParams; + + static + { + validConnectParams = new ParamList(); + validConnectParams.addParams(Session.class); + + validCloseParams = new ParamList(); + validCloseParams.addParams(int.class,String.class); + validCloseParams.addParams(Session.class,int.class,String.class); + + validErrorParams = new ParamList(); + validErrorParams.addParams(Throwable.class); + validErrorParams.addParams(Session.class,Throwable.class); + + validTextParams = new ParamList(); + validTextParams.addParams(String.class); + validTextParams.addParams(Session.class,String.class); + validTextParams.addParams(Reader.class); + validTextParams.addParams(Session.class,Reader.class); + + validBinaryParams = new ParamList(); + validBinaryParams.addParams(byte[].class,int.class,int.class); + validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class); + validBinaryParams.addParams(InputStream.class); + validBinaryParams.addParams(Session.class,InputStream.class); + + validFrameParams = new ParamList(); + validFrameParams.addParams(Frame.class); + validFrameParams.addParams(Session.class,Frame.class); + } + + @Override + public void onMethodAnnotation(JettyAnnotatedMetadata metadata, Class pojo, Method method, Annotation annotation) + { + LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation); + + if (isAnnotation(annotation,OnWebSocketConnect.class)) + { + assertValidSignature(method,OnWebSocketConnect.class,validConnectParams); + assertUnset(metadata.onConnect,OnWebSocketConnect.class,method); + metadata.onConnect = new CallableMethod(pojo,method); + return; + } + + if (isAnnotation(annotation,OnWebSocketMessage.class)) + { + if (isSignatureMatch(method,validTextParams)) + { + // Text mode + assertUnset(metadata.onText,OnWebSocketMessage.class,method); + metadata.onText = new OptionalSessionCallableMethod(pojo,method); + return; + } + + if (isSignatureMatch(method,validBinaryParams)) + { + // Binary Mode + // TODO + assertUnset(metadata.onBinary,OnWebSocketMessage.class,method); + metadata.onBinary = new OptionalSessionCallableMethod(pojo,method); + return; + } + + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams); + } + + if (isAnnotation(annotation,OnWebSocketClose.class)) + { + assertValidSignature(method,OnWebSocketClose.class,validCloseParams); + assertUnset(metadata.onClose,OnWebSocketClose.class,method); + metadata.onClose = new OptionalSessionCallableMethod(pojo,method); + return; + } + + if (isAnnotation(annotation,OnWebSocketError.class)) + { + assertValidSignature(method,OnWebSocketError.class,validErrorParams); + assertUnset(metadata.onError,OnWebSocketError.class,method); + metadata.onError = new OptionalSessionCallableMethod(pojo,method); + return; + } + + if (isAnnotation(annotation,OnWebSocketFrame.class)) + { + assertValidSignature(method,OnWebSocketFrame.class,validFrameParams); + assertUnset(metadata.onFrame,OnWebSocketFrame.class,method); + metadata.onFrame = new OptionalSessionCallableMethod(pojo,method); + return; + } + } + + public JettyAnnotatedMetadata scan(Class pojo) + { + JettyAnnotatedMetadata metadata = new JettyAnnotatedMetadata(); + scanMethodAnnotations(metadata,pojo); + return metadata; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java similarity index 93% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java index 07bad1a23c2..e7da751d6ad 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ListenerEventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java @@ -34,14 +34,14 @@ import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; /** * Handler for {@link WebSocketListener} based User WebSocket implementations. */ -public class ListenerEventDriver extends EventDriver +public class JettyListenerEventDriver extends AbstractEventDriver { - private static final Logger LOG = Log.getLogger(ListenerEventDriver.class); + private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class); private final WebSocketListener listener; private MessageAppender activeMessage; private boolean hasCloseBeenCalled = false; - public ListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener) + public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener) { super(policy,listener); this.listener = listener; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java new file mode 100644 index 00000000000..d62e9e72c68 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +public class JettyListenerImpl implements EventDriverImpl +{ + @Override + public EventDriver create(Object websocket, WebSocketPolicy policy) + { + WebSocketListener listener = (WebSocketListener)websocket; + return new JettyListenerEventDriver(policy,listener); + } + + @Override + public String describeRule() + { + return "class implements " + WebSocketListener.class.getName(); + } + + @Override + public boolean supports(Object websocket) + { + return (websocket instanceof WebSocketListener); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java new file mode 100644 index 00000000000..dc48b3a421b --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java @@ -0,0 +1,195 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events.annotated; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.events.ParamList; + +/** + * Basic scanner for Annotated Methods + */ +public abstract class AbstractMethodAnnotationScanner +{ + protected void assertIsPublicNonStatic(Method method) + { + int mods = method.getModifiers(); + if (!Modifier.isPublic(mods)) + { + StringBuilder err = new StringBuilder(); + err.append("Invalid declaration of "); + err.append(method); + err.append(StringUtil.__LINE_SEPARATOR); + + err.append("Method modifier must be public"); + + throw new InvalidWebSocketException(err.toString()); + } + + if (Modifier.isStatic(mods)) + { + StringBuilder err = new StringBuilder(); + err.append("Invalid declaration of "); + err.append(method); + err.append(StringUtil.__LINE_SEPARATOR); + + err.append("Method modifier may not be static"); + + throw new InvalidWebSocketException(err.toString()); + } + } + + protected void assertIsReturn(Method method, Class type) + { + if (!type.equals(method.getReturnType())) + { + StringBuilder err = new StringBuilder(); + err.append("Invalid declaration of "); + err.append(method); + err.append(StringUtil.__LINE_SEPARATOR); + + err.append("Return type must be ").append(type); + + throw new InvalidWebSocketException(err.toString()); + } + } + + protected void assertIsVoidReturn(Method method) + { + assertIsReturn(method,Void.TYPE); + } + + protected void assertUnset(CallableMethod callable, Class annoClass, Method method) + { + if (callable != null) + { + // Attempt to add duplicate frame type (a no-no) + StringBuilder err = new StringBuilder(); + err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on "); + err.append(method); + err.append(StringUtil.__LINE_SEPARATOR); + + err.append("@").append(annoClass.getSimpleName()).append(" previously declared at "); + err.append(callable.getMethod()); + + throw new InvalidWebSocketException(err.toString()); + } + } + + protected void assertValidSignature(Method method, Class annoClass, ParamList validParams) + { + assertIsPublicNonStatic(method); + assertIsReturn(method,Void.TYPE); + + boolean valid = false; + + // validate parameters + Class actual[] = method.getParameterTypes(); + for (Class[] params : validParams) + { + if (isSameParameters(actual,params)) + { + valid = true; + break; + } + } + + if (!valid) + { + throw InvalidSignatureException.build(method,annoClass,validParams); + } + } + + public boolean isAnnotation(Annotation annotation, Class annotationClass) + { + return annotation.annotationType().equals(annotationClass); + } + + public boolean isSameParameters(Class[] actual, Class[] params) + { + if (actual.length != params.length) + { + // skip + return false; + } + + int len = params.length; + for (int i = 0; i < len; i++) + { + if (!actual[i].equals(params[i])) + { + return false; // not valid + } + } + + return true; + } + + protected boolean isSignatureMatch(Method method, ParamList validParams) + { + assertIsPublicNonStatic(method); + assertIsReturn(method,Void.TYPE); + + // validate parameters + Class actual[] = method.getParameterTypes(); + for (Class[] params : validParams) + { + if (isSameParameters(actual,params)) + { + return true; + } + } + + return false; + } + + protected boolean isTypeAnnotated(Class pojo, Class expectedAnnotation) + { + return pojo.getAnnotation(expectedAnnotation) != null; + } + + public abstract void onMethodAnnotation(T metadata, Class pojo, Method method, Annotation annotation); + + public void scanMethodAnnotations(T metadata, Class pojo) + { + Class clazz = pojo; + + while ((clazz != null) && Object.class.isAssignableFrom(clazz)) + { + for (Method method : clazz.getDeclaredMethods()) + { + Annotation annotations[] = method.getAnnotations(); + if ((annotations == null) || (annotations.length <= 0)) + { + continue; // skip + } + for (Annotation annotation : annotations) + { + onMethodAnnotation(metadata,clazz,method,annotation); + } + } + + clazz = clazz.getSuperclass(); + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java new file mode 100644 index 00000000000..41849118836 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java @@ -0,0 +1,96 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events.annotated; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.WebSocketException; + +/** + * A Callable Method + */ +public class CallableMethod +{ + private static final Logger LOG = Log.getLogger(CallableMethod.class); + protected final Class pojo; + protected final Method method; + protected Class[] paramTypes; + + public CallableMethod(Class pojo, Method method) + { + this.pojo = pojo; + this.method = method; + this.paramTypes = method.getParameterTypes(); + } + + public void call(Object obj, Object... args) + { + if ((this.pojo == null) || (this.method == null)) + { + LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method); + return; // no call event method determined + } + + if (obj == null) + { + LOG.warn("Cannot call {} on null object",this.method); + return; + } + + if (args.length < paramTypes.length) + { + throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length [" + + paramTypes.length + "]"); + } + + try + { + this.method.invoke(obj,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + String err = String.format("Cannot call method %s on %s with args: %s",method,pojo,args); + throw new WebSocketException(err,e); + } + } + + public Method getMethod() + { + return method; + } + + public Class[] getParamTypes() + { + return paramTypes; + } + + public Class getPojo() + { + return pojo; + } + + @Override + public String toString() + { + return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString()); + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java similarity index 97% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java index e551d265656..6db93677e9d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethod.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.events.annotated; import java.io.InputStream; import java.io.Reader; @@ -108,7 +108,7 @@ public class EventMethod } } - protected Method getMethod() + public Method getMethod() { return method; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java similarity index 97% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java index 80e2bfe32a0..cf6a5929db8 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventMethods.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.events.annotated; /** * A representation of the methods available to call for a particular class. diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java similarity index 95% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java index bae7e421ba6..371bbc9c1a6 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/InvalidSignatureException.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java @@ -16,13 +16,14 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.events.annotated; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.events.ParamList; @SuppressWarnings("serial") public class InvalidSignatureException extends InvalidWebSocketException diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java new file mode 100644 index 00000000000..96181fc745c --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events.annotated; + +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Method; + +import org.eclipse.jetty.websocket.api.Session; + +/** + * Simple CallableMethod that manages the optional {@link Session} argument + */ +public class OptionalSessionCallableMethod extends CallableMethod +{ + private final boolean wantsSession; + private final boolean streaming; + + public OptionalSessionCallableMethod(Class pojo, Method method) + { + super(pojo,method); + + boolean foundConnection = false; + boolean foundStreaming = false; + + if (paramTypes != null) + { + for (Class paramType : paramTypes) + { + if (Session.class.isAssignableFrom(paramType)) + { + foundConnection = true; + } + if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType)) + { + foundStreaming = true; + } + } + } + + this.wantsSession = foundConnection; + this.streaming = foundStreaming; + } + + public void call(Object obj, Session connection, Object... args) + { + if (wantsSession) + { + Object fullArgs[] = new Object[args.length + 1]; + fullArgs[0] = connection; + System.arraycopy(args,0,fullArgs,1,args.length); + call(obj,fullArgs); + } + else + { + call(obj,args); + } + } + + public boolean isSessionAware() + { + return wantsSession; + } + + public boolean isStreaming() + { + return streaming; + } + + @Override + public String toString() + { + return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString()); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java index c7fa0941a66..22204249d1f 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageInputStream.java @@ -23,7 +23,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver; +import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver; /** * Support class for reading binary message data as an InputStream. @@ -35,14 +35,14 @@ public class MessageInputStream extends InputStream implements MessageAppender * Threshold (of bytes) to perform compaction at */ private static final int COMPACT_THRESHOLD = 5; - private final AnnotatedEventDriver driver; + private final JettyAnnotatedEventDriver driver; private final ByteBuffer buf; private int size; private boolean finished; private boolean needsNotification; private int readPosition; - public MessageInputStream(AnnotatedEventDriver driver) + public MessageInputStream(JettyAnnotatedEventDriver driver) { this.driver = driver; this.buf = ByteBuffer.allocate(BUFFER_SIZE); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java index 52d6962fcfc..b683bbb1513 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageReader.java @@ -23,7 +23,7 @@ import java.io.Reader; import java.nio.ByteBuffer; import org.eclipse.jetty.util.Utf8StringBuilder; -import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver; +import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver; /** * Support class for reading text message data as an Reader. @@ -32,13 +32,13 @@ import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver; */ public class MessageReader extends Reader implements MessageAppender { - private final AnnotatedEventDriver driver; + private final JettyAnnotatedEventDriver driver; private final Utf8StringBuilder utf; private int size; private boolean finished; private boolean needsNotification; - public MessageReader(AnnotatedEventDriver driver) + public MessageReader(JettyAnnotatedEventDriver driver) { this.driver = driver; this.utf = new Utf8StringBuilder(); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java index 8ba6bee2d40..baba8e3332c 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java @@ -24,41 +24,15 @@ import org.eclipse.jetty.websocket.api.InvalidWebSocketException; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket; -import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket; -import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket; -import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket; -import org.eclipse.jetty.websocket.common.annotations.FrameSocket; -import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket; -import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket; -import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket; -import org.eclipse.jetty.websocket.common.annotations.NoopSocket; import org.eclipse.jetty.websocket.common.annotations.NotASocket; import org.junit.Assert; import org.junit.Test; import examples.AdapterConnectCloseSocket; -import examples.AnnotatedBinaryArraySocket; -import examples.AnnotatedBinaryStreamSocket; -import examples.AnnotatedTextSocket; -import examples.AnnotatedTextStreamSocket; import examples.ListenerBasicSocket; public class EventDriverFactoryTest { - private void assertHasEventMethod(String message, EventMethod actual) - { - Assert.assertThat(message + " EventMethod",actual,notNullValue()); - - Assert.assertThat(message + " EventMethod.pojo",actual.pojo,notNullValue()); - Assert.assertThat(message + " EventMethod.method",actual.method,notNullValue()); - } - - private void assertNoEventMethod(String message, EventMethod actual) - { - Assert.assertThat(message + "Event method",actual,nullValue()); - } - /** * Test Case for no exceptions and 5 methods (extends WebSocketAdapter) */ @@ -70,291 +44,7 @@ public class EventDriverFactoryTest EventDriver driver = factory.wrap(socket); String classId = AdapterConnectCloseSocket.class.getSimpleName(); - Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class)); - } - - /** - * Test Case for bad declaration (duplicate OnWebSocketBinary declarations) - */ - @Test - public void testAnnotatedBadDuplicateBinarySocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - try - { - // Should toss exception - factory.getMethods(BadDuplicateBinarySocket.class); - Assert.fail("Should have thrown " + InvalidWebSocketException.class); - } - catch (InvalidWebSocketException e) - { - // Validate that we have clear error message to the developer - Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration")); - } - } - - /** - * Test Case for bad declaration (duplicate frame type methods) - */ - @Test - public void testAnnotatedBadDuplicateFrameSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - try - { - // Should toss exception - factory.getMethods(BadDuplicateFrameSocket.class); - Assert.fail("Should have thrown " + InvalidWebSocketException.class); - } - catch (InvalidWebSocketException e) - { - // Validate that we have clear error message to the developer - Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame")); - } - } - - /** - * Test Case for bad declaration a method with a non-void return type - */ - @Test - public void testAnnotatedBadSignature_NonVoidReturn() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - try - { - // Should toss exception - factory.getMethods(BadBinarySignatureSocket.class); - Assert.fail("Should have thrown " + InvalidWebSocketException.class); - } - catch (InvalidWebSocketException e) - { - // Validate that we have clear error message to the developer - Assert.assertThat(e.getMessage(),containsString("must be void")); - } - } - - /** - * Test Case for bad declaration a method with a public static method - */ - @Test - public void testAnnotatedBadSignature_Static() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - try - { - // Should toss exception - factory.getMethods(BadTextSignatureSocket.class); - Assert.fail("Should have thrown " + InvalidWebSocketException.class); - } - catch (InvalidWebSocketException e) - { - // Validate that we have clear error message to the developer - Assert.assertThat(e.getMessage(),containsString("may not be static")); - } - } - - /** - * Test Case for socket for binary array messages - */ - @Test - public void testAnnotatedBinaryArraySocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(AnnotatedBinaryArraySocket.class); - - String classId = AnnotatedBinaryArraySocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertHasEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertNoEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - - Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession()); - Assert.assertFalse(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming()); - } - - /** - * Test Case for socket for binary stream messages - */ - @Test - public void testAnnotatedBinaryStreamSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(AnnotatedBinaryStreamSocket.class); - - String classId = AnnotatedBinaryStreamSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertHasEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertNoEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - - Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession()); - Assert.assertTrue(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming()); - } - - /** - * Test Case for no exceptions and 4 methods (3 methods from parent) - */ - @Test - public void testAnnotatedMyEchoBinarySocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(MyEchoBinarySocket.class); - - String classId = MyEchoBinarySocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertHasEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertHasEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - } - - /** - * Test Case for no exceptions and 3 methods - */ - @Test - public void testAnnotatedMyEchoSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(MyEchoSocket.class); - - String classId = MyEchoSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertHasEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - } - - /** - * Test Case for annotated for text messages w/connection param - */ - @Test - public void testAnnotatedMyStatelessEchoSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(MyStatelessEchoSocket.class); - - String classId = MyStatelessEchoSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertNoEventMethod(classId + ".onClose",methods.onClose); - assertNoEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertHasEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - - Assert.assertTrue(classId + ".onText.hasSession",methods.onText.isHasSession()); - Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming()); - } - - /** - * Test Case for no exceptions and no methods - */ - @Test - public void testAnnotatedNoop() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(NoopSocket.class); - - String classId = NoopSocket.class.getSimpleName(); - - Assert.assertThat("Methods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertNoEventMethod(classId + ".onClose",methods.onClose); - assertNoEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertNoEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - } - - /** - * Test Case for no exceptions and 1 methods - */ - @Test - public void testAnnotatedOnFrame() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(FrameSocket.class); - - String classId = FrameSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertNoEventMethod(classId + ".onClose",methods.onClose); - assertNoEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertNoEventMethod(classId + ".onText",methods.onText); - assertHasEventMethod(classId + ".onFrame",methods.onFrame); - } - - /** - * Test Case for socket for simple text messages - */ - @Test - public void testAnnotatedTextSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(AnnotatedTextSocket.class); - - String classId = AnnotatedTextSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertHasEventMethod(classId + ".onException",methods.onError); - assertHasEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - - Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession()); - Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming()); - } - - /** - * Test Case for socket for text stream messages - */ - @Test - public void testAnnotatedTextStreamSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - EventMethods methods = factory.getMethods(AnnotatedTextStreamSocket.class); - - String classId = AnnotatedTextStreamSocket.class.getSimpleName(); - - Assert.assertThat("EventMethods for " + classId,methods,notNullValue()); - - assertNoEventMethod(classId + ".onBinary",methods.onBinary); - assertHasEventMethod(classId + ".onClose",methods.onClose); - assertHasEventMethod(classId + ".onConnect",methods.onConnect); - assertNoEventMethod(classId + ".onException",methods.onError); - assertHasEventMethod(classId + ".onText",methods.onText); - assertNoEventMethod(classId + ".onFrame",methods.onFrame); - - Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession()); - Assert.assertTrue(classId + ".onText.isStreaming",methods.onText.isStreaming()); + Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class)); } /** @@ -388,6 +78,6 @@ public class EventDriverFactoryTest EventDriver driver = factory.wrap(socket); String classId = ListenerBasicSocket.class.getSimpleName(); - Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class)); + Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class)); } } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java new file mode 100644 index 00000000000..eab47d13125 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java @@ -0,0 +1,340 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.events; + +import static org.hamcrest.Matchers.*; + +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket; +import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket; +import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket; +import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket; +import org.eclipse.jetty.websocket.common.annotations.FrameSocket; +import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket; +import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket; +import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket; +import org.eclipse.jetty.websocket.common.annotations.NoopSocket; +import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod; +import org.junit.Assert; +import org.junit.Test; + +import examples.AnnotatedBinaryArraySocket; +import examples.AnnotatedBinaryStreamSocket; +import examples.AnnotatedTextSocket; +import examples.AnnotatedTextStreamSocket; + +public class JettyAnnotatedScannerTest +{ + private void assertHasEventMethod(String message, CallableMethod actual) + { + Assert.assertThat(message + " CallableMethod",actual,notNullValue()); + + Assert.assertThat(message + " CallableMethod.pojo",actual.getPojo(),notNullValue()); + Assert.assertThat(message + " CallableMethod.method",actual.getMethod(),notNullValue()); + } + + private void assertNoEventMethod(String message, CallableMethod actual) + { + Assert.assertThat(message + " CallableMethod",actual,nullValue()); + } + + /** + * Test Case for bad declaration (duplicate OnWebSocketBinary declarations) + */ + @Test + public void testAnnotatedBadDuplicateBinarySocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + try + { + // Should toss exception + impl.scan(BadDuplicateBinarySocket.class); + Assert.fail("Should have thrown " + InvalidWebSocketException.class); + } + catch (InvalidWebSocketException e) + { + // Validate that we have clear error message to the developer + Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration")); + } + } + + /** + * Test Case for bad declaration (duplicate frame type methods) + */ + @Test + public void testAnnotatedBadDuplicateFrameSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + try + { + // Should toss exception + impl.scan(BadDuplicateFrameSocket.class); + Assert.fail("Should have thrown " + InvalidWebSocketException.class); + } + catch (InvalidWebSocketException e) + { + // Validate that we have clear error message to the developer + Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame")); + } + } + + /** + * Test Case for bad declaration a method with a non-void return type + */ + @Test + public void testAnnotatedBadSignature_NonVoidReturn() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + try + { + // Should toss exception + impl.scan(BadBinarySignatureSocket.class); + Assert.fail("Should have thrown " + InvalidWebSocketException.class); + } + catch (InvalidWebSocketException e) + { + // Validate that we have clear error message to the developer + Assert.assertThat(e.getMessage(),containsString("must be void")); + } + } + + /** + * Test Case for bad declaration a method with a public static method + */ + @Test + public void testAnnotatedBadSignature_Static() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + try + { + // Should toss exception + impl.scan(BadTextSignatureSocket.class); + Assert.fail("Should have thrown " + InvalidWebSocketException.class); + } + catch (InvalidWebSocketException e) + { + // Validate that we have clear error message to the developer + Assert.assertThat(e.getMessage(),containsString("may not be static")); + } + } + + /** + * Test Case for socket for binary array messages + */ + @Test + public void testAnnotatedBinaryArraySocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryArraySocket.class); + + String classId = AnnotatedBinaryArraySocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertHasEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertNoEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + + Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware()); + Assert.assertFalse(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming()); + } + + /** + * Test Case for socket for binary stream messages + */ + @Test + public void testAnnotatedBinaryStreamSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryStreamSocket.class); + + String classId = AnnotatedBinaryStreamSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertHasEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertNoEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + + Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware()); + Assert.assertTrue(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming()); + } + + /** + * Test Case for no exceptions and 4 methods (3 methods from parent) + */ + @Test + public void testAnnotatedMyEchoBinarySocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(MyEchoBinarySocket.class); + + String classId = MyEchoBinarySocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertHasEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertHasEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + } + + /** + * Test Case for no exceptions and 3 methods + */ + @Test + public void testAnnotatedMyEchoSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(MyEchoSocket.class); + + String classId = MyEchoSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertHasEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + } + + /** + * Test Case for annotated for text messages w/connection param + */ + @Test + public void testAnnotatedMyStatelessEchoSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(MyStatelessEchoSocket.class); + + String classId = MyStatelessEchoSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertNoEventMethod(classId + ".onClose",metadata.onClose); + assertNoEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertHasEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + + Assert.assertTrue(classId + ".onText.isSessionAware",metadata.onText.isSessionAware()); + Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming()); + } + + /** + * Test Case for no exceptions and no methods + */ + @Test + public void testAnnotatedNoop() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(NoopSocket.class); + + String classId = NoopSocket.class.getSimpleName(); + + Assert.assertThat("Methods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertNoEventMethod(classId + ".onClose",metadata.onClose); + assertNoEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertNoEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + } + + /** + * Test Case for no exceptions and 1 methods + */ + @Test + public void testAnnotatedOnFrame() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(FrameSocket.class); + + String classId = FrameSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertNoEventMethod(classId + ".onClose",metadata.onClose); + assertNoEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertNoEventMethod(classId + ".onText",metadata.onText); + assertHasEventMethod(classId + ".onFrame",metadata.onFrame); + } + + /** + * Test Case for socket for simple text messages + */ + @Test + public void testAnnotatedTextSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextSocket.class); + + String classId = AnnotatedTextSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertHasEventMethod(classId + ".onException",metadata.onError); + assertHasEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + + Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware()); + Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming()); + } + + /** + * Test Case for socket for text stream messages + */ + @Test + public void testAnnotatedTextStreamSocket() + { + JettyAnnotatedScanner impl = new JettyAnnotatedScanner(); + JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextStreamSocket.class); + + String classId = AnnotatedTextStreamSocket.class.getSimpleName(); + + Assert.assertThat("EventMethods for " + classId,metadata,notNullValue()); + + assertNoEventMethod(classId + ".onBinary",metadata.onBinary); + assertHasEventMethod(classId + ".onClose",metadata.onClose); + assertHasEventMethod(classId + ".onConnect",metadata.onConnect); + assertNoEventMethod(classId + ".onException",metadata.onError); + assertHasEventMethod(classId + ".onText",metadata.onText); + assertNoEventMethod(classId + ".onFrame",metadata.onFrame); + + Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware()); + Assert.assertTrue(classId + ".onText.isStreaming",metadata.onText.isStreaming()); + } +} 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 e5f75af903b..097ac5c8f9c 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 @@ -244,6 +244,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc return this.creator; } + public EventDriverFactory getEventDriverFactory() + { + return eventDriverFactory; + } + @Override public ExtensionFactory getExtensionFactory() {